[Sugar-devel] [PATCH] Browse: tabs usability improved

manuel quiñones manuq at laptop.org
Mon Sep 5 14:47:04 EDT 2011


A screen grab here:

http://dev.laptop.org/~manuq/browse_design/tabs/new_tabs2.png

2011/9/5 Manuel Quiñones <manuq at laptop.org>:
> The Add Tab button has been relocated next to the tab labels, allowing
> more space for the URL entry.
>
> Tabs are always shown.  There is at least one tab.  In that case, it
> cannot be closed.  We prevent the closing by hidding the 'X' button.
> Also, the close image was sugarized.
>
> There is now a fixed width for tabs.  The label text does ellipsize.
> The width depends on the amount of tabs.  There is a maximun size that
> is used when there is extra space.  There is a minimun size to prevent
> hiding the information.
>
> When a new tab is opened, it now shows an empty page, not the default
> page.  In the future, we will add a hint in this empty page, similar
> to what we have for an empty Journal.
>
> Added the option to "follow link in new tab" in the link's palette.
> Added an icon for this item, and also the icon for "follow link" is
> also updated.
>
> Signed-off-by: Manuel Quiñones <manuq at laptop.org>
> ---
>  browser.py                           |  115 ++++++++++++++++++++++++++++------
>  icons/browse-close-tab.svg           |   27 ++++++++
>  icons/browse-follow-link-new-tab.svg |   43 +++++++++++++
>  icons/browse-follow-link.svg         |   26 ++++++++
>  palettes.py                          |   27 ++++++---
>  webactivity.py                       |   31 +--------
>  webtoolbar.py                        |   16 +-----
>  widgets.py                           |   88 ++++++++++++++++++++++++++
>  8 files changed, 304 insertions(+), 69 deletions(-)
>  create mode 100644 icons/browse-close-tab.svg
>  create mode 100644 icons/browse-follow-link-new-tab.svg
>  create mode 100644 icons/browse-follow-link.svg
>  create mode 100644 widgets.py
>
> diff --git a/browser.py b/browser.py
> index 96e6fb1..0a32dd5 100644
> --- a/browser.py
> +++ b/browser.py
> @@ -21,6 +21,7 @@ import time
>
>  import gobject
>  import gtk
> +import pango
>  import hulahop
>  import xpcom
>  from xpcom.nsError import *
> @@ -31,13 +32,16 @@ from hulahop.webview import WebView
>  from sugar import env
>  from sugar.activity import activity
>  from sugar.graphics import style
> +from sugar.graphics.icon import Icon
>
>  import sessionstore
>  from palettes import ContentInvoker
>  from sessionhistory import HistoryListener
>  from progresslistener import ProgressListener
> +from widgets import BrowserNotebook
>
>  _ZOOM_AMOUNT = 0.1
> +_LIBRARY_PATH = '/usr/share/library-common/index.html'
>
>
>  class SaveListener(object):
> @@ -93,7 +97,7 @@ class CommandListener(object):
>         cert_exception.showDialog(self._window)
>
>
> -class TabbedView(gtk.Notebook):
> +class TabbedView(BrowserNotebook):
>     __gtype_name__ = 'TabbedView'
>
>     _com_interfaces_ = interfaces.nsIWindowCreator
> @@ -104,7 +108,7 @@ class TabbedView(gtk.Notebook):
>                               'user-stylesheet.css')
>
>     def __init__(self):
> -        gobject.GObject.__init__(self)
> +        BrowserNotebook.__init__(self)
>
>         self.props.show_border = False
>         self.props.scrollable = True
> @@ -140,8 +144,13 @@ class TabbedView(gtk.Notebook):
>                                                  interfaces.nsIWindowCreator)
>         window_watcher.setWindowCreator(window_creator)
>
> -        browser = Browser()
> -        self._append_tab(browser)
> +        self.connect('size-allocate', self.__size_allocate_cb)
> +        self.connect('page-added', self.__page_added_cb)
> +        self.connect('page-removed', self.__page_removed_cb)
> +
> +        self.new_tab()
> +        self._update_closing_buttons()
> +        self._update_tab_sizes()
>
>     def createChromeWindow(self, parent, flags):
>         if flags & interfaces.nsIWebBrowserChrome.CHROME_OPENAS_CHROME:
> @@ -160,25 +169,81 @@ class TabbedView(gtk.Notebook):
>
>             return browser.containerWindow
>         else:
> -            browser = Browser()
> +            browser = Browser(self)
>             self._append_tab(browser)
>
>             return browser.browser.containerWindow
>
> +    def __size_allocate_cb(self, widget, allocation):
> +        self._update_tab_sizes()
> +
> +    def __page_added_cb(self, notebook, child, pagenum):
> +        self._update_closing_buttons()
> +        self._update_tab_sizes()
> +
> +    def __page_removed_cb(self, notebook, child, pagenum):
> +        self._update_closing_buttons()
> +        self._update_tab_sizes()
> +
> +    def new_tab(self):
> +        browser = Browser(self)
> +        self._append_tab(browser)
> +        return browser
> +
>     def _append_tab(self, browser):
>         label = TabLabel(browser)
>         label.connect('tab-close', self.__tab_close_cb)
>
>         self.append_page(browser, label)
>         browser.show()
> -
>         self.set_current_page(-1)
> -        self.props.show_tabs = self.get_n_pages() > 1
> +
> +    def on_add_tab(self, gobject):
> +        self.new_tab()
>
>     def __tab_close_cb(self, label, browser):
>         self.remove_page(self.page_num(browser))
>         browser.destroy()
> -        self.props.show_tabs = self.get_n_pages() > 1
> +
> +    def _update_tab_sizes(self):
> +        """Update ta widths based in the amount of tabs."""
> +
> +        canvas_size = self.get_allocation()
> +        allowed_size = canvas_size.width
> +        n_pages = self.get_n_pages()
> +        tab_new_size = int(allowed_size * 1.0 / (n_pages+1))
> +        tab_max_size = int(allowed_size * 1.0 / (4+1))
> +        tab_min_size = int(allowed_size * 1.0 / (8+1))
> +
> +        if tab_new_size < tab_min_size:
> +            tab_new_size = tab_min_size
> +        elif tab_new_size > tab_max_size:
> +            tab_new_size = tab_max_size
> +
> +        for page_idx in range(n_pages):
> +            page = self.get_nth_page(page_idx)
> +            label = self.get_tab_label(page)
> +            label.update_size(tab_new_size)
> +
> +    def _update_closing_buttons(self):
> +        """Prevent closing the last tab."""
> +        first_page = self.get_nth_page(0)
> +        first_label = self.get_tab_label(first_page)
> +        if self.get_n_pages() == 1:
> +            first_label.hide_close_button()
> +        else:
> +            first_label.show_close_button()
> +
> +    def _load_homepage(self):
> +        """If new_tab is True, open the homepage in a new tab."""
> +        browser = self.current_browser
> +
> +        if os.path.isfile(_LIBRARY_PATH):
> +            browser.load_uri('file://' + _LIBRARY_PATH)
> +        else:
> +            default_page = os.path.join(activity.get_bundle_path(),
> +                                        "data/index.html")
> +            browser.load_uri(default_page)
>
>     def _get_current_browser(self):
>         return self.get_nth_page(self.get_current_page())
> @@ -202,7 +267,7 @@ class TabbedView(gtk.Notebook):
>             self.remove_page(self.get_n_pages() - 1)
>
>         for tab_session in tab_sessions:
> -            browser = Browser()
> +            browser = Browser(self)
>             self._append_tab(browser)
>             sessionstore.set_session(browser, tab_session)
>
> @@ -231,21 +296,32 @@ class TabLabel(gtk.HBox):
>         self._browser.connect('is-setup', self.__browser_is_setup_cb)
>
>         self._label = gtk.Label('')
> -        self.pack_start(self._label)
> +        self._label.set_ellipsize(pango.ELLIPSIZE_END)
> +        self._label.set_alignment(0, 0.5)
> +        self.pack_start(self._label, )
>         self._label.show()
>
> +        close_tab_icon = Icon(icon_name='browse-close-tab')
>         button = gtk.Button()
> -        button.connect('clicked', self.__button_clicked_cb)
> -        button.set_name('browse-tab-close')
>         button.props.relief = gtk.RELIEF_NONE
>         button.props.focus_on_click = False
> -        self.pack_start(button)
> -        button.show()
> +        icon_box = gtk.HBox()
> +        icon_box.pack_start(close_tab_icon, True, False, 0)
> +        button.add(icon_box)
> +        button.connect('clicked', self.__button_clicked_cb)
> +        button.set_name('browse-tab-close')
> +        self.pack_start(button, expand=False)
> +        button.show_all()
> +        self._close_button = button
> +
> +    def update_size(self, size):
> +        self.set_size_request(size, -1)
>
> -        close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE,
> -                                               gtk.ICON_SIZE_MENU)
> -        button.add(close_image)
> -        close_image.show()
> +    def hide_close_button(self):
> +        self._close_button.hide()
> +
> +    def show_close_button(self):
> +        self._close_button.show()
>
>     def __button_clicked_cb(self, button):
>         self.emit('tab-close', self._browser)
> @@ -272,9 +348,10 @@ class Browser(WebView):
>                      ([])),
>     }
>
> -    def __init__(self):
> +    def __init__(self, tabbed_view):
>         WebView.__init__(self)
>
> +        self.tabbed_view = tabbed_view
>         self.history = HistoryListener()
>         self.progress = ProgressListener()
>
> diff --git a/icons/browse-close-tab.svg b/icons/browse-close-tab.svg
> new file mode 100644
> index 0000000..7affd43
> --- /dev/null
> +++ b/icons/browse-close-tab.svg
> @@ -0,0 +1,27 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
> +  <!ENTITY fill_color "#FFFFFF">
> +  <!ENTITY stroke_color "#010101">
> +]>
> +<svg
> +   xmlns="http://www.w3.org/2000/svg"
> +   version="1.1"
> +   width="22.16"
> +   height="22.16"
> +   viewBox="0 0 22.16 22.16"
> +   id="svg2"
> +   xml:space="preserve">
> +  <g
> +   transform="matrix(1.3,0,0,1.3,-3.2682282,-3.3351543)"
> +   id="browse-dialog-cancel"
> +   style="stroke:&fill_color;;stroke-width:2.69230771;stroke-miterlimit:4;stroke-dasharray:none">
> +    <path
> +   d="M 14.798121,7.2131543 6.9900671,15.021208"
> +   id="path2986"
> +   style="fill:none;stroke:&fill_color;;stroke-width:2.69230771;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
> +    <path
> +   d="M 6.9900671,7.2131543 14.798121,15.021208"
> +   id="path3756"
> +   style="fill:none;stroke:&fill_color;;stroke-width:2.69230771;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
> +  </g>
> +</svg>
> \ No newline at end of file
> diff --git a/icons/browse-follow-link-new-tab.svg b/icons/browse-follow-link-new-tab.svg
> new file mode 100644
> index 0000000..8d9d644
> --- /dev/null
> +++ b/icons/browse-follow-link-new-tab.svg
> @@ -0,0 +1,43 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
> +  <!ENTITY fill_color "#FFFFFF">
> +  <!ENTITY stroke_color "#010101">
> +]>
> +<svg
> +   xmlns="http://www.w3.org/2000/svg"
> +   version="1.1"
> +   width="55.125"
> +   height="55"
> +   viewBox="0 0 55.125 55"
> +   id="svg2"
> +   xml:space="preserve">
> +<g
> +   transform="matrix(0.72828114,0,0,0.72828114,7.3907532,18.617266)"
> +   id="tab-add"
> +   style="display:block">
> +    <g
> +   transform="scale(0.8,0.8)"
> +   id="g5">
> +    <g
> +   transform="translate(6.5,6.5)"
> +   id="g7">
> +       <path
> +   d="m 0,50 55,0 0,-15 -5,0 0,-25 Q 50,5 45,5 L 10,5 Q 5,5 5,10 L 5,35 0,35 z M 30.768,38.767 c -0.002,1.774 -1.438,3.216 -3.214,3.214 -0.889,10e-4 -1.693,-0.359 -2.275,-0.941 -0.582,-0.581 -0.94,-1.385 -0.94,-2.27 l 0,-8.146 h -8.146 c -0.886,-10e-4 -1.689,-0.359 -2.271,-0.94 -0.582,-0.583 -0.942,-1.388 -0.942,-2.276 0,-1.773 1.439,-3.213 3.217,-3.211 h 8.143 v -8.143 c -0.003,-1.776 1.438,-3.217 3.212,-3.217 1.774,0 3.218,1.438 3.215,3.215 l 0.001,8.145 8.146,0.001 c 1.775,-0.005 3.212,1.438 3.213,3.213 0.002,1.775 -1.441,3.214 -3.215,3.215 h -8.143 v 8.141 z"
> +   id="path9"
> +   style="fill:&fill_color;" />
> +    </g>
> +    </g>
> +</g>
> +<g
> +   transform="translate(0,0.55369128)"
> +   id="g3868"><line
> +     style="fill:none;stroke:&fill_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
> +     x1="36.448467"
> +     x2="19.418867"
> +     y1="11.934766"
> +     y2="11.934766"
> +     id="line15" /><polyline
> +     transform="matrix(1.4,0,0,1.4,-2.8453325,2.1725664)"
> +     style="fill:none;stroke:&fill_color;;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round"
> +     points="     21.983,1.843 28.067,6.973 21.983,12.104    "
> +     id="polyline17" /></g></svg>
> \ No newline at end of file
> diff --git a/icons/browse-follow-link.svg b/icons/browse-follow-link.svg
> new file mode 100644
> index 0000000..43da884
> --- /dev/null
> +++ b/icons/browse-follow-link.svg
> @@ -0,0 +1,26 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
> +  <!ENTITY fill_color "#FFFFFF">
> +  <!ENTITY stroke_color "#010101">
> +]>
> +<svg
> +   xmlns="http://www.w3.org/2000/svg"
> +   version="1.1"
> +   width="55.125"
> +   height="55"
> +   viewBox="0 0 55.125 55"
> +   id="svg2"
> +   xml:space="preserve">
> +<g
> +   transform="translate(-0.37116731,15.564534)"
> +   id="g3868"><line
> +     style="fill:none;stroke:&fill_color;;stroke-width:5.25;stroke-linecap:round;stroke-linejoin:round"
> +     x1="40.705868"
> +     x2="15.161467"
> +     y1="11.934416"
> +     y2="11.934416"
> +     id="line15" /><polyline
> +     transform="matrix(2.1,0,0,2.1,-18.234832,-2.7088836)"
> +     style="fill:none;stroke:&fill_color;;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round"
> +     points="     21.983,1.843 28.067,6.973 21.983,12.104    "
> +     id="polyline17" /></g></svg>
> \ No newline at end of file
> diff --git a/palettes.py b/palettes.py
> index 9fbc370..1197cf4 100644
> --- a/palettes.py
> +++ b/palettes.py
> @@ -140,6 +140,17 @@ class LinkPalette(Palette):
>         else:
>             self.props.primary_text = url
>
> +        menu_item = MenuItem(_('Follow link'), 'browse-follow-link')
> +        menu_item.connect('activate', self.__follow_activate_cb)
> +        self.menu.append(menu_item)
> +        menu_item.show()
> +
> +        menu_item = MenuItem(_('Follow link in new tab'),
> +                             'browse-follow-link-new-tab')
> +        menu_item.connect('activate', self.__follow_activate_cb, True)
> +        self.menu.append(menu_item)
> +        menu_item.show()
> +
>         menu_item = MenuItem(_('Keep link'))
>         icon = Icon(icon_name='document-save', xo_color=profile.get_color(),
>                     icon_size=gtk.ICON_SIZE_MENU)
> @@ -156,14 +167,14 @@ class LinkPalette(Palette):
>         self.menu.append(menu_item)
>         menu_item.show()
>
> -        menu_item = MenuItem(_('Follow link'), 'edit-copy')
> -        menu_item.connect('activate', self.__follow_activate_cb)
> -        self.menu.append(menu_item)
> -        menu_item.show()
> -
> -    def __follow_activate_cb(self, menu_item):
> -        self._browser.load_uri(self._url)
> -        self._browser.grab_focus()
> +    def __follow_activate_cb(self, menu_item, new_tab=False):
> +        if new_tab:
> +            new_browser = self._browser.tabbed_view.new_tab()
> +            new_browser.load_uri(self._url)
> +            new_browser.grab_focus()
> +        else:
> +            self._browser.load_uri(self._url)
> +            self._browser.grab_focus()
>
>     def __copy_activate_cb(self, menu_item):
>         clipboard = gtk.Clipboard()
> diff --git a/webactivity.py b/webactivity.py
> index fb1fec8..9999d95 100644
> --- a/webactivity.py
> +++ b/webactivity.py
> @@ -177,8 +177,6 @@ from edittoolbar import EditToolbar
>  from viewtoolbar import ViewToolbar
>  import downloadmanager
>
> -_LIBRARY_PATH = '/usr/share/library-common/index.html'
> -
>  from model import Model
>  from sugar.presence.tubeconn import TubeConnection
>  from messenger import Messenger
> @@ -232,15 +230,12 @@ class WebActivity(activity.Activity):
>         self.set_tray(self._tray, gtk.POS_BOTTOM)
>         self._tray.show()
>
> -        self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self,
> -                    self._disable_multiple_tabs)
> +        self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self)
>         self._edit_toolbar = EditToolbar(self)
>         self._view_toolbar = ViewToolbar(self)
>
>         self._primary_toolbar.connect('add-link', self._link_add_button_cb)
>
> -        self._primary_toolbar.connect('add-tab', self._new_tab_cb)
> -
>         self._primary_toolbar.connect('go-home', self._go_home_button_cb)
>
>         if NEW_TOOLBARS:
> @@ -292,7 +287,7 @@ class WebActivity(activity.Activity):
>         elif not self._jobject.file_path:
>             # TODO: we need this hack until we extend the activity API for
>             # opening URIs and default docs.
> -            self._load_homepage()
> +            self._tabbed_view._load_homepage()
>
>         self.messenger = None
>         self.connect('shared', self._shared_cb)
> @@ -321,9 +316,6 @@ class WebActivity(activity.Activity):
>         else:
>             _logger.debug('Created activity')
>
> -    def _new_tab_cb(self, gobject):
> -        self._load_homepage(new_tab=True)
> -
>     def _shared_cb(self, activity_):
>         _logger.debug('My activity was shared')
>         self.initiating = True
> @@ -425,21 +417,6 @@ class WebActivity(activity.Activity):
>             self.messenger = Messenger(self.tube_conn, self.initiating,
>                                        self.model)
>
> -    def _load_homepage(self, new_tab=False):
> -        # If new_tab is True, open the homepage in a new tab.
> -        if new_tab:
> -            browser = Browser()
> -            self._tabbed_view._append_tab(browser)
> -        else:
> -            browser = self._tabbed_view.current_browser
> -
> -        if os.path.isfile(_LIBRARY_PATH):
> -            browser.load_uri('file://' + _LIBRARY_PATH)
> -        else:
> -            default_page = os.path.join(activity.get_bundle_path(),
> -                                        "data/index.html")
> -            browser.load_uri(default_page)
> -
>     def _get_data_from_file_path(self, file_path):
>         fd = open(file_path, 'r')
>         try:
> @@ -518,7 +495,7 @@ class WebActivity(activity.Activity):
>         self._add_link()
>
>     def _go_home_button_cb(self, button):
> -        self._load_homepage()
> +        self._tabbed_view._load_homepage()
>
>     def _key_press_cb(self, widget, event):
>         key_name = gtk.gdk.keyval_name(event.keyval)
> @@ -555,7 +532,7 @@ class WebActivity(activity.Activity):
>                 browser.web_navigation.reload(flags)
>             elif gtk.gdk.keyval_name(event.keyval) == "t":
>                 if not self._disable_multiple_tabs:
> -                    self._load_homepage(new_tab=True)
> +                    self._tabbed_view._load_homepage(new_tab=True)
>             else:
>                 return False
>
> diff --git a/webtoolbar.py b/webtoolbar.py
> index c0e097d..a4623be 100644
> --- a/webtoolbar.py
> +++ b/webtoolbar.py
> @@ -228,15 +228,12 @@ class PrimaryToolbar(ToolbarBase):
>         'add-link': (gobject.SIGNAL_RUN_FIRST,
>                      gobject.TYPE_NONE,
>                      ([])),
> -        'add-tab': (gobject.SIGNAL_RUN_FIRST,
> -                     gobject.TYPE_NONE,
> -                     ([])),
>         'go-home': (gobject.SIGNAL_RUN_FIRST,
>                      gobject.TYPE_NONE,
>                      ([])),
>     }
>
> -    def __init__(self, tabbed_view, act, disable_multiple_tabs):
> +    def __init__(self, tabbed_view, act):
>         ToolbarBase.__init__(self)
>
>         self._activity = act
> @@ -286,14 +283,6 @@ class PrimaryToolbar(ToolbarBase):
>         toolbar.insert(self._forward, -1)
>         self._forward.show()
>
> -        if not disable_multiple_tabs:
> -            self._add_tab = ToolButton('tab-add')
> -            self._add_tab.set_tooltip(_('Add a tab'))
> -            self._add_tab.props.sensitive = True
> -            self._add_tab.connect('clicked', self._add_tab_cb)
> -            toolbar.insert(self._add_tab, -1)
> -            self._add_tab.show()
> -
>         self._link_add = ToolButton('emblem-favorite')
>         self._link_add.set_tooltip(_('Bookmark'))
>         self._link_add.connect('clicked', self._link_add_clicked_cb)
> @@ -417,9 +406,6 @@ class PrimaryToolbar(ToolbarBase):
>         browser.load_uri(entry.props.text)
>         browser.grab_focus()
>
> -    def _add_tab_cb(self, button):
> -        self.emit('add-tab')
> -
>     def _go_home_cb(self, button):
>         self.emit('go-home')
>
> diff --git a/widgets.py b/widgets.py
> new file mode 100644
> index 0000000..288ed1a
> --- /dev/null
> +++ b/widgets.py
> @@ -0,0 +1,88 @@
> +# Copyright (C) 2006, Red Hat, Inc.
> +# Copyright (C) 2007, One Laptop Per Child
> +# Copyright (C) 2009, Tomeu Vizoso, Simon Schampijer
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 2 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
> +
> +import gobject
> +import gtk
> +
> +from sugar.graphics.notebook import Notebook
> +from sugar.graphics.icon import Icon
> +
> +
> +class TabAdd(gtk.HBox):
> +    __gtype_name__ = 'TabAdd'
> +
> +    __gsignals__ = {
> +        'tab-add': (gobject.SIGNAL_RUN_FIRST,
> +                    gobject.TYPE_NONE,
> +                    ([])),
> +    }
> +
> +    def __init__(self):
> +        gtk.HBox.__init__(self)
> +
> +        add_tab_icon = Icon(icon_name='add')
> +        button = gtk.Button()
> +        button.props.relief = gtk.RELIEF_NONE
> +        button.props.focus_on_click = False
> +        icon_box = gtk.HBox()
> +        icon_box.pack_start(add_tab_icon, True, False, 0)
> +        button.add(icon_box)
> +        button.connect('clicked', self.__button_clicked_cb)
> +        button.set_name('browse-tab-add')
> +        self.pack_start(button)
> +        button.show_all()
> +
> +    def __button_clicked_cb(self, button):
> +        self.emit('tab-add')
> +
> +
> +class BrowserNotebook(Notebook):
> +    """Handle an extra tab at the end with an Add Tab button."""
> +
> +    def __init__(self):
> +        Notebook.__init__(self)
> +        self.connect('switch-page', self.__on_switch_page)
> +
> +        tab_add = TabAdd()
> +        tab_add.connect('tab-add', self.on_add_tab)
> +        empty_page = gtk.HBox()
> +        self.append_page(empty_page, tab_add)
> +        empty_page.show()
> +
> +    def on_add_tab(self, gobject):
> +        raise NotImplementedError, "implement this in the subclass"
> +
> +    def __on_switch_page(self, notebook, page, page_num):
> +        """Don't switch to the extra tab at the end."""
> +        if page_num == Notebook.get_n_pages(self) - 1:
> +            self.set_current_page(-1)
> +            self.stop_emission("switch-page")
> +
> +    def get_n_pages(self):
> +        """Skip the extra tab at the end on the pages count."""
> +        return Notebook.get_n_pages(self) - 1
> +
> +    def append_page(self, page, label):
> +        """Append keeping the extra tab at the end."""
> +        return self.insert_page(page, label, self.get_n_pages())
> +
> +    def set_current_page(self, number):
> +        """If indexing from the end, skip the extra tab."""
> +        if number < 0:
> +            number = self.get_n_pages() - 1
> +        return Notebook.set_current_page(self, number)
> --
> 1.7.4.4
>
>



-- 
.. manuq ..


More information about the Sugar-devel mailing list