[gnome-clocks] Major rework of the toolbar code



commit 1d6ff4a7227087f59651d1611208e1b021d3a7e9
Author: Paolo Borelli <pborelli gnome org>
Date:   Sun Dec 30 18:04:06 2012 +0100

    Major rework of the toolbar code
    
    Refactor toolbar creation and handling to untangle interaction with each
    clock. Now the toolbar is passed in to every clock, which in tunr can add
    add and remove items, manage sensitivity, etc.

 gnomeclocks/alarm.py     |   98 +++++++++++----
 gnomeclocks/app.py       |  309 ++++------------------------------------------
 gnomeclocks/clocks.py    |   31 ++----
 gnomeclocks/stopwatch.py |    4 +-
 gnomeclocks/timer.py     |    4 +-
 gnomeclocks/widgets.py   |  224 +++++++++++++++++++++++++++++++---
 gnomeclocks/world.py     |   88 ++++++++++----
 7 files changed, 381 insertions(+), 377 deletions(-)
---
diff --git a/gnomeclocks/alarm.py b/gnomeclocks/alarm.py
index 010685c..d64cf33 100644
--- a/gnomeclocks/alarm.py
+++ b/gnomeclocks/alarm.py
@@ -20,10 +20,10 @@ import os
 import errno
 import json
 from datetime import timedelta
-from gi.repository import GLib, GObject, GdkPixbuf, Gtk
+from gi.repository import GLib, GObject, Gtk
 from clocks import Clock
 from utils import Alert, Dirs, LocalizedWeekdays, SystemSettings, TimeString, WallClock
-from widgets import SelectableIconView, ContentView
+from widgets import Toolbar, ToolButton, SymbolicToolButton, SelectableIconView, ContentView
 
 
 wallclock = WallClock.get_default()
@@ -56,7 +56,7 @@ class AlarmsStorage:
                 try:
                     n, h, m, d = (a['name'], int(a['hour']), int(a['minute']), a['days'])
                     # support the old format that didn't have the active key
-                    active = a['active'] if a.has_key('active') else True
+                    active = a['active'] if 'active' in a else True
                 except:
                     # skip alarms which do not have the required fields
                     continue
@@ -187,6 +187,7 @@ class AlarmItem:
             self.stop()
         return self.state != last_state
 
+
 class AlarmDialog(Gtk.Dialog):
     def __init__(self, parent, alarm=None):
         if alarm:
@@ -450,9 +451,28 @@ class AlarmStandalone(Gtk.EventBox):
 
 
 class Alarm(Clock):
-    def __init__(self):
+    def __init__(self, embed, toolbar):
+        Clock.__init__(self, _("Alarm"), embed, toolbar)
+
         # Translators: "New" refers to an alarm
-        Clock.__init__(self, _("Alarm"), _("New"))
+        self.new_button = ToolButton(_("New"))
+        self.new_button.connect('clicked', self._on_new_clicked)
+
+        self.select_button = SymbolicToolButton("object-select-symbolic")
+        self.select_button.connect('clicked', self._on_select_clicked)
+
+        self.done_button = ToolButton(_("Done"))
+        self.done_button.get_style_context().add_class('suggested-action')
+        self.done_button.connect("clicked", self._on_done_clicked)
+
+        self.back_button = SymbolicToolButton("go-previous-symbolic")
+        self.back_button.connect('clicked', self._on_back_clicked)
+
+        self.edit_button = ToolButton(_("Edit"))
+        self.edit_button.connect('clicked', self._on_edit_clicked)
+
+        self.delete_button = Gtk.Button(_("Delete"))
+        self.delete_button.connect('clicked', self._on_delete_clicked)
 
         self.notebook = Gtk.Notebook()
         self.notebook.set_show_tabs(False)
@@ -470,7 +490,6 @@ class Alarm(Clock):
         self.notebook.append_page(contentview, None)
 
         self.storage = AlarmsStorage()
-
         self.load_alarms()
         self.show_all()
 
@@ -479,6 +498,27 @@ class Alarm(Clock):
 
         wallclock.connect("time-changed", self._tick_alarms)
 
+    def _on_new_clicked(self, button):
+        self.activate_new()
+
+    def _on_select_clicked(self, button):
+        self.set_mode(Clock.Mode.SELECTION)
+
+    def _on_done_clicked(self, button):
+        self.set_mode(Clock.Mode.NORMAL)
+
+    def _on_back_clicked(self, button):
+        self.embed.spotlight(lambda: self.set_mode(Clock.Mode.NORMAL))
+
+    def _on_edit_clicked(self, button):
+        self.standalone.open_edit_dialog()
+
+    def _on_delete_clicked(self, button):
+        selection = self.iconview.get_selection()
+        alarms = [self.liststore[path][2] for path in selection]
+        self.delete_alarms(alarms)
+        self.iconview.selection_deleted()
+
     def _thumb_data_func(self, view, cell, store, i, data):
         alarm = store.get_value(i, 2)
         cell.text = alarm.alarm_time_string
@@ -496,10 +536,11 @@ class Alarm(Clock):
                 self.standalone.alarm.stop()
             self.notebook.set_current_page(0)
             self.iconview.set_selection_mode(False)
-        elif mode is Clock.Mode.STANDALONE:
-            self.notebook.set_current_page(1)
         elif mode is Clock.Mode.SELECTION:
             self.iconview.set_selection_mode(True)
