Merge lp:~kjtehprogrammer/pantheon-files/editable-pathbar into lp:~elementary-apps/pantheon-files/trunk

Proposed by KJ Lawrence
Status: Merged
Approved by: Danielle Foré
Approved revision: 1501
Merged at revision: 1513
Proposed branch: lp:~kjtehprogrammer/pantheon-files/editable-pathbar
Merge into: lp:~elementary-apps/pantheon-files/trunk
Diff against target: 2198 lines (+448/-1335)
10 files modified
libwidgets/BreadcrumbsElements.vala (+2/-1)
libwidgets/BreadcrumbsEntry.vala (+0/-494)
libwidgets/CMakeLists.txt (+1/-3)
libwidgets/LocationBar.vala (+354/-502)
libwidgets/tests/CMakeLists.txt (+0/-27)
libwidgets/tests/tests-pathbar.vala (+0/-213)
src/View/Chrome/TopMenu.vala (+5/-8)
src/View/LocationBar.vala (+75/-81)
src/View/ViewContainer.vala (+4/-5)
src/View/Window.vala (+7/-1)
To merge this branch: bzr merge lp:~kjtehprogrammer/pantheon-files/editable-pathbar
Reviewer Review Type Date Requested Status
Avi Romanoff (community) Approve
Jeremy Wootten Approve
Danielle Foré Approve
Review via email: mp+216531@code.launchpad.net

Commit message

* Makes the pathbar editable text when it receives focus. The entire path can be modified. It's also possible to put "ssh://user@domain" (or ftp, sftp, smb, etc) and get prompted for a password to connect.
* Switches Gtk.EventBox and custom Entry widget out with Gtk.Entry

Description of the change

Makes the pathbar editable text when it receives focus. The entire path can be modified. It's also possible to put "ssh://user@domain" (or ftp, sftp, smb, etc) and get prompted for a password to connect.

This is a great jumping off point for searching in files. The breadcrumb entry class already attempts to auto-complete what you're typing for the directory path (which still works in this change), we would just need to modify how it displays results and tie it into Zeitgeist.

To post a comment you must log in.
Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

This branch basically works but there a few problems:

1) Upon entering edit mode, the cursor appears at the end of the existing path and the path does not appear selected. If you start typing, the whole of the path disappears. If what is typed next does not begin with "/" then it is appended to the path that disappeared, otherwise a new path is created. This could be confusing to an inexperienced user. I suggest that the current path continue to be displayed and what is typed is appended to it, unless the first character typed is "Delete". This is what you would expect to happen based on the position of the cursor. Need UI team input on this.

2) After entering edit mode, press BackSpace to delete the existing path, then Enter. The path bar shows the root directory but a completely different folder is loaded.

3) Line 50: Remove or change to "debug ( .. )" debugging message that does not indicate an error.
   Line 78: Remove commented out code.
   Line 248, 261, 274: Recommended comment style is /* .... */

review: Needs Fixing
Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

I'm not sure I follow on #1. When you click an empty area the entire path -should- appear and be completely selected. Typing without unselecting the path (clicking off or pressing an arrow key) should wipe out the entire path, similar to the way Windows Explorer works. Here's how it appears on mine when I clicked an empty area to edit the path: http://imgur.com/P2kn2lC

#2, good catch I'll have to look into that.

Revision history for this message
Danielle Foré (danrabbit) wrote :

I really don't like that it selects the entire entry when you click into it. I would expect it to place the cursor at the end without any selection. I'm not sure Windows anything is the best example of good UX :p

I also noticed a bug where auto-completing with tab navigates to the next directory (which of itself feels a little pre-emptive but not necessarily wrong) but the entry doesn't update to reflect the change in path.

I do like the overall concept of just switching to a plain text entry though. I'm sure that would make the code simpler and eliminate bugs caused by the custom widget.

I also noticed a regression where the "go" icon shows in the entry even when you haven't typed a path that is different from your current path.

review: Needs Fixing
Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

Looks like it is a theme issue then - I am running it on Ubuntu 14.04
rather than Isis and the path appears in bold rather than highlighted for
some reason but other text selections (e.g. file renaming) appear
highlghted. It looks like Daniel agrees with me that the path should not
be selected initially (i.e. typing just appends).

On 4 May 2014 03:32, KJ Lawrence <email address hidden> wrote:

> I'm not sure I follow on #1. When you click an empty area the entire path
> -should- appear and be completely selected. Typing without unselecting the
> path (clicking off or pressing an arrow key) should wipe out the entire
> path, similar to the way Windows Explorer works. Here's how it appears on
> mine when I clicked an empty area to edit the path:
> http://imgur.com/P2kn2lC
>
> #2, good catch I'll have to look into that.
> --
>
> https://code.launchpad.net/~kjtehprogrammer/pantheon-files/editable-pathbar/+merge/216531
> You are reviewing the proposed merge of
> lp:~kjtehprogrammer/pantheon-files/editable-pathbar into lp:pantheon-files.
>

1487. By KJ Lawrence

- Fixes some code styling and removes some debugging code
- Fixes regression where the "go" icon shows up without changing anything
- Removes auto-highlighting all text
- Fixed path issues with file://, trash:// and network://

1488. By KJ Lawrence

Merging with trunk

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Sorry for the long update, a lot going on lately. Give the latest commit a shot. The commit message gives a rundown of everything that was changed.

Daniel, if you make me remove the pre-emptive directory switching with tab you'll have made these last two hours for (almost) nothing! However, it would not be difficult to do so (just one line), your call on how you want it to flow. :) I can say that it is sort of nice to have the pre-emptive directory switching because it gives you a view of all of the files in a folder so you have any idea of what to continue typing going forward. I'm sure it's a draw back on extremely large folders though.

Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

Good work, but there are still a few issues with this:

1) Line 358 - unused variable "files"
2) If you type the name of a non-existent folder with a space in the name and then choose "Create folder", the folder is created with "%20" instead of "space" in the name.
3) Keep pressing "Backspace" - you can delete the last "/". Then pressing "Enter" does not load root directory view, although the location bar displays it (because the generated path is invalid - "file://" instead of "file:///").
4) Pressing "Ctrl-Backspace" then "Enter" when the path has spaces gives a "directory not found" error - this is related to point 2).

I like the pre-emptive directory switching.

review: Needs Fixing
1489. By KJ Lawrence

- Fixes escaping issues with the paths (would affect paths with spaces)
- Fixed issue with auto-completion with certain special characters (#)
- Clearing out the entire path takes you to the root folder

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Good catches. It's a pain to track down these encoding issues with spaces and special characters... try this commit and see if everything is working for you. Try testing special characters in the path and everything.

Revision history for this message
Avi Romanoff (aroman) wrote :

A few things from an end-user's perspective (haven't even looked at the code)

1. Unlike other entry fields, I can't use <ctrl>+left/right to jump between words. In this case, words = directories. This is really important, I think.

2. Similar to #1, <ctrl>+a doesn't select-all. It should. (Shouldn't we get #1 and #2 for free?)

3. Typing ~/some_dir does not resolve to /home/$USER/some_dir. Instead, it looks like it's resolving to /some_dir.

4. I shouldn't be able to bread-crumb-ize non-existent paths. See: http://i.imgur.com/hxhuba8.png

review: Needs Fixing
Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

You give me too much credit, Haha. I used the existing text entry field, which pretty much implements all input text field functions , obviously missing those two and connect menus. I can certainly try to switch it out though.

How did you manage #4?

Revision history for this message
Avi Romanoff (aroman) wrote :

I didn't have to do anything special for #4 :p

I just clicked in the pathbar, typed "some_directory", and hit enter. The pathbar indicates that I'm viewing a non-existent directory, when in reality the file view never changes.

Revision history for this message
Danielle Foré (danrabbit) wrote :

Hm something funny is going on. I can also make the pathbar display stuff that isn't the current directory :p http://imgur.com/91Jmr70

review: Needs Fixing
1490. By KJ Lawrence

Switching Gtk.EventBox and custom Entry widget out with Gtk.Entry

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Not done yet, just wanted to get this committed. Would love for you guys to test it and get opinions!

Switched out Gtk.EventBox and the custom Entry widget we had with an actual Gtk.Entry widget. It plays nicely with elementary theming, but I have no idea beyond that! There are a couple of things I coded around (positioning completion text during the draw for one) that I don't think I did quite right (might be prone to breaking if smaller/larger texts are set for the location bar).

Since it's switched out with a Gtk.Entry widget there are a couple of nice advantages! There's the standard context menu (Copy/Cut/Paste, etc). You can also use CTRL + L/R to move between directories. There's a lot less code (got to completely eliminate a file) and what there is has been simplified. Also, since it's a Gtk.Entry widget you can do a progress bar similar to Midori now - I'm sure that's useful for something.

I'm sure there are plenty of code styling issues and debug statements that have not been cleaned up, so don't worry too much on those right now. I still have to fix some pathing and completion issues and need to get the "Goto" icon back.

Revision history for this message
Corentin Noël (tintou) wrote :

I like what you did there, with a lightweight code, Files will be even easier to maintain! There are still some warnings but as you said it's a WIP.

1491. By KJ Lawrence

- Added "Go" arrow back to Location bar
- Fixed file pathing issues with special characters (#, " ", etc)
- Reorganized some of the LocationBar code to make it easier to follow
- Implemented 'up' functionality

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

One step closer. Go arrow is back (highlights immediately now). I think I've finally got this pathing issue resolved for everything, but please do try to break it. I also implemented the "Up" functionality since we never had it before (but had the groundwork in place for it).

Besides cleaning the code up and fixing styling, the only thing that's left is to fix Ctrl + A functionality. It looks like the entire Window is handling that instead of just the view, so pressing Ctrl + A highlights everything in the files view - might be a little tricky to track that one down.

Please test! Code should be clean enough for review now too if people want to start looking at that.

1492. By KJ Lawrence

- Code styling fixes and some comments
- Fix for auto-completion where multiple results are turned

1493. By KJ Lawrence

Merging with trunk

1494. By KJ Lawrence

Fixed auto-complete issue with ~

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Okay, just some final touches. I cleaned up the code a little bit more and fixed an issue with auto-completion where if you found multiple results (I had a folder with "ctags-trunk" and "ctags-better-php") it would auto-complete up until they diverged and then take you to a "Folder 'ctags-' does not exist" page. Now it will auto-complete until they diverge but keep you where you are at.

I'm honestly not sure how to change things to fix selecting with Ctrl+A with the pathbar - accelerators confuse me!

1495. By KJ Lawrence

Fixed issue where multiple directories with the same starting letter (but with different capitalizations) would crash Files when auto-completing

1496. By KJ Lawrence

Fixes auto-completion with spaces
Unescapes paths that show up in Back/Forward button

Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

The following issues are now fixed:
1) Now handles paths with spaces correctly.
2) Ctrl left and right work as expected
3) ~ resolves to use home directory
4) I cannot reproduce Daniel's problem with this revision, although the steps involved were not stated.

The following issues remain:
1) It is still possible to delete the initial "/"; pressing "Enter" then produces a "Folder '.' can't be found" message. If you select Create folder '.' then an error message is produced. See inline diff comment at line 927.

2) Ctrl-A selects all folders in the current view instead of the whole path. This is because the event goes to the top window default handler first. A solution to this is to intercept the event at the window level and pass directly to the location bar if it has focus. e.g. In Window.vala -

            key_press_event.connect ((event) => {
                if (top_menu.location_bar.bread.is_focus)
                    return top_menu.location_bar.bread.key_press_event (event);
                else
                    return false;
            });

3) Rather than use hex values to compare with event.keyval it would be better to use Gdk.Key.<keyname>. e.g. Gdk.Key.Tab instead of 0xff09: A list of the correct names can be found in /usr/share/vala-0.24/vapi/gdk-3.0.vapi. It would be a good idea to test for alternative keys as well e.g. KP_Tab.

4) The breadcrumbs still pre-emptively display non-existent paths while the view displays the 'Folder not found' dialog, although personally I do not find this objectionable as you have the option to create the path at that point.

5) The entry is in insert mode to begin with. Design team should confirm whether insert or overwrite mode is better.

