[gnome-boxes/wip/feborges/flowbox: 13/13] icon-view: Port to GtkFlowBox



commit a9c0483d9680e8afef54c05412d6e59e9430b19e
Author: Felipe Borges <felipeborges gnome org>
Date:   Wed Dec 20 18:19:38 2017 +0100

    icon-view: Port to GtkFlowBox
    
    Finally IconView can be directly binded to Collection the same way
    as ListView is. This is one important step towards providing full
    independence between the model and the views.
    
    We can also now revert the app.vala, app-window.vala hack introduced
    before to prevent the IconView to break while ListView was being
    ported.

 data/gnome-boxes.gresource.xml |    2 +
 data/ui/icon-view-child.ui     |   95 ++++++++
 data/ui/icon-view.ui           |   34 +++
 src/Makefile.am                |    1 +
 src/app-window.vala            |    2 +-
 src/app.vala                   |    7 -
 src/icon-view-child.vala       |   91 ++++++++
 src/icon-view.vala             |  460 +++++++++++++---------------------------
 src/meson.build                |    1 +
 9 files changed, 376 insertions(+), 317 deletions(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index 12c462b..77c1d09 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -14,6 +14,8 @@
     <file preprocess="xml-stripblanks">ui/display-toolbar.ui</file>
     <file preprocess="xml-stripblanks">ui/editable-entry.ui</file>
     <file preprocess="xml-stripblanks">ui/empty-boxes.ui</file>
+    <file preprocess="xml-stripblanks">ui/icon-view.ui</file>
+    <file preprocess="xml-stripblanks">ui/icon-view-child.ui</file>
     <file preprocess="xml-stripblanks">ui/kbd-shortcuts-window.ui</file>
     <file preprocess="xml-stripblanks">ui/list-view.ui</file>
     <file preprocess="xml-stripblanks">ui/list-view-row.ui</file>
diff --git a/data/ui/icon-view-child.ui b/data/ui/icon-view-child.ui
new file mode 100644
index 0000000..9328ccb
--- /dev/null
+++ b/data/ui/icon-view-child.ui
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.9 -->
+  <template class="BoxesIconViewChild" parent="GtkBox">
+    <property name="visible">True</property>
+    <property name="orientation">vertical</property>
+
+    <child>
+      <object class="GtkOverlay" id="overlay">
+        <property name="visible">True</property>
+
+        <child type="overlay">
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="valign">end</property>
+            <property name="margin">10</property>
+            <property name="margin-start">20</property>
+            <property name="margin-end">20</property>
+
+            <child>
+              <object class="GtkImage" id="favorite">
+                <property name="visible">True</property>
+                <property name="width-request">16</property>
+                <property name="height-request">16</property>
+              </object>
+            </child>
+
+            <child>
+              <object class="GtkCheckButton" id="selection_button">
+                <property name="visible">False</property>
+                <property name="can-focus">False</property>
+              </object>
+              <packing>
+                <property name="pack-type">end</property>
+              </packing>
+            </child>
+
+          </object>
+        </child>
+
+        <child>
+          <object class="GtkStack" id="stack">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkImage" id="thumbnail">
+                <property name="visible">True</property>
+              </object>
+
+              <packing>
+                <property name="name">thumbnail</property>
+              </packing>
+            </child>
+
+            <child>
+              <object class="GtkSpinner" id="spinner">
+                <property name="visible">True</property>
+                <property name="height-request">8</property>
+                <property name="width-request">8</property>
+                <property name="halign">0.5</property>
+                <property name="valign">0.5</property>
+                <style>
+                  <class name="slow-spinner"/>
+                </style>
+              </object>
+
+              <packing>
+                <property name="name">spinner</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+
+      </object>
+    </child>
+
+    <child>
+      <object class="GtkLabel" id="machine_name">
+        <property name="visible">True</property>
+        <property name="width-request">192</property>
+        <property name="max-width-chars">128</property>
+        <property name="justify">left</property>
+        <property name="halign">start</property>
+        <property name="hexpand">True</property>
+        <property name="valign">center</property>
+        <property name="ellipsize">end</property>
+        <property name="margin-top">10</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+      </packing>
+    </child>
+
+  </template>
+</interface>
diff --git a/data/ui/icon-view.ui b/data/ui/icon-view.ui
new file mode 100644
index 0000000..322e1b4
--- /dev/null
+++ b/data/ui/icon-view.ui
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.9 -->
+  <template class="BoxesIconView" parent="GtkScrolledWindow">
+    <property name="visible">True</property>
+    <property name="hscrollbar-policy">never</property>
+    <property name="vscrollbar-policy">automatic</property>
+
+    <child>
+      <object class="GtkViewport" id="viewport">
+        <property name="visible">True</property>
+        <style>
+          <class name="view"/>
+          <class name="content-view"/>
+        </style>
+
+        <child>
+          <object class="GtkFlowBox" id="flowbox">
+            <property name="visible">True</property>
+            <property name="border-width">12</property>
+            <property name="column-spacing">20</property>
+            <property name="row-spacing">6</property>
+            <property name="valign">start</property>
+            <style>
+              <class name="boxes-list-box"/>
+              <class name="view"/>
+              <class name="content-view"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/Makefile.am b/src/Makefile.am
index 129e1a2..1f46272 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -114,6 +114,7 @@ gnome_boxes_SOURCES =                               \
        i-properties-provider.vala              \
        i-collection-view.vala                  \
        icon-view.vala                          \
+       icon-view-child.vala                    \
        installer-media.vala                    \
        installed-media.vala                    \
        keys-input-popover.vala                 \
diff --git a/src/app-window.vala b/src/app-window.vala
index c6c4689..2b2e900 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -95,7 +95,7 @@ private class Boxes.AppWindow: Gtk.ApplicationWindow, Boxes.UI {
     [GtkChild]
     public Gtk.Stack below_bin;
     [GtkChild]
-    public IconView icon_view;
+    private IconView icon_view;
     [GtkChild]
     private ListView list_view;
 
diff --git a/src/app.vala b/src/app.vala
index f7e5508..600fa91 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -126,13 +126,6 @@ private class Boxes.App: Gtk.Application {
 
         collection = new Collection ();
 
-        collection.item_added.connect ((item) => {
-            main_window.icon_view.add_item (item);
-        });
-        collection.item_removed.connect ((item) => {
-            main_window.icon_view.remove_item (item);
-        });
-
         brokers.insert ("libvirt", LibvirtBroker.get_default ());
         if (Config.HAVE_OVIRT)
             brokers.insert ("ovirt", OvirtBroker.get_default ());
diff --git a/src/icon-view-child.vala b/src/icon-view-child.vala
new file mode 100644
index 0000000..473a2b1
--- /dev/null
+++ b/src/icon-view-child.vala
@@ -0,0 +1,91 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/icon-view-child.ui")]
+private class Boxes.IconViewChild : Gtk.Box {
+    public bool _selection_mode;
+    public bool selection_mode {
+        get { return _selection_mode; }
+        set {
+            _selection_mode = value;
+
+            selection_button.visible = _selection_mode;
+
+            if (!_selection_mode)
+                selected = false;
+        }
+        default = false ;
+    }
+
+    public bool _selected;
+    public bool selected {
+        get { return _selected; }
+        set { _selected = selection_mode && value; }
+        default = false;
+    }
+
+    public CollectionItem item { get; private set; }
+    private Machine machine {
+        get { return item as Machine; }
+    }
+
+    [GtkChild]
+    private Gtk.CheckButton selection_button;
+    [GtkChild]
+    private Gtk.Stack stack;
+    [GtkChild]
+    private Gtk.Image thumbnail;
+    [GtkChild]
+    private Gtk.Spinner spinner;
+    [GtkChild]
+    private Gtk.Image favorite;
+    [GtkChild]
+    private Gtk.Label machine_name;
+
+    private Boxes.MachineThumbnailer thumbnailer;
+
+    private Binding selected_binding;
+
+
+    public IconViewChild (CollectionItem item) {
+        this.item = item;
+
+        thumbnailer = machine.thumbnailer;
+
+        thumbnailer.favorite_emblem_enabled = false;
+        thumbnailer.notify["thumbnail"].connect (() => {
+            thumbnail.set_from_pixbuf (thumbnailer.thumbnail);
+        });
+        thumbnail.set_from_pixbuf (thumbnailer.thumbnail);
+
+        selected_binding = bind_property ("selected", selection_button, "active", 
BindingFlags.BIDIRECTIONAL);
+
+        machine.config.notify["categories"].connect (update_favorite);
+        machine.notify["under-construction"].connect (update_thumbnail);
+
+        update_thumbnail ();
+        update_favorite ();
+
+        machine.bind_property ("name", machine_name, "label", BindingFlags.SYNC_CREATE);
+    }
+
+    private void update_thumbnail () {
+        if (machine.under_construction) {
+            stack.visible_child = spinner;
+            spinner.start ();
+            spinner.visible = true;
+        }
+        else {
+            stack.visible_child = thumbnail;
+            spinner.stop ();
+        }
+    }
+
+    private void update_favorite () {
+        if ("favorite" in machine.config.categories)
+            favorite.set_from_icon_name ("starred-symbolic", Gtk.IconSize.MENU);
+        else
+            favorite.clear ();
+    }
+}
\ No newline at end of file
diff --git a/src/icon-view.vala b/src/icon-view.vala
index 1e8494b..6073ce8 100644
--- a/src/icon-view.vala
+++ b/src/icon-view.vala
@@ -6,11 +6,20 @@ public enum Boxes.SelectionCriteria {
     RUNNING
 }
 
-private class Boxes.IconView: Gd.MainView, Boxes.ICollectionView, Boxes.UI {
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/icon-view.ui")]
+private class Boxes.IconView: Gtk.ScrolledWindow, Boxes.ICollectionView, Boxes.UI {
     public UIState previous_ui_state { get; protected set; }
     public UIState ui_state { get; protected set; }
 
+    public CollectionFilter filter { get; protected set; }
+
+    [GtkChild]
+    private Gtk.FlowBox flowbox;
+
+    private GLib.List<CollectionItem> hidden_items;
+
     private AppWindow window;
+    private Boxes.ActionsPopover context_popover;
 
     private Category _category;
     public Category category {
@@ -21,101 +30,33 @@ private class Boxes.IconView: Gd.MainView, Boxes.ICollectionView, Boxes.UI {
         }
     }
 
-    private enum ModelColumns {
-        SCREENSHOT = Gd.MainColumns.ICON,
-        TITLE = Gd.MainColumns.PRIMARY_TEXT,
-        INFO = Gd.MainColumns.SECONDARY_TEXT,
-        SELECTED = Gd.MainColumns.SELECTED,
-        PULSE = Gd.MainColumns.PULSE,
-        ITEM = Gd.MainColumns.LAST,
-
-        LAST
-    }
-
-    private Gtk.ListStore store;
-    private Gtk.TreeModelFilter model_filter;
-    private Boxes.ActionsPopover context_popover;
-    private GLib.List<CollectionItem> hidden_items;
-
-    public CollectionFilter filter { get; protected set; }
-
-    private const Gtk.CornerType[] right_corners = { Gtk.CornerType.TOP_RIGHT, Gtk.CornerType.BOTTOM_RIGHT };
-    private const Gtk.CornerType[] bottom_corners = { Gtk.CornerType.BOTTOM_LEFT, 
Gtk.CornerType.BOTTOM_RIGHT };
-
-    public const Gdk.RGBA FRAME_BORDER_COLOR = { 0x3b / 255.0, 0x3c / 255.0, 0x38 / 255.0, 1.0 };
-    public const Gdk.RGBA FRAME_BACKGROUND_COLOR = { 0x2d / 255.0, 0x2d / 255.0, 0x2d / 255.0, 1.0 };
-    public const double FRAME_RADIUS = 2.0;
-
-    public const Gdk.RGBA CENTERED_EMBLEM_COLOR = { 0xbe / 255.0, 0xbe / 255.0, 0xbe / 255.0, 1.0 };
-    public const Gdk.RGBA SMALL_EMBLEM_COLOR = { 1.0, 1.0, 1.0, 1.0 };
-    public const int BIG_EMBLEM_SIZE = 32;
-    public const int SMALL_EMBLEM_SIZE = 16;
-    public const int EMBLEM_PADDING = 8;
-
     construct {
         category = new Category (_("New and Recent"), Category.Kind.NEW);
         hidden_items = new GLib.List<CollectionItem> ();
+
+        setup_flowbox ();
+
         filter = new CollectionFilter ();
-        setup_view ();
-        notify["ui-state"].connect (ui_state_changed);
         filter.notify["text"].connect (() => {
-            model_filter.refilter ();
+            flowbox.invalidate_filter ();
         });
         filter.filter_func_changed.connect (() => {
-            model_filter.refilter ();
+            flowbox.invalidate_filter ();
         });
+
+        notify["ui-state"].connect (ui_state_changed);
     }
 
     public void setup_ui (AppWindow window) {
         this.window = window;
 
         window.notify["selection-mode"].connect (() => {
-            set_selection_mode (window.selection_mode);
-            if (!window.selection_mode)
-                unselect_all (); // Reset selection on exiting selection mode
+            flowbox.selection_mode = window.selection_mode ? Gtk.SelectionMode.MULTIPLE :
+                                                             Gtk.SelectionMode.NONE;
+            update_selection_mode ();
         });
 
-        var icon_view = get_generic_view () as Gtk.IconView;
-        icon_view.button_release_event.connect (on_button_press_event);
-        icon_view.key_press_event.connect (on_key_press_event);
         context_popover = new Boxes.ActionsPopover (window);
-        context_popover.relative_to = icon_view;
-    }
-
-    private void ui_state_changed () {
-        if (ui_state == UIState.COLLECTION)
-            unselect_all ();
-    }
-
-    private void update_screenshot (Gtk.TreeIter iter) {
-        CollectionItem item;
-
-        store.get (iter, ModelColumns.ITEM, out item);
-        Machine machine = item as Machine;
-        return_if_fail (machine != null);
-        var pixbuf = machine.thumbnailer.thumbnail;
-
-        store.set (iter, ModelColumns.SCREENSHOT, pixbuf);
-        queue_draw ();
-    }
-
-    private Gtk.TreeIter append (string title,
-                                 string? info,
-                                 CollectionItem item) {
-        Gtk.TreeIter iter;
-
-        store.append (out iter);
-        store.set (iter, Gd.MainColumns.ID, "%p".printf (item));
-        store.set (iter, ModelColumns.TITLE, title);
-        if (info != null)
-            store.set (iter, ModelColumns.INFO, info);
-        store.set (iter, ModelColumns.SELECTED, false);
-        store.set (iter, ModelColumns.ITEM, item);
-        update_screenshot (iter);
-
-        item.set_data<Gtk.TreeIter?> ("iter", iter);
-
-        return iter;
     }
 
     public void add_item (CollectionItem item) {
@@ -146,300 +87,201 @@ private class Boxes.IconView: Gd.MainView, Boxes.ICollectionView, Boxes.UI {
 
             return;
         }
-
-        var info = machine.status?? machine.info;
-        var iter = append (machine.name, info,  item);
-        var thumbnail_id = machine.thumbnailer.notify["thumbnail"].connect (() => {
-            // apparently iter is stable after insertion/removal/sort
-            update_screenshot (iter);
-        });
-        item.set_data<ulong> ("thumbnail_id", thumbnail_id);
-
-        var categories_id = machine.config.notify["categories"].connect (() => {
-            // apparently iter is stable after insertion/removal/sort
-            update_screenshot (iter);
-        });
-        item.set_data<ulong> ("categories_id", categories_id);
-
-        var name_id = item.notify["name"].connect (() => {
-            // apparently iter is stable after insertion/removal/sort
-            store.set (iter, ModelColumns.TITLE, item.name);
-            queue_draw ();
-        });
-        item.set_data<ulong> ("name_id", name_id);
-
-        var info_id = machine.notify["info"].connect (on_machine_info_changed);
-        item.set_data<ulong> ("info_id", info_id);
-        var status_id = machine.notify["status"].connect (on_machine_info_changed);
-        item.set_data<ulong> ("status_id", status_id);
-
-        setup_activity (iter, machine);
-        var under_construct_id = machine.notify["under-construction"].connect (() => {
-            // apparently iter is stable after insertion/removal/sort
-            setup_activity (iter, machine);
-            queue_draw ();
-        });
-        item.set_data<ulong> ("under_construct_id", under_construct_id);
-        var machine_state_id = machine.notify["state"].connect (() => {
-            // apparently iter is stable after insertion/removal/sort
-            setup_activity (iter, machine);
-            queue_draw ();
-        });
-        item.set_data<ulong> ("machine_state_id", machine_state_id);
-
-        item.set_state (window.ui_state);
     }
 
-    public List<CollectionItem> get_selected_items () {
-        var selected = new List<CollectionItem> ();
+    public void remove_item (CollectionItem item) {
+        hidden_items.remove (item);
+    }
 
-        store.foreach ((store, path, iter) => {
-            CollectionItem item;
-            bool is_selected;
+    public void select_by_criteria (SelectionCriteria criteria) {
+        window.selection_mode = true;
 
-            store.get (iter,
-                       ModelColumns.SELECTED, out is_selected,
-                       ModelColumns.ITEM, out item);
-            if (is_selected && item != null)
-                selected.append (item);
+        switch (criteria) {
+        default:
+        case SelectionCriteria.ALL:
+            foreach_child ((box_child) => { select_child (box_child); });
+
+            break;
+        case SelectionCriteria.NONE:
+            foreach_child ((box_child) => { unselect_child (box_child); });
+
+            break;
+        case SelectionCriteria.RUNNING:
+            foreach_child ((box_child) => {
+                var item = get_item_for_child (box_child);
+                if (item != null && item is Machine && (item as Machine).is_running)
+                    select_child (box_child);
+                else
+                    unselect_child (box_child);
+            });
 
-            return false;
-        });
+            break;
+        }
 
-        return (owned) selected;
+        App.app.notify_property ("selected-items");
     }
 
-    public void remove_item (CollectionItem item) {
-        hidden_items.remove (item);
+    public List<CollectionItem> get_selected_items () {
+        var selected = new List<CollectionItem> ();
 
-        var iter = item.get_data<Gtk.TreeIter?> ("iter");
-        if (iter == null) {
-            debug ("item not in view or already removed");
-            return;
+        foreach (var box_child in flowbox.get_selected_children ()) {
+            var item = get_item_for_child (box_child);
+            selected.append (item);
         }
 
-        store.remove (ref iter);
-        item.set_data<Gtk.TreeIter?> ("iter", null);
-
-        var name_id = item.get_data<ulong> ("name_id");
-        item.disconnect (name_id);
-        var status_id = item.get_data<ulong> ("status_id");
-        item.disconnect (status_id);
-        var info_id = item.get_data<ulong> ("info_id");
-        item.disconnect (info_id);
-        var under_construct_id = item.get_data<ulong> ("under_construct_id");
-        item.disconnect (under_construct_id);
-        var machine_state_id = item.get_data<ulong> ("machine_state_id");
-        if (machine_state_id != 0)
-            item.disconnect (machine_state_id);
-
-        if (item as Machine != null) {
-            var machine = item as Machine;
-            var categories_id = item.get_data<ulong> ("categories_id");
-            machine.config.disconnect (categories_id);
-
-            var thumbnail_id = item.get_data<ulong> ("thumbnail_id");
-            machine.thumbnailer.disconnect (thumbnail_id);
-        }
+        return (owned) selected;
     }
 
-    private CollectionItem get_item_for_iter (Gtk.TreeIter iter) {
-        GLib.Value value;
-
-        store.get_value (iter, ModelColumns.ITEM, out value);
+    public void activate_first_item () {
+        Gtk.FlowBoxChild first_child = null;
+        foreach_child ((box_child) => {
+            if (first_child == null)
+                first_child = box_child;
+        });
 
-        return (CollectionItem) value;
+        if (first_child == null)
+            flowbox.child_activated (first_child);
     }
 
+    private void setup_flowbox () {
+        flowbox.bind_model (App.app.collection.items, (item) => {
+            var child = new Gtk.FlowBoxChild ();
+            child.halign = Gtk.Align.START;
+            var box = new IconViewChild (item as CollectionItem);
+            child.add (box);
 
-    private CollectionItem get_item_for_path (Gtk.TreePath path) {
-        Gtk.TreeIter filter_iter, iter;
-
-        model_filter.get_iter (out filter_iter, path);
-        model_filter.convert_iter_to_child_iter (out iter, filter_iter);
+            box.notify["selected"].connect (() => {
+                propagate_view_child_selection (child);
+            });
 
-        return get_item_for_iter (iter);
-    }
+            box.visible = true;
+            child.visible = true;
 
-    private bool model_visible (Gtk.TreeModel model, Gtk.TreeIter iter) {
-        var item = get_item_for_iter (iter);
-        if (item  == null)
-            return false;
+            return child;
+        });
 
-        return filter.filter (item);
-    }
+        flowbox.button_release_event.connect (on_button_press_event);
+        flowbox.key_press_event.connect (on_key_press_event);
 
-    public void activate_first_item () {
-        if (model_filter.iter_n_children (null) == 1) {
-            Gtk.TreePath path = new Gtk.TreePath.from_string ("0");
-            (get_generic_view () as Gtk.IconView).item_activated (path);
-        }
-    }
+        flowbox.set_filter_func (model_filter);
 
-    private void setup_view () {
-        store = new Gtk.ListStore (ModelColumns.LAST,
-                                   typeof (string),
-                                   typeof (string),
-                                   typeof (string),
-                                   typeof (string),
-                                   typeof (Gdk.Pixbuf),
-                                   typeof (long),
-                                   typeof (bool),
-                                   typeof (uint),
-                                   typeof (CollectionItem));
-        store.set_default_sort_func ((store, a, b) => {
-            CollectionItem item_a, item_b;
-
-            store.get (a, ModelColumns.ITEM, out item_a);
-            store.get (b, ModelColumns.ITEM, out item_b);
-
-            if (item_a == null || item_b == null) // FIXME?!
-                return 0;
-
-            return item_a.compare (item_b);
-        });
-        store.set_sort_column_id (Gtk.SortColumn.DEFAULT, Gtk.SortType.ASCENDING);
-        store.row_deleted.connect (() => {
-            App.app.notify_property ("selected-items");
-        });
-        store.row_inserted.connect (() => {
-            App.app.notify_property ("selected-items");
-        });
-
-        model_filter = new Gtk.TreeModelFilter (store, null);
-        model_filter.set_visible_func (model_visible);
+        flowbox.child_activated.connect ((child) => {
+            if (window.selection_mode)
+                return;
 
-        set_view_type (Gd.MainViewType.ICON);
-        set_model (model_filter);
-        item_activated.connect ((view, id, path) => {
-            var item = get_item_for_path (path);
+            var item = get_item_for_child (child);
             if (item is LibvirtMachine && (item as LibvirtMachine).importing)
                 return;
+
             window.select_item (item);
         });
-        view_selection_changed.connect (() => {
-            queue_draw ();
-            App.app.notify_property ("selected-items");
-        });
 
-        show_all ();
+        update_selection_mode ();
     }
 
-    public void select_by_criteria (SelectionCriteria criteria) {
-        window.selection_mode = true;
+    private CollectionItem? get_item_for_child (Gtk.FlowBoxChild child) {
+        var view = child.get_child () as IconViewChild;
+        if (view == null)
+            return null;
 
-        model_filter.foreach ( (filter_model, filter_path, filter_iter) => {
-            Gtk.TreeIter iter;
-            model_filter.convert_iter_to_child_iter (out iter, filter_iter);
-            bool selected;
-            switch (criteria) {
-            default:
-            case SelectionCriteria.ALL:
-                selected = true;
-                break;
-            case SelectionCriteria.NONE:
-                selected = false;
-                break;
-            case SelectionCriteria.RUNNING:
-                CollectionItem item;
-                store.get (iter, ModelColumns.ITEM, out item);
-                selected = item != null && item is Machine &&
-                    (item as Machine).is_running;
-                break;
-            }
-            store.set (iter, ModelColumns.SELECTED, selected);
-            return false;
-        });
-        queue_draw ();
-        App.app.notify_property ("selected-items");
+        return view.item;
     }
 
-    private void setup_activity (Gtk.TreeIter iter, Machine machine) {
-        var activity_timeout = machine.get_data<uint> ("activity_timeout");
-        if (activity_timeout > 0) {
-            Source.remove (activity_timeout);
-            machine.set_data<uint> ("activity_timeout", 0);
-        }
-
-        if (!machine.under_construction) {
-            store.set (iter, ModelColumns.PULSE, 0);
-            var machine_state_id = machine.get_data<ulong> ("machine_state_id");
-            if (machine_state_id != 0) {
-                machine.disconnect (machine_state_id);
-                machine.set_data<ulong> ("machine_state_id", 0);
-            }
-
-            return;
-        }
+    private void foreach_child (Func<Gtk.FlowBoxChild> func) {
+        flowbox.forall ((child) => {
+            var view_child = child as Gtk.FlowBoxChild;
+            if (view_child == null)
+                return;
 
-        var pulse = 1;
-        store.set (iter, ModelColumns.PULSE, pulse++);
+            func (view_child);
+        });
+    }
 
-        if (machine.state == Machine.MachineState.SAVED)
-            return;
+    private bool model_filter (Gtk.FlowBoxChild child) {
+        if (child  == null)
+            return false;
 
-        activity_timeout = Timeout.add (150, () => {
-            var machine_iter = machine.get_data<Gtk.TreeIter?> ("iter");
-            if (machine_iter == null)
-                return false; // Item removed
+        var item = get_item_for_child (child);
+        if (item  == null)
+            return false;
 
-            store.set (machine_iter, ModelColumns.PULSE, pulse++);
-            queue_draw ();
+        return filter.filter (item as CollectionItem);
+    }
 
-            return true;
-        });
-        machine.set_data<uint> ("activity_timeout", activity_timeout);
+    private void ui_state_changed () {
+        if (ui_state == UIState.COLLECTION)
+            flowbox.unselect_all ();
     }
 
     private bool on_button_press_event (Gdk.EventButton event) {
         if (event.type != Gdk.EventType.BUTTON_RELEASE || event.button != 3)
             return false;
 
-        var generic_view = get_generic_view () as Gd.MainViewGeneric;
-        var path = generic_view.get_path_at_pos ((int) event.x, (int) event.y);
-        if (path == null)
-            return false;
+        var child = flowbox.get_child_at_pos ((int) event.x, (int) event.y);
 
-        return launch_context_popover_for_path (path);
+        return launch_context_popover_for_child (child);
     }
 
     private bool on_key_press_event (Gdk.EventKey event) {
         if (event.keyval != Gdk.Key.Menu)
             return false;
 
-        var icon_view = get_generic_view () as Gtk.IconView;
-        Gtk.TreePath path;
-        Gtk.CellRenderer cell;
-        if (!icon_view.get_cursor (out path, out cell))
+        var child = flowbox.get_selected_children ().nth_data (0);
+        if (child == null)
             return false;
 
-        return launch_context_popover_for_path (path);
+        return launch_context_popover_for_child (child);
     }
 
-    private bool launch_context_popover_for_path (Gtk.TreePath path) {
-        var item = get_item_for_path (path);
+    private bool launch_context_popover_for_child (Gtk.FlowBoxChild child) {
+        var item = get_item_for_child (child);
         if (item == null)
             return false;
 
-        var icon_view = get_generic_view () as Gtk.IconView;
-        Gdk.Rectangle rect;
-        icon_view.get_cell_rect (path, null, out rect);
-
         context_popover.update_for_item (item);
-        rect.height /= 2; // Show in the middle
-        context_popover.pointing_to = rect;
+        context_popover.set_relative_to (child);
         context_popover.show ();
 
         return true;
     }
 
-    private void on_machine_info_changed (GLib.Object object, GLib.ParamSpec spec) {
-        var machine = object as Machine;
-        var iter = machine.get_data<Gtk.TreeIter?> ("iter");
-        return_if_fail (iter != null);
+    private void update_selection_mode () {
+        foreach_child ((child) => {
+            var view_child = child.get_child () as Boxes.IconViewChild;
 
-        var info = machine.status?? machine.info;
-        store.set (iter, ModelColumns.INFO, info);
-        queue_draw ();
+            if (view_child.selection_mode != window.selection_mode)
+                view_child.selection_mode = window.selection_mode;
+
+            unselect_child (child);
+        });
+    }
+
+    private void propagate_view_child_selection (Gtk.FlowBoxChild child) {
+        var view_child = child.get_child () as IconViewChild;
+
+        if (view_child.selected)
+            select_child (child);
+        else
+            unselect_child (child);
+    }
+
+    private void select_child (Gtk.FlowBoxChild child) {
+        var view_child = child.get_child () as IconViewChild;
+
+        flowbox.select_child (child);
+        if (!view_child.selected)
+            view_child.selected = true;
+
+        App.app.notify_property ("selected-items");
+    }
+
+    private void unselect_child (Gtk.FlowBoxChild child) {
+        var view_child = child.get_child () as IconViewChild;
+
+        flowbox.unselect_child (child);
+        if (view_child.selected)
+            view_child.selected = false;
+
+        App.app.notify_property ("selected-items");
     }
 }
diff --git a/src/meson.build b/src/meson.build
index 97c9287..765a969 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -28,6 +28,7 @@ vala_sources = [
   'libvirt-cloned-media.vala',
   'libvirt-media.vala',
   'iso-extractor.vala',
+  'icon-view-child.vala',
   'libvirt-broker.vala',
   'libvirt-machine.vala',
   'libvirt-machine-properties.vala',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]