+        elif mode is Clock.Mode.STANDALONE:
+            self.notebook.set_current_page(1)
+        self.update_toolbar()
 
     @GObject.Signal
     def alarm_ringing(self):
@@ -520,34 +561,27 @@ class Alarm(Clock):
     def _on_item_activated(self, iconview, path):
         alarm = self.liststore[path][2]
         self.standalone.set_alarm(alarm)
-        self.emit("item-activated")
+        self.embed.spotlight(lambda: self.set_mode(Clock.Mode.STANDALONE))
 
     def _on_selection_changed(self, iconview):
-        self.emit("selection-changed")
-
-    @GObject.Property(type=bool, default=False)
-    def can_select(self):
-        return len(self.liststore) != 0
-
-    def get_selection(self):
-        return self.iconview.get_selection()
-
-    def delete_selected(self):
-        selection = self.get_selection()
-        alarms = [self.liststore[path][2] for path in selection]
-        self.delete_alarms(alarms)
-        self.emit("selection-changed")
+        selection = iconview.get_selection()
+        n_selected = len(selection)
+        self.toolbar.set_selection(n_selected)
+        if n_selected > 0:
+            self.embed.show_floatingbar(self.delete_button)
+        else:
+            self.embed.hide_floatingbar()
 
     def load_alarms(self):
         self.alarms = self.storage.load()
         for alarm in self.alarms:
             self._add_alarm_item(alarm)
+        self.select_button.set_sensitive(self.alarms)
 
     def save_alarms(self):
         self.storage.save(self.alarms)
         self.liststore.clear()
         self.load_alarms()
-        self.notify("can-select")
 
     def add_alarm(self, alarm):
         if alarm in self.alarms:
@@ -572,7 +606,21 @@ class Alarm(Clock):
         self.alarms = [a for a in self.alarms if a not in alarms]
         self.save_alarms()
 
-    def open_new_dialog(self):
+    def update_toolbar(self):
+        self.toolbar.clear()
+        if self.mode is Clock.Mode.NORMAL:
+            self.toolbar.set_mode(Toolbar.Mode.NORMAL)
+            self.toolbar.add_widget(self.new_button)
+            self.toolbar.add_widget(self.select_button, Gtk.PackType.END)
+        elif self.mode is Clock.Mode.SELECTION:
+            self.toolbar.set_mode(Toolbar.Mode.SELECTION)
+            self.toolbar.add_widget(self.done_button, Gtk.PackType.END)
+        elif self.mode is Clock.Mode.STANDALONE:
+            self.toolbar.set_mode(Toolbar.Mode.STANDALONE)
+            self.toolbar.add_widget(self.back_button)
+            self.toolbar.add_widget(self.edit_button, Gtk.PackType.END)
+
+    def activate_new(self):
         window = AlarmDialog(self.get_toplevel())
         window.connect("response", self._on_dialog_response)
         window.show_all()
diff --git a/gnomeclocks/app.py b/gnomeclocks/app.py
index 767aa85..54501ff 100644
--- a/gnomeclocks/app.py
+++ b/gnomeclocks/app.py
@@ -18,14 +18,12 @@
 
 import os
 import sys
-from gettext import ngettext
-from gi.repository import Gtk, Gdk, GObject, Gio, GtkClutter
-from clocks import Clock
+from gi.repository import Gtk, Gdk, Gio, GtkClutter
 from world import World
 from alarm import Alarm
 from stopwatch import Stopwatch
 from timer import Timer
-from widgets import Embed
+from widgets import Toolbar, Embed
 from utils import Dirs
 from gnomeclocks import VERSION, AUTHORS, COPYRIGHTS
 
@@ -55,79 +53,47 @@ class Window(Gtk.ApplicationWindow):
 
         self.set_size_request(640, 480)
 
-        self.world = World()
-        self.alarm = Alarm()
-        self.stopwatch = Stopwatch()
-        self.timer = Timer()
-        self.world.connect("item-activated", self._on_item_activated)
-        self.world.connect("selection-changed", self._on_selection_changed)
-        self.alarm.connect("item-activated", self._on_item_activated)
-        self.alarm.connect("selection-changed", self._on_selection_changed)
-        self.alarm.connect("alarm-ringing", self._on_alarm_ringing)
-        self.timer.connect("alarm-ringing", self._on_alarm_ringing)
-        self.views = (self.world, self.alarm, self.stopwatch, self.timer)
-
-        self.toolbar = ClocksToolbar(self.views)
-        self.toolbar.connect("back-clicked", self._on_back_clicked)
-        self.toolbar.connect("clock-changed", self._on_clock_changed)
-        self.toolbar.connect("done-clicked", self._on_done_clicked)
+        self.toolbar = Toolbar()
+        self.toolbar.connect("page-changed", self._on_page_changed)
 
         self.notebook = Gtk.Notebook()
         self.notebook.set_show_tabs(False)
         self.notebook.set_show_border(False)
-        for view in self.views:
-            self.notebook.append_page(view, None)
 
         self.embed = Embed(self.notebook)
