elementaryart team mailing list archive
-
elementaryart team
-
Mailing list archive
-
Message #02034
[Merge] lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
Tom Beckmann has proposed merging lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite.
Requested reviews:
Christian Dywan (kalikiana)
For more details, see:
https://code.launchpad.net/~tombeckmann/granite/granite-dynamic-notebook-gkt-based/+merge/104050
Replace the current DynamicNotebook class with a new Gtk Based one.
Short excerpt from last meeting:
DanRabbit: So, we've had like a billion dynamic notebook widgets so far right?
mamemame187: XD yes
DanRabbit: Well Tom decided to do one by just working with a plain gtk.notebook
agent00tai: DanRabbit: correct xD
DanRabbit: and in its pure simplicity, it seems to be the only one that actually like.. works.
codygarver2: unless DanRabbit cares about a notebook, I refuse to think about them
tom95: we can either take this stable one or wait for luna+1 for whenever until xapantus is ready
DanRabbit: It's not fancy, but it works.
DanRabbit: The only issue I have is with the tab overflow appearance/behavior
hcabaguio: why did we try to do custom one's when working with gtk.notebook is pretty good?
DanRabbit: other than that, it's no worse than our currently very inconsistent one
DanRabbit: hcabaguio: getting overly ambitious with animations and stuff
DanRabbit: So, I think kalikiana should be the one to review this (because of this experience with Midori)
DanRabbit: and if he okay's it, I say we go for it.
codygarver2: so it shall be
* agent00tai fully approves
codygarver2: tom95: request a review and assign kalikiana
So I think mefrio had some suggestions for additional methods and improvements.
Kalikana, if you miss anything, please list it here.
--
https://code.launchpad.net/~tombeckmann/granite/granite-dynamic-notebook-gkt-based/+merge/104050
Your team elementaryart (old) is subscribed to branch lp:granite.
=== modified file 'demo/main.vala'
--- demo/main.vala 2012-04-27 13:26:24 +0000
+++ demo/main.vala 2012-04-29 23:45:22 +0000
@@ -209,10 +209,10 @@
/* DynamicNotebook */
var dynamic_notebook = new DynamicNotebook ();
notebook.append_page (dynamic_notebook, new Gtk.Label ("Dynamic Notebook"));
- dynamic_notebook.append_page (new Gtk.Label ("Page 1"), "Page 1");
- dynamic_notebook.append_page (new Gtk.Label ("Page 2"), "Page 2");
- dynamic_notebook.append_page (new Gtk.Label ("Page 3"), "Page 3");
- dynamic_notebook.add_button_clicked.connect ( () => { dynamic_notebook.append_page (new Gtk.Label("New page"), "New tab"); });
+ dynamic_notebook.append_page ("Page 1", new Gtk.Label ("Page 1"));
+ dynamic_notebook.append_page ("Page 2", new Gtk.Label ("Page 2"));
+ dynamic_notebook.append_page ("Page 3", new Gtk.Label ("Page 3"));
+ dynamic_notebook.new_page.connect ( () => { return {"New tab", new Gtk.Label("New page"), null}; });
/*Light window*/
var light_window_button = new Gtk.Button.with_label ("Show LightWindow");
=== modified file 'lib/Widgets/DynamicNotebook.vala'
--- lib/Widgets/DynamicNotebook.vala 2012-04-06 09:49:00 +0000
+++ lib/Widgets/DynamicNotebook.vala 2012-04-29 23:45:22 +0000
@@ -1,903 +1,292 @@
-using Granite.Widgets;
-
-using Gtk;
-
-
-public class Granite.Widgets.Tab : Object {
- public string text;
- public Gdk.Pixbuf? pixbuf { set; get; default = null; }
-
- internal Gtk.StateFlags close_button = Gtk.StateFlags.NORMAL;
- internal Gtk.StateFlags state = Gtk.StateFlags.NORMAL;
- internal double offset = 0.0;
- internal double draw_offset = 0.0;
- internal double drag_origin = 0.0;
- internal bool removed = false;
- double initial_offset = 1.0;
- double initial_draw_offset = 0.0;
- public bool loading { set; get; default = false;}
- internal Cairo.Surface surface;
-
- public Gtk.Widget widget;
-
- public signal bool close_button_clicked ();
- public signal void need_redraw ();
- public signal void need_recache ();
-
- public bool is_animated () {
- bool return_value = (!removed && initial_offset != 1.0) ||
- (drag_origin == 0.0 && initial_draw_offset != 0.0) ||
- (removed && initial_offset != 0.0);
- return return_value;
- }
-
- public Tab (string text, string? stock_id = null, bool loading = false) {
- this.text = text;
- if (stock_id != null) pixbuf = Gtk.IconTheme.get_default ().load_icon (stock_id, 16, 0);
- this.loading = loading;
- notify["pixbuf"].connect ( () => { need_recache (); });
- }
-
- internal void start_animation () {
- initial_offset = offset;
- initial_draw_offset = draw_offset;
- }
-
- internal void do_animation (double x) {
- x = (double)Math.sin ((double)x * Math.PI/2);
-
- draw_offset = initial_draw_offset * (1.0 - x);
- if (!removed)
- offset = initial_offset * (1.0 - x) + 1.0 * x;
- else
- offset = initial_offset * (1.0 - x);
- }
-
- internal void select () {
- state = Gtk.StateFlags.ACTIVE;
- }
-
- internal void unselect () {
- state = Gtk.StateFlags.NORMAL;
- }
-
- internal void hover () {
- if (state == Gtk.StateFlags.NORMAL)
- state = Gtk.StateFlags.PRELIGHT;
- }
-
- internal void shrunk () {
- offset = 0.0;
- }
-
- internal bool draw_with_cache (Cairo.Context cr, double x) {
- if (offset == 1.0 && surface != null && state == Gtk.StateFlags.NORMAL &&
- close_button == Gtk.StateFlags.NORMAL && !loading) {
- cr.set_source_surface (surface, x + draw_offset, 0);
- cr.paint ();
- return true;
- }
- return false;
- }
+
+const string BUTTON_STYLE = """
+* {
+ -GtkButton-default-border : 0;
+ -GtkButton-default-outside-border : 0;
+ -GtkButton-inner-border: 0;
+ -GtkWidget-focus-line-width : 0;
+ -GtkWidget-focus-padding : 0;
+ padding: 0;
}
-
-
-internal class Granite.Widgets.Tabs : Gtk.EventBox {
-
- Gtk.StyleContext tab_context;
- Gtk.StyleContext label_context;
- Gtk.StyleContext button_context;
-
- internal Gee.ArrayList<Tab> tabs;
- internal static Gtk.CssProvider style_provider;
- private const string STYLESHEET_AMBIANCE = """
- .dynamic-notebook tab:active {
- background-color:#000;
- background-image: -gtk-gradient (linear, left bottom, left top,
- from (shade (@dark_bg_color, 0.96)),
- to (shade (@dark_bg_color, 1.4)));
- }
- .dynamic-notebook tab .dynamic-label:active {
- color: @dark_fg_color;
- }
- .dynamic-label {
- color: @fg_color;
- }
- """;
- private const string STYLESHEET_ADWAITA = """
- .dynamic-label {
- color: @fg_color;
- }
- """;
- internal const int style_priority = Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION;
-
- Gdk.Pixbuf close_pixbuf;
- double scrolling = 0.0;
-
- const double radius = 5;
- const double max_width = 200;
- const double min_width = 120;
- double width = max_width;
- protected double overlap = 3;
- const int close_size = 16;
- const double close_margin = 1;
- const double y = 5;
- const int shadow_size = 3;
-
- Gdk.EventMotion? saved_event_motion = null;
-
- Cairo.Surface left_surface;
- Cairo.Surface right_surface;
- Cairo.Pattern center_pattern;
-
- uint timeout_remove = -1;
- uint scroll_timeout = -1;
- uint timeout_anim = -1;
-
- int _page = 0;
- public int page {
- get {
- return _page;
- }
- set {
- if (_page != value) {
- if (_page < tabs.size) tabs[_page].unselect ();
- _page = value;
- if (0 <= _page && _page < tabs.size)
- tabs[_page].select ();
+""";
+
+namespace Granite.Widgets {
+
+ public struct Tab {
+ string label;
+ Gtk.Widget page;
+ string? icon;
+ }
+
+ public class DynamicNotebook : Gtk.EventBox {
+
+ /**
+ * the underlying GtkNotebook, in case you need a method not provided by this class
+ **/
+ public Gtk.Notebook notebook;
+ /**
+ * connect to this signal to be notified when a page is closed.
+ * @return return true to let the tab be closed
+ **/
+ public signal bool page_closed (Gtk.Widget page, uint num);
+ /**
+ * the plus button was pressed, you should return a Tab struct and fill it with the
+ * appropriate content
+ **/
+ public signal Tab? new_page ();
+ /**
+ * the notebook page was swtiched
+ **/
+ public signal void switch_page (Gtk.Widget page, uint num);
+ /**
+ * Show or hide tab icons. Doesn't apply to existing tabs.
+ **/
+ public bool show_icon;
+
+ private Gtk.CssProvider button_fix;
+
+ private int tab_width = 150;
+ private int max_tab_width = 150;
+
+ /**
+ * create a new dynamic notebook
+ **/
+ public DynamicNotebook () {
+
+ this.button_fix = new Gtk.CssProvider ();
+ try {
+ this.button_fix.load_from_data (BUTTON_STYLE, -1);
+ } catch (Error e) { warning (e.message); }
+
+ this.notebook = new Gtk.Notebook ();
+ this.visible_window = false;
+ this.get_style_context ().add_class ("dynamic-notebook");
+
+ this.notebook.scrollable = true;
+ this.notebook.show_border = false;
+
+ this.draw.connect ( (ctx) => {
+ this.get_style_context ().render_activity (ctx, 0, 0, this.get_allocated_width (), 27);
+ return false;
+ });
+
+ this.add (this.notebook);
+
+
+ var add = new Gtk.Button ();
+ add.add (new Gtk.Image.from_icon_name ("list-add-symbolic", Gtk.IconSize.MENU));
+ add.margin_left = 6;
+ add.relief = Gtk.ReliefStyle.NONE;
+ this.notebook.set_action_widget (add, Gtk.PackType.START);
+ add.show_all ();
+ add.get_style_context ().add_provider (button_fix, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ add.clicked.connect ( () => {
+ var t = this.new_page ();
+ this.append_page (t.label, t.page, t.icon);
+ });
+
+ this.size_allocate.connect ( () => {
+ this.recalc_size ();
+ });
+
+ this.key_press_event.connect ( (e) => {
+ switch (e.keyval){
+ case 119: //ctrl+w
+ if (Signal.has_handler_pending (this, //if no one listens, just kill it!
+ Signal.lookup ("page-closed", typeof (DynamicNotebook)), 0, true)) {
+ var sure = this.page_closed (this.notebook.get_nth_page (this.notebook.page),
+ this.notebook.page);
+ if (sure)
+ this.notebook.remove_page (this.notebook.page);
+ } else {
+ this.notebook.remove_page (this.notebook.page);
+ }
+ return true;
+ case 116: //ctrl+t
+ var t = this.new_page ();
+ this.append_page (t.label, t.page, t.icon);
+ return true;
+ case 49: //ctrl+[1-8]
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ if ((e.state & Gdk.ModifierType.CONTROL_MASK) != 0){
+ var i = e.keyval - 49;
+ this.notebook.page = (int)((i >= this.notebook.get_n_pages ()) ?
+ this.notebook.get_n_pages () - 1 : i);
+ return true;
+ }
+ break;
+ /*case 65289: //tab (and shift+tab) not working :( (Gtk seems to move focus)
+ case 65056:
+ if ((e.state & Gdk.ModifierType.SHIFT_MASK) != 0){
+ this.prev ();
+ return true;
+ }else if ((e.state & Gdk.ModifierType.CONTROL_MASK) != 0){
+ this.next ();
+ return true;
+ }
+ break;*/
+ }
+ return false;
+ });
+
+ this.notebook.button_press_event.connect ( (e) => {
+ /*if (e.button == 1) {
+ this.get_parent_window ().begin_move_drag ((int)e.button, (int)e.x_root, (int)e.y_root, e.time);
+ return true;
+ }*/
+ return false;
+ });
+ }
+
+ private void recalc_size () {
+ if (this.notebook.get_n_pages () == 0)
+ return;
+
+ var offset = 130;
+ this.tab_width = (this.get_allocated_width () - offset) / this.notebook.get_n_pages ();
+ if (this.tab_width > max_tab_width)
+ this.tab_width = max_tab_width;
+
+ for (var i=0;i<this.notebook.get_n_pages ();i++) {
+ this.notebook.get_tab_label (this.notebook.get_nth_page (i)).width_request = tab_width;
+ }
+ }
+
+ private void next () {
+ this.notebook.page = (this.notebook.page + 1 >= this.notebook.get_n_pages ())?
+ this.notebook.page = 0 : this.notebook.page + 1;
+ }
+ private void prev () {
+ this.notebook.page = (this.notebook.page - 1 < 0)?this.notebook.get_n_pages () - 1:
+ this.notebook.page-1;
+ }
+
+ public uint append_tab (Tab tab) {
+ return this.append_page (tab.label, tab.page, tab.icon);
+ }
+
+ /**
+ * add a page to the notebook
+ * @param label The label for the tab
+ * @param page The tab page
+ * @param icon An optional icon for the tab. Use a Gtk icon_name
+ **/
+ public uint append_page (string label, Gtk.Widget page, string? icon = null) {
+ var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+
+ var close = new Gtk.Button ();
+ close.add (new Gtk.Image.from_stock (Gtk.Stock.CLOSE, Gtk.IconSize.MENU));
+ close.get_style_context ().add_provider (button_fix, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ close.relief = Gtk.ReliefStyle.NONE;
+
+ var lbl = new Gtk.EventBox ();
+ var l = new Gtk.Label (label);
+ l.set_tooltip_text (label);
+ lbl.add (l);
+ l.ellipsize = Pango.EllipsizeMode.END;
+ lbl.visible_window = false;
+
+ var spinner = new Gtk.Spinner ();
+
+ box.width_request = tab_width;
+ box.pack_start (close, false);
+ box.pack_start (lbl);
+
+ Gtk.Image img;
+ if (icon != null)
+ img = new Gtk.Image.from_icon_name (icon, Gtk.IconSize.MENU);
+ else
+ img = new Gtk.Image.from_icon_name ("empty", Gtk.IconSize.MENU);
+ box.pack_start (img, false);
+ box.pack_start (spinner, false);
+
+ var idx = this.notebook.append_page (page, box);
+ this.notebook.set_tab_reorderable (page, true);
+
+ page.show_all ();
+ this.notebook.page = idx;
+
+ box.show_all ();
+ if (!show_icon)
+ img.hide ();
+ spinner.hide ();
+
+ lbl.button_release_event.connect ( (e) => {
+ if (e.button == 2) {
+ this.close_by_button (close);
+ return true;
+ }
+ return false;
+ });
+
+ lbl.scroll_event.connect ( (e) => {
+ if (e.direction == Gdk.ScrollDirection.UP)
+ this.prev ();
else
- error ("The selected tab doesn't exist.");
- queue_draw ();
- }
- }
- }
-
- public Gtk.PositionType tab_position { set; get; default = Gtk.PositionType.BOTTOM; }
- public bool draw_unselected_background { set; get; default = true; }
-
- int start_dragging = -1;
- int spinner_count = 0;
-
- public signal void switch_page (Tab tab);
- public signal void page_removed (Tab tab);
-
- /**
- * Emitted when the user makes a double click on an empty space.
- **/
- public signal void need_new_tab ();
-
- // FIXME: probably needs to be moved somewhere with a public API
- Gdk.Pixbuf load_pixbuf_with_fallbacks (string[] icons_name, int icon_size) {
- Gdk.Pixbuf pixbuf = null;
- try {
- var icon_info = Gtk.IconTheme.get_default ().choose_icon (icons_name, icon_size, 0);
- if (icon_info != null) {
- pixbuf = icon_info.load_symbolic_for_context (button_context, null);
- }
- }
- catch (Error e) {
- try {
- pixbuf = Gtk.IconTheme.get_default ().load_icon ("gtk-missing-image", icon_size, 0);
- }
- catch (Error e) {
- error ("gtk-missing-image not found");
- }
- }
- return pixbuf;
- }
-
- static construct {
- install_style_property (new GLib.ParamSpecDouble ("tab-overlap",
- "Tab overlap",
- "Tab overlap",
- 0, 50, 3,
- ParamFlags.READABLE));
- }
-
- public Tabs () {
- tabs = new Gee.ArrayList<Tab>();
- style_get ("tab-overlap", out overlap, null);
-
- if (style_provider == null) {
- style_provider = new Gtk.CssProvider ();
- try {
- if (Gtk.Settings.get_default ().gtk_theme_name == "Ambiance") {
- //style_provider.load_from_data (STYLESHEET_AMBIANCE, -1);
- }
- else if (Gtk.Settings.get_default ().gtk_theme_name == "Adwaita") {
- style_provider.load_from_data (STYLESHEET_ADWAITA, -1);
- }
- } catch (Error e) {
- warning ("The tab bar will not look as intended: %s", e.message);
- }
- }
-
-
- height_request = 35;
- width_request = (int)(2*max_width);
-
- /* Set up the StyleContexts */
- tab_context = new Gtk.StyleContext ();
- label_context = get_style_context ();;
- button_context = new Gtk.StyleContext ();
-
- var path = get_style_context ().get_path ().copy ();
-
- var pos = path.append_type (typeof (Gtk.Notebook));
- path.iter_add_class (pos, "notebook");
- path.iter_add_class (pos, "dynamic-notebook");
- path.iter_add_region (pos, "tab", Gtk.RegionFlags.EVEN); /* for a tab */
-
- var path_label = path.copy ();
- pos = path_label.append_type (typeof (Gtk.Label));
- path_label.iter_add_class (pos, "dynamic-label"); /* for a label */
-
- var path_button = path.copy ();
- pos = path_label.append_type (typeof (Gtk.Button)); /* for a button */
-
- tab_context.set_path (path);
- label_context.set_path (path_label);
- button_context.set_path (path_button);
-
- get_style_context ().add_class ("dynamic-notebook");
-
- /* Add our nice provider... */
- get_style_context ().add_provider (style_provider, style_priority);
- tab_context.add_provider (style_provider, style_priority);
- //label_context.add_provider (style_provider, style_priority);
- button_context.add_provider (style_provider, style_priority);
-
- size_allocate.connect (on_size_allocate);
-
- add_events (Gdk.EventMask.POINTER_MOTION_MASK);
-
- Timeout.add (80, () => {
- foreach (var tab in tabs) {
- if(tab.loading) {
- spinner_count++;
- queue_draw ();
+ this.next ();
+ return false;
+ });
+
+ close.clicked.connect ( () => this.close_by_button (close) );
+
+ this.recalc_size ();
+
+ return idx;
+ }
+
+ private void close_by_button (Gtk.Button close) {
+ int i; //find the label widget that fits the close button's parent
+ for (i=0;i<this.notebook.get_n_pages (); i++) {
+ if (close.get_parent () ==
+ this.notebook.get_tab_label (this.notebook.get_nth_page (i)))
break;
- }
- }
- return true;
- });
- close_pixbuf = load_pixbuf_with_fallbacks ( {"window-close-symbolic", "window-close", "gtk-close"}, close_size);
- }
-
- public void add_tab (Tab tab) {
- tabs.add (tab);
- tab.need_redraw.connect (() => { queue_draw (); });
- tab.need_recache.connect (() => { cache_tab (tab); queue_draw (); });
-
- switch_page (tab);
- page = tabs.size -1;
-
- update_tab_size (get_allocated_width ());
- tab.shrunk ();
- launch_animations ();
- }
-
- public override bool draw (Cairo.Context cr) {
- double x = radius;
-
- /* First; the background */
- Gtk.render_background (get_style_context (), cr, 0, 0, get_allocated_width (), get_allocated_height ());
-
- /* Scroll */
- cr.translate (scrolling, 0);
- cr.save ();
-
- /* We have to save these tabs because we want them to be drawn on top of the other ones. */
- double x_selected = 0;
- Tab? tab_selected = null;
- double x_dragged = 0;
- Tab? tab_dragged = null;
-
- foreach (var tab in tabs) {
- if (tab.state != Gtk.StateFlags.ACTIVE && tabs.index_of (tab) != start_dragging) {
- draw_tab (cr, x, tab);
- }
- else if (tab.state == Gtk.StateFlags.ACTIVE) {
- x_selected = x;
- tab_selected = tab;
- }
- else {
- x_dragged = x;
- tab_dragged = tab;
- }
- x += width*tab.offset - overlap;
- }
-
- if (tab_selected != null) {
- draw_tab (cr, x_selected, tab_selected);
- }
-
- if (tab_dragged != null) {
- draw_tab (cr, x_dragged, tab_dragged);
- }
-
- return true;
- }
-
- void draw_tab (Cairo.Context cr, double x, Tab tab, bool use_cache = true) {
- double height = get_allocated_height () - y;
- double y_origin = tab_position == Gtk.PositionType.TOP ? 0 : y;
-
- if (!(use_cache && tab.draw_with_cache (cr, x - radius))) {
- if (width*tab.offset < 2*radius) /* Then it is too small */
- return;
-
- draw_tab_background (cr, x + tab.draw_offset, y_origin, width*tab.offset, height, radius, tab);
- draw_label (cr, x + tab.draw_offset, y_origin, height, tab.text, tab);
- draw_close_button (cr, x + tab.draw_offset, y_origin, height, tab);
- draw_pixbuf_icon (cr, x + tab.draw_offset, y_origin, height, tab);
- cr.restore ();
- cr.save ();
- }
- }
-
- void draw_pixbuf_icon (Cairo.Context cr, double x, double y, double height, Tab tab) {
- if (tab.loading)
- Gtk.paint_spinner (get_style (), cr, Gtk.StateType.ACTIVE, this, "",
- spinner_count, (int)(x + width*tab.offset - overlap - close_margin - close_size),
- (int)(y + height /2 - close_size/2), close_size, close_size);
- else if (tab.pixbuf != null) {
- Gdk.cairo_set_source_pixbuf (cr, tab.pixbuf,
- (int)(x + width*tab.offset - overlap - close_margin - close_size),
- (int)(y + height /2 - close_size/2));
- cr.paint ();
- }
- }
-
- void draw_close_button (Cairo.Context cr, double x, double y, double height, Tab tab) {
- if (tab.close_button == Gtk.StateFlags.PRELIGHT) {
- Gtk.render_background (button_context, cr,
- x + overlap, y + height/2 - (close_size + 2*close_margin)/2,
- close_size + 2*close_margin, close_size + 2*close_margin);
- Gtk.render_frame (button_context, cr,
- x + overlap, y + height/2 - (close_size + 2*close_margin)/2,
- close_size + 2*close_margin, close_size + 2*close_margin);
- }
- Gdk.cairo_set_source_pixbuf (cr, close_pixbuf, x + overlap + close_margin, y + height /2 - close_size/2);
- cr.paint ();
- }
-
- void draw_label (Cairo.Context cr, double x, double y, double height, string text, Tab tab) {
- double left_padding = 5 + overlap + close_size + 2*close_margin;
-
- var layout = create_pango_layout (text);
- double layout_width = width - 2 * radius - 2* close_margin - close_size;
- /* Do we need to take into account the icon or the loading spinner? */
- if (tab.pixbuf != null || tab.loading) {
- layout_width -= 2*close_margin + close_size;
- }
- layout.set_width (Pango.units_from_double (layout_width));
- layout.set_ellipsize (Pango.EllipsizeMode.END);
-
- Pango.Rectangle extents;
- layout.get_extents (null, out extents);
- double layout_height = Pango.units_to_double (extents.height);
-
- label_context.set_state (tab.state);
-
- Gtk.render_layout (label_context, cr, x + left_padding, y + height/2 - layout_height/2, layout);
- }
-
- void update_tab_size (double alloc_width) {
- var old_width = width;
- width = double.min (double.max ((int)((alloc_width + (tabs.size - 1)*overlap - 2*radius)/tabs.size), min_width),
- max_width);
-
- double offset = old_width/width;
- /* Let's create the new tab cache. */
- foreach (var tab in tabs) {
- cache_tab (tab);
- /* This is old_width/width, useful to have the tabs dynamically resized */
- tab.offset = offset;
- }
- }
-
- void cache_tab (Tab tab) {
- /* Reset some values */
- tab.offset = 1.0;
- var draw_offset = tab.draw_offset;
- var state = tab.state;
- var loading = tab.loading;
- tab.loading = false;
- tab.draw_offset = 0;
- tab.state = Gtk.StateFlags.NORMAL;
-
- var buf = new Granite.Drawing.BufferSurface ( (int)(width +2*radius), get_allocated_height ());
- draw_tab (buf.context, radius, tab, false /* don't use cache */);
-
- tab.surface = buf.surface;
-
- /* Restore the values */
- tab.state = state;
- tab.loading = loading;
- tab.draw_offset = draw_offset;
- }
-
- void on_size_allocate (Gtk.Allocation alloc) {
-
- var border_color = tab_context.get_border_color (Gtk.StateFlags.NORMAL);
- border_color.alpha -= 0.2;
-
-
- var buf = new Granite.Drawing.BufferSurface ( (int)(2*radius), (int)(alloc.height + 2*shadow_size));
- draw_tab_background_shape (buf.context, radius, shadow_size, 50, alloc.height - 5, radius, radius);
- Gdk.cairo_set_source_rgba (buf.context, border_color);
- buf.context.fill ();
- buf.gaussian_blur (shadow_size);
- left_surface = buf.surface;
-
- buf = new Granite.Drawing.BufferSurface ( (int)(2*radius), (int)(alloc.height + 2*shadow_size));
- draw_tab_background_shape (buf.context, -50 + radius, shadow_size, 50, alloc.height - 5, radius, radius);
- Gdk.cairo_set_source_rgba (buf.context, border_color);
- buf.context.fill ();
- buf.gaussian_blur (shadow_size);
- right_surface = buf.surface;
-
- buf = new Granite.Drawing.BufferSurface ( (int)(2), (int)(alloc.height + 2*shadow_size));
- double y = tab_position == Gtk.PositionType.TOP ? 0 : this.y;
- draw_tab_background_shape (buf.context, -25, y, 50, alloc.height - 5, radius, radius);
- Gdk.cairo_set_source_rgba (buf.context, border_color);
- buf.context.fill ();
- buf.gaussian_blur (shadow_size);
-
- center_pattern = new Cairo.Pattern.for_surface (buf.surface);
- center_pattern.set_extend (Cairo.Extend.REPEAT);
-
- update_tab_size (alloc.width);
- }
-
- void draw_tab_background (Cairo.Context cr, double x, double y, double width, double height,
- double radius, Tab tab) {
- double border_size = 0.8;
-
- var border_color = tab_context.get_border_color (tab.state);
- if (draw_unselected_background || tab.state == Gtk.StateFlags.ACTIVE) {
- cr.set_source_surface (left_surface, x - radius, y - shadow_size);
- cr.paint ();
- cr.set_source_surface (right_surface, x + width - radius, y - shadow_size);
- cr.paint ();
-
-
- cr.rectangle (x + radius, y - shadow_size, width - 2*radius, height + 2*shadow_size);
- cr.set_source (center_pattern);
- cr.fill ();
-
- tab_context.set_state (tab.state);
- Gdk.cairo_set_source_rgba (cr, border_color);
- draw_tab_background_shape (cr, x, y, width, height, radius, radius);
- cr.fill ();
-
- double y_origin = y;
- if (tab_position == Gtk.PositionType.BOTTOM)
- y_origin += border_size;
-
- draw_tab_background_shape (cr, x + border_size, y_origin,
- width - 2*border_size, height - border_size, radius, radius - border_size);
- cr.set_source_rgba (1, 1, 1, 0.8);
- cr.clip ();
- Gtk.render_background ( tab_context, cr, x - radius, y - 3, width + 2* radius, height + 6);
- }
- else {
- /* Just a light gradient */
- cr.move_to (x + width - overlap/2, y);
- cr.line_to (x + width - overlap/2, y + height);
- var gradient = new Cairo.Pattern.linear (0, 0, 0, height);
- gradient.add_color_stop_rgba (0.0, border_color.red, border_color.green, border_color.blue, 0.0);
- gradient.add_color_stop_rgba (1.0, border_color.red, border_color.green, border_color.blue, 1.0);
- cr.set_source (gradient);
- cr.set_line_width (1.0);
- cr.stroke ();
- }
- }
-
- void draw_tab_background_shape (Cairo.Context cr, double x, double y, double width,
- double height, double radius_t, double radius_l) {
- switch (tab_position) {
- case Gtk.PositionType.BOTTOM:
- cr.move_to (x - radius_l, y + height);
- cr.curve_to (x, y + height, x, y + height - radius_t, x, y + height - radius_t);
- cr.line_to (x, y + radius_t);
- cr.curve_to (x, y, x + radius_l, y, x + radius_l, y);
- cr.line_to (x + width - radius_l, y);
- cr.curve_to (x + width, y, x + width, y + radius_t, x + width, y + radius_t);
- cr.line_to (x + width, y + height - radius_t);
- cr.curve_to (x + width, y + height,
- x + width + radius_l, y + height,
- x + width + radius_l, y + height);
- break;
-
- case Gtk.PositionType.TOP:
- cr.move_to (x - radius_l, y);
- cr.curve_to (x, y, x, y + radius_t, x, y + radius_t);
- cr.line_to (x, y + height - radius_t);
- cr.curve_to (x, y + height, x + radius_l, y + height, x + radius_l, y + height);
- cr.line_to (x + width - radius_l, y + height);
- cr.curve_to (x + width, y + height,
- x + width, y + height - radius_t,
- x + width, y + height - radius_t);
- cr.line_to (x + width, y + radius_t);
- cr.curve_to (x + width, y, x + width + radius_l, y, x + width + radius_l, y);
- break;
- }
- }
-
- public override bool scroll_event (Gdk.EventScroll event) {
- Source.remove (scroll_timeout);
- double impulse = 0.0;
- double step = 0.1;
- if (event.direction == Gdk.ScrollDirection.DOWN) {
- step = -step;
- }
- double start = scrolling;
- scroll_timeout = Timeout.add (30, () => {
- impulse += step;
- double dt = Math.sin ((impulse) * Math.PI/2);
- scrolling = double.min (double.max (-(tabs.size * (width - overlap) + 2*radius - get_allocated_width ()), start - 200*dt), 0);
- queue_draw ();
-
- if(impulse > 1.0 || impulse < -1.0)
- return false;
- return true;
- });
- return true;
- }
-
- public override bool leave_notify_event (Gdk.EventCrossing event) {
- foreach (var tab in tabs) {
- if (tab.state != Gtk.StateFlags.ACTIVE)
- tab.state = Gtk.StateFlags.NORMAL;
- tab.close_button = Gtk.StateFlags.NORMAL;
- }
- saved_event_motion = null;
- queue_draw ();
- return true;
- }
-
- public override bool button_press_event (Gdk.EventButton event) {
- event.x -= radius + scrolling;
- if (event.button == 1) {
- start_dragging = (int)(event.x/(width - overlap));
- if (start_dragging >= tabs.size) {
- start_dragging = -1;
- /* click on an empty space */
- if (event.type == Gdk.EventType.2BUTTON_PRESS)
- need_new_tab ();
- }
- else {
- tabs[start_dragging].drag_origin = event.x - start_dragging * (width - overlap);
- }
- }
-
- return false;
- }
-
- /**
- * Internally remove a tab.
- *
- * @param n_tab the tab index in the tabs array. Errors aren't handled, so, it must be
- * between 0 and the maximum index.
- **/
- void remove_tab_internal (int n_tab) {
- /* Prepare the tab */
- tabs[n_tab].state = Gtk.StateFlags.NORMAL;
- tabs[n_tab].removed = true;
- tabs[n_tab].offset = 1.0;
-
- /* Launch the removing animation */
- launch_animations ();
-
- /* If it was selected */
- if (page == n_tab && page > 0)
- page--;
- else if (n_tab == 0 && page == 0)
- page = int.min (page + 1, tabs.size - 1);
- if (page < tabs.size)
- tabs[page].select ();
- page_removed (tabs[n_tab]);
- if (page < tabs.size)
- switch_page (tabs[page]);
- }
-
- public override bool button_release_event (Gdk.EventButton event) {
- Tab? tab_removed = null;
- foreach (var tab in tabs) {
- if (tab.removed) tab_removed = tab;
- }
- if (tab_removed != null) remove_tab (tab_removed);
-
- event.x -= radius + scrolling;
-
- /* Wich tab are we on? */
- int n_tab = (int)(event.x/(width - overlap));
- if (n_tab < tabs.size && n_tab >= 0) {
- if (event.button == 1) { /* It is a left click */
- /* we unselect all tabs */
- foreach (var tab in tabs) {
- tab.state = Gtk.StateFlags.NORMAL;
- }
- /* we select the good one */
- tabs[n_tab].select ();
- /* Let's see of it is on the close button */
- double offset = event.x - n_tab * (width - overlap) - overlap;
- if (0 < offset < close_margin*2 + close_size) { /* then it is a click on the close_button */
- if (tabs[n_tab].close_button_clicked ())
- remove_tab_internal (n_tab);
- }
- else {
- page = n_tab;
- switch_page (tabs[page]);
- }
- }
- else if (event.button == 2) {
- remove_tab_internal (n_tab);
- }
- }
-
- /* If a tab was dragged, we need to release it. */
- if (start_dragging != -1) {
- var tab = tabs[start_dragging];
- tab.drag_origin = 0.0;
- launch_animations ();
- start_dragging = -1;
- }
-
- queue_draw ();
- return true;
- }
-
- void launch_animations () {
- Source.remove (timeout_anim);
-
- foreach (var tab in tabs) {
- tab.start_animation ();
- }
- double dt = 0.0;
- timeout_anim = Timeout.add (35, () => {
- bool need_continue = true;
- dt += 0.2;
- if (dt >= 1.0) {
- dt = 1.0;
- need_continue = false;
- }
- Tab? tab_to_remove = null;
- foreach (var tab in tabs) {
- bool tab_animated = tab.is_animated ();
- if (tab_animated)
- tab.do_animation (dt);
- if (tab.removed) {
- tab_to_remove = tab;
- }
- }
- if (!need_continue && tab_to_remove != null) {
- remove_tab (tab_to_remove);
- }
-
- if (!need_continue && saved_event_motion != null) {
- motion_notify_event (saved_event_motion);
- }
-
- queue_draw ();
- return need_continue;
- });
- }
-
- public void remove_tab (Tab tab) {
- int n_tab = tabs.index_of (tab);
- tabs.remove (tab);
- if (_page >= n_tab)
- _page--;
- Source.remove (timeout_remove);
- timeout_remove = Timeout.add (2000, () => {
- update_tab_size (get_allocated_width ());
- launch_animations ();
- timeout_remove = -1;
- return false;
- });
- }
-
- public override bool motion_notify_event (Gdk.EventMotion event) {
- event.x -= radius + scrolling;
- bool need_draw = false;
-
- if (start_dragging != -1) {
- need_draw = true;
- tabs[start_dragging].draw_offset = -(tabs[start_dragging].drag_origin +
- start_dragging * (width - overlap) - event.x);
- if (tabs[start_dragging].draw_offset < - width/2 && start_dragging > 0) {
- var tab = tabs[start_dragging];
- tabs[start_dragging] = tabs[start_dragging - 1];
- var old_tab = tabs[start_dragging];
- start_dragging --;
- tabs[start_dragging] = tab;
- tabs[start_dragging].draw_offset = -(tabs[start_dragging].drag_origin +
- start_dragging * (width - overlap) - event.x);
- old_tab.draw_offset = - width + overlap;
- launch_animations ();
- }
- else if (tabs[start_dragging].draw_offset > width/2 && start_dragging < tabs.size - 1) {
- var tab = tabs[start_dragging];
- tabs[start_dragging] = tabs[start_dragging + 1];
- var old_tab = tabs[start_dragging];
- start_dragging ++;
- tabs[start_dragging] = tab;
- tabs[start_dragging].draw_offset = -(tabs[start_dragging].drag_origin +
- start_dragging * (width - overlap) - event.x);
- old_tab.draw_offset = width - overlap;
- launch_animations ();
- }
- }
- else {
- int n_tab = (int)(event.x/(width - overlap));
-
- foreach (var tab in tabs) {
- tab.close_button = Gtk.StateFlags.NORMAL;
- if (tab.state != Gtk.StateFlags.ACTIVE && tab.state != Gtk.StateFlags.NORMAL) {
- tab.state = Gtk.StateFlags.NORMAL;
- need_draw = true;
- }
- }
-
- if (n_tab < tabs.size) {
-
- double offset = event.x - n_tab * (width - overlap) - overlap;
- if (0 < offset < close_margin *2 + close_size &&
- get_allocated_height ()/2 - close_size/2 - close_margin < event.y - y <
- get_allocated_height ()/2 + close_size/2 + close_margin) {
- tabs[n_tab].close_button = Gtk.StateFlags.PRELIGHT;
- }
- tabs[n_tab].hover ();
- need_draw = true;
- }
- }
-
- if (need_draw)
- queue_draw ();
-
- event.x += radius + scrolling;
- saved_event_motion = event;
-
-
- return true;
- }
-}
-
-public class Granite.Widgets.DynamicNotebook : Gtk.Grid {
- Granite.Widgets.Tabs tabs;
- public signal void add_button_clicked ();
- public signal void new_tab_created (Tab tab);
-
- public signal void switch_page (Widget page, uint num);
- public signal void page_added (Widget page, uint num);
- public signal void page_removed (Widget page, uint num);
- public signal void is_empty ();
-
- public int page { set {
- /* Hide the old one */
- tabs.tabs[tabs.page].widget.set_child_visible (false);
- tabs.page = value;
- /* Show the new one */
- tabs.tabs[tabs.page].widget.set_child_visible (true);
- show_all ();
- } get { return tabs.page; } }
-
- Gtk.EventBox add_eventbox;
- Gtk.Button add_button;
- public DynamicNotebook () {
- add_eventbox = new Gtk.EventBox ();
- add_button = new Gtk.Button();
- add_button.set_image (new Gtk.Image.from_pixbuf (Gtk.IconTheme.get_default ().load_icon ("add", 16, 0)));
- add_button.set_relief (Gtk.ReliefStyle.NONE);
- add_eventbox.add (add_button);
- add_eventbox.get_style_context ().add_class ("dynamic-notebook");
- add_eventbox.get_style_context ().add_provider (Tabs.style_provider, Tabs.style_priority);
- add_button.clicked.connect ( () => { add_button_clicked (); });
- tabs = new Granite.Widgets.Tabs ();
- tabs.hexpand = true;
- attach (tabs, 0, 0, 1, 1);
- attach (add_eventbox, 1, 0, 1, 1);
-
- get_style_context ().add_class ("notebook");
-
- tabs.switch_page.connect ( (t) => {
- page = tabs.tabs.index_of (t);
- });
-
- tabs.page_removed.connect ( (t) => {
- page_removed (t.widget, 0);
- if (tabs.tabs.size == 0) {
- is_empty ();
- }
- });
- tabs.need_new_tab.connect ( () => { add_button_clicked (); });
-
- tabs.show_all ();
- add_eventbox.show_all ();
- }
-
- public Tab new_tab () {
- var tab = append_page (new Gtk.Grid (), "New", "gtk-file");
- new_tab_created (tab);
- show_all ();
- return tab;
- }
-
- /**
- * granite_widgets_dynamic_notebook_append_page:
- *
- * Return value: (transfer full): a tab
- */
- public Tab append_page (Gtk.Widget widget, string label, string? icon_id = null) {
- var tab = new Tab (label, icon_id);
- //widget.set_has_window (true);
- tab.widget = widget;
- widget.hexpand = widget.vexpand = true;
- attach (widget, 0, 1, 2, 1);
- tabs.add_tab (tab);
- page_added (widget, tabs.tabs.size - 1);
- return tab;
- }
-
- public void remove_tab (Tab tab) {
- tabs.remove_tab (tab);
- }
-
- public void set_scrollable (bool scrollable) {
- }
-
- public void set_group_name (string name) {
- }
-
- public override void forall_internal (bool internals, Gtk.Callback callback) {
- if (internals) {
- callback (tabs);
- callback (add_eventbox);
- }
- foreach (var tab in tabs.tabs) {
- callback (tab.widget);
- }
- }
-
- public int page_num (Gtk.Widget widget) {
- foreach (var tab in tabs.tabs) {
- if (tab.widget == widget) return tabs.tabs.index_of (tab);
- }
- return -1;
- }
-
- public override void add (Gtk.Widget widget) {
- var tab = new Tab ("[Untitled]");
- tab.widget = widget;
- widget.hexpand = true;
- widget.vexpand = true;
- tabs.add_tab (tab);
- base.add (widget);
- }
-
-
- public void set_current_page (int page) {
- this.page = page;
- }
-
- public int get_current_page () {
- return page;
- }
-
- public int get_n_pages () {
- return (int) get_children ().length ();
- }
-
- public Tab get_nth_page (int page) {
- if (page < tabs.tabs.size && page >= 0)
- return tabs.tabs[page];
- else
- return null;
- }
-}
-
-
-
+ }
+ if (Signal.has_handler_pending (this, //if no one listens, just kill it!
+ Signal.lookup ("page-closed", typeof (DynamicNotebook)), 0, true)) {
+ var sure = this.page_closed (this.notebook.get_nth_page (i), i);
+ if (sure)
+ this.notebook.remove_page (i);
+ } else {
+ this.notebook.remove_page (i);
+ }
+ }
+
+ /**
+ * toggle the working state of the tab, which will cause a spinner to appear if it's working
+ **/
+ public void toggle_working (int num, bool enable) {
+ var box = (Gtk.Container)this.notebook.get_tab_label (this.notebook.get_nth_page (num));
+ if (enable) {
+ box.get_children ().nth_data (3).show_all ();
+ box.get_children ().nth_data (2).hide ();
+ } else {
+ box.get_children ().nth_data (2).show_all ();
+ box.get_children ().nth_data (3).hide ();
+ }
+
+ }
+
+ /**
+ * toggle whether the tab should be an app, e.g. show only its icon
+ **/
+ public void toggle_app_tab (int num, bool enable) {
+ var box = (Gtk.Container)this.notebook.get_tab_label (this.notebook.get_nth_page (num));
+ if (enable) {
+ box.get_children ().nth_data (0).hide ();
+ box.get_children ().nth_data (1).hide ();
+ } else {
+ box.get_children ().nth_data (0).show_all ();
+ box.get_children ().nth_data (1).show_all ();
+ }
+ }
+
+ }
+
+}
Follow ups
-
[Merge] lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: noreply, 2012-06-29
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: xapantu, 2012-06-29
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: Victor Eduardo, 2012-06-29
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: Victor Eduardo, 2012-06-29
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: Tom Beckmann, 2012-06-25
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: xapantu, 2012-06-25
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: Tom Beckmann, 2012-06-24
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: xapantu, 2012-06-24
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: Tom Beckmann, 2012-06-23
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: xapantu, 2012-05-12
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: Tom Beckmann, 2012-04-30
-
Re: lp:~tombeckmann/granite/granite-dynamic-notebook-gkt-based into lp:granite
From: xapantu, 2012-04-30