Merge lp:~teemperor/granite/table-view into lp:~elementary-pantheon/granite/granite

Proposed by Raphael Isemann on 2014-11-02
Status: Work in progress
Proposed branch: lp:~teemperor/granite/table-view
Merge into: lp:~elementary-pantheon/granite/granite
Diff against target: 454 lines (+407/-1)
4 files modified
demo/GraniteDemo.vala (+42/-1)
lib/CMakeLists.txt (+2/-0)
lib/Views/Table.vala (+333/-0)
lib/Views/TableItem.vala (+30/-0)
To merge this branch: bzr merge lp:~teemperor/granite/table-view
Reviewer Review Type Date Requested Status
xapantu (community) 2014-11-02 Needs Information on 2015-08-18
Review via email: mp+240380@code.launchpad.net

Description of the change

We have quite a lot of implementations of widgets that do nothing more than displaying a list of items. A good example is the keyboard-plug which has at least 5 of those widgets and all those widgets are reimplemented from scratch.

This is especially annoying since on each review you have to recheck if the lists work correctly to figure out minor problems as seen here: https://code.launchpad.net/~julien-spautz/switchboard-plug-keyboard/custom-shortcuts/+merge/212343

This branch introduces a widget that displays a Gee.List and in a MVC-compatible way.

The advantages are:

* Better performance as we can fine-tune this widget to not fire item_changed events if they are not necessary.

* Less code and less time spent with testing list-implementations!

* Design-changes regarding lists can be implemented easier by just changing the code here in granite. This also allows that all third-party developers are automatically updated to the new design if they use this widget.

See the granite-demo under Static notebook -> "Table"-tab and look at stdout to see it in action.

To post a comment you must log in.
lp:~teemperor/granite/table-view updated on 2014-11-02
806. By Raphael Isemann on 2014-11-02

We can now select items from the code

xapantu (xapantu) wrote :

Hum, in what way is that better than the classic MVC approach of a GtkListViem/GtkTreeView with a custom renderer?

review: Needs Information

Unmerged revisions

806. By Raphael Isemann on 2014-11-02

We can now select items from the code

805. By Raphael Isemann on 2014-11-02

Everything works as expected, but we are still firing too many item-changes

804. By Raphael Isemann on 2014-11-02

Implemented up-down (only for the underlying list)

803. By Raphael Isemann on 2014-11-01

We only accept lists now

802. By Raphael Isemann on 2014-11-01

