From f59d04baa9291cfa6566bc2911bc2e108c244d50 Mon Sep 17 00:00:00 2001 From: Fabio Erculiani Date: Sun, 12 Feb 2012 13:26:21 +0100 Subject: [PATCH] [rigo] add more interactions on the UI, consolidate current code (not complete yet) --- docs/TODO | 7 +- rigo/data/ui/gtk3/art/rigo.png | Bin 0 -> 4559 bytes rigo/data/ui/gtk3/art/rigo.svg | 77 +++++++++++++++++++++ rigo/data/ui/gtk3/rigo.ui | 14 +++- rigo/rigo/models/application.py | 9 +-- rigo/rigo_app.py | 114 ++++++++++++++++++++++++++++++-- 6 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 rigo/data/ui/gtk3/art/rigo.png create mode 100644 rigo/data/ui/gtk3/art/rigo.svg diff --git a/docs/TODO b/docs/TODO index 6ac988239..2983cfd75 100644 --- a/docs/TODO +++ b/docs/TODO @@ -6,16 +6,13 @@ Backlog (raw) - requires pygobject[cairo] - uniform license header - who clears the vote cache (and document cache) at some point? + - redesign entropy locking: use readers/writer file lock + - update equo, sulfur, magneto, eit code to use it - eit remote (for handling repos in server.conf) - rework client/ code, as done with eit - ditch etpUi - - fine grained locking support - (acquire entropy resources lock only when really needed - [eit add vs eit graph --> needed? pkg_id no more avail]) - kill the Big Entropy Lock :-) - - conditional deps support, implement in entropy.spm, class is there (add unit test) diff --git a/rigo/data/ui/gtk3/art/rigo.png b/rigo/data/ui/gtk3/art/rigo.png new file mode 100644 index 0000000000000000000000000000000000000000..45125008c8171b6df1118247b6b4093a18c3c6da GIT binary patch literal 4559 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 literal 0 HcmV?d00001 diff --git a/rigo/data/ui/gtk3/art/rigo.svg b/rigo/data/ui/gtk3/art/rigo.svg new file mode 100644 index 000000000..612568674 --- /dev/null +++ b/rigo/data/ui/gtk3/art/rigo.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + rigo + + diff --git a/rigo/data/ui/gtk3/rigo.ui b/rigo/data/ui/gtk3/rigo.ui index 1b77eb73b..34ba831cd 100644 --- a/rigo/data/ui/gtk3/rigo.ui +++ b/rigo/data/ui/gtk3/rigo.ui @@ -48,7 +48,6 @@ False - True True never automatic @@ -70,6 +69,19 @@ 1 + + + False + + + + + + True + True + 2 + + True diff --git a/rigo/rigo/models/application.py b/rigo/rigo/models/application.py index 6ca95d202..42e95bd51 100644 --- a/rigo/rigo/models/application.py +++ b/rigo/rigo/models/application.py @@ -333,7 +333,10 @@ class Application(object): def name(self): """Show user visible name""" repo = self._entropy.open_repository(self._repo_id) - return repo.retrieveName(self._pkg_id) + name = repo.retrieveName(self._pkg_id) + if name is None: + return _("N/A") + return name.capitalize() def is_installed(self): inst_repo = self._entropy.installed_repository() @@ -351,9 +354,7 @@ class Application(object): def get_markup(self): repo = self._entropy.open_repository(self._repo_id) - name = repo.retrieveName(self._pkg_id) - if name is None: - name = "N/A" + name = self.name version = repo.retrieveVersion(self._pkg_id) if version is None: version = "N/A" diff --git a/rigo/rigo_app.py b/rigo/rigo_app.py index 2a1fcbf4a..189a0e001 100644 --- a/rigo/rigo_app.py +++ b/rigo/rigo_app.py @@ -9,6 +9,9 @@ sys.path.insert(3, "/usr/lib/entropy/lib") sys.path.insert(4, "/usr/lib/entropy/client") sys.path.insert(5, "/usr/lib/entropy/rigo") +from threading import Lock + + from gi.repository import Gtk, Gdk, Gio, GLib, GObject from rigo.paths import DATA_DIR @@ -176,13 +179,28 @@ class AppListStore(Gtk.ListStore): class PackagesViewController(object): - def __init__(self, entropy_client, icons, entropy_ws, search_entry, view): + def __init__(self, entropy_client, icons, entropy_ws, rigo_sm, + search_entry, view): self._entropy = entropy_client self._icons = icons + self._rigo_sm = rigo_sm self._entropy_ws = entropy_ws self._search_entry = search_entry self._view = view + def _search_icon_release(self, search_entry, icon_pos, _other): + """ + Event associated to the Search bar icon click. + Here we catch secondary icon click to reset the search entry text. + """ + if search_entry is not self._search_entry: + return + if icon_pos != Gtk.EntryIconPosition.SECONDARY: + return + search_entry.set_text("") + self.clear() + search_entry.emit("changed") + def _search_changed(self, search_entry): GLib.timeout_add(700, self._search, search_entry.get_text()) @@ -208,34 +226,63 @@ class PackagesViewController(object): def setup(self): self._store = AppListStore(self._entropy, self._view, self._icons) - #Gtk.ListStore(GObject.TYPE_PYOBJECT) self._view.set_model(self._store) # setup searchEntry event self._search_entry.connect( "changed", self._search_changed) + # connect icon click event + self._search_entry.connect("icon-release", + self._search_icon_release) self._view.show() def clear(self): + self._rigo_sm.change_view_state( + Rigo.STATIC_VIEW_STATE) self._store.clear() ApplicationMetadata.discard() def append(self, opaque): self._store.append([opaque]) + self._rigo_sm.change_view_state( + Rigo.BROWSER_VIEW_STATE) def append_many(self, opaque_list): for opaque in opaque_list: self._store.append([opaque]) + self._rigo_sm.change_view_state( + Rigo.BROWSER_VIEW_STATE) def clear_safe(self): ApplicationMetadata.discard() + self._rigo_sm.change_view_state_safe( + Rigo.STATIC_VIEW_STATE) GLib.idle_add(self._store.clear) def append_safe(self, opaque): GLib.idle_add(self.append, opaque) + self._rigo_sm.change_view_state_safe( + Rigo.BROWSER_VIEW_STATE) def append_many_safe(self, opaque_list): GLib.idle_add(self.append_many, opaque_list) + self._rigo_sm.change_view_state_safe( + Rigo.BROWSER_VIEW_STATE) + +class WelcomeBox(Gtk.VBox): + + def __init__(self): + Gtk.VBox.__init__(self) + self._image_path = os.path.join(DATA_DIR, "ui/gtk3/art/rigo.png") + + def render(self): + image = Gtk.Image.new_from_file(self._image_path) + label = Gtk.Label() + label.set_markup(_("Browse Applications with ease")) + self.pack_start(image, False, False, False) + self.pack_start(label, False, False, 0) + label.show() + image.show() class NotificationBox(Gtk.VBox): @@ -247,7 +294,7 @@ class NotificationBox(Gtk.VBox): def render(self): label = Gtk.Label() label.set_markup(self._message) - self.pack_start(label, False, False, False) + self.pack_start(label, False, False, 0) label.show() @@ -291,7 +338,6 @@ class Rigo(Gtk.Application): def onDeleteWindow(self, *args): Gtk.main_quit(*args) - def __init__(self): self._builder = Gtk.Builder() self._builder.add_from_file(os.path.join(DATA_DIR, "ui/gtk3/rigo.ui")) @@ -300,10 +346,13 @@ class Rigo(Gtk.Application): self._app_vbox = self._builder.get_object("appVbox") self._search_entry = self._builder.get_object("searchEntry") self._scrolled_view = self._builder.get_object("scrolledView") + self._static_view = self._builder.get_object("staticViewVbox") icons = get_sc_icon_theme(DATA_DIR) self._view = AppTreeView(self._app_vbox, icons, True, store=None) self._scrolled_view.add(self._view) + self._notification = self._builder.get_object("notificationBox") + self._welcome_box = WelcomeBox() settings = Gtk.Settings.get_default() settings.set_property("gtk-error-bell", False) @@ -320,17 +369,70 @@ class Rigo(Gtk.Application): self._entropy_ws = EntropyWebService(self._entropy) - # Setup Packages View Controller class + self._state_mutex = Lock() + self._current_state = Rigo.STATIC_VIEW_STATE self._pvc = PackagesViewController( self._entropy, icons, self._entropy_ws, - self._search_entry, self._view) + self, self._search_entry, self._view) self._nc = NotificationController( self._entropy, self._notification) + BROWSER_VIEW_STATE = 1 + STATIC_VIEW_STATE = 2 + + def change_view_state(self, state, child_widget=None): + """ + Change Rigo Application UI state. + You can pass a custom widget that will be shown in case + of static view state. + """ + with self._state_mutex: + if state == Rigo.BROWSER_VIEW_STATE: + self._static_view.set_visible(False) + # release all the childrens of static_view + for child in self._static_view.get_children(): + self._static_view.remove(child) + self._scrolled_view.set_visible(True) + self._scrolled_view.show() + + elif state == Rigo.STATIC_VIEW_STATE: + self._scrolled_view.set_visible(False) + if child_widget is not None: + for child in self._static_view.get_children(): + self._static_view.remove(child) + self._static_view.pack_start(child_widget, + False, False, 0) + child_widget.show() + else: + # keep the current widget if any, or add the + # welcome widget + if not self._static_view.get_children(): + self._welcome_box.show() + self._static_view.pack_start(self._welcome_box, + False, False, 0) + + self._static_view.set_visible(True) + self._static_view.show() + + else: + raise AttributeError("wrong view state") + + self._current_state = state + + def change_view_state_safe(self, state, child_widget=None): + """ + Thread-safe version of change_view_state(). + """ + def _do_change(): + return self.change_view_state(state, child_widget=child_widget) + GLib.idle_add(_do_change) + def _on_style_updated(self, widget, init_css_callback, *args): init_css_callback(widget, *args) def run(self): + self._welcome_box.render() + self.change_view_state(self._current_state) self._pvc.setup() self._nc.setup() self._window.show()