-        self.embed._selectionToolbar._toolbarDelete.connect("clicked", self._on_delete_clicked)
+
+        self.world = World(self.toolbar, self.embed)
+        self.alarm = Alarm(self.toolbar, self.embed)
+        self.stopwatch = Stopwatch(self.toolbar, self.embed)
+        self.timer = Timer(self.toolbar, self.embed)
+        self.alarm.connect("alarm-ringing", self._on_alarm_ringing)
+        self.timer.connect("alarm-ringing", self._on_alarm_ringing)
+        self.views = (self.world, self.alarm, self.stopwatch, self.timer)
+
+        for view in self.views:
+            self.toolbar.append_page(view.label)
+            self.notebook.append_page(view, None)
+        self.notebook.set_current_page(0)
+        self.world.update_toolbar()
 
         vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
         vbox.pack_start(self.toolbar, False, False, 0)
         vbox.pack_end(self.embed, True, True, 0)
 
         self.add(vbox)
-
         vbox.show()
 
-    def _on_item_activated(self, view):
-        def show_clock_standalone():
-            view.set_mode(Clock.Mode.STANDALONE)
-            self.toolbar.update_toolbar(view)
-
-        self.embed.spotlight(show_clock_standalone)
-
-    def _on_selection_changed(self, view):
-        if self.notebook.get_current_page() == self.views.index(view):
-            selection = view.get_selection()
-            n_selected = len(selection)
-            self.embed.set_show_selectionbar(n_selected > 0)
-            self.toolbar.update_toolbar(view)
+    def _on_new_activated(self):
+        view = self.views[self.notebook.get_current_page()]
+        view.activate_new()
 
     def _on_alarm_ringing(self, view):
         self.notebook.set_current_page(self.views.index(view))
-        self.toolbar.update_toolbar(view)
-
-    def _on_back_clicked(self, button, view):
-        def show_clock_overview():
-            view.set_mode(Clock.Mode.NORMAL)
-            self.toolbar.update_toolbar(view)
-
-        self.embed.spotlight(show_clock_overview)
-
-    def _on_clock_changed(self, button, view):
-        self.notebook.set_current_page(self.views.index(view))
-        self.toolbar.update_toolbar(view)
-
-    def _on_done_clicked(self, button, view):
-        self.embed.set_show_selectionbar(False)
-
-    def _on_delete_clicked(self, widget):
-        view = self.views[self.notebook.get_current_page()]
-        view.delete_selected()
 
-    def _on_new_activated(self, action, param):
+    def _on_page_changed(self, toolbar, page):
+        self.notebook.set_current_page(page)
         view = self.views[self.notebook.get_current_page()]
-        view.open_new_dialog()
+        view.update_toolbar()
 
     def _on_about_activated(self, action, param):
         about = Gtk.AboutDialog()
@@ -166,233 +132,6 @@ class Window(Gtk.ApplicationWindow):
         about.show()
 
 
