[gnome-boxes] Add ListView widget
- From: Zeeshan Ali Khattak <zeeshanak src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-boxes] Add ListView widget
- Date: Sat, 22 Aug 2015 12:11:38 +0000 (UTC)
commit 45206f58001e74bfde5291bbfdee2e78248247ee
Author: Adrien Plazas <kekun plazas laposte net>
Date: Mon Jul 20 14:18:15 2015 +0200
Add ListView widget
This will be used in a subsequent commit by the app window to offer a
list view.
https://bugzilla.gnome.org/show_bug.cgi?id=733252
data/gnome-boxes.gresource.xml | 1 +
data/ui/list-view.ui | 38 ++++
src/Makefile.am | 1 +
src/list-view.vala | 415 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 455 insertions(+), 0 deletions(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index 6385c4f..c935191 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -14,6 +14,7 @@
<file preprocess="xml-stripblanks">ui/empty-boxes.ui</file>
<file preprocess="xml-stripblanks">ui/editable-entry.ui</file>
<file preprocess="xml-stripblanks">ui/resource-graph.ui</file>
+ <file preprocess="xml-stripblanks">ui/list-view.ui</file>
<file preprocess="xml-stripblanks">ui/list-view-row.ui</file>
<file preprocess="xml-stripblanks">ui/notification.ui</file>
<file preprocess="xml-stripblanks">ui/properties-toolbar.ui</file>
diff --git a/data/ui/list-view.ui b/data/ui/list-view.ui
new file mode 100644
index 0000000..47eb4a0
--- /dev/null
+++ b/data/ui/list-view.ui
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.9 -->
+ <template class="BoxesListView" 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="GtkListBox" id="list_box">
+ <property name="visible">True</property>
+ <property name="margin-start">24</property>
+ <property name="margin-end">24</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <signal name="button-release-event" handler="on_button_press_event"/>
+ <signal name="key-press-event" handler="on_key_press_event"/>
+ <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 c86a64a..00a6286 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -119,6 +119,7 @@ gnome_boxes_SOURCES = \
libvirt-broker.vala \
libvirt-machine.vala \
libvirt-machine-properties.vala \
+ list-view.vala \
list-view-row.vala \
machine.vala \
machine-thumbnailer.vala \
diff --git a/src/list-view.vala b/src/list-view.vala
new file mode 100644
index 0000000..aa03ba4
--- /dev/null
+++ b/src/list-view.vala
@@ -0,0 +1,415 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/list-view.ui")]
+private class Boxes.ListView: 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.ListBox list_box;
+
+ private ListStore store;
+ private GLib.List<CollectionItem> hidden_items;
+ private HashTable<CollectionItem, ItemConnections> items_connections;
+
+ private AppWindow window;
+ private Boxes.ActionsPopover context_popover;
+
+ private class ItemConnections: Object {
+ private ulong categories_id;
+ private ulong name_id;
+ private ulong info_id;
+
+ public weak ListView view { get; private set; }
+ public Machine machine { get; private set; }
+
+ public ItemConnections (ListView view, Machine machine) {
+ this.view = view;
+ this.machine = machine;
+
+ categories_id = machine.config.notify["categories"].connect (() => {
+ view.list_box.invalidate_sort ();
+ view.list_box.invalidate_filter ();
+ });
+ name_id = machine.notify["name"].connect (() => {
+ view.list_box.invalidate_sort ();
+ view.list_box.invalidate_filter ();
+ });
+ info_id = machine.notify["info"].connect (() => {
+ view.list_box.invalidate_sort ();
+ view.list_box.invalidate_filter ();
+ });
+ }
+
+ public override void dispose () {
+ machine.config.disconnect (categories_id);
+ machine.disconnect (name_id);
+ machine.disconnect (info_id);
+ base.dispose ();
+ }
+ }
+
+ construct {
+ hidden_items = new GLib.List<CollectionItem> ();
+ items_connections = new HashTable<CollectionItem, ItemConnections> (direct_hash, direct_equal);
+
+ setup_store ();
+ setup_list_box ();
+
+ filter = new CollectionFilter ();
+ filter.notify["text"].connect (() => {
+ list_box.invalidate_filter ();
+ });
+ filter.filter_func_changed.connect (() => {
+ list_box.invalidate_filter ();
+ });
+
+ notify["ui-state"].connect (ui_state_changed);
+ }
+
+ public void setup_ui (AppWindow window) {
+ this.window = window;
+
+ window.notify["selection-mode"].connect (() => {
+ list_box.selection_mode = window.selection_mode ? Gtk.SelectionMode.MULTIPLE :
+ Gtk.SelectionMode.NONE;
+
+ update_selection_mode ();
+ });
+
+ context_popover = new Boxes.ActionsPopover (window);
+ }
+
+ public void add_item (CollectionItem item) {
+ var machine = item as Machine;
+
+ if (machine == null) {
+ warning ("Cannot add item %p".printf (&item));
+
+ return;
+ }
+
+ var window = machine.window;
+ if (window.ui_state == UIState.WIZARD) {
+ // Don't show newly created items until user is out of wizard
+ hidden_items.append (item);
+
+ ulong ui_state_id = 0;
+
+ ui_state_id = window.notify["ui-state"].connect (() => {
+ if (window.ui_state == UIState.WIZARD)
+ return;
+
+ if (hidden_items.find (item) != null) {
+ add_item (item);
+ hidden_items.remove (item);
+ }
+ window.disconnect (ui_state_id);
+ });
+
+ return;
+ }
+
+ store.append (item);
+ items_connections[item] = new ItemConnections (this, machine);
+
+ item.set_state (window.ui_state);
+ }
+
+ public void remove_item (CollectionItem item) {
+ hidden_items.remove (item);
+ items_connections.remove (item);
+
+ uint index = 0;
+ while (index < store.get_n_items () && store.get_item (index) != item)
+ index++;
+
+ if (index >= store.get_n_items ()) {
+ debug ("item not in view or already removed");
+
+ return;
+ }
+
+ // FIXME: Dirty hack to workaround this bug in GTK+:
https://bugzilla.gnome.org/show_bug.cgi?id=752615
+ // Set the default sorting and filtering functions to make the model's and the view's orders match
before
+ // removing the element, then set the regular sorting and filtering functions back.
+ list_box.set_sort_func (default_sort);
+ list_box.set_filter_func (default_filter);
+
+ store.remove (index);
+
+ // FIXME: Workaround for bug #752615 (see above).
+ list_box.set_sort_func (model_sort);
+ list_box.set_filter_func (model_filter);
+ }
+
+ public void select_by_criteria (SelectionCriteria criteria) {
+ window.selection_mode = true;
+
+ switch (criteria) {
+ default:
+ case SelectionCriteria.ALL:
+ foreach_row ((box_row) => { select_row (box_row); });
+
+ break;
+ case SelectionCriteria.NONE:
+ foreach_row ((box_row) => { unselect_row (box_row); });
+
+ break;
+ case SelectionCriteria.RUNNING:
+ foreach_row ((box_row) => {
+ var item = get_item_for_row (box_row);
+ if (item != null && item is Machine && (item as Machine).is_running)
+ select_row (box_row);
+ else
+ unselect_row (box_row);
+ });
+
+ break;
+ }
+
+ App.app.notify_property ("selected-items");
+ }
+
+ public List<CollectionItem> get_selected_items () {
+ var selected = new List<CollectionItem> ();
+
+ foreach (var box_row in list_box.get_selected_rows ()) {
+ var item = get_item_for_row (box_row);
+ selected.append (item);
+ }
+
+ return (owned) selected;
+ }
+
+ public void activate_first_item () {
+ if (store.get_n_items () >= 1) {
+ var box_row = list_box.get_row_at_index (0);
+ list_box.row_activated (box_row);
+ }
+ }
+
+ private void setup_store () {
+ store = new ListStore (typeof (CollectionItem));
+
+ store.items_changed.connect (() => {
+ App.app.notify_property ("selected-items");
+ });
+
+ store.items_changed.connect (() => {
+ App.app.notify_property ("selected-items");
+ });
+ }
+
+ private void setup_list_box () {
+ list_box.selection_mode = Gtk.SelectionMode.NONE;
+ list_box.set_sort_func (model_sort);
+ list_box.set_filter_func (model_filter);
+
+ list_box.bind_model (store, (object) => {
+ var view_row = new ListViewRow (object as CollectionItem);
+ view_row.notify["selected"].connect (() => {
+ propagate_view_row_selection (view_row);
+ });
+
+ return view_row;
+ });
+
+ list_box.row_activated.connect ((box_row) => {
+ if (window.selection_mode)
+ return;
+
+ var item = get_item_for_row (box_row);
+ if (item is LibvirtMachine && (item as LibvirtMachine).importing)
+ return;
+
+ window.select_item (item);
+ });
+
+ update_selection_mode ();
+ }
+
+ private CollectionItem? get_item_for_row (Gtk.ListBoxRow box_row) {
+ var view = box_row.get_child () as ListViewRow;
+ if (view == null)
+ return null;
+
+ return view.item;
+ }
+
+ private void foreach_row (Func<Gtk.ListBoxRow> func) {
+ list_box.forall ((child) => {
+ var box_row = child as Gtk.ListBoxRow;
+ if (box_row == null)
+ return;
+
+ func (box_row);
+ });
+ }
+
+ // FIXME: Part of the workaround for this bug in GTK+: https://bugzilla.gnome.org/show_bug.cgi?id=752615
+ // Sort the items in the same order than in the model.
+ private int default_sort (Gtk.ListBoxRow box_row1, Gtk.ListBoxRow box_row2) {
+ var item1 = get_item_for_row (box_row1);
+ uint pos1 = 0;
+ while (pos1 < store.get_n_items () && store.get_item (pos1) != item1)
+ pos1++;
+
+ var item2 = get_item_for_row (box_row2);
+ uint pos2 = 0;
+ while (pos2 < store.get_n_items () && store.get_item (pos2) != item2)
+ pos2++;
+
+ return ((pos1 > pos2) ? 1 : 0) - ((pos1 < pos2) ? 1 : 0);
+ }
+
+ // FIXME: Part of the workaround for this bug in GTK+: https://bugzilla.gnome.org/show_bug.cgi?id=752615
+ // Show all items.
+ private bool default_filter (Gtk.ListBoxRow box_row) {
+ return true;
+ }
+
+ private int model_sort (Gtk.ListBoxRow box_row1, Gtk.ListBoxRow box_row2) {
+ var view_row1 = box_row1.get_child () as ListViewRow;
+ var view_row2 = box_row2.get_child () as ListViewRow;
+ var item1 = view_row1.item;
+ var item2 = view_row2.item;
+
+ if (item1 == null || item2 == null)
+ return 0;
+
+ return item1.compare (item2);
+ }
+
+ private bool model_filter (Gtk.ListBoxRow box_row) {
+ var view = box_row.get_child () as ListViewRow;
+ if (view == null)
+ return false;
+
+ var item = view.item;
+ if (item == null)
+ return false;
+
+ return filter.filter (item as CollectionItem);
+ }
+
+ private void ui_state_changed () {
+ if (ui_state == UIState.COLLECTION)
+ list_box.unselect_all ();
+ }
+
+ [GtkCallback]
+ private bool on_button_press_event (Gdk.EventButton event) {
+ if (event.type != Gdk.EventType.BUTTON_RELEASE)
+ return false;
+
+ switch (event.button) {
+ case 1:
+ return on_button_1_press_event (event);
+ case 3:
+ return on_button_3_press_event (event);
+ default:
+ return false;
+ }
+ }
+
+ private bool on_button_1_press_event (Gdk.EventButton event) {
+ if (!window.selection_mode)
+ return false;
+
+ // Necessary to avoid treating an event from a child widget which would mess with getting the
correct row.
+ if (event.window != list_box.get_window ())
+ return false;
+
+ var box_row = list_box.get_row_at_y ((int) event.y);
+ if (box_row == null)
+ return false;
+
+ toggle_row_selected (box_row);
+
+ return true;
+ }
+
+ private bool on_button_3_press_event (Gdk.EventButton event) {
+ var box_row = list_box.get_row_at_y ((int) event.y);
+ if (box_row == null)
+ return false;
+
+ return launch_context_popover_for_row (box_row);
+ }
+
+ [GtkCallback]
+ private bool on_key_press_event (Gdk.EventKey event) {
+ if (event.keyval != Gdk.Key.Menu)
+ return false;
+
+ var box_row = list_box.get_selected_row ();
+ if (box_row == null)
+ return false;
+
+ return launch_context_popover_for_row (box_row);
+ }
+
+ private bool launch_context_popover_for_row (Gtk.ListBoxRow box_row) {
+ var item = get_item_for_row (box_row);
+ if (item == null)
+ return false;
+
+ context_popover.update_for_item (item);
+ context_popover.relative_to = box_row;
+ context_popover.show ();
+
+ return true;
+ }
+
+ private void update_selection_mode () {
+ foreach_row ((box_row) => {
+ var view_row = box_row.get_child () as ListViewRow;
+
+ if (view_row.selection_mode != window.selection_mode)
+ view_row.selection_mode = window.selection_mode;
+
+ unselect_row (box_row);
+ });
+ }
+
+ private void propagate_view_row_selection (ListViewRow view_row) {
+ var box_row = view_row.parent as Gtk.ListBoxRow;
+
+ if (view_row.selected)
+ select_row (box_row);
+ else
+ unselect_row (box_row);
+ }
+
+ private void toggle_row_selected (Gtk.ListBoxRow box_row) {
+ var view_row = box_row.get_child () as ListViewRow;
+
+ if (view_row.selected)
+ unselect_row (box_row);
+ else
+ select_row (box_row);
+ }
+
+ private void select_row (Gtk.ListBoxRow box_row) {
+ var view_row = box_row.get_child () as ListViewRow;
+
+ list_box.select_row (box_row);
+ if (!view_row.selected)
+ view_row.selected = true;
+
+ App.app.notify_property ("selected-items");
+ }
+
+ private void unselect_row (Gtk.ListBoxRow box_row) {
+ var view_row = box_row.get_child () as ListViewRow;
+
+ list_box.unselect_row (box_row);
+ if (view_row.selected)
+ view_row.selected = false;
+
+ App.app.notify_property ("selected-items");
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]