Merge lp:~jeremywootten/pantheon-files/fix-1399674-linear-selection-in-icon-view into lp:~elementary-apps/pantheon-files/trunk
- fix-1399674-linear-selection-in-icon-view
- Merge into trunk
Proposed by
Jeremy Wootten
Status: | Merged |
---|---|
Approved by: | Cody Garver |
Approved revision: | 2050 |
Merged at revision: | 2053 |
Proposed branch: | lp:~jeremywootten/pantheon-files/fix-1399674-linear-selection-in-icon-view |
Merge into: | lp:~elementary-apps/pantheon-files/trunk |
Diff against target: |
321 lines (+215/-8) 3 files modified
src/View/AbstractDirectoryView.vala (+78/-8) src/View/AbstractTreeView.vala (+2/-0) src/View/IconView.vala (+135/-0) |
To merge this branch: | bzr merge lp:~jeremywootten/pantheon-files/fix-1399674-linear-selection-in-icon-view |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
elementary Apps team | Pending | ||
Review via email: mp+283740@code.launchpad.net |
Commit message
Implement linear selection with Shift-Click in IconView (lp:1399674)
Description of the change
This branch overrides the native Shift-Click behaviour of Gtk.IconView to provide a behaviour similar to that of ListView and ColumnView.
To post a comment you must log in.
- 2050. By Jeremy Wootten
-
Enable linear selection with cursor keys; refine linear selection to match ListView more closely
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/View/AbstractDirectoryView.vala' | |||
2 | --- src/View/AbstractDirectoryView.vala 2016-01-23 12:37:18 +0000 | |||
3 | +++ src/View/AbstractDirectoryView.vala 2016-02-06 15:43:57 +0000 | |||
4 | @@ -201,6 +201,10 @@ | |||
5 | 201 | gtk_tree_model_get(): this function increases the reference | 201 | gtk_tree_model_get(): this function increases the reference |
6 | 202 | count of the file object.*/ | 202 | count of the file object.*/ |
7 | 203 | protected GLib.List<GOF.File> selected_files = null; | 203 | protected GLib.List<GOF.File> selected_files = null; |
8 | 204 | /* support for linear selection mode in icon view */ | ||
9 | 205 | protected bool previous_selection_was_linear = false; | ||
10 | 206 | protected Gtk.TreePath? previous_linear_selection_path = null; | ||
11 | 207 | protected int previous_linear_selection_direction = 0; | ||
12 | 204 | 208 | ||
13 | 205 | private GLib.List<GLib.File> templates = null; | 209 | private GLib.List<GLib.File> templates = null; |
14 | 206 | 210 | ||
15 | @@ -2533,6 +2537,19 @@ | |||
16 | 2533 | bool in_trash = slot.location.has_uri_scheme ("trash"); | 2537 | bool in_trash = slot.location.has_uri_scheme ("trash"); |
17 | 2534 | bool in_recent = slot.location.has_uri_scheme ("recent"); | 2538 | bool in_recent = slot.location.has_uri_scheme ("recent"); |
18 | 2535 | 2539 | ||
19 | 2540 | |||
20 | 2541 | /* Implement linear selection in Icon View with cursor keys */ | ||
21 | 2542 | bool linear_select_required = false; | ||
22 | 2543 | if (!no_mods && !only_control_pressed) { | ||
23 | 2544 | if (only_shift_pressed && (this is IconView)) { | ||
24 | 2545 | linear_select_required = true; | ||
25 | 2546 | } else { | ||
26 | 2547 | previous_selection_was_linear = false; | ||
27 | 2548 | } | ||
28 | 2549 | } else { | ||
29 | 2550 | previous_selection_was_linear = false; | ||
30 | 2551 | } | ||
31 | 2552 | |||
32 | 2536 | switch (event.keyval) { | 2553 | switch (event.keyval) { |
33 | 2537 | case Gdk.Key.F10: | 2554 | case Gdk.Key.F10: |
34 | 2538 | if (only_control_pressed) { | 2555 | if (only_control_pressed) { |
35 | @@ -2628,6 +2645,32 @@ | |||
36 | 2628 | 2645 | ||
37 | 2629 | return true; | 2646 | return true; |
38 | 2630 | 2647 | ||
39 | 2648 | case Gdk.Key.Up: | ||
40 | 2649 | case Gdk.Key.Down: | ||
41 | 2650 | case Gdk.Key.Left: | ||
42 | 2651 | case Gdk.Key.Right: | ||
43 | 2652 | |||
44 | 2653 | if (linear_select_required && selected_files.length () > 0) { /* Only true for Icon View */ | ||
45 | 2654 | Gtk.TreePath? path = get_path_at_cursor (); | ||
46 | 2655 | if (path != null) { | ||
47 | 2656 | if (event.keyval == Gdk.Key.Right) { | ||
48 | 2657 | path.next (); | ||
49 | 2658 | } else if (event.keyval == Gdk.Key.Left) { | ||
50 | 2659 | path.prev (); | ||
51 | 2660 | } else if (event.keyval == Gdk.Key.Up) { | ||
52 | 2661 | path = up (path); | ||
53 | 2662 | } else if (event.keyval == Gdk.Key.Down) { | ||
54 | 2663 | path = down (path); | ||
55 | 2664 | } | ||
56 | 2665 | linear_select_path (path); | ||
57 | 2666 | return true; | ||
58 | 2667 | } | ||
59 | 2668 | } else { | ||
60 | 2669 | previous_selection_was_linear = false; | ||
61 | 2670 | previous_linear_selection_path = null; | ||
62 | 2671 | } | ||
63 | 2672 | break; | ||
64 | 2673 | |||
65 | 2631 | default: | 2674 | default: |
66 | 2632 | break; | 2675 | break; |
67 | 2633 | } | 2676 | } |
68 | @@ -2938,11 +2981,13 @@ | |||
69 | 2938 | var mods = event.state & Gtk.accelerator_get_default_mod_mask (); | 2981 | var mods = event.state & Gtk.accelerator_get_default_mod_mask (); |
70 | 2939 | bool no_mods = (mods == 0); | 2982 | bool no_mods = (mods == 0); |
71 | 2940 | bool control_pressed = ((mods & Gdk.ModifierType.CONTROL_MASK) != 0); | 2983 | bool control_pressed = ((mods & Gdk.ModifierType.CONTROL_MASK) != 0); |
72 | 2984 | bool shift_pressed = ((mods & Gdk.ModifierType.SHIFT_MASK) != 0); | ||
73 | 2941 | bool other_mod_pressed = (((mods & ~Gdk.ModifierType.SHIFT_MASK) & ~Gdk.ModifierType.CONTROL_MASK) != 0); | 2985 | bool other_mod_pressed = (((mods & ~Gdk.ModifierType.SHIFT_MASK) & ~Gdk.ModifierType.CONTROL_MASK) != 0); |
74 | 2942 | bool only_control_pressed = control_pressed && !other_mod_pressed; /* Shift can be pressed */ | 2986 | bool only_control_pressed = control_pressed && !other_mod_pressed; /* Shift can be pressed */ |
76 | 2943 | 2987 | bool only_shift_pressed = shift_pressed && !control_pressed && !other_mod_pressed; | |
77 | 2944 | bool path_selected = (path != null ? path_is_selected (path) : false); | 2988 | bool path_selected = (path != null ? path_is_selected (path) : false); |
78 | 2945 | bool on_blank = (click_zone == ClickZone.BLANK_NO_PATH || click_zone == ClickZone.BLANK_PATH); | 2989 | bool on_blank = (click_zone == ClickZone.BLANK_NO_PATH || click_zone == ClickZone.BLANK_PATH); |
79 | 2990 | bool linear_select_required = false; | ||
80 | 2946 | 2991 | ||
81 | 2947 | /* Block drag and drop to allow rubberbanding and prevent unwanted effects of | 2992 | /* Block drag and drop to allow rubberbanding and prevent unwanted effects of |
82 | 2948 | * dragging on blank areas | 2993 | * dragging on blank areas |
83 | @@ -2951,8 +2996,16 @@ | |||
84 | 2951 | 2996 | ||
85 | 2952 | /* Handle un-modified clicks or control-clicks here else pass on. | 2997 | /* Handle un-modified clicks or control-clicks here else pass on. |
86 | 2953 | */ | 2998 | */ |
87 | 2999 | linear_select_required = false; | ||
88 | 2954 | if (!no_mods && !only_control_pressed) { | 3000 | if (!no_mods && !only_control_pressed) { |
90 | 2955 | return window.button_press_event (event); | 3001 | if (only_shift_pressed && (this is IconView)) { |
91 | 3002 | linear_select_required = true; | ||
92 | 3003 | } else { | ||
93 | 3004 | previous_selection_was_linear = false; | ||
94 | 3005 | return window.button_press_event (event); | ||
95 | 3006 | } | ||
96 | 3007 | } else { | ||
97 | 3008 | previous_selection_was_linear = false; | ||
98 | 2956 | } | 3009 | } |
99 | 2957 | 3010 | ||
100 | 2958 | if (!path_selected && click_zone != ClickZone.HELPER) { | 3011 | if (!path_selected && click_zone != ClickZone.HELPER) { |
101 | @@ -2994,17 +3047,31 @@ | |||
102 | 2994 | */ | 3047 | */ |
103 | 2995 | 3048 | ||
104 | 2996 | if (!no_mods || (on_blank && (!activate_on_blank || !path_selected))) | 3049 | if (!no_mods || (on_blank && (!activate_on_blank || !path_selected))) |
106 | 2997 | result = false; /* Rubberband */ | 3050 | if (linear_select_required && selected_files.length () > 0) { |
107 | 3051 | linear_select_path (path); | ||
108 | 3052 | } else { | ||
109 | 3053 | previous_selection_was_linear = false; | ||
110 | 3054 | result = false; /* Rubberband */ | ||
111 | 3055 | } | ||
112 | 2998 | else | 3056 | else |
113 | 2999 | result = handle_primary_button_click (event, path); | 3057 | result = handle_primary_button_click (event, path); |
114 | 3000 | 3058 | ||
115 | 3059 | previous_linear_selection_path = path.copy (); | ||
116 | 3001 | break; | 3060 | break; |
117 | 3002 | 3061 | ||
118 | 3003 | case ClickZone.HELPER: | 3062 | case ClickZone.HELPER: |
123 | 3004 | if (path_selected) | 3063 | if (linear_select_required && selected_files.length () > 0) { |
124 | 3005 | unselect_path (path); | 3064 | linear_select_path (path); |
125 | 3006 | else | 3065 | } else { |
126 | 3007 | select_path (path); | 3066 | previous_selection_was_linear = false; |
127 | 3067 | previous_linear_selection_path = null; | ||
128 | 3068 | |||
129 | 3069 | if (path_selected) { | ||
130 | 3070 | unselect_path (path); | ||
131 | 3071 | } else { | ||
132 | 3072 | select_path (path); | ||
133 | 3073 | } | ||
134 | 3074 | } | ||
135 | 3008 | 3075 | ||
136 | 3009 | break; | 3076 | break; |
137 | 3010 | 3077 | ||
138 | @@ -3046,7 +3113,7 @@ | |||
139 | 3046 | result = handle_default_button_click (event); | 3113 | result = handle_default_button_click (event); |
140 | 3047 | break; | 3114 | break; |
141 | 3048 | } | 3115 | } |
143 | 3049 | 3116 | previous_linear_selection_path = path != null ? path.copy () : null; | |
144 | 3050 | return result; | 3117 | return result; |
145 | 3051 | } | 3118 | } |
146 | 3052 | 3119 | ||
147 | @@ -3296,6 +3363,9 @@ | |||
148 | 3296 | 3363 | ||
149 | 3297 | public virtual void sync_selection () {} | 3364 | public virtual void sync_selection () {} |
150 | 3298 | public virtual void highlight_path (Gtk.TreePath? path) {} | 3365 | public virtual void highlight_path (Gtk.TreePath? path) {} |
151 | 3366 | protected virtual void linear_select_path (Gtk.TreePath path) {} | ||
152 | 3367 | protected virtual Gtk.TreePath up (Gtk.TreePath path) {path.up (); return path;} | ||
153 | 3368 | protected virtual Gtk.TreePath down (Gtk.TreePath path) {path.down (); return path;} | ||
154 | 3299 | 3369 | ||
155 | 3300 | /** Abstract methods - must be overridden*/ | 3370 | /** Abstract methods - must be overridden*/ |
156 | 3301 | public abstract GLib.List<Gtk.TreePath> get_selected_paths () ; | 3371 | public abstract GLib.List<Gtk.TreePath> get_selected_paths () ; |
157 | 3302 | 3372 | ||
158 | === modified file 'src/View/AbstractTreeView.vala' | |||
159 | --- src/View/AbstractTreeView.vala 2015-10-06 14:23:33 +0000 | |||
160 | +++ src/View/AbstractTreeView.vala 2016-02-06 15:43:57 +0000 | |||
161 | @@ -133,6 +133,8 @@ | |||
162 | 133 | public override void select_path (Gtk.TreePath? path) { | 133 | public override void select_path (Gtk.TreePath? path) { |
163 | 134 | if (path != null) { | 134 | if (path != null) { |
164 | 135 | debug ("select path %s", path.to_string ()); | 135 | debug ("select path %s", path.to_string ()); |
165 | 136 | /* Ensure cursor follows last selection */ | ||
166 | 137 | tree.set_cursor (path, null, false); | ||
167 | 136 | tree.get_selection ().select_path (path); | 138 | tree.get_selection ().select_path (path); |
168 | 137 | } | 139 | } |
169 | 138 | } | 140 | } |
170 | 139 | 141 | ||
171 | === modified file 'src/View/IconView.vala' | |||
172 | --- src/View/IconView.vala 2016-01-15 19:53:37 +0000 | |||
173 | +++ src/View/IconView.vala 2016-02-06 15:43:57 +0000 | |||
174 | @@ -142,6 +142,8 @@ | |||
175 | 142 | 142 | ||
176 | 143 | public override void select_path (Gtk.TreePath? path) { | 143 | public override void select_path (Gtk.TreePath? path) { |
177 | 144 | if (path != null) { | 144 | if (path != null) { |
178 | 145 | /* Ensure cursor follows last selection */ | ||
179 | 146 | tree.set_cursor (path, null, false); | ||
180 | 145 | tree.select_path (path); | 147 | tree.select_path (path); |
181 | 146 | } | 148 | } |
182 | 147 | } | 149 | } |
183 | @@ -311,5 +313,138 @@ | |||
184 | 311 | tree_frozen = false; | 313 | tree_frozen = false; |
185 | 312 | } | 314 | } |
186 | 313 | } | 315 | } |
187 | 316 | |||
188 | 317 | protected override void linear_select_path (Gtk.TreePath path) { | ||
189 | 318 | /* We override the native Gtk.IconView behaviour when selecting files with Shift-Click */ | ||
190 | 319 | /* We wish to emulate the behaviour of ListView and ColumnView. This depends on whether the */ | ||
191 | 320 | /* the previous selection was made with the Shift key pressed */ | ||
192 | 321 | /* Note: 'first' and 'last' refer to position in selection, not the time selected */ | ||
193 | 322 | |||
194 | 323 | if (path == null) { | ||
195 | 324 | critical ("Ignoring attempt to select null path in linear_select_path"); | ||
196 | 325 | return; | ||
197 | 326 | } | ||
198 | 327 | if (previous_linear_selection_path != null && path.compare (previous_linear_selection_path) == 0) { | ||
199 | 328 | /* Ignore if repeat click on same file as before. We keep the previous linear selection direction. */ | ||
200 | 329 | return; | ||
201 | 330 | } | ||
202 | 331 | |||
203 | 332 | var selected_paths = tree.get_selected_items (); | ||
204 | 333 | /* Ensure the order of the selected files list matches the visible order */ | ||
205 | 334 | selected_paths.sort (Gtk.TreePath.compare); | ||
206 | 335 | |||
207 | 336 | var first_selected = selected_paths.first ().data; | ||
208 | 337 | var last_selected = selected_paths.last ().data; | ||
209 | 338 | bool before_first = path.compare (first_selected) <= 0; | ||
210 | 339 | bool after_last = path.compare (last_selected) >= 0; | ||
211 | 340 | bool direction_change = false; | ||
212 | 341 | |||
213 | 342 | direction_change = (before_first && previous_linear_selection_direction > 0) || | ||
214 | 343 | (after_last && previous_linear_selection_direction < 0); | ||
215 | 344 | |||
216 | 345 | var p = path.copy (); | ||
217 | 346 | Gtk.TreePath p2 = null; | ||
218 | 347 | |||
219 | 348 | unselect_all (); | ||
220 | 349 | Gtk.TreePath? end_path = null; | ||
221 | 350 | if (!previous_selection_was_linear && previous_linear_selection_path != null) { | ||
222 | 351 | end_path = previous_linear_selection_path; | ||
223 | 352 | } else if (before_first) { | ||
224 | 353 | end_path = direction_change ? first_selected : last_selected; | ||
225 | 354 | } else { | ||
226 | 355 | end_path = direction_change ? last_selected : first_selected; | ||
227 | 356 | } | ||
228 | 357 | |||
229 | 358 | if (before_first) { | ||
230 | 359 | do { | ||
231 | 360 | p2 = p.copy (); | ||
232 | 361 | select_path (p); | ||
233 | 362 | p.next (); | ||
234 | 363 | } while (p.compare (p2) != 0 && p.compare (end_path) <= 0); | ||
235 | 364 | } else if (after_last) { | ||
236 | 365 | do { | ||
237 | 366 | select_path (p); | ||
238 | 367 | p2 = p.copy (); | ||
239 | 368 | p.prev (); | ||
240 | 369 | } while (p.compare (p2) != 0 && p.compare (end_path) >= 0); | ||
241 | 370 | } else {/* between first and last */ | ||
242 | 371 | do { | ||
243 | 372 | p2 = p.copy (); | ||
244 | 373 | select_path (p); | ||
245 | 374 | p.prev (); | ||
246 | 375 | } while (p.compare (p2) != 0 && p.compare (first_selected) >= 0); | ||
247 | 376 | |||
248 | 377 | p = path.copy (); | ||
249 | 378 | do { | ||
250 | 379 | p2 = p.copy (); | ||
251 | 380 | p.next (); | ||
252 | 381 | unselect_path (p); | ||
253 | 382 | } while (p.compare (p2) != 0 && p.compare (last_selected) <= 0); | ||
254 | 383 | } | ||
255 | 384 | previous_selection_was_linear = true; | ||
256 | 385 | |||
257 | 386 | selected_paths = tree.get_selected_items (); | ||
258 | 387 | selected_paths.sort (Gtk.TreePath.compare); | ||
259 | 388 | |||
260 | 389 | first_selected = selected_paths.first ().data; | ||
261 | 390 | last_selected = selected_paths.last ().data; | ||
262 | 391 | |||
263 | 392 | if (path.compare (last_selected) == 0) { | ||
264 | 393 | previous_linear_selection_direction = 1; /* clicked after the (visually) first selection */ | ||
265 | 394 | } else if (path.compare (first_selected) == 0) { | ||
266 | 395 | previous_linear_selection_direction = -1; /* clicked before the (visually) first selection */ | ||
267 | 396 | } else { | ||
268 | 397 | critical ("Linear selection did not become end point - this should not happen!"); | ||
269 | 398 | previous_linear_selection_direction = 0; | ||
270 | 399 | } | ||
271 | 400 | previous_linear_selection_path = path.copy (); | ||
272 | 401 | /* Ensure cursor in correct place, regardless of any selections made in this function */ | ||
273 | 402 | tree.set_cursor (path, null, false); | ||
274 | 403 | tree.scroll_to_path (path, false, 0.5f, 0.5f); | ||
275 | 404 | } | ||
276 | 405 | |||
277 | 406 | protected override Gtk.TreePath up (Gtk.TreePath path) { | ||
278 | 407 | int item_row = tree.get_item_row (path); | ||
279 | 408 | if (item_row == 0) { | ||
280 | 409 | return path; | ||
281 | 410 | } | ||
282 | 411 | int cols = get_n_cols (); | ||
283 | 412 | int index = path.get_indices ()[0]; | ||
284 | 413 | Gtk.TreePath new_path; | ||
285 | 414 | Gtk.TreeIter? iter = null; | ||
286 | 415 | new_path = new Gtk.TreePath.from_indices (index - cols, -1); | ||
287 | 416 | if (tree.model.get_iter (out iter, new_path)) { | ||
288 | 417 | return new_path; | ||
289 | 418 | } else { | ||
290 | 419 | return path; | ||
291 | 420 | } | ||
292 | 421 | } | ||
293 | 422 | protected override Gtk.TreePath down (Gtk.TreePath path) { | ||
294 | 423 | int cols = get_n_cols (); | ||
295 | 424 | int index = path.get_indices ()[0]; | ||
296 | 425 | |||
297 | 426 | Gtk.TreePath new_path; | ||
298 | 427 | Gtk.TreeIter? iter = null; | ||
299 | 428 | new_path = new Gtk.TreePath.from_indices (index + cols, -1); | ||
300 | 429 | if (tree.model.get_iter (out iter, new_path)) { | ||
301 | 430 | return new_path; | ||
302 | 431 | } else { | ||
303 | 432 | return path; | ||
304 | 433 | } | ||
305 | 434 | } | ||
306 | 435 | |||
307 | 436 | /* When Icon View is automatically adjusting column number it does not expose the actual number of | ||
308 | 437 | * columns (get_columns () returns -1). So we have to write our own method. This is the only way | ||
309 | 438 | * (I can think of) that works on row 0. | ||
310 | 439 | */ | ||
311 | 440 | private int get_n_cols () { | ||
312 | 441 | var path = new Gtk.TreePath.from_indices (0, -1); | ||
313 | 442 | int index = 0; | ||
314 | 443 | while (tree.get_item_row (path) == 0) { | ||
315 | 444 | index++; | ||
316 | 445 | path.next (); | ||
317 | 446 | } | ||
318 | 447 | return index; | ||
319 | 448 | } | ||
320 | 314 | } | 449 | } |
321 | 315 | } | 450 | } |