-class ClockButton(Gtk.RadioButton):
-    _radio_group = None
-    _size_group = None
-
-    def __init__(self, text):
-        Gtk.RadioButton.__init__(self, group=ClockButton._radio_group, draw_indicator=False)
-        if not ClockButton._radio_group:
-            ClockButton._radio_group = self
-        if not ClockButton._size_group:
-            ClockButton._size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
-        # We use two labels to make sure they
-        # keep the same size even when using bold
-        self.label = Gtk.Label()
-        self.label.set_markup(text)
-        self.bold_label = Gtk.Label()
-        self.bold_label.set_markup("<b>%s</b>" % text)
-        ClockButton._size_group.add_widget(self.label)
-        ClockButton._size_group.add_widget(self.bold_label)
-        if self.get_active():
-            self.add(self.bold_label)
-        else:
-            self.add(self.label)
-        self.set_alignment(0.5, 0.5)
-        self.set_size_request(100, 34)
-        self.get_style_context().add_class('linked')
-
-    def do_toggled(self):
-        try:
-            label = self.get_child()
-            self.remove(label)
-            # We need to unset the flag manually until GTK fixes
-            # https://bugzilla.gnome.org/show_bug.cgi?id=688519
-            label.unset_state_flags(Gtk.StateFlags.ACTIVE)
-            if self.get_active():
-                self.add(self.bold_label)
-            else:
-                self.add(self.label)
-            self.show_all()
-        except TypeError:
-            # at construction the is no label yet
-            pass
-
-
-class SymbolicButton(Gtk.Button):
-    def __init__(self, iconname):
-        Gtk.Button.__init__(self)
-        icon = Gio.ThemedIcon.new_with_default_fallbacks(iconname)
-        image = Gtk.Image()
-        image.set_from_gicon(icon, Gtk.IconSize.MENU)
-        self.add(image)
-        self.set_size_request(34, 34)
-
-
-class ClocksToolbar(Gtk.Toolbar):
-    def __init__(self, views):
-        Gtk.Toolbar.__init__(self)
-
-        self.get_style_context().add_class("clocks-toolbar")
-        self.set_icon_size(Gtk.IconSize.MENU)
-        self.get_style_context().add_class(Gtk.STYLE_CLASS_MENUBAR)
-
-        self.views = views
-
-        size_group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
-
-        left_item = Gtk.ToolItem()
-        self.insert(left_item, -1)
-        size_group.add_widget(left_item)
-
-        left_box = Gtk.Box()
-        left_item.add(left_box)
-
-        self.new_button = Gtk.Button()
-        self.new_button.set_action_name("win.new")
-        self.new_button.set_size_request(64, 34)
-        left_box.pack_start(self.new_button, False, False, 0)
-
-        self.back_button = SymbolicButton("go-previous-symbolic")
-        self.back_button.connect("clicked",
-            lambda w: self.emit("back-clicked", self.current_view))
-        left_box.pack_start(self.back_button, False, False, 0)
-
-        center_item = Gtk.ToolItem()
-        center_item.set_expand(True)
-        self.insert(center_item, -1)
-
-        center_box = Gtk.Box()
-        center_item.add(center_box)
-
-        self.button_box = Gtk.Box()
-        self.button_box.set_homogeneous(True)
-        self.button_box.set_halign(Gtk.Align.CENTER)
-        self.button_box.get_style_context().add_class("linked")
-        center_box.pack_start(self.button_box, True, False, 0)
-
-        self.view_buttons = {}
-        self.current_view = None
-        for view in views:
-            button = ClockButton(view.label)
-            self.button_box.pack_start(button, True, True, 0)
-            button.connect("toggled", self._on_toggled, view)
-            self.view_buttons[view] = button
-            if hasattr(view, "can_select"):
-                view.connect("notify::can-select", self._on_can_select_changed)
-            if view == views[0]:
-                self.current_view = view
-                button.set_active(True)
-
-        self.title_label = Gtk.Label()
-        self.title_label.set_halign(Gtk.Align.CENTER)
-        self.title_label.set_valign(Gtk.Align.CENTER)
-        center_box.pack_start(self.title_label, True, False, 0)
-
-        right_item = Gtk.ToolItem()
-        size_group.add_widget(right_item)
-
-        right_box = Gtk.Box()
-        right_item.add(right_box)
-        self.insert(right_item, -1)
-
-        self.select_button = SymbolicButton("object-select-symbolic")
-        self.select_button.set_sensitive(self.current_view.can_select)
-        self.select_button.connect('clicked', self._on_select_clicked)
-        right_box.pack_end(self.select_button, False, False, 0)
-
-        self.edit_button = Gtk.Button(_("Edit"))
-        self.edit_button.set_size_request(64, 34)
-        self.edit_button.connect('clicked', self._on_edit_clicked)
-        right_box.pack_end(self.edit_button, False, False, 0)
-
-        self.done_button = Gtk.Button(_("Done"))
-        self.done_button.get_style_context().add_class('suggested-action')
-        self.done_button.set_size_request(64, 34)
-        self.done_button.connect("clicked",
-            lambda w: self.emit("done-clicked", self.current_view))
-        right_box.pack_end(self.done_button, False, False, 0)
-
-        self.show_all()
-        self.update_toolbar(self.current_view)
-
-    @GObject.Signal(arg_types=(Clock,))
-    def back_clicked(self, view):
-        self.select_button.set_sensitive(view.can_select)
-
-    @GObject.Signal(arg_types=(Clock,))
-    def clock_changed(self, view):
-        self.current_view = view
-        if hasattr(view, "can_select"):
-            self.select_button.set_sensitive(view.can_select)
-
-    @GObject.Signal(arg_types=(Clock,))
-    def done_clicked(self, view):
-        self.current_view.set_mode(Clock.Mode.NORMAL)
-        self.update_toolbar(self.current_view)
-
-    def update_toolbar(self, view):
-        if view is not self.current_view:
-            self.view_buttons[view].set_active(True)
-        if view.mode is Clock.Mode.NORMAL:
-            self._show_normal_toolbar()
-        elif view.mode is Clock.Mode.STANDALONE:
-            self._show_standalone_toolbar()
-        elif view.mode is Clock.Mode.SELECTION:
-            self._show_selection_toolbar()
-
-    def _show_normal_toolbar(self):
-        self.get_style_context().remove_class("selection-mode")
-        self.button_box.show()
-        if self.current_view.new_label:
-            self.new_button.set_label(self.current_view.new_label)
-            self.new_button.show()
-        else:
-            self.new_button.hide()
-        self.select_button.set_visible(hasattr(self.current_view, "can_select"))
-        self.back_button.hide()
-        self.title_label.hide()
-        self.edit_button.hide()
-        self.done_button.hide()
-
-    def _show_standalone_toolbar(self):
-        self.get_style_context().remove_class("selection-mode")
-        self.button_box.hide()
-        self.new_button.hide()
-        self.select_button.hide()
-        self.back_button.show_all()
-        standalone = self.current_view.standalone
-        self.title_label.set_markup("<b>%s</b>" % standalone.get_name())
-        self.title_label.show()
-        self.edit_button.set_visible(standalone.can_edit)
-        self.done_button.hide()
-
-    def _show_selection_toolbar(self):
-        self.get_style_context().add_class("selection-mode")
-        self.button_box.hide()
-        self.new_button.hide()
-        self.select_button.hide()
-        self.back_button.hide()
-        self._set_selection_label(0)
-        self.title_label.show()
-        self.edit_button.hide()
-        self.done_button.show()
-        selection = self.current_view.get_selection()
-        self._set_selection_label(len(selection))
-
-    def _on_toggled(self, widget, view):
-        self.emit("clock-changed", view)
-
-    def _set_selection_label(self, n):
-        if n == 0:
-            self.title_label.set_markup("(%s)" % _("Click on items to select them"))
-        else:
-            msg = ngettext("{0} item selected", "{0} items selected", n).format(n)
-            self.title_label.set_markup("<b>%s</b>" % (msg))
-
-    def _on_can_select_changed(self, view, pspec):
-        if view == self.current_view:
-            self.select_button.set_sensitive(view.can_select)
-
-    def _on_select_clicked(self, button):
-        self.current_view.set_mode(Clock.Mode.SELECTION)
-        self.update_toolbar(self.current_view)
-
-    def _on_edit_clicked(self, button):
-        standalone = self.current_view.standalone
-        standalone.open_edit_dialog()
-
-
 class ClocksApplication(Gtk.Application):
     def __init__(self):
         Gtk.Application.__init__(self)