review: Needs Fixing
1497. By KJ Lawrence

Use Gdk.Key name instead of Hex value
Fixes Ctrl+A in Location Bar
Fixes issue where missing root '/' would try to create a new directory

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

1. I could have sworn I got that one... Well, fixed now (hopefully!)
2. That works quite well, thanks!
3. Never did like the hex values - took them from the original entry class, but didn't really think about how to switch them out.

For 4/5 that's really a design team call. I too like the pre-emptive stuff. If the directory I navigate to doesn't exist it's pretty convenient for it to ask me to make it. Otherwise I can just press Ctrl+L or click the pathbar and go back to navigating around. It's nicer than Nautilus' and Windows Explorer's error popup saying it doesn't exist - at least I can do something with it this way.

Revision history for this message
Danielle Foré (danrabbit) wrote :

KJ, do you have any idea why the go icon looks so light? Is there perhaps some leftover code where the color is being overridden or the opacity is being changed? It's definitely not the same color as (for example) the clear icon in search entries.

I'm still not sure I'm a massive fan of switching directories before pressing "enter", but I think we can go with it and see if it gets reported as a bug ;)

Anyways, this branch looks awesome. It fixes some minor visual issues with box-shadow in gtk 3.14 as well :)

review: Approve
Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

Good work!

Sorry, theres one more issue that I've just noticed - the substitutions for "~", "ssh:" etc happen even when they are just part of the filename and not only at the start of the path. For example, try to create a directory called "a_folder_with_~_in_the_name". This is resolved to "/a_folder_with/home/username/in_the_name". The behaviour should be the same as the "mkdir" command in this respect.

review: Needs Fixing
Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Daniel, it's 20% transparent right now. I could make it 10% or even 5% to make it a little more obvious. There wouldn't be as much of an effect when hovering over it, though there might still be enough of one. I'm not sure if there's a way to make it darker. I think I noticed the difference between how dark it looked in Midori and in the current Files, but not sure if the transparency change will get them closer to each other or not.

Jeremy, so I should just swap out the ~ on the first character? Should I still swap it if there's a slash in front of it, or only if it's the first character?

Revision history for this message
Danielle Foré (danrabbit) wrote :

KJ, if we're using a regular gtk.entry now the color should be completely handled in the theme :)

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Interesting... I didn't know until you mentioned it, but I guess Gtk.Entry can automatically handle the go icon it looks like. I'll have to update some of the code to use that rather than manually handling it - might make things even more cleaner. I'm still learning Gtk and am honestly amazed by how much the Gtk.Entry widget can do, haha.

Right now the Go icon is being drawn independently so I doubt you'll be able to control it through the theme. After I change it to be drawn by the entry directly you should be good then.

Revision history for this message
Danielle Foré (danrabbit) wrote :

Oh wow xD

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Yeah, a lot of my work on this was based off of the original "BreadcrumbsEntry" class that existed before, which basically re-created the entire Gtk.Entry widget. I didn't realize how much so until I actually switched to using an Entry widget instead and half of the code vanished (the big block of red below)...

Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

I would take as a guide the behaviour of the 'cd' command when in a terminal in the root directory when followed by the path in the location bar. If you type "cd ~" it takes you to the home directory. If you type "cd /~" it gives "No such file or directory" (unless you happen to have a folder named "~" in your root directory). So I would say only substitute it if it is the first character in the path.

1498. By KJ Lawrence

Use the secondary icon for 'go' instead of manually drawing it

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Okay, ~ is only accepted as the first character and the Go icon is set as the secondary icon for Gtk.Entry, so it should be themable. I didn't notice any difference when hovering, but I may be missing something from my setup. There's no more code to manually drawing the arrow anymore, so I think any hover effect is now out of my control.

Changing the cursor for a Gtk.Entry is a pain in the ass, just an FYI. Not sure if I did it right (line 974 in the diff shows what I had to do), so that should be reviewed, but it works the way you would expect.

1499. By KJ Lawrence

Merging in trunk!

Revision history for this message
Cody Garver (codygarver) wrote :

libwidgets/LocationBar.vala:248.13-248.42: warning: local variable `width' declared but never used
        int width = get_allocated_width ();
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1500. By KJ Lawrence

Fixes a unused variable warning

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Fixed

Revision history for this message
Jeremy Wootten (jeremywootten) :
review: Approve
Revision history for this message
Avi Romanoff (aroman) wrote :

Looking really good, but I have three minor issues:

1. Network URI support (which is apparently being targeted by this bug as well) is still buggy. Specifically, typing a URI (ex: `afp://Wan.local/`) does in fact cause Files to display a prompt for username/password, but it also causes the tab bar to show "This folder does not exist", and this title does not go away even when the credentials are entered and the share is loaded.

See: http://i.imgur.com/kL7HB1U.png

I dunno if this is actually supposed to be fixed by this branch, but https://bugs.launchpad.net/pantheon-files/+bug/1123059 is linked.

2. When changing directories, the whole pathbar animates, even if most the breadcrumb hierarchy stayed constant. For example, clicking on a directory "foo" inside ~/Developer/test/foo causes the entire pathbar to be re-drawn/re-animated/re-slide-in, when what should happen is only the "/foo" breadcrumb should be appended/animated in.

3. Clicking on the right-most breadcrumb causes the pathbar to re-animate. Why?

review: Needs Fixing
Revision history for this message
Cody Garver (codygarver) wrote :
Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

1. The network URI support was actually what I initially started all of this for, the rest was just kind of fill in place. Though, it was just for being able to type in the path, versus using the network dialog box. I can dig into the tab issue and see what I can find.

2. I didn't see that with normal files usage. Just clicking a folder in the normal view was correct, it just appended the extra breadcrumb entry without re-rendering. Though, I did notice that if you click any of the breadcrumbs that it re-renders. Apparently that's the way the existing Files setup is, so I'd have to dig into that to make it a little more smooth. It does it somewhere though, so this shouldn't be too difficult.

3. It does this for the current Files code too - not really sure why, I'd have to dig into it. It re-renders for any of the breadcrumbs that are clicked (but doesn't redraw all of the breadcrumbs when you're just navigating around, which is kind of strange).

1501. By KJ Lawrence

- Fixes issue where breadcrumbs would always fully redraw when clicked
- Removes unused code

Revision history for this message
KJ Lawrence (kjtehprogrammer) wrote :

Okay, that commit should fix the breadcrumb fully redrawing issue.

Thanks Cody, I thought I saw something on that before. Should I look at it as part of my changes or leave it alone then, since it has its own bug report? If I leave it alone, do we need to make these changes dependent on it, or just commit anyways?

Revision history for this message
Avi Romanoff (aroman) wrote :

I can confirm that the breadcrumb full-redrawing bug is fixed -- awesome!