First version of the new Table-class

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'demo/GraniteDemo.vala'
2--- demo/GraniteDemo.vala 2014-05-12 02:42:17 +0000
3+++ demo/GraniteDemo.vala 2014-11-02 22:04:23 +0000
4@@ -152,6 +152,22 @@
5 }
6 }
7
8+ private class Person: GLib.Object, Granite.Widgets.Views.TableItem {
9+
10+ public string name { get; private set; }
11+ public string address { get; private set; }
12+
13+ public Person(string name, string address) {
14+ this.name = name;
15+ this.address = address;
16+ }
17+
18+ public string[] get_values () {
19+ string[] result = {name, address};
20+ return result;
21+ }
22+ }
23+
24 private Gtk.Grid main_layout; // outer-most container
25 private Granite.Widgets.ModeButton mode_button;
26 private int dark_mode_index;
27@@ -283,6 +299,31 @@
28 staticnotebook.append_page (get_overlay_bar_widget (), new Gtk.Label ("Overlay Bar"));
29 staticnotebook.append_page (new Gtk.Label ("Page 3"), new Gtk.Label ("Page 3"));
30
31+ var test_table = new Granite.Widgets.Views.Table (new string[] {"Person", "Address"});
32+
33+ var persons = new Gee.ArrayList<Person>();
34+ persons.add (new Person("Dan", "elementary Headquarter"));
35+ persons.add (new Person("Cody", "Launchpad"));
36+ persons.add (new Person("Sergey", "In the forest"));
37+
38+ test_table.show_list (persons);
39+ test_table.selected_item_changed.connect ((person) => {
40+ print ((person as Person).name + " got selected!\n");
41+ });
42+
43+ test_table.add_item_clicked.connect (() => {
44+ test_table.append_item (
45+ new Person(
46+ "New User" + Random.next_int ().to_string (),
47+ "Limbo"));
48+ });
49+
50+ // Select cody
51+ test_table.selected_item = persons.get (1);
52+
53+ staticnotebook.append_page (test_table, new Gtk.Label ("Table"));
54+
55+
56 staticnotebook.page_changed.connect (() => {
57 pageone.set_text ("Page changed");
58 });
59@@ -551,4 +592,4 @@
60
61 return application.run (args);
62 }
63-}
64+}
65\ No newline at end of file
66
67=== modified file 'lib/CMakeLists.txt'
68--- lib/CMakeLists.txt 2014-01-18 21:47:46 +0000
69+++ lib/CMakeLists.txt 2014-11-02 22:04:23 +0000
70@@ -19,6 +19,8 @@
71 Services/ContractorProxy.vala
72 Services/IconFactory.vala
73 Services/SimpleCommand.vala
74+ Views/Table.vala
75+ Views/TableItem.vala
76 Widgets/Utils.vala
77 Widgets/WrapLabel.vala
78 Widgets/AboutDialog.vala
79
80=== added directory 'lib/Views'
81=== added file 'lib/Views/Table.vala'
82--- lib/Views/Table.vala 1970-01-01 00:00:00 +0000
83+++ lib/Views/Table.vala 2014-11-02 22:04:23 +0000
84@@ -0,0 +1,333 @@
85+/***
86+ Copyright (C) 2014 Raphael Isemann <raphael@elementaryos.org>
87+
88+ This program or library is free software; you can redistribute it
89+ and/or modify it under the terms of the GNU Lesser General Public
90+ License as published by the Free Software Foundation; either
91+ version 3 of the License, or (at your option) any later version.
92+
93+ This library is distributed in the hope that it will be useful,
94+ but WITHOUT ANY WARRANTY; without even the implied warranty of
95+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
96+ Lesser General Public License for more details.
97+
98+ You should have received a copy of the GNU Lesser General
99+ Public License along with this library; if not, write to the
100+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
101+ Boston, MA 02110-1301 USA.
102+***/
103+
104+using Gtk;
105+using Gdk;
106+
107+namespace Granite.Widgets.Views {
108+
109+ public class Table : Gtk.Grid {
110+
111+ public signal void selected_item_changed (TableItem selected);
112+ public signal void add_item_clicked ();
113+
114+ bool _allows_remove = true;
115+ public bool allows_remove {
116+ get {
117+ return _allows_remove;
118+ }
119+ set {
120+ _allows_remove = value;
121+ if (value) {
122+ remove_button.show_all ();
123+ } else {
124+ remove_button.hide ();
125+ }
126+ update_toolbar_visibility ();
127+ }
128+ }
129+
130+
131+ bool _allows_add = true;
132+ public bool allows_add {
133+ get {
134+ return _allows_add;
135+ }
136+ set {
137+ _allows_add = value;
138+ if (value) {
139+ add_button.show_all ();
140+ } else {
141+ add_button.hide ();
142+ }
143+ update_toolbar_visibility ();
144+ }
145+ }
146+
147+ bool _allows_reordering = true;
148+ public bool allows_reordering {
149+ get {
150+ return _allows_reordering;
151+ }
152+ set {
153+ _allows_reordering = value;
154+ if (value) {
155+ up_button.show_all ();
156+ down_button.show_all ();
157+ } else {
158+ up_button.hide ();
159+ down_button.hide ();
160+ }
161+ update_toolbar_visibility ();
162+ }
163+ }
164+
165+ TableItem? _selected_item = null;
166+ public TableItem? selected_item {
167+ get {
168+ return selected_item;
169+ }
170+ set {
171+ _selected_item = null;
172+ for (int i = 0; i < item_list.size; i++) {
173+ if (item_list.get (i) == value) {
174+ select (i);
175+ break;
176+ }
177+ }
178+ }
179+ }
180+
181+ Gtk.TreeView tree_view;
182+ ListStore list_model;
183+ Type[] column_types;
184+ int columns;
185+
186+ Gee.List<TableItem> item_list;
187+
188+ // true if we are currently in progress of changing the internal list
189+ // This is necessary to prevent multiple
190+ bool list_is_changing = false;
191+
192+ Gtk.Toolbar toolbar;
193+ Gtk.ToolButton up_button;
194+ Gtk.ToolButton down_button;
195+ Gtk.ToolButton add_button;
196+ Gtk.ToolButton remove_button;
197+
198+ public Table(string[] headers) {
199+ tree_view = new Gtk.TreeView ();
200+ columns = headers.length;
201+ column_types = {};
202+
203+ foreach (var header in headers) {
204+ column_types += typeof (string);
205+ }
206+
207+ list_model = new ListStore.newv(column_types);
208+ tree_view.set_model (list_model);
209+
210+ tree_view.cursor_changed.connect (cursor_changed_handler);
211+
212+ int attr_index = 0;
213+ foreach (string header in headers) {
214+ Gtk.CellRendererText cell = new Gtk.CellRendererText ();
215+ tree_view.insert_column_with_attributes (-1, headers[attr_index], cell,
216+ "text", attr_index);
217+ attr_index++;
218+ }
219+
220+ var scroll = new Gtk.ScrolledWindow(null, null);
221+ scroll.hscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
222+ scroll.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
223+ scroll.shadow_type = Gtk.ShadowType.IN;
224+ scroll.add (tree_view);
225+ scroll.expand = true;
226+
227+ var toolbar = new Gtk.Toolbar();
228+ toolbar.set_style(Gtk.ToolbarStyle.ICONS);
229+ toolbar.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR);
230+ toolbar.set_show_arrow(false);
231+ toolbar.hexpand = true;
232+
233+ scroll.get_style_context().set_junction_sides(Gtk.JunctionSides.BOTTOM);
234+ toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
235+ toolbar.get_style_context().set_junction_sides(Gtk.JunctionSides.TOP);
236+
237+ add_button = new Gtk.ToolButton (null, _("Add…"));
238+ remove_button = new Gtk.ToolButton (null, _("Remove"));
239+ up_button = new Gtk.ToolButton (null, _("Move up"));
240+ down_button = new Gtk.ToolButton (null, _("Move down"));
241+
242+ add_button.set_tooltip_text (_("Add…"));
243+ remove_button.set_tooltip_text (_("Remove"));
244+ up_button.set_tooltip_text (_("Move up"));
245+ down_button.set_tooltip_text (_("Move down"));
246+
247+ add_button.set_icon_name ("list-add-symbolic");
248+ remove_button.set_icon_name ("list-remove-symbolic");
249+ up_button.set_icon_name ("go-up-symbolic");
250+ down_button.set_icon_name ("go-down-symbolic");
251+
252+ up_button.sensitive = false;
253+ down_button.sensitive = false;
254+ remove_button.sensitive = false;
255+
256+ add_button.clicked.connect (() => {
257+ add_item_clicked ();
258+ });
259+ remove_button.clicked.connect (remove_selected);
260+ up_button.clicked.connect (up_pressed);
261+ down_button.clicked.connect (down_pressed);
262+
263+ toolbar.insert (add_button, -1);
264+ toolbar.insert (remove_button, -1);
265+ toolbar.insert (up_button, -1);
266+ toolbar.insert (down_button, -1);
267+
268+ this.attach (scroll, 0, 0, 1, 1);
269+ this.attach (toolbar, 0, 1, 1, 1);
270+ }
271+
272+ void fire_item_selected (TableItem item) {
273+ if (!list_is_changing) {
274+ _selected_item = item;
275+ selected_item_changed (item);
276+ }
277+ }
278+
279+ void select (int index) {
280+ var path = new TreePath.from_indices (index);
281+ tree_view.set_cursor (path, null, false);
282+ }
283+
284+ void remove_item (int index) {
285+ item_list.remove_at (index);
286+ list_model.remove (get_nth_iter (index));
287+ select (index - 1);
288+
289+ if (item_list.size == 0) {
290+ _selected_item = null;
291+ }
292+ }
293+
294+ void insert_item (int index, TableItem item) {
295+ item_list.insert (index, item);
296+ TreeIter iter;
297+ list_model.insert_before (out iter, get_nth_iter (index));
298+ fill_out_iter (iter, item);
299+ select (index);
300+ }
301+
302+ void fill_out_iter (TreeIter iter, TableItem item) {
303+ var values = item.get_values ();
304+ for (int column = 0; column < columns; column++) {
305+ list_model.set (iter, column, values[column]);
306+ }
307+ }
308+
309+ TreeIter? get_nth_iter (int index) {
310+ var path = new TreePath.from_indices (index);
311+ TreeIter iter;
312+ if (list_model.get_iter (out iter, path)) {
313+ return iter;
314+ }
315+ return null;
316+ }
317+
318+ void remove_selected () {
319+ int index = get_current_index ();
320+ if (index != -1) {
321+ item_list.remove_at (get_current_index ());
322+ list_model.remove (get_current_iter ());
323+ }
324+ }
325+
326+ void internal_append_item (TableItem item) {
327+ Gtk.TreeIter iter;
328+ list_model.append (out iter);
329+ fill_out_iter (iter, item);
330+ }
331+
332+ void cursor_changed_handler () {
333+ int index = get_current_index ();
334+
335+ remove_button.sensitive = (index != -1);
336+
337+ if (index != -1) {
338+ up_button.sensitive = true;
339+ down_button.sensitive = true;
340+ if (index == 0) {
341+ up_button.sensitive = false;
342+ }
343+ if (index == item_list.size - 1) {
344+ down_button.sensitive = false;
345+ }
346+ fire_item_selected (item_list.get (index));
347+ }
348+ }
349+
350+ void up_pressed () {
351+ list_is_changing = true;
352+ int index = get_current_index ();
353+ var item = item_list[index];
354+ remove_item (index);
355+ insert_item (index - 1, item);
356+ list_is_changing = false;
357+ }
358+
359+ void down_pressed () {
360+ list_is_changing = true;
361+ int index = get_current_index ();
362+ var item = item_list[index];
363+ remove_item (index);
364+ insert_item (index + 1, item);
365+ list_is_changing = false;
366+ }
367+
368+
369+ TreeIter? get_current_iter () {
370+ Gtk.TreePath current_path;
371+ tree_view.get_cursor (out current_path, null);
372+
373+ TreeIter iter;
374+ if (list_model.get_iter (out iter, current_path)) {
375+ return iter;
376+ }
377+ return null;
378+ }
379+
380+ int get_current_index () {
381+ Gtk.TreePath path;
382+
383+ tree_view.get_cursor (out path, null);
384+
385+ if (path != null)
386+ {
387+ return (path.get_indices ())[0];
388+ }
389+ return -1;
390+ }
391+
392+ void update_toolbar_visibility () {
393+ if (!allows_add && !allows_remove && !allows_reordering) {
394+ toolbar.hide ();
395+ } else {
396+ toolbar.show ();
397+ }
398+ }
399+
400+ public void append_item (TableItem item) {
401+ internal_append_item (item);
402+ item_list.add (item);
403+ }
404+
405+ public void show_list (Gee.List<TableItem> items) {
406+ item_list = items;
407+
408+ list_model = new ListStore.newv(column_types);
409+ tree_view.set_model (list_model);
410+
411+ foreach (var item in items) {
412+ internal_append_item (item);
413+ }
414+ }
415+
416+ }
417+}
418\ No newline at end of file
419
420=== added file 'lib/Views/TableItem.vala'
421--- lib/Views/TableItem.vala 1970-01-01 00:00:00 +0000
422+++ lib/Views/TableItem.vala 2014-11-02 22:04:23 +0000
423@@ -0,0 +1,30 @@
424+/***
425+ Copyright (C) 2014 Raphael Isemann <raphael@elementaryos.org>
426+
427+ This program or library is free software; you can redistribute it
428+ and/or modify it under the terms of the GNU Lesser General Public
429+ License as published by the Free Software Foundation; either
430+ version 3 of the License, or (at your option) any later version.
431+
432+ This library is distributed in the hope that it will be useful,
433+ but WITHOUT ANY WARRANTY; without even the implied warranty of
434+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
435+ Lesser General Public License for more details.
436+
437+ You should have received a copy of the GNU Lesser General
438+ Public License along with this library; if not, write to the
439+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
440+ Boston, MA 02110-1301 USA.
441+***/
442+
443+using Gtk;
444+using Gdk;
445+
446+namespace Granite.Widgets.Views {
447+
448+ public interface TableItem : GLib.Object {
449+
450+ public abstract string[] get_values ();
451+
452+ }
453+}
454\ No newline at end of file

Subscribers

People subscribed via source and target branches