diff --git a/gnomeclocks/clocks.py b/gnomeclocks/clocks.py
index 0aebecc..5ef70d5 100644
--- a/gnomeclocks/clocks.py
+++ b/gnomeclocks/clocks.py
@@ -16,7 +16,7 @@
 #
 # Author: Seif Lotfy <seif lotfy collabora co uk>
 
-from gi.repository import GObject, Gtk
+from gi.repository import Gtk
 
 
 class Clock(Gtk.EventBox):
@@ -25,39 +25,24 @@ class Clock(Gtk.EventBox):
         STANDALONE = 1
         SELECTION = 2
 
-    def __init__(self, label, new_label=None):
+    def __init__(self, label, toolbar, embed):
         Gtk.EventBox.__init__(self)
 
-        # We catch map/unmap here to allow pausing of expensive UI
-        # updates, like for the stopwatch, when corresponding UI is not
-        # visible.
+        self.toolbar = toolbar
+        self.embed = embed
+
         self.connect('map', self._ui_thaw)
         self.connect('unmap', self._ui_freeze)
 
         self.label = label
-        self.new_label = new_label
         self.mode = Clock.Mode.NORMAL
         self.get_style_context().add_class('view')
         self.get_style_context().add_class('content-view')
 
-    @GObject.Signal
-    def item_activated(self):
-        pass
-
-    @GObject.Signal
-    def selection_changed(self):
-        pass
-
-    def open_new_dialog(self):
-        pass
-
-    def get_selection(self):
-        pass
-
-    def delete_selected(self):
-        pass
+    def update_toolbar(self):
+        self.toolbar.clear()
 
-    def set_mode(self, mode):
+    def activate_new(self):
         pass
 
     def _ui_freeze(self, widget):
diff --git a/gnomeclocks/stopwatch.py b/gnomeclocks/stopwatch.py
index e1fb0de..5504cde 100644
--- a/gnomeclocks/stopwatch.py
+++ b/gnomeclocks/stopwatch.py
@@ -31,8 +31,8 @@ class Stopwatch(Clock):
         RUNNING = 1
         STOPPED = 2
 
-    def __init__(self):
-        Clock.__init__(self, _("Stopwatch"))
+    def __init__(self, toolbar, embed):
+        Clock.__init__(self, _("Stopwatch"), toolbar, embed)
 
         self.state = Stopwatch.State.RESET
 
diff --git a/gnomeclocks/timer.py b/gnomeclocks/timer.py
index 68bacdf..be732de 100644
--- a/gnomeclocks/timer.py
+++ b/gnomeclocks/timer.py
@@ -152,8 +152,8 @@ class Timer(Clock):
         RUNNING = 1
         PAUSED = 2
 
-    def __init__(self):
-        Clock.__init__(self, _("Timer"))
+    def __init__(self, toolbar, embed):
+        Clock.__init__(self, _("Timer"), toolbar, embed)
         self.state = Timer.State.STOPPED
         self.timeout_id = 0
         self._last_set_time = None
diff --git a/gnomeclocks/widgets.py b/gnomeclocks/widgets.py
index a86f69b..32e3785 100644
--- a/gnomeclocks/widgets.py
+++ b/gnomeclocks/widgets.py
@@ -16,10 +16,189 @@
 #
 # Author: Seif Lotfy <seif lotfy collabora co uk>
 
+from gettext import ngettext
 from gi.repository import GObject, Gio, Gtk, Gdk, Pango
 from gi.repository import Clutter, GtkClutter
 
 