I think this should be merged to trunk and those other bugs (which were not introduced by this branch) should be fixed separately. I say, approve.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libwidgets/BreadcrumbsElements.vala'
2--- libwidgets/BreadcrumbsElements.vala 2014-05-13 21:10:32 +0000
3+++ libwidgets/BreadcrumbsElements.vala 2014-06-14 19:18:12 +0000
4@@ -41,6 +41,7 @@
5 }
6 }
7 Gdk.Pixbuf icon;
8+ public bool hidden = false;
9 public bool display = true;
10 public bool display_text = true;
11 public string? text_displayed = null;
12@@ -187,4 +188,4 @@
13
14 return x;
15 }
16-}
17+}
18\ No newline at end of file
19
20=== removed file 'libwidgets/BreadcrumbsEntry.vala'
21--- libwidgets/BreadcrumbsEntry.vala 2013-08-10 20:32:58 +0000
22+++ libwidgets/BreadcrumbsEntry.vala 1970-01-01 00:00:00 +0000
23@@ -1,494 +0,0 @@
24-/*
25- * Copyright (c) 2011 Lucas Baudin <xapantu@gmail.com>
26- *
27- * Marlin is free software; you can redistribute it and/or
28- * modify it under the terms of the GNU General Public License as
29- * published by the Free Software Foundation; either version 2 of the
30- * License, or (at your option) any later version.
31- *
32- * Marlin is distributed in the hope that it will be useful,
33- * but WITHOUT ANY WARRANTY; without even the implied warranty of
34- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
35- * General Public License for more details.
36- *
37- * You should have received a copy of the GNU General Public
38- * License along with this program; see the file COPYING. If not,
39- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
40- * Boston, MA 02111-1307, USA.
41- *
42- */
43-
44-public class Marlin.View.Chrome.BreadcrumbsEntry : GLib.Object {
45- Gtk.IMContext im_context;
46- public string text = "";
47- public int cursor = 0;
48- public string completion = "";
49- uint timeout;
50- bool blink = true;
51- public Gdk.Pixbuf arrow_img;
52-
53- double selection_mouse_start = -1;
54- double selection_mouse_end = -1;
55- double selection_start = 0;
56- double selection_end = 0;
57- int selected_start = -1;
58- int selected_end = -1;
59- internal bool hover = false;
60- bool focus = false;
61-
62- bool is_selecting = false;
63- bool need_selection_update = false;
64-
65- double text_width;
66- double text_height;
67-
68- public signal void enter ();
69- public signal void backspace ();
70- public signal void left ();
71- public signal void up ();
72- public signal void down ();
73- public signal void left_full ();
74- public signal void need_draw ();
75- public signal void paste ();
76- public signal void need_completion ();
77- public signal void completed ();
78- public signal void escape ();
79-
80- /**
81- * Create a new BreadcrumbsEntry object. It is used to display the entry
82- * which is a the left of the pathbar. It is *not* a Gtk.Widget, it is
83- * only a class which holds some data and draws an entry to a given
84- * Cairo.Context.
85- * Events must be sent to the appropriate function (key_press_event,
86- * key_release_event, mouse_motion_event, etc...). These events must be
87- * relative to the widget, so, you need to do some things like
88- * event.x -= entry_x before sending the events.
89- * It can be drawn using the draw() function.
90- **/
91- public BreadcrumbsEntry () {
92- im_context = new Gtk.IMMulticontext ();
93- im_context.commit.connect (commit);
94-
95- /* Load arrow image */
96- try {
97- arrow_img = Gtk.IconTheme.get_default ().load_icon ("go-jump-symbolic", 16, Gtk.IconLookupFlags.GENERIC_FALLBACK);
98- } catch (Error err) {
99- stderr.printf ("Unable to load home icon: %s", err.message);
100- }
101- }
102-
103- /**
104- * Call this function if the parent widgets has the focus, it will start
105- * computing the blink cursor, will enable cursor and selection drawing.
106- **/
107- public void show () {
108- focus = true;
109-
110- if (timeout > 0)
111- Source.remove (timeout);
112-
113- timeout = Timeout.add (700, () => {
114- blink = !blink;
115- need_draw ();
116-
117- return true;
118- });
119- }
120-
121- /**
122- * Delete the text selected.
123- **/
124- public void delete_selection () {
125- if (selected_start > 0 && selected_end > 0) {
126- int first = selected_start > selected_end ? selected_end : selected_start;
127- int second = selected_start > selected_end ? selected_start : selected_end;
128-
129- text = text.slice (0, first) + text.slice (second, text.length);
130- reset_selection ();
131- cursor = first;
132- }
133- }
134-
135- /**
136- * Insert some text at the cursor position.
137- *
138- * @param to_insert The text you want to insert.
139- **/
140- public void insert (string to_insert) {
141- if (to_insert != null && to_insert.length > 0) {
142- int first = selected_start > selected_end ? selected_end : selected_start;
143- int second = selected_start > selected_end ? selected_start : selected_end;
144-
145- if (first != second && second > 0) {
146- text = text.slice (0, first) + to_insert + text.slice (second, text.length);
147- selected_start = -1;
148- selected_end = -1;
149- selection_start = 0;
150- selection_end = 0;
151- cursor = first + to_insert.length;
152- } else {
153- text = text.slice (0,cursor) + to_insert + text.slice (cursor, text.length);
154- cursor += to_insert.length;
155- }
156- }
157-
158- need_completion ();
159- }
160-
161- /**
162- * A callback from our im_context.
163- **/
164- private void commit (string character) {
165- insert (character);
166- }
167-
168- public void key_press_event (Gdk.EventKey event) {
169- /* FIXME: I can't find the vapi to not use hardcoded key value. */
170- /* FIXME: we should use Gtk.BindingSet, but the vapi file seems buggy */
171-
172- bool control_pressed = (event.state & Gdk.ModifierType.CONTROL_MASK) == 4;
173- bool shift_pressed = ! ((event.state & Gdk.ModifierType.SHIFT_MASK) == 0);
174-
175- switch (event.keyval) {
176- case 0xff51: /* left */
177- if (cursor > 0 && !control_pressed && !shift_pressed) {
178- cursor --; /* No control pressed, the cursor is not at the begin */
179- reset_selection ();
180- } else if (cursor == 0 && control_pressed) {
181- left_full (); /* Control pressed, the cursor is at the begin */
182- } else if (control_pressed) {
183- cursor = 0;
184- } else if (cursor > 0 && shift_pressed) {
185- if (selected_start < 0) {
186- selected_start = cursor;
187- }
188-
189- if (selected_end < 0) {
190- selected_end = cursor;
191- }
192-
193- cursor--;
194- selected_start = cursor;
195- need_selection_update = true;
196- } else {
197- left ();
198- }
199-
200- break;
201-
202- case 0xff53: /* right */
203- if (cursor < text.length && !shift_pressed) {
204- cursor++;
205- reset_selection ();
206- } else if (cursor < text.length && shift_pressed) {
207- if (selected_start < 0) {
208- selected_start = cursor;
209- }
210-
211- if (selected_end < 0) {
212- selected_end = cursor;
213- }
214-
215- cursor++;
216- selected_start = cursor;
217- need_selection_update = true;
218- } else if (!shift_pressed) {
219- complete ();
220- }
221-
222- break;
223-
224- case 0xff0d: /* enter */
225- reset_selection ();
226- enter ();
227- break;
228-
229- case 0xff08: /* backspace */
230- if (get_selection () != null) {
231- delete_selection ();
232- need_completion ();
233- } else if (cursor > 0) {
234- text = text.slice (0, cursor - 1) + text.slice (cursor, text.length);
235- cursor--;
236- need_completion ();
237- } else {
238- backspace ();
239- }
240-
241- break;
242-
243- case 0xffff: /* delete */
244- if (get_selection () == null && cursor < text.length && control_pressed) {
245- text = text.slice (0, cursor);
246- } else if (get_selection () == null && cursor < text.length) {
247- text = text.slice (0, cursor) + text.slice (cursor + 1, text.length);
248- } else if (get_selection () != null) {
249- delete_selection ();
250- }
251-
252- need_completion ();
253- break;
254-
255- case 0xff09: /* tab */
256- complete ();
257- break;
258-
259- case 0xff54: /* down */
260- down ();
261- break;
262-
263- case 0xff52: /* up */
264- up ();
265- break;
266-
267- case 0xff1b: /* escape */
268- escape ();
269- break;
270-
271- case 0xff50: /* Home */
272- cursor = 0;
273- break;
274-
275- case 0xff57: /* End */
276- cursor = text.length;
277- break;
278-
279- default:
280- im_context.filter_keypress (event);
281- break;
282- }
283-
284- blink = true;
285- print ("%x\n", event.keyval);
286- }
287-
288- public void complete () {
289- reset_selection ();
290-
291- if (completion != "") {
292- text += completion + "/";
293- cursor += completion.length + 1;
294- completion = "";
295- completed ();
296- }
297- }
298-
299- public string? get_selection () {
300- int first = selected_start > selected_end ? selected_end : selected_start;
301- int second = selected_start > selected_end ? selected_start : selected_end;
302-
303- if (!(first < 0 || second < 0))
304- return text.slice (first,second);
305-
306- return null;
307- }
308-
309- public void key_release_event (Gdk.EventKey event) {
310- im_context.filter_keypress (event);
311- }
312-
313- public void mouse_motion_event (Gdk.EventMotion event, double width) {
314- hover = false;
315-
316- if (event.x < width && event.x > width - arrow_img.get_width ())
317- hover = true;
318-
319- if (is_selecting)
320- selection_mouse_end = event.x > 0 ? event.x : 1;
321- }
322-
323- public void mouse_press_event(Gdk.EventButton event, double width) {
324- reset_selection ();
325- blink = true;
326-
327- if (event.x < width && event.x > width - arrow_img.get_width ()) {
328- enter ();
329- } else if (event.x >= 0) {
330- is_selecting = true;
331- selection_mouse_start = event.x;
332- selection_mouse_end = event.x;
333- } else if (event.x >= -20) {
334- is_selecting = true;
335- selection_mouse_start = -1;
336- selection_mouse_end = -1;
337- }
338- need_draw ();
339- }
340-
341- public void mouse_release_event (Gdk.EventButton event) {
342- selection_mouse_end = event.x;
343- is_selecting = false;
344- }
345-
346- /**
347- * Reset the current selection. This function won't ask for re-drawing,
348- * so, you will need to re-draw your entry by hand. It can be used after
349- * a #text set, to avoid weird things.
350- **/
351- public void reset_selection () {
352- selected_start = -1;
353- selected_end = -1;
354- selection_start = 0;
355- selection_end = 0;
356- }
357-
358- private void update_selection (Cairo.Context cr, Gtk.Widget widget) {
359- double last_diff = double.MAX;
360- Pango.Layout layout = widget.create_pango_layout (text);
361-
362- if (selection_mouse_start > 0) {
363- selected_start = -1;
364- selection_start = 0;
365- cursor = text.length;
366-
367- for (int i = 0; i <= text.length; i++) {
368- layout.set_text (text.slice(0, i), -1);
369-
370- if (Math.fabs (selection_mouse_start - get_width (layout)) < last_diff) {
371- last_diff = Math.fabs (selection_mouse_start - get_width (layout));
372- selection_start = get_width (layout);
373- selected_start = i;
374- }
375- }
376-
377- selection_mouse_start = -1;
378- }
379-
380- if (selection_mouse_end > 0) {
381- last_diff = double.MAX;
382- selected_end = -1;
383- selection_end = 0;
384- cursor = text.length;
385-
386- for (int i = 0; i <= text.length; i++) {
387- layout.set_text (text.slice (0, i), -1);
388-
389- if (Math.fabs (selection_mouse_end - get_width (layout)) < last_diff) {
390- last_diff = Math.fabs (selection_mouse_end - get_width (layout));
391- selection_end = get_width (layout);
392- selected_end = i;
393- cursor = i;
394- }
395- }
396-
397- selection_mouse_end = -1;
398- }
399- }
400-
401- private void computetext_width (Pango.Layout pango) {
402- int text_width, text_height;
403- pango.get_size (out text_width, out text_height);
404- this.text_width = Pango.units_to_double (text_width);
405- this.text_height = Pango.units_to_double (text_height);
406- }
407-
408- /**
409- * A utility function to get the width of a Pango.Layout. Maybe it could
410- * be moved to a less specific file/lib.
411- *
412- * @param pango a pango layout
413- * @return the width of the layout
414- **/
415- private double get_width (Pango.Layout pango) {
416- int text_width, text_height;
417- pango.get_size (out text_width, out text_height);
418- return Pango.units_to_double (text_width);
419- }
420-
421- private void update_selection_key (Cairo.Context cr, Gtk.Widget widget) {
422- Pango.Layout layout = widget.create_pango_layout (text);
423- layout.set_text (text.slice (0, selected_end), -1);
424- selection_end = get_width (layout);
425- layout.set_text (text.slice (0, selected_start), -1);
426- selection_start = get_width (layout);
427- need_selection_update = false;
428- }
429-
430- public void draw(Cairo.Context cr,
431- double x, double height, double width,
432- Gtk.Widget widget, Gtk.StyleContext button_context) {
433-
434- update_selection (cr, widget);
435-
436- if (need_selection_update)
437- update_selection_key (cr, widget);
438-
439- cr.set_source_rgba (0, 0, 0, 0.8);
440-
441- Pango.Layout layout = widget.create_pango_layout (text);
442- computetext_width (layout);
443- button_context.render_layout (cr, x, height / 2 - text_height / 2, layout);
444-
445- layout.set_text (text.slice (0, cursor), -1);
446-
447- if (blink && focus) {
448- cr.rectangle (x + get_width (layout), height / 6, 1, 4 * height / 6);
449- cr.fill ();
450- }
451-
452- if (text != "") {
453- Gdk.cairo_set_source_pixbuf (cr,arrow_img,
454- x + width - arrow_img.get_width() - 10,
455- height/2 - arrow_img.get_height() / 2);
456-
457- if (hover)
458- cr.paint ();
459- else
460- cr.paint_with_alpha (0.8);
461- }
462-
463- /* draw completion */
464- cr.move_to (x + text_width, height / 2 - text_height / 2);
465- layout.set_text (completion, -1);
466-
467-#if VALA_0_14
468- Gdk.RGBA color = button_context.get_color (Gtk.StateFlags.NORMAL);
469-#else
470- Gdk.RGBA color = Gdk.RGBA ();
471- button_context.get_color (Gtk.StateFlags.NORMAL, color);
472-#endif
473- cr.set_source_rgba (color.red, color.green, color.blue, color.alpha - 0.3);
474- Pango.cairo_show_layout (cr, layout);
475-
476- /* draw selection */
477- if (focus && selected_start >= 0 && selected_end >= 0) {
478- cr.rectangle (x + selection_start, height / 6, selection_end - selection_start, 4 * height / 6);
479-#if VALA_0_14
480- color = button_context.get_background_color (Gtk.StateFlags.SELECTED);
481-#else
482- button_context.get_background_color (Gtk.StateFlags.SELECTED, color);
483-#endif
484- Gdk.cairo_set_source_rgba (cr, color);
485- cr.fill ();
486-
487- layout.set_text (get_selection (), -1);
488-
489-#if VALA_0_14
490- color = button_context.get_color (Gtk.StateFlags.SELECTED);
491-#else
492- button_context.get_color (Gtk.StateFlags.SELECTED, color);
493-#endif
494- Gdk.cairo_set_source_rgba (cr, color);
495- cr.move_to (x + Math.fmin (selection_start, selection_end),
496- height / 2 - text_height / 2);
497-
498- Pango.cairo_show_layout (cr, layout);
499- }
500- }
501-
502- public void reset () {
503- text = "";
504- cursor = 0;
505- completion = "";
506- }
507-
508- public void hide () {
509- focus = false;
510- if (timeout > 0)
511- Source.remove (timeout);
512- }
513-
514- ~BreadcrumbsEntry () {
515- hide ();
516- }
517-}
518
519=== modified file 'libwidgets/CMakeLists.txt'
520--- libwidgets/CMakeLists.txt 2014-03-27 10:22:25 +0000
521+++ libwidgets/CMakeLists.txt 2014-06-14 19:18:12 +0000
522@@ -41,7 +41,6 @@
523 LocationBar.vala
524 PoofWindow.vala
525 BreadcrumbsElements.vala
526- BreadcrumbsEntry.vala
527 PACKAGES
528 gtk+-3.0
529 granite
530@@ -66,5 +65,4 @@
531 SOVERSION ${MARLINWIDGETS_SOVERSION})
532
533 install (TARGETS ${PKGNAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/)
534-target_link_libraries(${PKGNAME} ${DEPS_LIBRARIES})
535-add_subdirectory(tests)
536+target_link_libraries(${PKGNAME} ${DEPS_LIBRARIES})
537\ No newline at end of file
538
539=== modified file 'libwidgets/LocationBar.vala'
540--- libwidgets/LocationBar.vala 2014-05-13 21:10:32 +0000
541+++ libwidgets/LocationBar.vala 2014-06-14 19:18:12 +0000
542@@ -34,27 +34,24 @@
543 string? text_displayed;
544 }
545
546-public abstract class Marlin.View.Chrome.BasePathBar : Gtk.EventBox {
547+public abstract class Marlin.View.Chrome.BasePathBar : Gtk.Entry {
548
549 public string current_right_click_path;
550 public string current_right_click_root;
551- //double right_click_root;
552+
553+ protected string text_completion = "";
554+ protected bool multiple_completions = false;
555+ protected bool text_changed = false;
556+ protected bool arrow_hovered = false;
557+ protected bool ignore_focus_in = false;
558+ protected bool ignore_change = false;
559+ private Gdk.Pixbuf arrow_img;
560
561 /* if we must display the BreadcrumbsElement which are in newbreads. */
562 bool view_old = false;
563
564- /* Used to decide if this button press event must be send to the
565- * integrated entry or not. */
566- double x_render_saved = 0;
567-
568- /* if we have the focus or not
569- * FIXME: this should be replaced with some nice Gtk.Widget method. */
570- public new bool focus = false;
571-
572- public Gtk.ActionGroup clipboard_actions;
573-
574 /* This list will contain all BreadcrumbsElement */
575- Gee.ArrayList<BreadcrumbsElement> elements;
576+ protected Gee.ArrayList<BreadcrumbsElement> elements;
577
578 /* This list will contain the BreadcrumbsElement which are animated */
579 Gee.List<BreadcrumbsElement> newbreads;
580@@ -67,19 +64,18 @@
581
582 Gtk.StyleContext button_context;
583 Gtk.StyleContext button_context_active;
584- public BreadcrumbsEntry entry;
585
586 /**
587 * When the user click on a breadcrumb, or when he enters a path by hand
588 * in the integrated entry
589 **/
590- public signal void activate_alternate (string path);
591- public signal void changed (string changed);
592+ public signal void activate_alternate (File file);
593+ public signal void path_changed (File file);
594 public signal void need_completion ();
595
596 List<IconDirectory?> icons;
597
598- string text = "";
599+ string current_path = "";
600
601 int selected = -1;
602 int space_breads = 12;
603@@ -87,7 +83,10 @@
604 int y;
605 string protocol;
606
607+ public signal void completed ();
608 public signal void escape ();
609+ public signal void up ();
610+ public signal void down ();
611
612 private int timeout = -1;
613
614@@ -96,17 +95,16 @@
615
616 private Granite.Services.IconFactory icon_factory;
617
618-
619 construct {
620- add_events (Gdk.EventMask.BUTTON_PRESS_MASK
621- | Gdk.EventMask.BUTTON_RELEASE_MASK
622- | Gdk.EventMask.KEY_PRESS_MASK
623- | Gdk.EventMask.KEY_RELEASE_MASK
624- | Gdk.EventMask.POINTER_MOTION_MASK
625- | Gdk.EventMask.LEAVE_NOTIFY_MASK);
626- init_clipboard ();
627 icon_factory = Granite.Services.IconFactory.get_default ();
628 icons = new List<IconDirectory?> ();
629+
630+ /* Load arrow image */
631+ try {
632+ arrow_img = Gtk.IconTheme.get_default ().load_icon ("go-jump-symbolic", 16, Gtk.IconLookupFlags.GENERIC_FALLBACK);
633+ } catch (Error err) {
634+ stderr.printf ("Unable to load home icon: %s", err.message);
635+ }
636
637 button_context = get_style_context ();
638 button_context.add_class ("button");
639@@ -118,92 +116,26 @@
640 Granite.Widgets.Utils.set_theming (this, ".noradius-button{border-radius:0px;}", null,
641 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
642
643- left_padding = 5;//border.left;
644+ left_padding = border.left;
645 right_padding = border.right;
646
647- set_can_focus (true);
648- set_visible_window (false);
649-
650 /* x padding */
651 x = 0;
652 /* y padding */
653 y = 0;
654
655 elements = new Gee.ArrayList<BreadcrumbsElement> ();
656-
657- entry = new BreadcrumbsEntry ();
658-
659- entry.enter.connect (on_entry_enter);
660-
661- /* Let's connect the signals ;)
662- * FIXME: there could be a separate function for each signal */
663- entry.need_draw.connect (queue_draw);
664-
665- entry.left.connect (() => {
666- if (elements.size > 0) {
667- var element = elements[elements.size - 1];
668- elements.remove (element);
669-
670- if (element.display) {
671- if (entry.text[0] != '/') {
672- entry.text = element.text + "/" + entry.text;
673- entry.cursor = element.text.length + 1;
674- } else {
675- entry.text = element.text + entry.text;
676- entry.cursor = element.text.length;
677- }
678- entry.reset_selection ();
679- }
680- }
681- });
682-
683- entry.left_full.connect (() => {
684- string tmp = entry.text;
685- string tmp_entry = "";
686-
687- foreach (BreadcrumbsElement element in elements) {
688- if (element.display) {
689- if (tmp_entry[0] != '/')
690- tmp_entry += element.text + "/";
691- else
692- tmp_entry += element.text;
693- }
694- }
695-
696- entry.text = tmp_entry + tmp;
697- elements.clear ();
698- });
699-
700- entry.backspace.connect (() => {
701- if (elements.size > 0) {
702- string strloc = get_elements_path ();
703- File location = File.new_for_commandline_arg (strloc);
704- location = location.get_parent ();
705-
706- if (location == null)
707- location = File.new_for_commandline_arg (protocol);
708-
709- changed (location.get_uri () + "/");
710- grab_focus ();
711- }
712- });
713-
714- entry.escape.connect (() => {
715- escape ();
716- });
717-
718- entry.need_completion.connect (() => {
719- need_completion ();
720- });
721-
722- entry.paste.connect (() => {
723- var display = get_display ();
724- Gdk.Atom atom = Gdk.SELECTION_CLIPBOARD;
725- Gtk.Clipboard clipboard = Gtk.Clipboard.get_for_display (display, atom);
726- clipboard.request_text (request_text);
727- });
728-
729- entry.hide ();
730+
731+ secondary_icon_activatable = true;
732+ secondary_icon_sensitive = true;
733+ truncate_multiline = true;
734+ activate.connect (on_activate);
735+ icon_press.connect (on_activate);
736+ motion_notify_event.connect (on_motion_notify);
737+ focus_in_event.connect (on_focus_in);
738+ focus_out_event.connect (on_focus_out);
739+ grab_focus.connect_after (on_grab_focus);
740+ changed.connect (on_change);
741
742 /* Drag and drop */
743 Gtk.TargetEntry target_uri_list = {"text/uri-list", 0, 0};
744@@ -213,6 +145,170 @@
745 drag_motion.connect (on_drag_motion);
746 drag_data_received.connect (on_drag_data_received);
747 }
748+
749+ public override bool key_press_event (Gdk.EventKey event) {
750+ switch (event.keyval) {
751+ case Gdk.Key.KP_Tab:
752+ case Gdk.Key.Tab:
753+ complete ();
754+ return true;
755+
756+ case Gdk.Key.KP_Down:
757+ case Gdk.Key.Down:
758+ down ();
759+ return true;
760+
761+ case Gdk.Key.KP_Up:
762+ case Gdk.Key.Up:
763+ up ();
764+ return true;
765+
766+ case Gdk.Key.Escape:
767+ escape ();
768+ return true;
769+ }
770+
771+ return base.key_press_event (event);
772+ }
773+
774+ public override bool button_press_event (Gdk.EventButton event) {
775+ if (is_focus)
776+ return base.button_press_event (event);
777+
778+ foreach (BreadcrumbsElement element in elements)
779+ element.pressed = false;
780+
781+ var el = get_element_from_coordinates ((int) event.x, (int) event.y);
782+
783+ if (el != null)
784+ el.pressed = true;
785+
786+ queue_draw ();
787+
788+ if (timeout == -1 && event.button == 1) {
789+ timeout = (int) Timeout.add (150, () => {
790+ select_bread_from_coord (event);
791+ timeout = -1;
792+ return false;
793+ });
794+ }
795+
796+ if (event.button == 2) {
797+ if (el != null) {
798+ selected = elements.index_of (el);
799+ var newpath = get_path_from_element (el);
800+ activate_alternate (get_file_for_path (newpath));
801+ }
802+
803+ return true;
804+ }
805+
806+ if (event.button == 3)
807+ return select_bread_from_coord (event);
808+
809+ return true;
810+ }
811+
812+ public override bool button_release_event (Gdk.EventButton event) {
813+ reset_elements_states ();
814+
815+ if (timeout != -1) {
816+ Source.remove ((uint) timeout);
817+ timeout = -1;
818+ }
819+
820+ if (is_focus)
821+ return base.button_release_event (event);
822+
823+ if (event.button == 1) {
824+ var el = get_element_from_coordinates ((int) event.x, (int) event.y);
825+ if (el != null) {
826+ selected = elements.index_of (el);
827+ var newpath = get_path_from_element (el);
828+ path_changed (get_file_for_path (newpath));
829+ } else
830+ grab_focus ();
831+ }
832+
833+ return base.button_release_event (event);
834+ }
835+
836+ void on_change () {
837+ if (ignore_change) {
838+ ignore_change = false;
839+ return;
840+ }
841+
842+ set_entry_icon (true, (text.length > 0) ? "Navigate to: " + text : "");
843+ text_completion = "";
844+ need_completion ();
845+ }
846+
847+ bool on_motion_notify (Gdk.EventMotion event) {
848+ int x = (int) event.x;
849+ double x_render = 0;
850+ double x_previous = -10;
851+ set_tooltip_text ("");
852+
853+ if (is_focus)
854+ return base.motion_notify_event (event);
855+
856+ foreach (BreadcrumbsElement element in elements) {
857+ if (element.display) {
858+ x_render += element.real_width;
859+ if (x <= x_render + 5 && x > x_previous + 5) {
860+ selected = elements.index_of (element);
861+ set_tooltip_text (_("Go to %s").printf (element.text));
862+ break;
863+ }
864+
865+ x_previous = x_render;
866+ }
867+ }
868+
869+ if (event.x > 0 && event.x < x_render + 5)
870+ set_entry_cursor (new Gdk.Cursor (Gdk.CursorType.ARROW));
871+ else
872+ set_entry_cursor (null);
873+
874+ return base.motion_notify_event (event);
875+ }
876+
877+ bool on_focus_out (Gdk.EventFocus event) {
878+ if (is_focus) {
879+ ignore_focus_in = true;
880+ return base.focus_out_event (event);
881+ }
882+
883+ ignore_focus_in = false;
884+ set_entry_icon (false);
885+ set_entry_text ("");
886+
887+ return base.focus_out_event (event);
888+ }
889+
890+ bool on_focus_in (Gdk.EventFocus event) {
891+ if (ignore_focus_in)
892+ return base.focus_in_event (event);
893+
894+ set_entry_text (GLib.Uri.unescape_string (get_elements_path ()
895+ .replace ("file:////", "/")
896+ .replace ("file:///", "/")
897+ .replace ("trash:///", "")
898+ .replace ("network:///", "")));
899+
900+ return base.focus_in_event (event);
901+ }
902+
903+ void on_grab_focus () {
904+ select_region (0, 0);
905+ set_position (-1);
906+ }
907+
908+ void on_activate () {
909+ path_changed (get_file_for_path (text + text_completion));
910+ text_completion = "";
911+ }
912
913 void on_drag_begin (Gdk.DragContext drag_context) {
914 critical ("Start drag...");
915@@ -242,11 +338,11 @@
916 var newpath = get_path_from_element (el);
917 print ("Move to: %s\n", newpath);
918 var target_file = GLib.File.new_for_uri (newpath);
919- on_file_droped (uris, target_file, real_action);
920+ on_file_dropped (uris, target_file, real_action);
921 }
922 }
923
924- protected abstract void on_file_droped (List<GLib.File> uris, GLib.File target_file, Gdk.DragAction real_action);
925+ protected abstract void on_file_dropped (List<GLib.File> uris, GLib.File target_file, Gdk.DragAction real_action);
926
927 bool on_drag_motion (Gdk.DragContext context, int x, int y, uint time) {
928 Gtk.drag_unhighlight (this);
929@@ -265,6 +361,70 @@
930
931 return false;
932 }
933+
934+ protected void add_icon (IconDirectory icon) {
935+ if (icon.gicon != null)
936+ icon.icon = icon_factory.load_symbolic_icon_from_gicon (button_context, icon.gicon, 16);
937+ else
938+ icon.icon = icon_factory.load_symbolic_icon (button_context, icon.icon_name, 16);
939+
940+ icons.append (icon);
941+ }
942+
943+ public void complete () {
944+ if (text_completion.length == 0)
945+ return;
946+
947+ string path = text + text_completion;
948+
949+ /* If there are multiple results, tab as far as we can, otherwise do the entire result */
950+ if (!multiple_completions) {
951+ set_entry_text (path + "/");
952+ completed ();
953+ } else
954+ set_entry_text (path);
955+ }
956+
957+ public void reset_elements_states () {
958+ foreach (BreadcrumbsElement element in elements)
959+ element.pressed = false;
960+
961+ queue_draw ();
962+ }
963+
964+ public void set_entry_text (string text) {
965+ ignore_change = true;
966+ text_completion = "";
967+ this.text = text;
968+ set_position (-1);
969+ }
970+
971+ public void set_entry_cursor (Gdk.Cursor? cursor) {
972+ /* Only child 13 needs to be modified for the cursor - there may be a better way to do this */
973+ get_window ().get_children ().nth_data (13).set_cursor (cursor ?? new Gdk.Cursor (Gdk.CursorType.XTERM));
974+ }
975+
976+ public void set_entry_icon (bool active, string? tooltip = null) {
977+ if (!active)
978+ secondary_icon_pixbuf = null;
979+ else {
980+ secondary_icon_pixbuf = arrow_img;
981+ secondary_icon_tooltip_text = tooltip;
982+ }
983+ }
984+
985+ public double get_all_breadcrumbs_width (out int breadcrumbs_count) {
986+ double total_width = 0.0;
987+ breadcrumbs_count = 0;
988+ foreach (BreadcrumbsElement element in elements) {
989+ if (element.display) {
990+ total_width += element.width;
991+ element.max_width = -1;
992+ breadcrumbs_count++;
993+ }
994+ }
995+ return total_width;
996+ }
997
998 private BreadcrumbsElement? get_element_from_coordinates (int x, int y) {
999 double x_render = 0;
1000@@ -295,39 +455,6 @@
1001 return newpath;
1002 }
1003
1004- protected void add_icon (IconDirectory icon) {
1005- if (icon.gicon != null)
1006- icon.icon = icon_factory.load_symbolic_icon_from_gicon (button_context, icon.gicon, 16);
1007- else
1008- icon.icon = icon_factory.load_symbolic_icon (button_context, icon.icon_name, 16);
1009-
1010- icons.append (icon);
1011- }
1012-
1013- protected void init_clipboard () {
1014- clipboard_actions = new Gtk.ActionGroup ("ClipboardActions");
1015- clipboard_actions.add_actions (action_entries, this);
1016- }
1017-
1018- static const Gtk.ActionEntry[] action_entries = {
1019- /* name, stock id */ { "Cut", Gtk.Stock.CUT,
1020- /* label, accelerator */ null, null,
1021- /* tooltip */ N_("Cut the selected text to the clipboard"),
1022- action_cut },
1023- /* name, stock id */ { "Copy", Gtk.Stock.COPY,
1024- /* label, accelerator */ null, null,
1025- /* tooltip */ N_("Copy the selected text to the clipboard"),
1026- action_copy },
1027- /* name, stock id */ { "Paste", Gtk.Stock.PASTE,
1028- /* label, accelerator */ null, null,
1029- /* tooltip */ N_("Paste the text stored on the clipboard"),
1030- action_paste },
1031- /* name, stock id */ { "Paste Into Folder", Gtk.Stock.PASTE,
1032- /* label, accelerator */ null, null,
1033- /* tooltip */ N_("Paste the text stored on the clipboard"),
1034- action_paste }
1035- };
1036-
1037 /**
1038 * Get the current path of the PathBar, based on the elements that it contains
1039 **/
1040@@ -337,37 +464,36 @@
1041
1042 foreach (BreadcrumbsElement element in elements) {
1043 if (element.display)
1044- strpath += element.text + "/"; /* sometimes, + "/" is useless
1045- * but we are never careful enough */
1046- /* FIXME make sure the comment never happen
1047- * -> it seems to be necessary in all cases */
1048+ strpath += element.text + "/";
1049 }
1050
1051 return strpath;
1052 }
1053-
1054- private void action_paste (Gtk.Action action) {
1055- var display = get_display ();
1056- Gdk.Atom atom = Gdk.SELECTION_CLIPBOARD;
1057- Gtk.Clipboard clipboard = Gtk.Clipboard.get_for_display (display, atom);
1058- clipboard.request_text (request_text);
1059- }
1060-
1061- private void action_copy (Gtk.Action action) {
1062- string? selection = entry.get_selection ();
1063- if (selection != null) { /* else, it means that there is no selection */
1064- var display = get_display ();
1065- Gdk.Atom atom = Gdk.SELECTION_CLIPBOARD;
1066- Gtk.Clipboard clipboard = Gtk.Clipboard.get_for_display (display, atom);
1067- clipboard.set_text (entry.get_selection (), entry.get_selection ().length);
1068- }
1069- }
1070-
1071- private void action_cut (Gtk.Action action) {
1072- action_copy (action);
1073- entry.delete_selection ();
1074- }
1075-
1076+
1077+ /**
1078+ * Gets a properly escaped GLib.File for the given path
1079+ **/
1080+ public File get_file_for_path (string path) {
1081+ string reserved_chars = (GLib.Uri.RESERVED_CHARS_GENERIC_DELIMITERS + GLib.Uri.RESERVED_CHARS_SUBCOMPONENT_DELIMITERS + " ").replace("#", "");
1082+ string newpath = GLib.Uri.unescape_string (path ?? "");
1083+
1084+ /* Format our path so its valid */
1085+ if (newpath == "")
1086+ newpath = "/";
1087+
1088+ if (newpath[0] == '~')
1089+ newpath = newpath.replace("~", Environment.get_home_dir ());
1090+
1091+ if (!newpath.contains("://"))
1092+ newpath = Marlin.ROOT_FS_URI + newpath;
1093+
1094+ newpath = newpath.replace("ssh:", "sftp:");
1095+ newpath = GLib.Uri.escape_string (newpath, reserved_chars, true);
1096+
1097+ File file = File.new_for_commandline_arg (newpath);
1098+ return file;
1099+ }
1100+
1101 /**
1102 * Select the breadcrumb to make a right click. This function check
1103 * where the user click, then, it loads a context menu with the others
1104@@ -377,7 +503,7 @@
1105 * @param event a button event to compute the coords of the new menu.
1106 *
1107 **/
1108- private bool select_bread_from_coord (Gdk.EventButton event) {
1109+ private bool select_bread_from_coord (Gdk.EventButton event) {
1110 var el = get_element_from_coordinates ((int) event.x, (int) event.y);
1111
1112 if (el != null) {
1113@@ -397,104 +523,12 @@
1114 }
1115
1116 return false;
1117- }
1118-
1119- public override bool button_press_event (Gdk.EventButton event) {
1120- foreach (BreadcrumbsElement element in elements)
1121- element.pressed = false;
1122-
1123- var el = get_element_from_coordinates ((int) event.x, (int) event.y);
1124-
1125- if (el != null)
1126- el.pressed = true;
1127-
1128- queue_draw ();
1129-
1130- if (timeout == -1 && event.button == 1) {
1131- timeout = (int) Timeout.add (800, () => {
1132- select_bread_from_coord (event);
1133- timeout = -1;
1134- return false;
1135- });
1136- }
1137-
1138- if (event.button == 2) {
1139- if (el != null) {
1140- selected = elements.index_of (el);
1141- var newpath = get_path_from_element (el);
1142- activate_alternate (newpath);
1143- }
1144- }
1145-
1146- if (event.button == 3)
1147- return select_bread_from_coord(event);
1148-
1149- if (focus) {
1150- event.x -= x_render_saved;
1151- entry.mouse_press_event(event, get_allocated_width() - x_render_saved);
1152- }
1153-
1154- return true;
1155- }
1156-
1157- public override bool button_release_event (Gdk.EventButton event) {
1158- reset_elements_states ();
1159-
1160- if (timeout != -1) {
1161- Source.remove ((uint) timeout);
1162- timeout = -1;
1163- }
1164-
1165- if (event.button == 1) {
1166- var el = get_element_from_coordinates ((int) event.x, (int) event.y);
1167- if (el != null) {
1168- selected = elements.index_of (el);
1169- var newpath = get_path_from_element (el);
1170- print ("%s\n\n\n", newpath);
1171- changed (newpath);
1172- } else {
1173- grab_focus ();
1174- }
1175- }
1176-
1177- if (focus) {
1178- event.x -= x_render_saved;
1179- entry.mouse_release_event(event);
1180- }
1181-
1182- return true;
1183- }
1184-
1185- private void on_entry_enter () {
1186- text = get_elements_path ();
1187-
1188- if (text != "")
1189- changed (text + "/" + entry.text + entry.completion);
1190- else
1191- changed (entry.text + entry.completion);
1192-
1193- //entry.reset();
1194- }
1195-
1196- public override bool key_press_event (Gdk.EventKey event) {
1197- entry.key_press_event (event);
1198- queue_draw ();
1199- return true;
1200- }
1201-
1202- public override bool key_release_event (Gdk.EventKey event) {
1203- entry.key_release_event (event);
1204- queue_draw ();
1205- return true;
1206- }
1207+ }
1208
1209 public virtual string? update_breadcrumbs (string newpath, string breadpath) {
1210 string strloc;
1211
1212- debug ("Update breadcrumb text %s", newpath);
1213-
1214 if (Posix.strncmp (newpath, "./", 2) == 0) {
1215- entry.reset ();
1216 return null;
1217 }
1218
1219@@ -507,7 +541,6 @@
1220 }
1221
1222 return strloc;
1223-
1224 }
1225
1226 /**
1227@@ -517,19 +550,18 @@
1228 * be animated.
1229 **/
1230 public void change_breadcrumbs (string newpath) {
1231- debug ("Change breadcrumbs to %s", newpath);
1232- var explode_protocol = newpath.split ("://");
1233+ var explode_protocol = Uri.unescape_string (newpath).split ("://");
1234
1235 if (explode_protocol.length > 1) {
1236 protocol = explode_protocol[0] + "://";
1237- text = explode_protocol[1];
1238+ current_path = explode_protocol[1];
1239 } else {
1240- text = newpath;
1241+ current_path = newpath;
1242 protocol = Marlin.ROOT_FS_URI;
1243 }
1244
1245 selected = -1;
1246- var breads = text.split ("/");
1247+ var breads = current_path.split ("/");
1248 var newelements = new Gee.ArrayList<BreadcrumbsElement> ();
1249 if (breads.length == 0 || breads[0] == "") {
1250 var element = new BreadcrumbsElement (protocol, left_padding, right_padding);
1251@@ -567,15 +599,6 @@
1252
1253 int max_path = int.min (elements.size, newelements.size);
1254
1255- bool same = true;
1256-
1257- for (int i = 0; i < max_path; i++) {
1258- if (newelements[i].text != elements[i].text) {
1259- same = false;
1260- break;
1261- }
1262- }
1263-
1264 foreach (IconDirectory icon in icons) {
1265 if (icon.protocol && icon.path == protocol) {
1266 newelements[0].set_icon(icon.icon);
1267@@ -637,7 +660,6 @@
1268
1269 elements.clear ();
1270 elements = newelements;
1271- entry.reset ();
1272 }
1273
1274 uint anim = 0;
1275@@ -720,246 +742,76 @@
1276 });
1277 }
1278
1279-/* disabled, waiting for deletion or fix the hardcoded stuff
1280- * or just draw elements by elements with widgets state flags */
1281-#if 0
1282- private void draw_selection (Cairo.Context cr) {
1283- /* If a dir is selected (= mouse hover)*/
1284- if (selected != -1) {
1285- int height = get_allocated_height();
1286- /* FIXME: this block could be cleaned up, +7 and +5 are
1287- * hardcoded. */
1288- double x_hl = y + right_padding + left_padding;
1289-
1290- if (selected > 0) {
1291- foreach(BreadcrumbsElement element in elements) {
1292- if (element.display) {
1293- x_hl += element.real_width;
1294- }
1295-
1296- if (element == elements[selected - 1]) {
1297- break;
1298- }
1299- }
1300- }
1301-
1302- x_hl += 7;
1303- double first_stop = x_hl - 7 * (height / 2 - y)/(height / 2 - height / 3) + 5;
1304- double text_width = (elements[selected].max_width > 0 ? elements[selected].max_width : elements[selected].text_width);
1305-
1306- cr.move_to(first_stop,
1307- y + 1);
1308- cr.line_to(x_hl + 3,
1309- height / 2);
1310- cr.line_to(first_stop,
1311- height - y - 1);
1312-
1313- x_hl += text_width;
1314-
1315- double second_stop = x_hl - 7 * (height / 2 - y)/(height / 2 - height / 3) + 5;
1316- cr.line_to(second_stop,
1317- height - y - 1);
1318- cr.line_to(x_hl + 3,
1319- height / 2);
1320- cr.line_to(second_stop,
1321- y + 1);
1322- cr.close_path();
1323-#if VALA_0_14
1324- Gdk.RGBA color = button_context.get_background_color(Gtk.StateFlags.SELECTED);
1325-#else
1326- Gdk.RGBA color = Gdk.RGBA();
1327- button_context.get_background_color(Gtk.StateFlags.SELECTED, color);
1328-#endif
1329-
1330- Cairo.Pattern pat = new Cairo.Pattern.linear(first_stop, y, second_stop, y);
1331- pat.add_color_stop_rgba(0.7, color.red, color.green, color.blue, 0);
1332- pat.add_color_stop_rgba(1, color.red, color.green, color.blue, 0.6);
1333-
1334- cr.set_source(pat);
1335- cr.fill();
1336- }
1337- }
1338-#endif
1339-
1340- public override bool motion_notify_event (Gdk.EventMotion event) {
1341- int x = (int) event.x;
1342- double x_render = 0;
1343- double x_previous = -10;
1344- selected = -1;
1345- set_tooltip_text ("");
1346-
1347- foreach (BreadcrumbsElement element in elements) {
1348- if (element.display) {
1349- x_render += element.real_width;
1350- if (x <= x_render + 5 && x > x_previous + 5) {
1351- selected = elements.index_of (element);
1352- //TODO doesn't work
1353- set_tooltip_text (_("Go to %s").printf (element.text));
1354- break;
1355- }
1356-
1357- x_previous = x_render;
1358- }
1359- }
1360-
1361- event.x -= x_render_saved;
1362- entry.mouse_motion_event (event, get_allocated_width () - x_render_saved);
1363-
1364- if (event.x > 0 && event.x + x_render_saved < get_allocated_width () - entry.arrow_img.get_width ())
1365- get_window ().set_cursor (new Gdk.Cursor (Gdk.CursorType.XTERM));
1366- else
1367- get_window ().set_cursor (null);
1368-
1369- return true;
1370- }
1371-
1372- public override bool leave_notify_event (Gdk.EventCrossing event) {
1373- selected = -1;
1374- entry.hover = false;
1375- queue_draw ();
1376- get_window ().set_cursor (null);
1377- return false;
1378- }
1379-
1380- public override bool focus_out_event (Gdk.EventFocus event) {
1381- focus = false;
1382- //button_context = button_widget_context;
1383- button_context.set_state (Gtk.StateFlags.NORMAL);
1384- entry.hide ();
1385- return true;
1386- }
1387-
1388- public override bool focus_in_event (Gdk.EventFocus event) {
1389- entry.show ();
1390- //button_context = entry_context;
1391- //button_context.set_state (StateFlags.ACTIVE);
1392- focus = true;
1393- return true;
1394- }
1395-
1396- private void request_text (Gtk.Clipboard clip, string? text) {
1397- if (text != null)
1398- entry.insert (text.replace ("\n", ""));
1399- }
1400-
1401- public double get_all_breadcrumbs_width (out int breadcrumbs_count) {
1402- double total_width = 0.0;
1403- breadcrumbs_count = 0;
1404- foreach (BreadcrumbsElement element in elements) {
1405- if (element.display) {
1406- total_width += element.width;
1407- element.max_width = -1;
1408- breadcrumbs_count++;
1409- }
1410- }
1411- return total_width;
1412- }
1413-
1414- public void reset_elements_states () {
1415- foreach (BreadcrumbsElement element in elements)
1416- element.pressed = false;
1417-
1418- queue_draw ();
1419- }
1420-
1421 public override bool draw (Cairo.Context cr) {
1422 if (button_context_active == null) {
1423 button_context_active = new Gtk.StyleContext ();
1424 button_context_active.set_path(button_context.get_path ());
1425 button_context_active.set_state (Gtk.StateFlags.ACTIVE);
1426 }
1427-
1428- //button_context.set_state (StateFlags.NORMAL);
1429- //propagate_draw (get_child (), cr);
1430+
1431+ base.draw (cr);
1432 double height = get_allocated_height ();
1433 double width = get_allocated_width ();
1434- double margin = y;
1435-
1436- /* Ensure there is an editable area to the right of the breadcrumbs */
1437- double width_marged = width - 2*margin - MINIMUM_LOCATION_BAR_ENTRY_WIDTH;
1438- double height_marged = height - 2*margin;
1439- double x_render = margin;
1440-
1441- /* Draw toolbar background */
1442- button_context.render_background (cr, 0, margin, width, height_marged);
1443-
1444- int breadcrumbs_displayed = 0;
1445- double max_width = get_all_breadcrumbs_width (out breadcrumbs_displayed);
1446-
1447- if (max_width > width_marged) { /* let's check if the breadcrumbs are bigger than the widget */
1448- /* each element must not be bigger than the width/breadcrumbs count */
1449- double max_element_width = width_marged/breadcrumbs_displayed;
1450-
1451+
1452+ if (!is_focus) {
1453+ double margin = y;
1454+
1455+ /* Ensure there is an editable area to the right of the breadcrumbs */
1456+ double width_marged = width - 2*margin - MINIMUM_LOCATION_BAR_ENTRY_WIDTH;
1457+ double height_marged = height - 2*margin;
1458+ double x_render = margin;
1459+ int breadcrumbs_displayed = 0;
1460+ double max_width = get_all_breadcrumbs_width (out breadcrumbs_displayed);
1461+
1462+ if (max_width > width_marged) { /* let's check if the breadcrumbs are bigger than the widget */
1463+ /* each element must not be bigger than the width/breadcrumbs count */
1464+ double max_element_width = width_marged/breadcrumbs_displayed;
1465+
1466+ foreach (BreadcrumbsElement element in elements) {
1467+ if (element.display && element.width < max_element_width) {
1468+ breadcrumbs_displayed --;
1469+ max_element_width += (max_element_width - element.width)/breadcrumbs_displayed;
1470+ }
1471+ }
1472+
1473+ foreach (BreadcrumbsElement element in elements)
1474+ if (element.display && element.width > max_element_width)
1475+ element.max_width = max_element_width - element.left_padding - element.right_padding - element.last_height/2;
1476+ }
1477+
1478+ cr.save ();
1479+ /* Really draw the elements */
1480 foreach (BreadcrumbsElement element in elements) {
1481- if (element.display && element.width < max_element_width) {
1482- breadcrumbs_displayed --;
1483- max_element_width += (max_element_width - element.width)/breadcrumbs_displayed;
1484+ if (element.display) {
1485+ x_render = element.draw (cr, x_render, margin, height_marged, button_context, this);
1486+ /* save element x axis position */
1487+ element.x = x_render - element.real_width;
1488 }
1489 }
1490
1491- foreach (BreadcrumbsElement element in elements)
1492- if (element.display && element.width > max_element_width)
1493- element.max_width = max_element_width - element.left_padding - element.right_padding - element.last_height/2;
1494- }
1495-
1496- cr.save ();
1497- /* Really draw the elements */
1498- foreach (BreadcrumbsElement element in elements) {
1499- if (element.display) {
1500- x_render = element.draw (cr, x_render, margin, height_marged, button_context, this);
1501- /* save element x axis position */
1502- element.x = x_render - element.real_width;
1503- }
1504- }
1505-
1506- /* Draw the old breadcrumbs, only for the animations */
1507- if (view_old)
1508- foreach (BreadcrumbsElement element in newbreads)
1509- if (element.display)
1510- x_render = element.draw(cr, x_render, margin, height_marged, button_context, this);
1511-
1512- //draw_selection(cr);
1513-
1514- x_render_saved = x_render + space_breads/2;
1515-
1516- cr.restore ();
1517- /* Draw frame: it must be done at the end to be on the background drawn by pressed breadcrumbs */
1518-
1519- if (focus) {
1520- cr.save ();
1521- x_render -= height_marged/2 + 3;
1522-
1523- cr.move_to (0, 0);
1524- cr.line_to (0, y + height_marged + 3);
1525- cr.line_to (x_render, y + height_marged + 3);
1526- cr.line_to (x_render, y + height_marged);
1527- cr.line_to (x_render + height_marged / 2, y + height_marged / 2);
1528- cr.line_to (x_render, y);
1529- cr.line_to (x_render, 0);
1530- cr.close_path ();
1531- cr.clip ();
1532- button_context.render_frame (cr, 0, margin, width, height_marged);
1533- cr.restore ();
1534- cr.save ();
1535-
1536- cr.move_to (x_render, get_allocated_height());
1537- cr.line_to (x_render, y + height_marged);
1538- cr.line_to (x_render + height_marged / 2, y + height_marged / 2);
1539- cr.line_to (x_render, y);
1540- cr.line_to (x_render, 0);
1541- cr.line_to (get_allocated_width(), 0);
1542- cr.line_to (get_allocated_width(), y + height_marged);
1543- cr.close_path ();
1544- cr.clip ();
1545- button_context_active.render_background (cr, 0, margin, width, height_marged);
1546- button_context_active.render_frame (cr, 0, margin, width, height_marged);
1547- cr.restore ();
1548- x_render += height_marged / 2 + 3;
1549+ /* Draw the old breadcrumbs, only for the animations */
1550+ if (view_old)
1551+ foreach (BreadcrumbsElement element in newbreads)
1552+ if (element.display)
1553+ x_render = element.draw(cr, x_render, margin, height_marged, button_context, this);
1554+
1555+ cr.restore ();
1556 } else {
1557- button_context.render_frame (cr, 0, margin, width, height_marged);
1558+ if (text_completion != "") {
1559+ int layout_width, layout_height;
1560+ double text_width, text_height;
1561+
1562+ cr.set_source_rgba (0, 0, 0, 0.4);
1563+ Pango.Layout layout = create_pango_layout (text);
1564+ layout.get_size (out layout_width, out layout_height);
1565+ text_width = Pango.units_to_double (layout_width);
1566+ text_height = Pango.units_to_double (layout_height);
1567+ cr.move_to (text_width + 4, text_height / 4);
1568+ layout.set_text (text_completion, -1);
1569+ Pango.cairo_show_layout (cr, layout);
1570+ }
1571 }
1572-
1573- entry.draw (cr, x_render + space_breads / 2, height, width - x_render, this, button_context);
1574+
1575 return true;
1576 }
1577
1578@@ -977,4 +829,4 @@
1579 var file = File.new_for_commandline_arg (newpath);
1580 return file.get_parent () != null;
1581 }
1582-}
1583+}
1584\ No newline at end of file
1585
1586=== removed directory 'libwidgets/tests'
1587=== removed file 'libwidgets/tests/CMakeLists.txt'
1588--- libwidgets/tests/CMakeLists.txt 2014-01-24 18:36:41 +0000
1589+++ libwidgets/tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
1590@@ -1,27 +0,0 @@
1591-include_directories(../../libcore/)
1592-find_package(Vala REQUIRED)
1593-include(ValaPrecompile)
1594-vala_precompile(VALA_C widgets-test
1595- tests-pathbar.vala
1596-PACKAGES
1597- gtk+-3.0
1598- gee-0.8
1599- posix
1600- marlinwidgets
1601-OPTIONS
1602- --thread
1603- --vapidir=${CMAKE_BINARY_DIR}/libwidgets/
1604- --vapidir=${CMAKE_BINARY_DIR}/libcore/
1605-)
1606-
1607-find_package(PkgConfig)
1608-pkg_check_modules(DEPS REQUIRED gtk+-3.0 gee-0.8)
1609-
1610-add_definitions(${DEPS_CFLAGS} ${DEPS_CFLAGS_OTHER})
1611-
1612-add_executable(widgets-test ${VALA_C})
1613-
1614-link_directories(${DEPS_LIBRARY_DIRS})
1615-target_link_libraries(widgets-test marlinwidgets ${DEPS_LIBRARIES})
1616-add_dependencies(widgets-test marlinwidgets)
1617-add_test_executable(widgets-test)
1618\ No newline at end of file
1619
1620=== removed file 'libwidgets/tests/tests-pathbar.vala'
1621--- libwidgets/tests/tests-pathbar.vala 2014-03-27 10:39:12 +0000
1622+++ libwidgets/tests/tests-pathbar.vala 1970-01-01 00:00:00 +0000
1623@@ -1,213 +0,0 @@
1624-/*
1625- * Copyright (c) 2011 Lucas Baudin <xapantu@gmail.com>
1626- *
1627- * Marlin is free software; you can redistribute it and/or
1628- * modify it under the terms of the GNU General Public License as
1629- * published by the Free Software Foundation; either version 2 of the
1630- * License, or (at your option) any later version.
1631- *
1632- * Marlin is distributed in the hope that it will be useful,
1633- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1634- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1635- * General Public License for more details.
1636- *
1637- * You should have received a copy of the GNU General Public
1638- * License along with this program; see the file COPYING. If not,
1639- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1640- * Boston, MA 02111-1307, USA.
1641- *
1642- */
1643-
1644-public class Breadcrumbs : Marlin.View.Chrome.BasePathBar {
1645- public Breadcrumbs (Gtk.UIManager ui) {}
1646- public override void load_right_click_menu (double x, double y) {}
1647- protected override void on_file_droped (List<GLib.File> uris, GLib.File target_file, Gdk.DragAction real_action) {}
1648-}
1649-
1650-void add_pathbar_tests () {
1651-/* TODO: they are broken currently */
1652- Test.add_func ("/marlin/pathbar/general", () => {
1653- Test.log_set_fatal_handler (() => { return false; });
1654- var breads = new Breadcrumbs (new Gtk.UIManager ());
1655- var bread_entry = new Marlin.View.Chrome.BreadcrumbsEntry ();
1656- assert (bread_entry is Marlin.View.Chrome.BreadcrumbsEntry);
1657- assert (bread_entry.text == "");
1658-#if VALA_0_24
1659- Gdk.Event e = new Gdk.Event (Gdk.EventType.KEY_PRESS);
1660- Gdk.EventKey event = e.key;
1661-#else
1662- Gdk.EventKey event = Gdk.EventKey ();
1663-#endif
1664- event.window = breads.get_window ();
1665- event.keyval = 0x061; /* a */
1666- bread_entry.key_press_event (event);
1667- assert (bread_entry.text == "a");
1668- bread_entry.key_press_event (event);
1669- assert (bread_entry.text == "aa");
1670- assert (bread_entry.cursor == 2);
1671-
1672- event.keyval = 0xff51; /* left */
1673- bread_entry.key_press_event (event);
1674- assert (bread_entry.cursor == 1);
1675- bread_entry.key_press_event (event);
1676- assert (bread_entry.cursor == 0);
1677- bread_entry.key_press_event (event);
1678- assert (bread_entry.cursor == 0);
1679- event.keyval = 0xff53; /* right */
1680- bread_entry.key_press_event (event);
1681- assert (bread_entry.cursor == 1);
1682-
1683- /* insertion in the middle */
1684- event.keyval = 0x062; /* b */
1685- bread_entry.key_press_event (event);
1686- assert (bread_entry.text == "aba");
1687- event.keyval = 0x063; /* c */
1688- bread_entry.key_press_event (event);
1689- assert (bread_entry.text == "abca");
1690-
1691- /* insertion at the end */
1692- assert (bread_entry.cursor == 3);
1693- event.keyval = 0xff53; /* right */
1694- bread_entry.key_press_event (event);
1695- assert (bread_entry.cursor == 4);
1696- event.keyval = 0x064; /* d */
1697- bread_entry.key_press_event (event);
1698- assert (bread_entry.text == "abcad");
1699-
1700- /* backspace */
1701- event.keyval = 0xff08; /* backspace */
1702- bread_entry.key_press_event (event);
1703- assert (bread_entry.text == "abca");
1704- event.keyval = 0xff51; /* left */
1705- bread_entry.key_press_event (event);
1706- event.keyval = 0xff08; /* backspace */
1707- bread_entry.key_press_event (event);
1708- assert (bread_entry.text == "aba");
1709- event.keyval = 0xff53; /* right */
1710- bread_entry.key_press_event (event);
1711- event.keyval = 0x063; /* c */
1712- bread_entry.key_press_event (event);
1713- assert (bread_entry.text == "abac");
1714- event.keyval = 0xff51; /* left */
1715- bread_entry.key_press_event (event);
1716-
1717- /* selection */
1718- event.state = Gdk.ModifierType.SHIFT_MASK;
1719- event.keyval = 0xff51; /* left */
1720- bread_entry.key_press_event (event);
1721- assert (bread_entry.get_selection () == "a");
1722- bread_entry.key_press_event (event);
1723- assert (bread_entry.get_selection () == "ba");
1724- bread_entry.key_press_event (event);
1725- assert (bread_entry.get_selection () == "aba");
1726- bread_entry.key_press_event (event);
1727- assert (bread_entry.get_selection () == "aba");
1728- event.keyval = 0xff53; /* right */
1729- bread_entry.key_press_event (event);
1730- assert (bread_entry.get_selection () == "ba");
1731- bread_entry.key_press_event (event);
1732- assert (bread_entry.get_selection () == "a");
1733- bread_entry.key_press_event (event);
1734- assert (bread_entry.get_selection () == "");
1735- bread_entry.key_press_event (event);
1736- assert (bread_entry.get_selection () == "c");
1737- event.keyval = 0xff51; /* left */
1738- bread_entry.key_press_event (event);
1739- assert (bread_entry.get_selection () == "");
1740- });
1741-
1742- Test.add_func ("/marlin/pathbar/start-selection", () => {
1743- Test.log_set_fatal_handler (() => { return false; });
1744- var breads = new Breadcrumbs (new Gtk.UIManager ());
1745- var bread_entry = new Marlin.View.Chrome.BreadcrumbsEntry ();
1746- assert (bread_entry is Marlin.View.Chrome.BreadcrumbsEntry);
1747- assert (bread_entry.text == "");
1748- bread_entry.text = "abdcefghij/";
1749- bread_entry.cursor = ("abdcefghij/").length;
1750-#if VALA_0_24
1751- Gdk.Event e = new Gdk.Event (Gdk.EventType.KEY_PRESS);
1752- Gdk.EventKey event = e.key;
1753-#else
1754- Gdk.EventKey event = Gdk.EventKey ();
1755-#endif
1756- event.state = Gdk.ModifierType.SHIFT_MASK;
1757- event.window = breads.get_window ();
1758- event.keyval = 0xff51; /* left */
1759- bread_entry.key_press_event (event);
1760- assert (bread_entry.get_selection () == "/");
1761- });
1762-
1763- Test.add_func ("/marlin/pathbar/backspace-without-text", () => {
1764- Test.log_set_fatal_handler (() => { return false; });
1765- var breads = new Breadcrumbs (new Gtk.UIManager ());
1766- var bread_entry = new Marlin.View.Chrome.BreadcrumbsEntry ();
1767- assert (bread_entry is Marlin.View.Chrome.BreadcrumbsEntry);
1768- assert (bread_entry.text == "");
1769-#if VALA_0_24
1770- Gdk.Event e = new Gdk.Event (Gdk.EventType.KEY_PRESS);
1771- Gdk.EventKey event = e.key;
1772-#else
1773- Gdk.EventKey event = Gdk.EventKey ();
1774-#endif
1775- event.window = breads.get_window ();
1776- event.keyval = 0xff08; /* backspace */
1777- bread_entry.key_press_event (event);
1778- assert (bread_entry.get_selection () == null);
1779- });
1780-
1781- Test.add_func ("/marlin/pathbar/backspace-keypress", () => {
1782- Test.log_set_fatal_handler (() => { return false; });
1783- var breads = new Breadcrumbs (new Gtk.UIManager());
1784- Marlin.View.Chrome.BreadcrumbsEntry bread_entry = breads.entry;
1785- assert (bread_entry is Marlin.View.Chrome.BreadcrumbsEntry);
1786- assert (bread_entry.text == "");
1787-#if VALA_0_24
1788- Gdk.Event e = new Gdk.Event (Gdk.EventType.KEY_PRESS);
1789- Gdk.EventKey event = e.key;
1790-#else
1791- Gdk.EventKey event = Gdk.EventKey ();
1792-#endif
1793- event.window = breads.get_window ();
1794- event.keyval = 0xff08; /* backspace */
1795- breads.key_press_event (event);
1796- assert (bread_entry.get_selection () == null);
1797-
1798- event.keyval = 0x061; /* a */
1799- breads.change_breadcrumbs ("");
1800- breads.change_breadcrumbs ("/");
1801- });
1802-
1803- Test.add_func ("/marlin/pathbar/go-to-trash", () => {
1804- Test.log_set_fatal_handler (() => { return false; });
1805- var breads = new Breadcrumbs (new Gtk.UIManager ());
1806- breads.change_breadcrumbs ("trash:///");
1807- breads.change_breadcrumbs ("/home/there");
1808- });
1809-
1810- Test.add_func ("/marlin/pathbar/non-file-system-location", () => {
1811- Test.log_set_fatal_handler (() => { return false; });
1812- var breads = new Breadcrumbs (new Gtk.UIManager ());
1813- breads.change_breadcrumbs ("trash://");
1814- breads.change_breadcrumbs ("trash://dir/folder/");
1815- assert (breads.get_elements_path () == "trash://dir/folder/");
1816- breads.change_breadcrumbs ("ftp://test@test.com/dir/folder/");
1817- assert (breads.get_elements_path () == "ftp://test@test.com/dir/folder/");
1818- });
1819-
1820-}
1821-
1822-void main (string[] args) {
1823- Gtk.init (ref args);
1824- Test.init (ref args);
1825- //Preferences.settings = new GLib.Settings("org.gnome.marlin.preferences");
1826-
1827- add_pathbar_tests ();
1828-
1829- Idle.add (() => {
1830- Test.run ();
1831- Gtk.main_quit ();
1832- return false;
1833- }
1834- );
1835- Gtk.main ();
1836-}
1837
1838=== modified file 'src/View/Chrome/TopMenu.vala'
1839--- src/View/Chrome/TopMenu.vala 2014-04-25 00:31:37 +0000
1840+++ src/View/Chrome/TopMenu.vala 2014-06-14 19:18:12 +0000
1841@@ -62,18 +62,15 @@
1842 win.current_tab.slot.view_box.grab_focus ();
1843 });
1844
1845- location_bar.activate.connect (() => {
1846- File file = File.new_for_commandline_arg (GLib.Uri.escape_string (
1847- location_bar.path,
1848- (GLib.Uri.RESERVED_CHARS_GENERIC_DELIMITERS + GLib.Uri.RESERVED_CHARS_SUBCOMPONENT_DELIMITERS).replace("#", ""),
1849- false));
1850+ location_bar.activate.connect ((file) => {
1851 win.current_tab.path_changed (file);
1852 });
1853
1854- location_bar.activate_alternate.connect ((a) => {
1855- win.add_tab (File.new_for_commandline_arg (a));
1856+ location_bar.activate_alternate.connect ((file) => {
1857+ win.add_tab (file);
1858 });
1859
1860+
1861 location_bar.show_all ();
1862 view_switcher.margin_right = 20;
1863 pack_start (location_bar);
1864@@ -81,4 +78,4 @@
1865 show ();
1866 }
1867 }
1868-}
1869+}
1870\ No newline at end of file
1871
1872=== modified file 'src/View/LocationBar.vala'
1873--- src/View/LocationBar.vala 2014-05-13 21:10:32 +0000
1874+++ src/View/LocationBar.vala 2014-06-14 19:18:12 +0000
1875@@ -27,10 +27,10 @@
1876 private string _path;
1877 public new string path {
1878 set {
1879- var new_path = value;
1880+ var new_path = GLib.Uri.unescape_string (value);
1881 _path = new_path;
1882- if (!bread.focus && !win.freeze_view_changes) {
1883- bread.entry.reset ();
1884+ if (!bread.is_focus && !win.freeze_view_changes) {
1885+ bread.text = "";
1886 bread.change_breadcrumbs (new_path);
1887 }
1888 }
1889@@ -41,8 +41,8 @@
1890
1891 Marlin.View.Window win;
1892
1893- public new signal void activate ();
1894- public signal void activate_alternate (string path);
1895+ public new signal void activate (GLib.File file);
1896+ public signal void activate_alternate (GLib.File file);
1897 public signal void escape ();
1898
1899 public override void get_preferred_width (out int minimum_width, out int natural_width) {
1900@@ -55,8 +55,8 @@
1901 bread = new Breadcrumbs (ui, win);
1902 bread.escape.connect (() => { escape(); });
1903
1904- bread.changed.connect (on_bread_changed);
1905- bread.activate_alternate.connect ((a) => { activate_alternate(a); });
1906+ bread.path_changed.connect (on_path_changed);
1907+ bread.activate_alternate.connect ((file) => { activate_alternate(file); });
1908
1909 margin_top = 4;
1910 margin_bottom = 4;
1911@@ -65,7 +65,7 @@
1912 pack_start (bread, true, true, 0);
1913 }
1914
1915- private void on_bread_changed (string changed) {
1916+ private void on_path_changed (File file) {
1917 if (win.freeze_view_changes)
1918 return;
1919
1920@@ -75,14 +75,7 @@
1921 else
1922 win.current_tab.slot.view_box.grab_focus ();
1923
1924- //_path = changed;
1925- path = changed;
1926- activate();
1927-
1928- /* This prevents that the location bar is left in a weird state
1929- * when going from a non-existent folder to another one underneath. */
1930- bread.entry.reset ();
1931- bread.change_breadcrumbs (changed);
1932+ activate(file);
1933 }
1934 }
1935
1936@@ -98,7 +91,7 @@
1937
1938 /* Used for the context menu we show when there is a right click */
1939 GOF.Directory.Async files_menu = null;
1940-
1941+
1942 bool autocompleted = false;
1943
1944 Marlin.View.Window win;
1945@@ -190,35 +183,45 @@
1946 IconDirectory icon = {"/", Marlin.ICON_FILESYSTEM_SYMBOLIC, false, null, null, null, false, null};
1947 icon.exploded = {"/"};
1948 add_icon (icon);
1949+
1950+ up.connect (() => {
1951+ File file = get_file_for_path (text);
1952+ File parent = file.get_parent ();
1953+
1954+ if (parent != null && file.get_uri () != parent.get_uri ())
1955+ change_breadcrumbs (parent.get_uri ());
1956+
1957+ win.current_tab.up ();
1958+ grab_focus ();
1959+ });
1960
1961- entry.down.connect (() => {
1962- /* focus back the view */
1963+ down.connect (() => {
1964+ // focus back the view
1965 if (win.current_tab.content_shown)
1966 win.current_tab.content.grab_focus ();
1967 else
1968 win.current_tab.slot.view_box.grab_focus ();
1969 });
1970
1971- entry.completed.connect (() => {
1972- string path = get_elements_path ();
1973- update_breadcrumbs (entry.text, path);
1974+ completed.connect (() => {
1975+ string path = "";
1976+ string newpath = update_breadcrumbs (get_file_for_path (text).get_uri (), path);
1977+
1978+ foreach (BreadcrumbsElement element in elements) {
1979+ if (!element.hidden)
1980+ path += element.text + "/";
1981+ }
1982+
1983+ if (path != newpath)
1984+ change_breadcrumbs (newpath);
1985+
1986 grab_focus ();
1987 });
1988-
1989- menu = new Gtk.Menu ();
1990- menu.show_all ();
1991-
1992+
1993 need_completion.connect (on_need_completion);
1994- }
1995-
1996- protected void merge_in_clipboard_actions () {
1997- ui.insert_action_group (clipboard_actions, 0);
1998- ui.ensure_update ();
1999- }
2000-
2001- protected void merge_out_clipboard_actions () {
2002- ui.remove_action_group (clipboard_actions);
2003- ui.ensure_update ();
2004+
2005+ menu = new Gtk.Menu ();
2006+ menu.show_all ();
2007 }
2008
2009 /**
2010@@ -230,56 +233,59 @@
2011 *
2012 **/
2013 private void on_file_loaded(GOF.File file) {
2014- if(file.is_folder () && file.get_display_name ().length > to_search.length) {
2015- if (file.get_display_name ().ascii_ncasecmp (to_search, to_search.length) == 0) {
2016+ string file_display_name = GLib.Uri.unescape_string (file.get_display_name ());
2017+ if(file.is_folder () && file_display_name.length > to_search.length) {
2018+ if (file_display_name.ascii_ncasecmp (to_search, to_search.length) == 0) {
2019 if (!autocompleted) {
2020- entry.completion = file.get_display_name ().slice (to_search.length, file.get_display_name ().length);
2021+ text_completion = file_display_name.slice (to_search.length, file_display_name.length);
2022 autocompleted = true;
2023 } else {
2024- string file_complet = file.get_display_name ().slice (to_search.length, file.get_display_name ().length);
2025+ string file_complet = file_display_name.slice (to_search.length, file_display_name.length);
2026 string to_add = "";
2027- for (int i = 0; i < (entry.completion.length > file_complet.length ? file_complet.length : entry.completion.length); i++) {
2028- if (entry.completion[i] == file_complet[i])
2029- to_add += entry.completion[i].to_string ();
2030+ for (int i = 0; i < (text_completion.length > file_complet.length ? file_complet.length : text_completion.length); i++) {
2031+ if (text_completion[i] == file_complet[i])
2032+ to_add += text_completion[i].to_string ();
2033 else
2034 break;
2035 }
2036- entry.completion = to_add;
2037+ text_completion = to_add;
2038+ multiple_completions = true;
2039 }
2040+
2041 /* autocompletion is case insensitive so we have to change the first completed
2042 * parts: the entry.text.
2043 */
2044- string str = entry.text.slice (0, entry.text.length - to_search.length);
2045- if (str == null)
2046- str = "";
2047- entry.text = str + file.get_display_name ().slice (0, to_search.length);
2048+ string str = text.slice (0, text.length - to_search.length);
2049+ if (str != null && !multiple_completions) {
2050+ text = str + file.get_display_name ().slice (0, to_search.length);
2051+ set_position (-1);
2052+ }
2053 }
2054 }
2055 }
2056
2057 public void on_need_completion () {
2058- to_search = "";
2059- string path = get_elements_path ();
2060- string[] stext = entry.text.split ("/");
2061- int stext_len = stext.length;
2062- if (stext_len > 0)
2063- to_search = stext[stext.length -1];
2064+ File file = get_file_for_path (text);
2065+ to_search = file.get_basename ();
2066
2067- entry.completion = "";
2068 autocompleted = false;
2069-
2070- path += entry.text;
2071- message ("path %s to_search %s", path, to_search);
2072- if (to_search != "")
2073- path = Marlin.Utils.get_parent (path);
2074-
2075- if (path != null && path.length > 0) {
2076- var directory = File.new_for_uri (path);
2077- files = GOF.Directory.Async.from_gfile (directory);
2078- if (files.file.exists) {
2079+ multiple_completions = false;
2080+
2081+ if (to_search != "" && file.has_parent (null))
2082+ file = file.get_parent ();
2083+ else
2084+ return;
2085+
2086+ var directory = file;
2087+ var files_cache = files;
2088+
2089+ files = GOF.Directory.Async.from_gfile (directory);
2090+ if (files.file.exists) {
2091+ /* Verify that we got a new instance of files so we do not double up events */
2092+ if (files_cache != files)
2093 files.file_loaded.connect (on_file_loaded);
2094- files.load ();
2095- }
2096+
2097+ files.load ();
2098 }
2099 }
2100
2101@@ -363,7 +369,7 @@
2102 file.launch (win.get_screen (), app);
2103 }
2104
2105- protected override void on_file_droped (List<GLib.File> uris, GLib.File target_file, Gdk.DragAction real_action) {
2106+ protected override void on_file_dropped (List<GLib.File> uris, GLib.File target_file, Gdk.DragAction real_action) {
2107 Marlin.FileOperations.copy_move(uris, null, target_file, real_action);
2108 }
2109
2110@@ -376,18 +382,6 @@
2111 return strloc;
2112 }
2113
2114- public override bool focus_out_event (Gdk.EventFocus event) {
2115- base.focus_out_event (event);
2116- merge_out_clipboard_actions ();
2117- return true;
2118- }
2119-
2120- public override bool focus_in_event (Gdk.EventFocus event) {
2121- base.focus_in_event (event);
2122- merge_in_clipboard_actions ();
2123- return true;
2124- }
2125-
2126 private void get_menu_position (Gtk.Menu menu, out int x, out int y, out bool push_in) {
2127 x = (int) menu_x_root;
2128 y = (int) menu_y_root;
2129@@ -414,4 +408,4 @@
2130 Gtk.get_current_event_time ());
2131 }
2132 }
2133-}
2134+}
2135\ No newline at end of file
2136
2137=== modified file 'src/View/ViewContainer.vala'
2138--- src/View/ViewContainer.vala 2014-04-30 09:13:59 +0000
2139+++ src/View/ViewContainer.vala 2014-06-14 19:18:12 +0000
2140@@ -75,8 +75,7 @@
2141
2142 path_changed.connect ((myfile) => {
2143 /* location didn't change, do nothing */
2144- if (slot != null && myfile != null && slot.directory.file.exists
2145- && slot.location.equal (myfile))
2146+ if (slot != null && myfile != null && slot.directory.file.exists && slot.location.equal (myfile))
2147 return;
2148
2149 change_view(view_mode, myfile);
2150@@ -322,7 +321,7 @@
2151 // You see if I would just use back(n) the reference to n would be passed
2152 // in the clusure, restulting in a value of n which would always be n=1. So
2153 // by introducting a new variable I can bypass this anoyance.
2154- var item = new Gtk.MenuItem.with_label (path);
2155+ var item = new Gtk.MenuItem.with_label (GLib.Uri.unescape_string (path));
2156 item.activate.connect (() => { back(cn); });
2157 back_menu.insert (item, -1);
2158 }
2159@@ -338,7 +337,7 @@
2160 var n = 1;
2161 foreach (var path in list) {
2162 int cn = n++; // For explenation look up
2163- var item = new Gtk.MenuItem.with_label (path);
2164+ var item = new Gtk.MenuItem.with_label (GLib.Uri.unescape_string (path));
2165 item.activate.connect (() => forward (cn));
2166 forward_menu.insert (item, -1);
2167 }
2168@@ -353,4 +352,4 @@
2169 }
2170
2171 }
2172-}
2173+}
2174\ No newline at end of file
2175
2176=== modified file 'src/View/Window.vala'
2177--- src/View/Window.vala 2014-06-06 18:51:14 +0000
2178+++ src/View/Window.vala 2014-06-14 19:18:12 +0000
2179@@ -243,6 +243,12 @@
2180 /*/
2181 /* Connect and abstract signals to local ones
2182 /*/
2183+ key_press_event.connect ((event) => {
2184+ if (top_menu.location_bar.bread.is_focus)
2185+ return top_menu.location_bar.bread.key_press_event (event);
2186+
2187+ return false;
2188+ });
2189
2190 window_state_event.connect ((event) => {
2191 if ((bool) event.changed_mask & Gdk.WindowState.MAXIMIZED) {
2192@@ -861,4 +867,4 @@
2193
2194 };
2195 }
2196-}
2197+}
2198\ No newline at end of file

Subscribers

People subscribed via source and target branches

to all changes: