Merge lp:~niclasl/granite/new-dn-api into lp:~elementary-pantheon/granite/granite

Proposed by Niclas Lockner
Status: Merged
Approved by: David Gomes
Approved revision: 656
Merged at revision: 667
Proposed branch: lp:~niclasl/granite/new-dn-api
Merge into: lp:~elementary-pantheon/granite/granite
Diff against target: 598 lines (+168/-121)
2 files modified
demo/GraniteDemo.vala (+14/-8)
lib/Widgets/DynamicNotebook.vala (+154/-113)
To merge this branch: bzr merge lp:~niclasl/granite/new-dn-api
Reviewer Review Type Date Requested Status
David Gomes (community) Approve
Mario Guerriero Pending
Review via email: mp+198149@code.launchpad.net

Commit message

Implemented a new DN API which will introduce better functionality in terms of tab dragging, tab moving and tab organization, but also bring ABI breaks to apps written against the old API.

Replaced the existing tab_added/tab_removed with signals that better match the functionality of Gtk.Notebook's page_added and page_removed. The signals new_tab_requested and close_tab_requested are now used to add and close tabs.

Tab objects are now always created outside DynamicNotebook, i.e. in the application that uses the notebook.

Splitted the tab_moved signal into the signals tab_reordered and tab_moved.

Implemented a settable upper limit of how many tabs to save when the restore tabs feature is enabled. The Granite Demo is extended to demonstrate this feature.

Fixes lp:1066578 lp:1170414 lp:1181657 lp:1198456 lp:1198513 and lp:1226058

To post a comment you must log in.
Revision history for this message
Niclas Lockner (niclasl) wrote :

Mario, Julián Unrrein said that you use DynamicNotebook a lot so I added you as a reviewer

lp:~niclasl/granite/new-dn-api updated
651. By Niclas Lockner

Implement a settable upper limit of the size of the set of restorable tabs

652. By Niclas Lockner

Don't allow max_restorable_tabs to be <= 0

653. By Niclas Lockner

Missing semicolon

654. By Niclas Lockner

Renamed and commented the DroppedDelegate in Tab

Revision history for this message
David Gomes (davidgomes) wrote :

Line 144 change "( (" to "((".
Reindent diff line 552.
Newline after diff line 567.
287-289 please put some curly braces there to make it more readable.

Change the demo's max_restorable_tabs to 5 so we can give an example of changing DN's default limit.

This is really good stuff.

review: Needs Fixing
lp:~niclasl/granite/new-dn-api updated
655. By Niclas Lockner

* Code-style fixes
* Set max_restorable_tabs in granite demo

656. By Niclas Lockner

Tab -> Spaces