+class PageButton(Gtk.RadioButton):
+    _radio_group = None
+    _size_group = None
+
+    def __init__(self, text, page):
+        Gtk.RadioButton.__init__(self, group=PageButton._radio_group, draw_indicator=False)
+        if not PageButton._radio_group:
+            PageButton._radio_group = self
+        if not PageButton._size_group:
+            PageButton._size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
+        self.page = page
+        # We use two labels to make sure they
+        # keep the same size even when using bold
+        self.label = Gtk.Label()
+        self.label.set_markup(text)
+        self.label.show()
+        self.bold_label = Gtk.Label()
+        self.bold_label.set_markup("<b>%s</b>" % text)
+        self.bold_label.show()
+        PageButton._size_group.add_widget(self.label)
+        PageButton._size_group.add_widget(self.bold_label)
+        if self.get_active():
+            self.add(self.bold_label)
+        else:
+            self.add(self.label)
+        self.set_alignment(0.5, 0.5)
+        self.set_size_request(100, 34)
+        self.get_style_context().add_class('linked')
+
+    def do_toggled(self):
+        try:
+            label = self.get_child()
+            self.remove(label)
+            # We need to unset the flag manually until GTK fixes
+            # https://bugzilla.gnome.org/show_bug.cgi?id=688519
+            label.unset_state_flags(Gtk.StateFlags.ACTIVE)
+            if self.get_active():
+                self.add(self.bold_label)
+            else:
+                self.add(self.label)
+            self.show_all()
+        except TypeError:
+            # at construction the is no label yet
+            pass
+
+
+class Toolbar(Gtk.Toolbar):
+    class Mode:
+        NORMAL = 0
+        SELECTION = 1
+        STANDALONE = 2
+
+    def __init__(self):
+        Gtk.Toolbar.__init__(self)
+
+        self.mode = Toolbar.Mode.NORMAL
+        self.n_pages = 0
+        self.cur_page = 0
+
+        self.get_style_context().add_class("clocks-toolbar")
+        self.set_icon_size(Gtk.IconSize.MENU)
+        self.get_style_context().add_class(Gtk.STYLE_CLASS_MENUBAR)
+
+        size_group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+
+        left_item = Gtk.ToolItem()
+        size_group.add_widget(left_item)
+        self.left_box = Gtk.Box()
+        left_item.add(self.left_box)
+        self.insert(left_item, -1)
+
+        center_item = Gtk.ToolItem()
+        center_item.set_expand(True)
+        self.center_box = Gtk.Box()
+        center_item.add(self.center_box)
+        self.insert(center_item, -1)
+
+        right_item = Gtk.ToolItem()
+        size_group.add_widget(right_item)
+        self.right_box = Gtk.Box()
+        right_item.add(self.right_box)
+        self.insert(right_item, -1)
+
+        self.pages_box = Gtk.Box()
+        self.pages_box.set_homogeneous(True)
+        self.pages_box.set_halign(Gtk.Align.CENTER)
+        self.pages_box.get_style_context().add_class("linked")
+        self.center_box.pack_start(self.pages_box, True, False, 0)
+
+        self.selection_label = Gtk.Label()
+        self.selection_label.set_halign(Gtk.Align.CENTER)
+        self.selection_label.set_valign(Gtk.Align.CENTER)
+        self.center_box.pack_start(self.selection_label, True, False, 0)
+        self.set_selection(0)
+
+        self.title_label = Gtk.Label()
+        self.title_label.set_halign(Gtk.Align.CENTER)
+        self.title_label.set_valign(Gtk.Align.CENTER)
+        self.center_box.pack_start(self.title_label, True, False, 0)
+
+        self.show_all()
+
+    def append_page(self, label):
+        button = PageButton(label, self.n_pages)
+        button.show()
+        self.pages_box.pack_start(button, True, True, 0)
+        button.connect("toggled", lambda b: self.set_page(b.page))
+        if self.n_pages == 0:
+            button.set_active(True)
+        self.n_pages += 1
+
+    def set_page(self, page):
+        if page != self.cur_page:
+            self.cur_page = page
+            self.emit("page-changed", page)
+
+    def add_widget(self, widget, pack=Gtk.PackType.START):
+        if pack == Gtk.PackType.START:
+            self.left_box.pack_start(widget, False, False, 0)
+        else:
+            self.right_box.pack_end(widget, False, False, 0)
+        widget.show()
+
+    def clear(self):
+        for w in self.left_box:
+            self.left_box.remove(w)
+        for w in self.right_box:
+            self.right_box.remove(w)
+
+    def set_mode(self, mode):
+        if mode is Toolbar.Mode.NORMAL:
+            self.get_style_context().remove_class("selection-mode")
+            self.pages_box.show()
+            self.selection_label.hide()
+            self.title_label.hide()
+        elif mode is Toolbar.Mode.SELECTION:
+            self.get_style_context().add_class("selection-mode")
+            self.pages_box.hide()
+            self.selection_label.show()
+            self.title_label.hide()
+        elif mode is Toolbar.Mode.STANDALONE:
+            self.get_style_context().remove_class("selection-mode")
+            self.pages_box.hide()
+            self.selection_label.hide()
+            self.title_label.show()
+
+    def set_selection(self, n):
+        if n == 0:
+            self.selection_label.set_markup("(%s)" % _("Click on items to select them"))
+        else:
+            msg = ngettext("{0} item selected", "{0} items selected", n).format(n)
+            self.selection_label.set_markup("<b>%s</b>" % (msg))
+
+    def set_title(self, title):
+        self.title_label.set_markup("<b>%s</b>" % title)
+
+    @GObject.Signal(arg_types=(int,))
+    def page_changed(self, page):
+        self.current_page = page
+
+
+class ToolButton(Gtk.Button):
+    def __init__(self, label):
+        Gtk.Button.__init__(self, label)
+        self.set_size_request(64, 34)
+
+
+class SymbolicToolButton(Gtk.Button):
+    def __init__(self, iconname):
+        Gtk.Button.__init__(self)
+        icon = Gio.ThemedIcon.new_with_default_fallbacks(iconname)
+        image = Gtk.Image()
+        image.set_from_gicon(icon, Gtk.IconSize.MENU)
+        image.show()
+        self.add(image)
+        self.set_size_request(34, 34)
+
+
 class Spinner(Gtk.SpinButton):
     def __init__(self, min_value, max_value):
         super(Spinner, self).__init__()
