=== modified file 'src/View/AbstractDirectoryView.vala'
--- src/View/AbstractDirectoryView.vala	2016-01-23 12:37:18 +0000
+++ src/View/AbstractDirectoryView.vala	2016-02-06 15:43:57 +0000
@@ -201,6 +201,10 @@
             gtk_tree_model_get(): this function increases the reference
             count of the file object.*/
         protected GLib.List<GOF.File> selected_files = null;
+        /* support for linear selection mode in icon view */
+        protected bool previous_selection_was_linear = false;
+        protected Gtk.TreePath? previous_linear_selection_path = null;
+        protected int previous_linear_selection_direction = 0;
 
         private GLib.List<GLib.File> templates = null;
 
@@ -2533,6 +2537,19 @@
             bool in_trash = slot.location.has_uri_scheme ("trash");
             bool in_recent = slot.location.has_uri_scheme ("recent");
 
+
+            /* Implement linear selection in Icon View with cursor keys */
+            bool linear_select_required = false;
+            if (!no_mods && !only_control_pressed) {
+                if (only_shift_pressed && (this is IconView)) {
+                    linear_select_required = true;
+                } else {
+                    previous_selection_was_linear = false;
+                }
+            } else {
+                previous_selection_was_linear = false;
+            }
+
             switch (event.keyval) {
                 case Gdk.Key.F10:
                     if (only_control_pressed) {
@@ -2628,6 +2645,32 @@
 
                    return true;
 
+                case Gdk.Key.Up:
+                case Gdk.Key.Down:
+                case Gdk.Key.Left:
+                case Gdk.Key.Right:
+
+                    if (linear_select_required && selected_files.length () > 0) { /* Only true for Icon View */
+                        Gtk.TreePath? path = get_path_at_cursor ();
+                        if (path != null) {
+                            if (event.keyval == Gdk.Key.Right) {
+                                path.next ();
+                            } else if (event.keyval == Gdk.Key.Left) {
+                                path.prev ();
+                            } else if (event.keyval == Gdk.Key.Up) {
+                                path = up (path);
+                            } else if (event.keyval == Gdk.Key.Down) {
+                                path = down (path);
+                            }
+                            linear_select_path (path);
+                            return true;
+                        }
+                    } else {
+                        previous_selection_was_linear = false;
+                        previous_linear_selection_path = null;
+                    }
+                    break;
+
                 default:
                     break;
             }
@@ -2938,11 +2981,13 @@
             var mods = event.state & Gtk.accelerator_get_default_mod_mask ();
             bool no_mods = (mods == 0);
             bool control_pressed = ((mods & Gdk.ModifierType.CONTROL_MASK) != 0);
+            bool shift_pressed = ((mods & Gdk.ModifierType.SHIFT_MASK) != 0);
             bool other_mod_pressed = (((mods & ~Gdk.ModifierType.SHIFT_MASK) & ~Gdk.ModifierType.CONTROL_MASK) != 0);
             bool only_control_pressed = control_pressed && !other_mod_pressed; /* Shift can be pressed */
-
+            bool only_shift_pressed = shift_pressed && !control_pressed && !other_mod_pressed;
             bool path_selected = (path != null ? path_is_selected (path) : false);
             bool on_blank = (click_zone == ClickZone.BLANK_NO_PATH || click_zone == ClickZone.BLANK_PATH);
+            bool linear_select_required = false;
 
             /* Block drag and drop to allow rubberbanding and prevent unwanted effects of
              * dragging on blank areas
@@ -2951,8 +2996,16 @@
 
             /* Handle un-modified clicks or control-clicks here else pass on.
              */
+            linear_select_required = false;
             if (!no_mods && !only_control_pressed) {
-                return window.button_press_event (event);
+                if (only_shift_pressed && (this is IconView)) {
+                    linear_select_required = true;
+                } else {
+                    previous_selection_was_linear = false;
+                    return window.button_press_event (event);
+                }
+            } else {
+                previous_selection_was_linear = false;
             }
 
             if (!path_selected && click_zone != ClickZone.HELPER) {
@@ -2994,17 +3047,31 @@
                              */
 
                             if (!no_mods || (on_blank && (!activate_on_blank || !path_selected)))
-                                 result = false; /* Rubberband */
+                                if (linear_select_required && selected_files.length () > 0) {
+                                    linear_select_path (path);
+                                } else {
+                                    previous_selection_was_linear = false;
+                                    result = false; /* Rubberband */
+                                }
                             else
                                 result = handle_primary_button_click (event, path);
 
+                            previous_linear_selection_path = path.copy ();
                             break;
 
                         case ClickZone.HELPER:
-                            if (path_selected)
-                                unselect_path (path);
-                            else
-                                select_path (path);
+                            if (linear_select_required && selected_files.length () > 0) {
+                                linear_select_path (path);
+                            } else {
+                                previous_selection_was_linear = false;
+                                previous_linear_selection_path = null;
+
+                                if (path_selected) {
+                                    unselect_path (path);
+                                } else {
+                                    select_path (path);
+                                }
+                            }
 
                             break;
 
@@ -3046,7 +3113,7 @@
                     result = handle_default_button_click (event);
                     break;
             }
-
+            previous_linear_selection_path = path != null ? path.copy () : null;
             return result;
         }
 
