[gnome-clocks/wip/flowbox: 2/2] port to flowbox



commit 1215a262d111ffb803ce1032681c135bfb98aa82
Author: Paolo Borelli <pborelli gnome org>
Date:   Sun Feb 21 10:32:37 2016 +0100

    port to flowbox
    
    This is an unifinished WIP commit.
    The idea is to reimplement the ContentView widget using FlowBox
    instead of IconView.
    For now I am adding a Content2View widget so that I can leave the
    old code untouched and only work on Alarm without touching World.
    Once things are working we can rename back to ContentView and drop
    
    The code is mostly working, the most noticeable TODO items are:
     - the selection checkbox looks ugly
     - port more css (font size, inactive and ringing states etc)
     - the way flowbox places tiles when resizing the windows looks bad,
       the space among tiles changes and this is especially noticeable
       because the second row moves when the action bar appears
     - once alarm is fine, port world (should be just be a matter of
       creating a custom Tile template)
     - once they are both ported delete a load of custom code (the custom
       iconview, the custom cell renderers, a lot of css etc).

 data/css/gnome-clocks.css       |   14 ++
 data/gnome-clocks.gresource.xml |    1 +
 data/ui/alarm.ui                |    5 +-
 data/ui/alarmtile.ui            |   62 ++++++++++
 src/alarm.vala                  |   24 ++++-
 src/widgets.vala                |  253 +++++++++++++++++++++++++++++++++++++++
 6 files changed, 355 insertions(+), 4 deletions(-)
---
diff --git a/data/css/gnome-clocks.css b/data/css/gnome-clocks.css
index 9dd322e..8d8a862 100644
--- a/data/css/gnome-clocks.css
+++ b/data/css/gnome-clocks.css
@@ -20,6 +20,11 @@ window > stack:backdrop {
     font-size: 32px;
 }
 