@@ -203,6 +382,11 @@ class SelectableIconView(Gtk.IconView):
         store = self.get_model()
         return [row.path for row in store if row[self.selection_col]]
 
+    def selection_deleted(self):
+        # IconView is not very smart and does not emit selection-changed
+        # if selected items are deleted, so we give it a push ourselves...
+        self.emit("selection-changed")
+
     def set_selection_mode(self, active):
         if self.selection_mode != active:
             # clear selection
@@ -265,7 +449,7 @@ class ContentView(Gtk.Box):
         self.show_all()
 
 
-class SelectionToolbar:
+class FloatingToolbar:
     DEFAULT_WIDTH = 300
 
     def __init__(self, parent_actor):
@@ -273,7 +457,7 @@ class SelectionToolbar:
         self.widget.set_show_arrow(False)
         self.widget.set_icon_size(Gtk.IconSize.LARGE_TOOLBAR)
         self.widget.get_style_context().add_class('osd')
-        self.widget.set_size_request(SelectionToolbar.DEFAULT_WIDTH, -1)
+        self.widget.set_size_request(FloatingToolbar.DEFAULT_WIDTH, -1)
 
         self.actor = GtkClutter.Actor.new_with_contents(self.widget)
         self.actor.set_opacity(0)
@@ -291,19 +475,25 @@ class SelectionToolbar:
         constraint.set_factor(0.95)
         self.actor.add_constraint(constraint)
 
-        self._leftBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
-        self._leftGroup = Gtk.ToolItem()
-        self._leftGroup.set_expand(True)
-        self._leftGroup.add(self._leftBox)
-        self.widget.insert(self._leftGroup, -1)
-        self._toolbarDelete = Gtk.Button(_("Delete"))
-        self._leftBox.pack_start(self._toolbarDelete, True, True, 0)
+        self.button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+        item = Gtk.ToolItem()
+        item.set_expand(True)
+        item.add(self.button_box)
+        self.widget.insert(item, -1)
 
         self.widget.show_all()
         self.actor.hide()
 
         self._transition = None
 
+    def add_widget(self, widget):
+        widget.show()
+        self.button_box.pack_start(widget, True, True, 0)
+
+    def clear(self):
+        for w in self.button_box:
+            self.button_box.remove(w)
+
     def fade_in(self):
         self.actor.show()
         self.actor.save_easing_state()
@@ -372,8 +562,8 @@ class Embed(GtkClutter.Embed):
                              Clutter.BinAlignment.FILL,
                              Clutter.BinAlignment.FILL)
 