@@ -3296,6 +3363,9 @@
 
         public virtual void sync_selection () {}
         public virtual void highlight_path (Gtk.TreePath? path) {}
+        protected virtual void linear_select_path (Gtk.TreePath path) {}
+        protected virtual Gtk.TreePath up (Gtk.TreePath path) {path.up (); return path;}
+        protected virtual Gtk.TreePath down (Gtk.TreePath path) {path.down (); return path;}
 
 /** Abstract methods - must be overridden*/
         public abstract GLib.List<Gtk.TreePath> get_selected_paths () ;

=== modified file 'src/View/AbstractTreeView.vala'
--- src/View/AbstractTreeView.vala	2015-10-06 14:23:33 +0000
+++ src/View/AbstractTreeView.vala	2016-02-06 15:43:57 +0000
@@ -133,6 +133,8 @@
         public override void select_path (Gtk.TreePath? path) {
             if (path != null) {
                 debug ("select path %s", path.to_string ());
+                /* Ensure cursor follows last selection */
+                tree.set_cursor (path, null, false);
                 tree.get_selection ().select_path (path);
             }
         }

=== modified file 'src/View/IconView.vala'
--- src/View/IconView.vala	2016-01-15 19:53:37 +0000
+++ src/View/IconView.vala	2016-02-06 15:43:57 +0000
@@ -142,6 +142,8 @@
 
         public override void select_path (Gtk.TreePath? path) {
             if (path != null) {
+                /* Ensure cursor follows last selection */
+                tree.set_cursor (path, null, false);
                 tree.select_path (path);
             }
         }
@@ -311,5 +313,138 @@
                 tree_frozen = false;
             }
         }