+flowboxchild check {
+   min-width: 40px;
+   min-height: 40px;
+}
+
 /* world */
 
 .clocks-digital-renderer.light.stripe {
@@ -42,6 +47,15 @@ window > stack:backdrop {
 
 /* alarms */
 
+flowboxchild grid.tile {
+    background-color: transparent;
+    background-image: -gtk-gradient(radial,
+                                    center center, 0,
+                                    center bottom, 1.0,
+                                    from(shade(@theme_selected_bg_color,1.2)), to(@theme_selected_bg_color));
+    color: white;
+}
+
 .clocks-digital-renderer.active {
     background-color: transparent;
     background-image: -gtk-gradient(radial,
diff --git a/data/gnome-clocks.gresource.xml b/data/gnome-clocks.gresource.xml
index c2390e8..eb0cf40 100644
--- a/data/gnome-clocks.gresource.xml
+++ b/data/gnome-clocks.gresource.xml
@@ -9,6 +9,7 @@
     <file preprocess="xml-stripblanks">ui/worldlocationdialog.ui</file>
     <file preprocess="xml-stripblanks">ui/world.ui</file>
     <file preprocess="xml-stripblanks">ui/alarmringing.ui</file>
+    <file preprocess="xml-stripblanks">ui/alarmtile.ui</file>
     <file preprocess="xml-stripblanks">ui/alarmsetupdialog.ui</file>
     <file preprocess="xml-stripblanks">ui/alarm.ui</file>
     <file preprocess="xml-stripblanks">ui/stopwatch.ui</file>
diff --git a/data/ui/alarm.ui b/data/ui/alarm.ui
index 99c7bf4..d06168b 100644
--- a/data/ui/alarm.ui
+++ b/data/ui/alarm.ui
@@ -1,8 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.6 -->
+  <requires lib="gtk+" version="3.18"/>
   <template class="ClocksAlarmFace" parent="GtkStack">
     <property name="visible">True</property>
+    <property name="can_focus">False</property>
     <signal name="notify::visible-child" handler="visible_child_changed" swapped="no"/>
     <child>
       <object class="GtkGrid" id="empty_view">
@@ -42,7 +43,7 @@
       </object>
     </child>
     <child>
-      <object class="ClocksContentView" id="content_view">
+      <object class="ClocksContent2View" id="content_view">
         <property name="visible">True</property>
         <signal name="item-activated" handler="item_activated" swapped="no"/>
       </object>
diff --git a/data/ui/alarmtile.ui b/data/ui/alarmtile.ui
new file mode 100644
index 0000000..c15e1ff
--- /dev/null
+++ b/data/ui/alarmtile.ui
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ClocksAlarmTile" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="halign">start</property>
+    <property name="valign">start</property>
+    <child>
+      <object class="GtkGrid" id="tile_grid">
+        <property name="width_request">256</property>
+        <property name="height_request">256</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkLabel" id="time_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="day_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">1</property>
+          </packing>
+        </child>
+        <style>
+          <class name="tile"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="name_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <style>
+          <class name="name"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/src/alarm.vala b/src/alarm.vala
index dde81f7..297d3e8 100644
--- a/src/alarm.vala
+++ b/src/alarm.vala
@@ -286,6 +286,23 @@ private class Item : Object, ContentItem {
     }
 }
 
+[GtkTemplate (ui = "/org/gnome/clocks/ui/alarmtile.ui")]
+private class Tile : Gtk.Grid {
+    public Item alarm { get; construct set; }
+
+    [GtkChild]
+    private Gtk.Widget name_label;
+    [GtkChild]
+    private Gtk.Widget time_label;
+
+    public Tile (Item alarm) {
+        Object (alarm: alarm);
+
+        alarm.bind_property ("name", name_label, "label", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE);
+        alarm.bind_property ("time-label", time_label, "label", BindingFlags.DEFAULT | 
BindingFlags.SYNC_CREATE);
+    }
+}
+
 [GtkTemplate (ui = "/org/gnome/clocks/ui/alarmsetupdialog.ui")]
 private class SetupDialog : Gtk.Dialog {
     private Utils.WallClock.Format format;
@@ -529,7 +546,7 @@ public class Face : Gtk.Stack, Clocks.Clock {
     [GtkChild]
     private Gtk.Widget empty_view;
     [GtkChild]
-    private ContentView content_view;
+    private Content2View content_view;
     [GtkChild]
     private RingingPanel ringing_panel;
 
@@ -569,7 +586,10 @@ public class Face : Gtk.Stack, Clocks.Clock {
         new_button.action_name = "win.new";
         header_bar.pack_start (new_button);
 
-        content_view.bind_model (alarms);
+        content_view.bind_model (alarms, (item) => {
+            return new Tile ((Item)item);
+        });
+
         content_view.set_header_bar (header_bar);
 
         load ();
diff --git a/src/widgets.vala b/src/widgets.vala
index 12c7ec8..0dfad94 100644
--- a/src/widgets.vala
+++ b/src/widgets.vala
@@ -740,6 +740,259 @@ public class ContentView : Gtk.Bin {
     }
 }
 
+public class Content2View : Gtk.Bin {
+    public enum Mode {
+        NORMAL,
+        SELECTION
+    }
+
+    public Mode mode {
+        get {
+            return _mode;
+        }
+
+        private set {
+            if (_mode != value) {
+                _mode = value;
+
+                switch (_mode) {
+                case Mode.SELECTION:
+                    header_bar.mode = HeaderBar.Mode.SELECTION;
+                    action_bar.show ();
+                    break;
+                case Mode.NORMAL:
+                    header_bar.mode = HeaderBar.Mode.NORMAL;
+                    action_bar.hide ();
+                    // clear current selection
+                    model.unselect_all ();
+                    break;
+                default:
+                    assert_not_reached ();
+                }
+
+                update_header_bar ();
+            }
+        }
+    }
+
+    private bool can_select {
+        get {
+            return _can_select;
+        }
+
+        private set {
+            if (_can_select != value) {
+                _can_select = value;
+
+                // show the select button only if we are mapped,
+                // since we do not want to show it when the geolocation
+                // query ends, but we are on another page
+                select_button.visible = _can_select && get_mapped ();
+            }
+        }
+    }
+
+    private Mode _mode;
+    private bool _can_select;
+    private ContentStore model;
+    private Gtk.FlowBox flow_box;
+    private Gtk.Button select_button;
+    private Gtk.Button cancel_button;
+    private SelectionMenuButton selection_menubutton;
+    private Gtk.Grid grid;
+    private Gtk.ActionBar action_bar;
+    private Gtk.Button delete_button;
+    private HeaderBar? header_bar;
+
+    construct {
+        flow_box = new Gtk.FlowBox ();
+        flow_box.selection_mode = Gtk.SelectionMode.NONE;
+        flow_box.row_spacing = 12;
+        flow_box.column_spacing = 12;
+        flow_box.margin_top = 12;
+        flow_box.margin_bottom = 12;
+        flow_box.margin_left = 12;
+        flow_box.margin_right = 12;
+
+        flow_box.child_activated.connect ((child) => {
+            var item = model.get_item (child.get_index ()) as ContentItem;
+            if (item != null) {
+                item_activated (item);
+            }
+        });
+
+        var scrolled_window = new Gtk.ScrolledWindow (null, null);
+        scrolled_window.add (flow_box);
+        scrolled_window.hexpand = true;
+        scrolled_window.vexpand = true;
+        scrolled_window.halign = Gtk.Align.FILL;
+        scrolled_window.valign = Gtk.Align.FILL;
+
+        grid = new Gtk.Grid ();
+        grid.attach (scrolled_window, 0, 0, 1, 1);
+
+        action_bar = new Gtk.ActionBar ();
+        action_bar.no_show_all = true;
+        grid.attach (action_bar, 0, 1, 1, 1);
+
+        delete_button = new Gtk.Button ();
+        delete_button.label = _("Delete");
+        delete_button.visible = true;
+        delete_button.sensitive = false;
+        delete_button.halign = Gtk.Align.END;
+        delete_button.hexpand = true;
+        delete_button.clicked.connect (() => {
+            model.delete_selected ();
+            mode = Mode.NORMAL;
+        });
+
+        action_bar.pack_end (delete_button);
+
+        add (grid);
+        grid.show_all ();
+    }
+
+    public signal void item_activated (ContentItem item);
+
+    public delegate Gtk.Widget Content2ViewCreateWidgetFunc (ContentItem item);
+
+    public void bind_model (ContentStore store, owned Content2ViewCreateWidgetFunc create_func) {
+        model = store;
+        model.items_changed.connect ((position, removed, added) => {
+            var first_selectable = model.find ((i) => {
+                return i.selectable;
+            });
+
+            can_select = first_selectable != null;
+        });
+
+        model.selection_changed.connect (() => {
+            var n_items = model.get_n_selected ();
+            selection_menubutton.n_items = n_items;
+
+            if (n_items != 0) {
+                delete_button.sensitive = true;
+            } else {
+                delete_button.sensitive = false;
+            }
+        });
+
+        flow_box.bind_model (model, (object) => {
+            var item = (ContentItem) object;
+            var inner = create_func (item);
+
+            // wrap the widget in an event box to handle righ-click
+            var event_box = new Gtk.EventBox ();
+            event_box.add (inner);
+            event_box.button_press_event.connect ((event) => {
+                // On right click, swicth to selection mode automatically
+                if (item.selectable && event.button == Gdk.BUTTON_SECONDARY) {
+                    mode = Mode.SELECTION;
+                }
+
+                if (item.selectable && mode == Mode.SELECTION) {
+                    item.selected = !item.selected;
+                    return true;
+                } else if (event.button == Gdk.BUTTON_PRIMARY) {
+                    item_activated (item);
+                    return true;
+                }
+
+                return false;
+            });
+
+            // wrap the widget in overlay for the selection check box
+            var overlay = new Gtk.Overlay ();
+            overlay.halign = Gtk.Align.START;
+            overlay.valign = Gtk.Align.START;
+            overlay.add (event_box);
+
+            var check = new Gtk.CheckButton ();
+            check.no_show_all = true;
+            check.halign = Gtk.Align.END;
+            check.valign = Gtk.Align.END;
+            check.margin_bottom = 8;
+            check.margin_end = 8;
+
+            item.bind_property ("selected", check, "active", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE 
| BindingFlags.BIDIRECTIONAL);
+            item.bind_property ("selectable", check, "visible", BindingFlags.DEFAULT | 
BindingFlags.SYNC_CREATE,
+                                 (binding, selectable, ref visible) => {
+                visible = this.mode == Mode.SELECTION && (item).selectable;
+                return true;
+            });
+
+            bind_property ("mode", check, "visible", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE,
+                           (binding, mode, ref visible) => {
+                visible = mode == Mode.SELECTION && (item).selectable;
+                return true;
+            });
+
+            overlay.add_overlay (check);
+
+            // manually wrap in flowboxchild ourselves since we want to set alignment
+            var flow_box_child = new Gtk.FlowBoxChild ();
+            flow_box_child.halign = Gtk.Align.START;
+            flow_box_child.valign = Gtk.Align.START;
+            flow_box_child.add (overlay);
+
+            return flow_box_child;
+        });
+    }
+
+    public void select_all () {
+        mode = Mode.SELECTION;
+        model.select_all ();
+    }
+
+    public void unselect_all () {
+        model.unselect_all ();
+    }
+
+    public bool escape_pressed () {
+        if (mode == Mode.SELECTION) {
+            mode = Mode.NORMAL;
+            return true;
+        }
+        return false;
+    }
+
+    public void set_header_bar (HeaderBar bar) {
+        header_bar = bar;
+
+        select_button = new Gtk.Button ();
+        var select_button_image = new Gtk.Image.from_icon_name ("object-select-symbolic", Gtk.IconSize.MENU);
+        select_button.set_image (select_button_image);
+        select_button.valign = Gtk.Align.CENTER;
+        select_button.no_show_all = true;
+        select_button.clicked.connect (() => {
+            mode = Mode.SELECTION;
+        });
+        header_bar.pack_end (select_button);
+
+        cancel_button = new Gtk.Button.with_label (_("Cancel"));
+        cancel_button.no_show_all = true;
+        cancel_button.valign = Gtk.Align.CENTER;
+        cancel_button.clicked.connect (() => {
+            mode = Mode.NORMAL;
+        });
+        header_bar.pack_end (cancel_button);
+
+        selection_menubutton = new SelectionMenuButton ();
+    }
+
+    public void update_header_bar () {
+        switch (header_bar.mode) {
+        case HeaderBar.Mode.SELECTION:
+            header_bar.custom_title = selection_menubutton;
+            cancel_button.show ();
+            break;
+        case HeaderBar.Mode.NORMAL:
+            select_button.visible = can_select;
+            break;
+        }
+    }
+}
+
 public class AmPmToggleButton : Gtk.Button {
     public enum AmPm {
         AM,


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