-        self._selectionToolbar = SelectionToolbar(self._contentsActor)
-        self._overlayLayout.add(self._selectionToolbar.actor,
+        self._floatingToolbar = FloatingToolbar(self._contentsActor)
+        self._overlayLayout.add(self._floatingToolbar.actor,
                                 Clutter.BinAlignment.FIXED,
                                 Clutter.BinAlignment.FIXED)
         self.show_all()
@@ -413,8 +603,10 @@ class Embed(GtkClutter.Embed):
         self._background.restore_easing_state()
         self._background.connect('transition-stopped::opacity', self._spotlight_finished)
 
-    def set_show_selectionbar(self, show):
-        if show:
-            self._selectionToolbar.fade_in()
-        else:
-            self._selectionToolbar.fade_out()
+    def show_floatingbar(self, widget):
+        self._floatingToolbar.clear()
+        self._floatingToolbar.add_widget(widget)
+        self._floatingToolbar.fade_in()
+
+    def hide_floatingbar(self):
+        self._floatingToolbar.fade_out()
diff --git a/gnomeclocks/world.py b/gnomeclocks/world.py
index 27e5d0b..7ec1b6c 100644
--- a/gnomeclocks/world.py
+++ b/gnomeclocks/world.py
@@ -20,11 +20,11 @@ import os
 import errno
 import time
 import json
-from gi.repository import GLib, GObject, Gio, GdkPixbuf, Gtk
+from gi.repository import GLib, Gio, GdkPixbuf, Gtk
 from gi.repository import GWeather
 from clocks import Clock
 from utils import Dirs, TimeString, WallClock
-from widgets import SelectableIconView, ContentView
+from widgets import Toolbar, ToolButton, SymbolicToolButton, SelectableIconView, ContentView
 
 
 # keep the GWeather world around as a singletom, otherwise
@@ -238,7 +238,7 @@ class WorldStandalone(Gtk.EventBox):
         self.sun_grid.attach(sunrise_label, 1, 0, 1, 1)
         self.sun_grid.attach(self.sunrise_time_label, 2, 0, 1, 1)
         self.sun_grid.attach(sunset_label, 1, 1, 1, 1)
-        self.sun_grid.attach(self.sunset_time_label, 2, 1, 1, 1 )
+        self.sun_grid.attach(self.sunset_time_label, 2, 1, 1, 1)
         self.sun_grid.set_margin_bottom(24)
         self.sun_grid.set_hexpand(False)
         self.sun_grid.set_halign(Gtk.Align.CENTER)
@@ -293,9 +293,25 @@ class WorldStandalone(Gtk.EventBox):
 
 
 class World(Clock):
-    def __init__(self):
+    def __init__(self, embed, toolbar):
+        Clock.__init__(self, _("World"), embed, toolbar)
+
         # Translators: "New" refers to a world clock
-        Clock.__init__(self, _("World"), _("New"))
+        self.new_button = ToolButton(_("New"))
+        self.new_button.connect('clicked', self._on_new_clicked)
+
+        self.select_button = SymbolicToolButton("object-select-symbolic")
+        self.select_button.connect('clicked', self._on_select_clicked)
+
+        self.done_button = ToolButton(_("Done"))
+        self.done_button.get_style_context().add_class('suggested-action')
+        self.done_button.connect("clicked", self._on_done_clicked)
+
+        self.back_button = SymbolicToolButton("go-previous-symbolic")
+        self.back_button.connect('clicked', self._on_back_clicked)
+
+        self.delete_button = Gtk.Button(_("Delete"))
+        self.delete_button.connect('clicked', self._on_delete_clicked)
 
         self.notebook = Gtk.Notebook()
         self.notebook.set_show_tabs(False)
@@ -318,7 +334,6 @@ class World(Clock):
         self.notebook.append_page(contentview, None)
 
         self.storage = WorldClockStorage()
-        self.clocks = []
         self.load_clocks()
         self.show_all()
 
@@ -327,6 +342,24 @@ class World(Clock):
 
         wallclock.connect("time-changed", self._tick_clocks)
 
+    def _on_new_clicked(self, button):
+        self.activate_new()
+
+    def _on_select_clicked(self, button):
+        self.set_mode(Clock.Mode.SELECTION)
+
+    def _on_done_clicked(self, button):
+        self.set_mode(Clock.Mode.NORMAL)
+
+    def _on_back_clicked(self, button):
+        self.embed.spotlight(lambda: self.set_mode(Clock.Mode.NORMAL))
+
+    def _on_delete_clicked(self, button):
+        selection = self.iconview.get_selection()
+        clocks = [self.liststore[path][2] for path in selection]
+        self.delete_clocks(clocks)
+        self.iconview.selection_deleted()
+
     def _thumb_data_func(self, view, cell, store, i, data):
         clock = store.get_value(i, 2)
         cell.text = clock.time_string
@@ -347,6 +380,7 @@ class World(Clock):
             self.notebook.set_current_page(1)
         elif mode is Clock.Mode.SELECTION:
             self.iconview.set_selection_mode(True)
+        self.update_toolbar()
 
     def _tick_clocks(self, *args):
         for c in self.clocks:
@@ -358,28 +392,22 @@ class World(Clock):
     def _on_item_activated(self, iconview, path):
         clock = self.liststore[path][2]
         self.standalone.set_clock(clock)
-        self.emit("item-activated")
+        self.embed.spotlight(lambda: self.set_mode(Clock.Mode.STANDALONE))
 
     def _on_selection_changed(self, iconview):
-        self.emit("selection-changed")
-
-    @GObject.Property(type=bool, default=False)
-    def can_select(self):
-        return len(self.liststore) != 0
-
-    def get_selection(self):
-        return self.iconview.get_selection()
-
-    def delete_selected(self):
-        selection = self.get_selection()
-        clocks = [self.liststore[path][2] for path in selection]
-        self.delete_clocks(clocks)
-        self.emit("selection-changed")
+        selection = iconview.get_selection()
+        n_selected = len(selection)
+        self.toolbar.set_selection(n_selected)
+        if n_selected > 0:
+            self.embed.show_floatingbar(self.delete_button)
+        else:
+            self.embed.hide_floatingbar()
 
     def load_clocks(self):
         self.clocks = self.storage.load()
         for clock in self.clocks:
             self._add_clock_item(clock)
+        self.select_button.set_sensitive(self.clocks)
 
     def add_clock(self, location):
         if any(c.location.equal(location) for c in self.clocks):
@@ -389,21 +417,33 @@ class World(Clock):
         self.clocks.append(clock)
         self.storage.save(self.clocks)
         self._add_clock_item(clock)
+        self.select_button.set_sensitive(True)
         self.show_all()
 
     def _add_clock_item(self, clock):
         label = GLib.markup_escape_text(clock.name)
         self.liststore.append([False, "<b>%s</b>" % label, clock])
-        self.notify("can-select")
 
     def delete_clocks(self, clocks):
         self.clocks = [c for c in self.clocks if c not in clocks]
         self.storage.save(self.clocks)
         self.liststore.clear()
         self.load_clocks()
-        self.notify("can-select")
 
-    def open_new_dialog(self):
+    def update_toolbar(self):
+        self.toolbar.clear()
+        if self.mode is Clock.Mode.NORMAL:
+            self.toolbar.set_mode(Toolbar.Mode.NORMAL)
+            self.toolbar.add_widget(self.new_button)
+            self.toolbar.add_widget(self.select_button, Gtk.PackType.END)
+        elif self.mode is Clock.Mode.SELECTION:
+            self.toolbar.set_mode(Toolbar.Mode.SELECTION)
+            self.toolbar.add_widget(self.done_button, Gtk.PackType.END)
+        elif self.mode is Clock.Mode.STANDALONE:
+            self.toolbar.set_mode(Toolbar.Mode.STANDALONE)
+            self.toolbar.add_widget(self.back_button)
+
+    def activate_new(self):
         window = NewWorldClockDialog(self.get_toplevel())
         window.connect("response", self._on_dialog_response)
         window.show_all()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]