+
+        protected override void linear_select_path (Gtk.TreePath path) {
+            /* We override the native Gtk.IconView behaviour when selecting files with Shift-Click */
+            /* We wish to emulate the behaviour of ListView and ColumnView. This depends on whether the */
+            /* the previous selection was made with the Shift key pressed */
+            /* Note: 'first' and 'last' refer to position in selection, not the time selected */
+
+            if (path == null) {
+                critical ("Ignoring attempt to select null path in linear_select_path");
+                return;
+            }
+            if (previous_linear_selection_path != null && path.compare (previous_linear_selection_path) == 0) {
+                /* Ignore if repeat click on same file as before. We keep the previous linear selection direction. */
+                return;
+            }
+
+            var selected_paths = tree.get_selected_items ();
+            /* Ensure the order of the selected files list matches the visible order */
+            selected_paths.sort (Gtk.TreePath.compare);
+
+            var first_selected = selected_paths.first ().data;
+            var last_selected = selected_paths.last ().data;
+            bool before_first = path.compare (first_selected) <= 0;
+            bool after_last = path.compare (last_selected) >= 0;
+            bool direction_change = false;
+
+            direction_change = (before_first && previous_linear_selection_direction > 0) ||
+                               (after_last && previous_linear_selection_direction < 0);
+
+            var p = path.copy ();
+            Gtk.TreePath p2 = null;
+
+            unselect_all ();
+            Gtk.TreePath? end_path = null;
+            if (!previous_selection_was_linear && previous_linear_selection_path != null) {
+                end_path = previous_linear_selection_path;
+            } else if (before_first) {
+                end_path = direction_change ? first_selected : last_selected;
+            } else {
+                end_path = direction_change ? last_selected : first_selected;
+            }
+
+            if (before_first) {
+                do {
+                    p2 = p.copy ();
+                    select_path (p);
+                    p.next ();
+                } while (p.compare (p2) != 0 && p.compare (end_path) <= 0);
+            } else if (after_last) {
+                do {
+                    select_path (p);
+                    p2 = p.copy ();
+                    p.prev ();
+                } while (p.compare (p2) != 0 && p.compare (end_path) >= 0);
+            } else {/* between first and last */
+                do {
+                    p2 = p.copy ();
+                    select_path (p);
+                    p.prev ();
+                } while (p.compare (p2) != 0 && p.compare (first_selected) >= 0);
+
+                p = path.copy ();
+                do {
+                    p2 = p.copy ();
+                    p.next ();
+                    unselect_path (p);
+                } while (p.compare (p2) != 0 && p.compare (last_selected) <= 0);
+            }
+            previous_selection_was_linear = true;
+
+            selected_paths = tree.get_selected_items ();
+            selected_paths.sort (Gtk.TreePath.compare);
+
+            first_selected = selected_paths.first ().data;
+            last_selected = selected_paths.last ().data;
+
+            if (path.compare (last_selected) == 0) {
+                previous_linear_selection_direction = 1; /* clicked after the (visually) first selection */
+            } else if (path.compare (first_selected) == 0) {
+                previous_linear_selection_direction = -1; /* clicked before the (visually) first selection */
+            } else {
+                critical ("Linear selection did not become end point - this should not happen!");
+                previous_linear_selection_direction = 0;
+            }
+            previous_linear_selection_path = path.copy ();
+            /* Ensure cursor in correct place, regardless of any selections made in this function */
+            tree.set_cursor (path, null, false);
+            tree.scroll_to_path (path, false, 0.5f, 0.5f);
+        }
+
+        protected override Gtk.TreePath up (Gtk.TreePath path) {
+            int item_row = tree.get_item_row (path);
+            if (item_row == 0) {
+                return path;
+            }
+            int cols = get_n_cols ();
+            int index = path.get_indices ()[0];
+            Gtk.TreePath new_path;
+            Gtk.TreeIter? iter = null;
+            new_path = new Gtk.TreePath.from_indices (index - cols, -1);
+            if (tree.model.get_iter (out iter, new_path)) {
+                return new_path;
+            } else {
+                return path;
+            }
+        }
+        protected override Gtk.TreePath down (Gtk.TreePath path) {
+            int cols = get_n_cols ();
+            int index = path.get_indices ()[0];
+
+            Gtk.TreePath new_path;
+            Gtk.TreeIter? iter = null;
+            new_path = new Gtk.TreePath.from_indices (index + cols, -1);
+            if (tree.model.get_iter (out iter, new_path)) {
+                return new_path;
+            } else {
+                return path;
+            }
+        }
+
+        /* When Icon View is automatically adjusting column number it does not expose the actual number of
+         * columns (get_columns () returns -1). So we have to write our own method. This is the only way
+         * (I can think of) that works on row 0. 
+         */   
+        private int get_n_cols () {
+            var path = new Gtk.TreePath.from_indices (0, -1);
+            int index = 0;
+            while (tree.get_item_row (path) == 0) {
+                index++;
+                path.next ();
+            }
+            return index;
+        }
     }
 }