Revision history for this message
David Gomes (davidgomes) :
review: Approve

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 2013-09-16 23:14:20 +0000
3+++ demo/GraniteDemo.vala 2013-12-10 01:10:02 +0000
4@@ -380,6 +380,7 @@
5
6 dynamic_notebook.allow_duplication = true;
7 dynamic_notebook.allow_restoring = true;
8+ dynamic_notebook.max_restorable_tabs = 5;
9 dynamic_notebook.allow_pinning = true;
10 dynamic_notebook.show_icons = true;
11 dynamic_notebook.add_button_tooltip = "New user tab";
12@@ -397,16 +398,22 @@
13 tab2.restore_data = "2";
14 dynamic_notebook.insert_tab (tab2, -1);
15
16- dynamic_notebook.tab_added.connect ((t) => {
17- t.page = new Gtk.Label (@"Page $i");
18- t.label = @"user$i@elementaryos: ~";
19+ dynamic_notebook.new_tab_requested.connect (() => {
20+ var t = new Granite.Widgets.Tab (@"user$i@elementaryos: ~",
21+ new ThemedIcon ("empty"),
22+ new Gtk.Label (@"Page $i"));
23 t.restore_data = i.to_string ();
24 i++;
25+ dynamic_notebook.insert_tab (t, -1);
26 });
27
28- dynamic_notebook.tab_restored.connect ((t) => {
29- print ("Restored tab %s\n", t.label);
30- t.page = new Gtk.Label ("Page " + t.restore_data);
31+ dynamic_notebook.tab_restored.connect ((label, data, icon) => {
32+ var t = new Granite.Widgets.Tab (label,
33+ icon,
34+ new Gtk.Label ("Page " + data));
35+ t.restore_data = data;
36+ dynamic_notebook.insert_tab (t, -1);
37+ print ("Restored tab %s\n", label);
38 });
39
40 dynamic_notebook.tab_duplicated.connect ((t) => {
41@@ -429,8 +436,7 @@
42 });
43
44 dynamic_notebook.tab_removed.connect ((t) => {
45- print ("Going to remove %s\n", t.label);
46- return true;
47+ print ("Removed tab %s\n", t.label);
48 });
49
50 return dynamic_notebook;
51
52=== modified file 'lib/Widgets/DynamicNotebook.vala'
53--- lib/Widgets/DynamicNotebook.vala 2013-12-03 01:03:16 +0000
54+++ lib/Widgets/DynamicNotebook.vala 2013-12-10 01:10:02 +0000
55@@ -25,8 +25,10 @@
56 Gdk.ModifierType.CONTROL_MASK |
57 Gdk.ModifierType.MOD1_MASK);
58
59+ public delegate void DroppedDelegate ();
60+
61 private class TabPageContainer : Gtk.EventBox {
62- internal weak Tab _tab;
63+ private weak Tab _tab;
64
65 public Tab tab {
66 get { return _tab; }
67@@ -59,7 +61,7 @@
68 if (value != _pinned) {
69 if (value) {
70 _label.visible = false;
71- close.visible = false;
72+ close_button.visible = false;
73 _icon.margin_left = 1;
74 _working.margin_left = 1;
75
76@@ -95,6 +97,15 @@
77 **/
78 public string restore_data { get; set; }
79
80+ /**
81+ * An optional delegate that is called when the tab is dropped from the set
82+ * of restorable tabs in DynamicNotebook.
83+ * A tab is dropped either when Clear All is pressed, or when
84+ * the tab is the oldest tab in the set of restorable tabs and
85+ * the number of restorable tabs has exceeded the upper limit.
86+ */
87+ public DroppedDelegate dropped_callback = null;
88+
89 internal TabPageContainer page_container;
90 public Gtk.Widget page {
91 get {
92@@ -136,13 +147,13 @@
93 set {
94 if (value != _fixed) {
95 _label.visible = value;
96- close.visible = value;
97+ close_button.visible = value;
98 }
99 _fixed = value;
100 }
101 }
102
103- internal Gtk.Button close;
104+ internal Gtk.Button close_button;
105 public Gtk.Menu menu { get; set; }
106
107 //We need to be able to toggle these from the notebook.
108@@ -165,13 +176,13 @@
109 this._working = new Gtk.Spinner ();
110 _working.start();
111
112- this.close = new Gtk.Button ();
113- close.add (new Gtk.Image.from_icon_name ("window-close-symbolic", Gtk.IconSize.MENU));
114- close.tooltip_text = _("Close Tab");
115- close.relief = Gtk.ReliefStyle.NONE;
116+ this.close_button = new Gtk.Button ();
117+ close_button.add (new Gtk.Image.from_icon_name ("window-close-symbolic", Gtk.IconSize.MENU));
118+ close_button.tooltip_text = _("Close Tab");
119+ close_button.relief = Gtk.ReliefStyle.NONE;
120
121 var tab_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
122- tab_box.pack_start (close, false);
123+ tab_box.pack_start (close_button, false);
124 tab_box.pack_start (_label);
125 tab_box.pack_start (_icon, false);
126 tab_box.pack_start (_working, false);
127@@ -185,7 +196,6 @@
128
129 page_container = new TabPageContainer (this);
130 this.page = page ?? new Gtk.Label("");
131- page_container.show_all ();
132
133 restore_data = "";
134
135@@ -236,9 +246,9 @@
136 this.button_press_event.connect ((e) => {
137 e.state &= MODIFIER_MASK;
138
139- if (e.button == 2 && e.state == 0 && close.visible) {
140+ if (e.button == 2 && e.state == 0 && close_button.visible) {
141 this.closed ();
142- } else if (e.button == 2 && e.state == Gdk.ModifierType.SHIFT_MASK && close.visible) {
143+ } else if (e.button == 2 && e.state == Gdk.ModifierType.SHIFT_MASK && close_button.visible) {
144 this.close_others ();
145 } else if (e.button == 1 && e.type == Gdk.EventType.2BUTTON_PRESS && duplicate_m.visible) {
146 this.duplicate ();
147@@ -263,68 +273,85 @@
148 });*/
149
150 page_container.button_press_event.connect (() => { return true; }); //dont let clicks pass through
151- close.clicked.connect ( () => this.closed () );
152+ close_button.clicked.connect (() => this.closed ());
153 working = false;
154 }
155+
156+ public void close () {
157+ closed ();
158+ }
159 }
160
161 private class ClosedTabs : GLib.Object {
162
163- public signal void restored (Tab tab);
164+ public signal void restored (string label, string restore_data, GLib.Icon? icon);
165 public signal void cleared ();
166
167- private struct Entry {
168- string title;
169- string data;
170+ private int _max_restorable_tabs = 10;
171+ public int max_restorable_tabs {
172+ get { return _max_restorable_tabs; }
173+ set {
174+ assert (value > 0);
175+ _max_restorable_tabs = value;
176+ }
177+ }
178+
179+ internal struct Entry {
180+ string label;
181+ string restore_data;
182 GLib.Icon? icon;
183+ DroppedDelegate? dropped_callback;
184 }
185
186- private Entry[] closed_tabs;
187+ private Gee.LinkedList<Entry?> closed_tabs;
188
189 public ClosedTabs () {
190- closed_tabs = {};
191+ closed_tabs = new Gee.LinkedList<Entry?> ();
192 }
193
194 public bool empty {
195 get {
196- return closed_tabs.length == 0;
197+ return closed_tabs.size == 0;
198 }
199 }
200
201 public void push (Tab tab) {
202 foreach (var entry in closed_tabs)
203- if (tab.restore_data == entry.data)
204+ if (tab.restore_data == entry.restore_data)
205 return;
206- Entry tmp = { tab.label, tab.restore_data, tab.icon };
207- closed_tabs += tmp;
208- }
209-
210- public Tab pop () {
211- assert (closed_tabs.length > 0);
212- Entry entry = closed_tabs[closed_tabs.length - 1];
213- var tab = new Tab (entry.title);
214- tab.restore_data = entry.data;
215- tab.icon = entry.icon;
216- closed_tabs.resize (closed_tabs.length - 1);
217- return tab;
218- }
219-
220- public Tab? pick (string search) {
221- var tab = (Tab) null;
222- Entry[] copy = {};
223-
224- foreach (var entry in closed_tabs) {
225- if (entry.data != search) {
226- copy += entry;
227- } else {
228- tab = new Tab (entry.title);
229- tab.restore_data = entry.data;
230- tab.icon = entry.icon;
231- }
232+
233+ // Insert the element at the end of the list.
234+ Entry e = { tab.label, tab.restore_data, tab.icon, tab.dropped_callback };
235+ closed_tabs.add (e);
236+
237+ // If the maximum size is exceeded, remove from the beginning of the list.
238+ if (closed_tabs.size > max_restorable_tabs) {
239+ var elem = closed_tabs.poll_head ();
240+ var dropped_callback = elem.dropped_callback;
241+
242+ if (dropped_callback != null)
243+ dropped_callback ();
244 }
245-
246- closed_tabs = copy;
247- return tab;
248+ }
249+
250+ public Entry pop () {
251+ assert (closed_tabs.size > 0);
252+ return closed_tabs.poll_tail ();
253+ }
254+
255+ public Entry pick (string search) {
256+ Entry picked = {null, null, null};
257+
258+ for (int i = 0; i < closed_tabs.size; i++) {
259+ var entry = closed_tabs[i];
260+
261+ if (entry.restore_data == search) {
262+ picked = closed_tabs.remove_at (i);
263+ break;
264+ }
265+ }
266+
267+ return picked;
268 }
269
270 public Gtk.Menu menu {
271@@ -332,7 +359,7 @@
272 var _menu = new Gtk.Menu ();
273
274 foreach (var entry in closed_tabs) {
275- var item = new Gtk.ImageMenuItem.with_label (entry.title);
276+ var item = new Gtk.ImageMenuItem.with_label (entry.label);
277 item.set_always_show_image (true);
278 if (entry.icon != null) {
279 var icon = new Gtk.Image.from_gicon (entry.icon, Gtk.IconSize.MENU);
280@@ -341,7 +368,8 @@
281 _menu.prepend (item);
282
283 item.activate.connect (() => {
284- this.restored (pick (entry.data));
285+ var e = pick (entry.restore_data);
286+ this.restored (e.label, e.restore_data, e.icon);
287 });
288 }
289
290@@ -353,7 +381,13 @@
291 _menu.append (item);
292
293 item.activate.connect (() => {
294- closed_tabs = {};
295+ foreach (var entry in closed_tabs) {
296+ if (entry.dropped_callback != null) {
297+ entry.dropped_callback ();
298+ }
299+ }
300+
301+ closed_tabs.clear ();
302 cleared ();
303 });
304 }
305@@ -403,7 +437,7 @@
306 set {
307 if (value != _tabs_closable)
308 tabs.foreach ((t) => {
309- t.close.visible = value;
310+ t.close_button.visible = value;
311 });
312 _tabs_closable = value;
313 }
314@@ -466,6 +500,15 @@
315 }
316
317 /**
318+ * Set or get the upper limit of the size of the set
319+ * of restorable tabs.
320+ */
321+ public int max_restorable_tabs {
322+ get { return closed_tabs.max_restorable_tabs; }
323+ set { closed_tabs.max_restorable_tabs = value; }
324+ }
325+
326+ /**
327 * Controls the '+' add button visibility
328 */
329 bool _add_button_visible = true;
330@@ -526,7 +569,6 @@
331 }
332 }
333
334-
335 public string group_name {
336 get { return notebook.group_name; }
337 set { notebook.group_name = value; }
338@@ -543,16 +585,19 @@
339 private Gtk.CssProvider button_fix;
340
341 private int tab_width = 150;
342- private int max_tab_width = 150;
343- private int tab_width_pinned = 18;
344+ private static const int MAX_TAB_WIDTH = 150;
345+ private static const int TAB_WIDTH_PINNED = 18;
346
347 public signal void tab_added (Tab tab);
348- public signal bool tab_removed (Tab tab);
349+ public signal void tab_removed (Tab tab);
350 Tab? old_tab; //stores a reference for tab_switched
351 public signal void tab_switched (Tab? old_tab, Tab new_tab);
352- public signal void tab_moved (Tab tab, int new_pos, bool new_window, int x, int y);
353+ public signal void tab_reordered (Tab tab, int new_pos);
354+ public signal void tab_moved (Tab tab, int x, int y);
355 public signal void tab_duplicated (Tab duplicated_tab);
356- public signal void tab_restored (Tab tab);
357+ public signal void tab_restored (string label, string data, GLib.Icon? icon);
358+ public signal void new_tab_requested ();
359+ public signal bool close_tab_requested (Tab tab);
360
361 private Gtk.MenuItem new_tab_m;
362 private Gtk.MenuItem restore_tab_m;
363@@ -605,7 +650,7 @@
364 menu.show_all ();
365
366 new_tab_m.activate.connect (() => {
367- insert_new_tab_at_end ();
368+ new_tab_requested ();
369 });
370
371 restore_tab_m.activate.connect (() => {
372@@ -613,13 +658,12 @@
373 });
374
375 closed_tabs = new ClosedTabs ();
376- closed_tabs.restored.connect ((tab) => {
377+ closed_tabs.restored.connect ((label, restore_data, icon) => {
378 if (!allow_restoring)
379 return;
380 restore_button.sensitive = !closed_tabs.empty;
381 restore_tab_m.sensitive = !closed_tabs.empty;
382- insert_tab (tab, -1);
383- this.tab_restored (tab);
384+ this.tab_restored (label, restore_data, icon);
385 });
386
387 closed_tabs.cleared.connect (() => {
388@@ -647,7 +691,7 @@
389 restore_button.show_all ();
390
391 add_button.clicked.connect (() => {
392- insert_new_tab_at_end ();
393+ new_tab_requested ();
394 });
395
396 restore_button.clicked.connect (() => {
397@@ -666,7 +710,7 @@
398
399 this.button_press_event.connect ((e) => {
400 if (e.type == Gdk.EventType.2BUTTON_PRESS && e.button == 1) {
401- insert_new_tab_at_end ();
402+ new_tab_requested ();
403 } else if (e.button == 2 && allow_restoring) {
404 restore_last_tab ();
405 return true;
406@@ -695,7 +739,7 @@
407 case Gdk.Key.@t:
408 case Gdk.Key.@T:
409 if (e.state == Gdk.ModifierType.CONTROL_MASK) {
410- insert_new_tab_at_end ();
411+ new_tab_requested ();
412 return true;
413 } else if (e.state == (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) && allow_restoring) {
414 restore_last_tab ();
415@@ -758,6 +802,8 @@
416
417 ~Notebook () {
418 notebook.switch_page.disconnect (on_switch_page);
419+ notebook.page_added.disconnect (on_page_added);
420+ notebook.page_removed.disconnect (on_page_removed);
421 notebook.page_reordered.disconnect (on_page_reordered);
422 notebook.create_window.disconnect (on_create_window);
423 }
424@@ -779,21 +825,27 @@
425 }
426
427 void on_page_added (Gtk.Widget page, uint pagenum) {
428- insert_callbacks ((page as TabPageContainer).tab);
429+ var t = (page as TabPageContainer).tab;
430+
431+ insert_callbacks (t);
432+ tab_added (t);
433 }
434
435 void on_page_removed (Gtk.Widget page, uint pagenum) {
436- remove_callbacks ((page as TabPageContainer).tab);
437+ var t = (page as TabPageContainer).tab;
438+
439+ remove_callbacks (t);
440+ tab_removed (t);
441 }
442
443 void on_page_reordered (Gtk.Widget page, uint pagenum) {
444- tab_moved ((page as TabPageContainer).tab, (int) pagenum, false, -1, -1);
445+ tab_reordered ((page as TabPageContainer).tab, (int) pagenum);
446 recalc_order ();
447 }
448
449 unowned Gtk.Notebook on_create_window (Gtk.Widget page, int x, int y) {
450 var tab = notebook.get_tab_label (page) as Tab;
451- tab_moved (tab, 0, true, x, y);
452+ tab_moved (tab, x, y);
453 recalc_order ();
454 return (Gtk.Notebook) null;
455 }
456@@ -843,9 +895,9 @@
457 }
458
459 var offset = 130;
460- this.tab_width = (this.get_allocated_width () - offset - pinned_tabs * tab_width_pinned) / unpinned_tabs;
461- if (tab_width > max_tab_width)
462- tab_width = max_tab_width;
463+ this.tab_width = (this.get_allocated_width () - offset - pinned_tabs * TAB_WIDTH_PINNED) / unpinned_tabs;
464+ if (tab_width > MAX_TAB_WIDTH)
465+ tab_width = MAX_TAB_WIDTH;
466
467 if (tab_width < 0)
468 tab_width = 0;
469@@ -854,7 +906,7 @@
470 this.notebook.get_tab_label (this.notebook.get_nth_page (i)).width_request = tab_width;
471
472 if ((this.notebook.get_tab_label (this.notebook.get_nth_page (i)) as Tab).pinned) {
473- this.notebook.get_tab_label (this.notebook.get_nth_page (i)).width_request = tab_width_pinned;
474+ this.notebook.get_tab_label (this.notebook.get_nth_page (i)).width_request = TAB_WIDTH_PINNED;
475 }
476 }
477
478@@ -865,11 +917,10 @@
479 if (!allow_restoring || closed_tabs.empty)
480 return;
481
482- var tab = closed_tabs.pop ();
483+ var restored = closed_tabs.pop ();
484 restore_button.sensitive = !closed_tabs.empty;
485 restore_tab_m.sensitive = !closed_tabs.empty;
486- insert_tab (tab, -1);
487- this.tab_restored (tab);
488+ this.tab_restored (restored.label, restored.restore_data, restored.icon);
489 }
490
491 private void switch_pin_tab (Tab tab) {
492@@ -880,7 +931,7 @@
493 var pin_state = !tab.pinned;
494 if (pin_state) {
495 tab._icon.visible = !tab.working;
496- tab.close.visible = tabs_closable;
497+ tab.close_button.visible = tabs_closable;
498 } else {
499 tab._icon.visible = show_icons && !tab.working;
500 }
501@@ -890,30 +941,8 @@
502 }
503
504 public void remove_tab (Tab tab) {
505- if (Signal.has_handler_pending (this, Signal.lookup ("tab-removed", typeof (DynamicNotebook)), 0, true)) {
506- var sure = tab_removed (tab);
507- if (!sure)
508- return;
509- }
510-
511- var pos = get_tab_position (tab);
512-
513- if (pos != -1) {
514- notebook.remove_page (pos);
515- if (tab.page.get_parent () != null)
516- tab.page.unparent ();
517- }
518-
519- if (tab.label != "" && tab.restore_data != "") {
520- closed_tabs.push (tab);
521- restore_button.sensitive = !closed_tabs.empty;
522- restore_tab_m.sensitive = !closed_tabs.empty;
523- }
524- }
525-
526- [Deprecated (since=0.2)]
527- public void remove_tab_force (Tab tab) {
528- var pos = get_tab_position (tab);
529+ var pos = get_tab_position (tab);
530+
531 if (pos != -1)
532 notebook.remove_page (pos);
533 }
534@@ -948,7 +977,7 @@
535
536 public void set_tab_position (Tab tab, int position) {
537 notebook.reorder_child (tab.page_container, position);
538- tab_moved (tab, position, false, -1, -1);
539+ tab_reordered (tab, position);
540 recalc_order ();
541 }
542
543@@ -964,12 +993,6 @@
544 return notebook.get_nth_page (index);
545 }
546
547- private void insert_new_tab_at_end () {
548- var t = new Tab ();
549- notebook.page = (int) this.insert_tab (t, -1);
550- this.tab_added (t);
551- }
552-
553 public uint insert_tab (Tab tab, int index) {
554 return_if_fail (tabs.index (tab) < 0);
555
556@@ -990,13 +1013,13 @@
557 tab.pinned = false;
558
559 tab.width_request = tab_width;
560- tab.close.get_style_context ().add_provider (button_fix,
561- Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
562+ tab.close_button.get_style_context ().add_provider (button_fix,
563+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
564 this.recalc_size ();
565 this.recalc_order ();
566
567 if (!tabs_closable)
568- tab.close.visible = false;
569+ tab.close_button.visible = false;
570
571 return i;
572 }
573@@ -1018,7 +1041,25 @@
574 }
575
576 private void on_tab_closed (Tab tab) {
577+ if (Signal.has_handler_pending (this, Signal.lookup ("close-tab-requested", typeof (DynamicNotebook)), 0, true)) {
578+ var sure = close_tab_requested (tab);
579+
580+ if (!sure)
581+ return;
582+ }
583+
584+ var pos = get_tab_position (tab);
585+
586 remove_tab (tab);
587+
588+ if (pos != -1 && tab.page.get_parent () != null)
589+ tab.page.unparent ();
590+
591+ if (tab.label != "" && tab.restore_data != "") {
592+ closed_tabs.push (tab);
593+ restore_button.sensitive = !closed_tabs.empty;
594+ restore_tab_m.sensitive = !closed_tabs.empty;
595+ }
596 }
597
598 private void on_close_others (Tab tab) {

Subscribers

People subscribed via source and target branches