Merge lp:~jeremywootten/pantheon-files/fix-1399674-linear-selection-in-icon-view into lp:~elementary-apps/pantheon-files/trunk

Proposed by Jeremy Wootten on 2016-01-24
Status: Merged
Approved by: Cody Garver on 2016-02-09
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
Reviewer Review Type Date Requested Status
elementary Apps team 2016-01-24 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 on 2016-02-06

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 gtk_tree_model_get(): this function increases the reference
6 count of the file object.*/
7 protected GLib.List<GOF.File> selected_files = null;
8+ /* support for linear selection mode in icon view */
9+ protected bool previous_selection_was_linear = false;
10+ protected Gtk.TreePath? previous_linear_selection_path = null;
11+ protected int previous_linear_selection_direction = 0;
12
13 private GLib.List<GLib.File> templates = null;
14
15@@ -2533,6 +2537,19 @@
16 bool in_trash = slot.location.has_uri_scheme ("trash");
17 bool in_recent = slot.location.has_uri_scheme ("recent");
18
19+
20+ /* Implement linear selection in Icon View with cursor keys */
21+ bool linear_select_required = false;
22+ if (!no_mods && !only_control_pressed) {
23+ if (only_shift_pressed && (this is IconView)) {
24+ linear_select_required = true;
25+ } else {
26+ previous_selection_was_linear = false;
27+ }
28+ } else {
29+ previous_selection_was_linear = false;
30+ }
31+
32 switch (event.keyval) {
33 case Gdk.Key.F10:
34 if (only_control_pressed) {
35@@ -2628,6 +2645,32 @@
36
37 return true;
38
39+ case Gdk.Key.Up:
40+ case Gdk.Key.Down:
41+ case Gdk.Key.Left:
42+ case Gdk.Key.Right:
43+
44+ if (linear_select_required && selected_files.length () > 0) { /* Only true for Icon View */
45+ Gtk.TreePath? path = get_path_at_cursor ();
46+ if (path != null) {
47+ if (event.keyval == Gdk.Key.Right) {
48+ path.next ();
49+ } else if (event.keyval == Gdk.Key.Left) {
50+ path.prev ();
51+ } else if (event.keyval == Gdk.Key.Up) {
52+ path = up (path);
53+ } else if (event.keyval == Gdk.Key.Down) {
54+ path = down (path);
55+ }
56+ linear_select_path (path);
57+ return true;
58+ }
59+ } else {
60+ previous_selection_was_linear = false;
61+ previous_linear_selection_path = null;
62+ }
63+ break;
64+
65 default:
66 break;
67 }
68@@ -2938,11 +2981,13 @@
69 var mods = event.state & Gtk.accelerator_get_default_mod_mask ();
70 bool no_mods = (mods == 0);
71 bool control_pressed = ((mods & Gdk.ModifierType.CONTROL_MASK) != 0);
72+ bool shift_pressed = ((mods & Gdk.ModifierType.SHIFT_MASK) != 0);
73 bool other_mod_pressed = (((mods & ~Gdk.ModifierType.SHIFT_MASK) & ~Gdk.ModifierType.CONTROL_MASK) != 0);
74 bool only_control_pressed = control_pressed && !other_mod_pressed; /* Shift can be pressed */
75-
76+ bool only_shift_pressed = shift_pressed && !control_pressed && !other_mod_pressed;
77 bool path_selected = (path != null ? path_is_selected (path) : false);
78 bool on_blank = (click_zone == ClickZone.BLANK_NO_PATH || click_zone == ClickZone.BLANK_PATH);
79+ bool linear_select_required = false;
80
81 /* Block drag and drop to allow rubberbanding and prevent unwanted effects of
82 * dragging on blank areas
83@@ -2951,8 +2996,16 @@
84
85 /* Handle un-modified clicks or control-clicks here else pass on.
86 */
87+ linear_select_required = false;
88 if (!no_mods && !only_control_pressed) {
89- return window.button_press_event (event);
90+ if (only_shift_pressed && (this is IconView)) {
91+ linear_select_required = true;
92+ } else {
93+ previous_selection_was_linear = false;
94+ return window.button_press_event (event);
95+ }
96+ } else {
97+ previous_selection_was_linear = false;
98 }
99
100 if (!path_selected && click_zone != ClickZone.HELPER) {
101@@ -2994,17 +3047,31 @@
102 */
103
104 if (!no_mods || (on_blank && (!activate_on_blank || !path_selected)))
105- result = false; /* Rubberband */
106+ if (linear_select_required && selected_files.length () > 0) {
107+ linear_select_path (path);
108+ } else {
109+ previous_selection_was_linear = false;
110+ result = false; /* Rubberband */
111+ }
112 else
113 result = handle_primary_button_click (event, path);
114
115+ previous_linear_selection_path = path.copy ();
116 break;
117
118 case ClickZone.HELPER:
119- if (path_selected)
120- unselect_path (path);
121- else
122- select_path (path);
123+ if (linear_select_required && selected_files.length () > 0) {
124+ linear_select_path (path);
125+ } else {
126+ previous_selection_was_linear = false;
127+ previous_linear_selection_path = null;
128+
129+ if (path_selected) {
130+ unselect_path (path);
131+ } else {
132+ select_path (path);
133+ }
134+ }
135
136 break;
137
138@@ -3046,7 +3113,7 @@
139 result = handle_default_button_click (event);
140 break;
141 }
142-
143+ previous_linear_selection_path = path != null ? path.copy () : null;
144 return result;
145 }
146
147@@ -3296,6 +3363,9 @@
148
149 public virtual void sync_selection () {}
150 public virtual void highlight_path (Gtk.TreePath? path) {}
151+ protected virtual void linear_select_path (Gtk.TreePath path) {}
152+ protected virtual Gtk.TreePath up (Gtk.TreePath path) {path.up (); return path;}
153+ protected virtual Gtk.TreePath down (Gtk.TreePath path) {path.down (); return path;}
154
155 /** Abstract methods - must be overridden*/
156 public abstract GLib.List<Gtk.TreePath> get_selected_paths () ;
157
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 public override void select_path (Gtk.TreePath? path) {
163 if (path != null) {
164 debug ("select path %s", path.to_string ());
165+ /* Ensure cursor follows last selection */
166+ tree.set_cursor (path, null, false);
167 tree.get_selection ().select_path (path);
168 }
169 }
170
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
176 public override void select_path (Gtk.TreePath? path) {
177 if (path != null) {
178+ /* Ensure cursor follows last selection */
179+ tree.set_cursor (path, null, false);
180 tree.select_path (path);
181 }
182 }
183@@ -311,5 +313,138 @@
184 tree_frozen = false;
185 }
186 }
187+
188+ protected override void linear_select_path (Gtk.TreePath path) {
189+ /* We override the native Gtk.IconView behaviour when selecting files with Shift-Click */
190+ /* We wish to emulate the behaviour of ListView and ColumnView. This depends on whether the */
191+ /* the previous selection was made with the Shift key pressed */
192+ /* Note: 'first' and 'last' refer to position in selection, not the time selected */
193+
194+ if (path == null) {
195+ critical ("Ignoring attempt to select null path in linear_select_path");
196+ return;
197+ }
198+ if (previous_linear_selection_path != null && path.compare (previous_linear_selection_path) == 0) {
199+ /* Ignore if repeat click on same file as before. We keep the previous linear selection direction. */
200+ return;
201+ }
202+
203+ var selected_paths = tree.get_selected_items ();
204+ /* Ensure the order of the selected files list matches the visible order */
205+ selected_paths.sort (Gtk.TreePath.compare);
206+
207+ var first_selected = selected_paths.first ().data;
208+ var last_selected = selected_paths.last ().data;
209+ bool before_first = path.compare (first_selected) <= 0;
210+ bool after_last = path.compare (last_selected) >= 0;
211+ bool direction_change = false;
212+
213+ direction_change = (before_first && previous_linear_selection_direction > 0) ||
214+ (after_last && previous_linear_selection_direction < 0);
215+
216+ var p = path.copy ();
217+ Gtk.TreePath p2 = null;
218+
219+ unselect_all ();
220+ Gtk.TreePath? end_path = null;
221+ if (!previous_selection_was_linear && previous_linear_selection_path != null) {
222+ end_path = previous_linear_selection_path;
223+ } else if (before_first) {
224+ end_path = direction_change ? first_selected : last_selected;
225+ } else {
226+ end_path = direction_change ? last_selected : first_selected;
227+ }
228+
229+ if (before_first) {
230+ do {
231+ p2 = p.copy ();
232+ select_path (p);
233+ p.next ();
234+ } while (p.compare (p2) != 0 && p.compare (end_path) <= 0);
235+ } else if (after_last) {
236+ do {
237+ select_path (p);
238+ p2 = p.copy ();
239+ p.prev ();
240+ } while (p.compare (p2) != 0 && p.compare (end_path) >= 0);
241+ } else {/* between first and last */
242+ do {
243+ p2 = p.copy ();
244+ select_path (p);
245+ p.prev ();
246+ } while (p.compare (p2) != 0 && p.compare (first_selected) >= 0);
247+
248+ p = path.copy ();
249+ do {
250+ p2 = p.copy ();
251+ p.next ();
252+ unselect_path (p);
253+ } while (p.compare (p2) != 0 && p.compare (last_selected) <= 0);
254+ }
255+ previous_selection_was_linear = true;
256+
257+ selected_paths = tree.get_selected_items ();
258+ selected_paths.sort (Gtk.TreePath.compare);
259+
260+ first_selected = selected_paths.first ().data;
261+ last_selected = selected_paths.last ().data;
262+
263+ if (path.compare (last_selected) == 0) {
264+ previous_linear_selection_direction = 1; /* clicked after the (visually) first selection */
265+ } else if (path.compare (first_selected) == 0) {
266+ previous_linear_selection_direction = -1; /* clicked before the (visually) first selection */
267+ } else {
268+ critical ("Linear selection did not become end point - this should not happen!");
269+ previous_linear_selection_direction = 0;
270+ }
271+ previous_linear_selection_path = path.copy ();
272+ /* Ensure cursor in correct place, regardless of any selections made in this function */
273+ tree.set_cursor (path, null, false);
274+ tree.scroll_to_path (path, false, 0.5f, 0.5f);
275+ }
276+
277+ protected override Gtk.TreePath up (Gtk.TreePath path) {
278+ int item_row = tree.get_item_row (path);
279+ if (item_row == 0) {
280+ return path;
281+ }
282+ int cols = get_n_cols ();
283+ int index = path.get_indices ()[0];
284+ Gtk.TreePath new_path;
285+ Gtk.TreeIter? iter = null;
286+ new_path = new Gtk.TreePath.from_indices (index - cols, -1);
287+ if (tree.model.get_iter (out iter, new_path)) {
288+ return new_path;
289+ } else {
290+ return path;
291+ }
292+ }
293+ protected override Gtk.TreePath down (Gtk.TreePath path) {
294+ int cols = get_n_cols ();
295+ int index = path.get_indices ()[0];
296+
297+ Gtk.TreePath new_path;
298+ Gtk.TreeIter? iter = null;
299+ new_path = new Gtk.TreePath.from_indices (index + cols, -1);
300+ if (tree.model.get_iter (out iter, new_path)) {
301+ return new_path;
302+ } else {
303+ return path;
304+ }
305+ }
306+
307+ /* When Icon View is automatically adjusting column number it does not expose the actual number of
308+ * columns (get_columns () returns -1). So we have to write our own method. This is the only way
309+ * (I can think of) that works on row 0.
310+ */
311+ private int get_n_cols () {
312+ var path = new Gtk.TreePath.from_indices (0, -1);
313+ int index = 0;
314+ while (tree.get_item_row (path) == 0) {
315+ index++;
316+ path.next ();
317+ }
318+ return index;
319+ }
320 }
321 }

Subscribers

People subscribed via source and target branches

to all changes: