diff --git a/rigo/data/ui/gtk3/css/softwarecenter.css b/rigo/data/ui/gtk3/css/rigo.css similarity index 95% rename from rigo/data/ui/gtk3/css/softwarecenter.css rename to rigo/data/ui/gtk3/css/rigo.css index a510ef69c..215f43696 100644 --- a/rigo/data/ui/gtk3/css/softwarecenter.css +++ b/rigo/data/ui/gtk3/css/rigo.css @@ -73,3 +73,8 @@ GtkViewport { border-width: 0; padding: 0; } + +#rigoWindow { + background-color: white; + /* color: black; */ +} diff --git a/rigo/data/ui/gtk3/css/softwarecenter.highcontrast.css b/rigo/data/ui/gtk3/css/rigo.highcontrast.css similarity index 100% rename from rigo/data/ui/gtk3/css/softwarecenter.highcontrast.css rename to rigo/data/ui/gtk3/css/rigo.highcontrast.css diff --git a/rigo/data/ui/gtk3/css/softwarecenter.highcontrastinverse.css b/rigo/data/ui/gtk3/css/rigo.highcontrastinverse.css similarity index 100% rename from rigo/data/ui/gtk3/css/softwarecenter.highcontrastinverse.css rename to rigo/data/ui/gtk3/css/rigo.highcontrastinverse.css diff --git a/rigo/data/ui/gtk3/rigo.ui b/rigo/data/ui/gtk3/rigo.ui index 7f246ddb4..8c6bd144b 100644 --- a/rigo/data/ui/gtk3/rigo.ui +++ b/rigo/data/ui/gtk3/rigo.ui @@ -10,6 +10,8 @@ False Rigo Browser + center + system-software-update @@ -123,148 +125,11 @@ 0 - - - True - False - - - True - False - - - True - False - - - True - False - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK - gtk-missing-image - - - - - True - True - 0 - - - - - True - False - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK - gtk-missing-image - - - - - True - True - 1 - - - - - True - False - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK - gtk-missing-image - - - - - True - True - 2 - - - - - True - False - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK - gtk-missing-image - - - - - True - True - 3 - - - - - True - False - - - True - True - - gtk-missing-image - - - - - True - True - 4 - - - - - - - False - False - 0 - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - True - - - False - False - 1 - - - - - True - True - 5 - 1 - - False False + 8 0 @@ -275,11 +140,54 @@ 0.019999999552965164 0.019999999552965164 Application markup + True + + + True + True + 1 + + + + + True + False + + + True + False + + + + + + False + False + 0 + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0.98000001907348633 + Downloaded times + True + True + + + False + False + 1 + + False False - 1 + 5 + 2 diff --git a/rigo/rigo/models/application.py b/rigo/rigo/models/application.py index 1fdf4487a..75423f70b 100644 --- a/rigo/rigo/models/application.py +++ b/rigo/rigo/models/application.py @@ -134,6 +134,9 @@ class ApplicationMetadata(object): """ Thread executing generic (both rating and doc) metadata retrieval. """ + cache_miss = WebService.CacheMiss + ws_exception = WebService.WebServiceException + while True: sem.acquire() discard_signal.set(False) @@ -191,7 +194,7 @@ class ApplicationMetadata(object): try: outcome[(key, repo_id)] = request_map[request]( [key], cache=True, cached=True)[key] - except WebService.CacheMiss: + except cache_miss: uncached_keys.append(key) for key in uncached_keys: @@ -201,7 +204,7 @@ class ApplicationMetadata(object): # FIXME, lxnay: work more instances in parallel? outcome[(key, repo_id)] = request_map[request]( [key], cache = True)[key] - except WebService.WebServiceException as wse: + except ws_exception as wse: const_debug_write( __name__, "%s, WebServiceExc: %s" % (name, wse,) @@ -541,7 +544,7 @@ class Application(object): description = _("No description") if len(description) > 79: description = description[:80].strip() + "..." - text = "%s %s\n%s" % ( + text = "%s %s\n%s" % ( GObject.markup_escape_text(name), GObject.markup_escape_text(version), GObject.markup_escape_text(description)) diff --git a/rigo/rigo/ui/gtk3/models/appliststore.py b/rigo/rigo/ui/gtk3/models/appliststore.py index e914ad8b3..a38c6b673 100644 --- a/rigo/rigo/ui/gtk3/models/appliststore.py +++ b/rigo/rigo/ui/gtk3/models/appliststore.py @@ -23,6 +23,15 @@ class AppListStore(Gtk.ListStore): _MISSING_ICON_MUTEX = Lock() _ICON_CACHE = {} + __gsignals__ = { + # Redraw signal, requesting UI update + # for given pkg_match object + "redraw-request" : (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, ), + ), + } + def __init__(self, entropy_client, entropy_ws, view, icons): Gtk.ListStore.__init__(self) self._view = view @@ -42,12 +51,6 @@ class AppListStore(Gtk.ListStore): AppListStore._ICON_CACHE.clear() return outcome - def _ui_redraw_callback(self, *args): - if const_debug_enabled(): - const_debug_write(__name__, - "_ui_redraw_callback()") - GLib.idle_add(self._view.queue_draw) - @property def _missing_icon(self): """ @@ -63,14 +66,18 @@ class AppListStore(Gtk.ListStore): AppListStore._MISSING_ICON = _missing_icon return _missing_icon - def get_icon(self, pkg_match, app=None): + def get_icon(self, pkg_match): cached = AppListStore._ICON_CACHE.get(pkg_match) if cached is not None: return cached - if app is None: - app = Application(self._entropy, self._entropy_ws, pkg_match, - redraw_callback=self._ui_redraw_callback) + def _ui_redraw_callback(*args): + if const_debug_enabled(): + const_debug_write(__name__, + "_ui_redraw_callback()") + self.emit("redraw-request", pkg_match) + app = Application(self._entropy, self._entropy_ws, pkg_match, + redraw_callback=_ui_redraw_callback) icon, cache_hit = app.get_details().icon if icon is None: if cache_hit: @@ -113,28 +120,58 @@ class AppListStore(Gtk.ListStore): return pixbuf def is_installed(self, pkg_match): + def _ui_redraw_callback(*args): + if const_debug_enabled(): + const_debug_write(__name__, + "_ui_redraw_callback()") + self.emit("redraw-request", pkg_match) + app = Application(self._entropy, self._entropy_ws, pkg_match, - redraw_callback=self._ui_redraw_callback) + redraw_callback=_ui_redraw_callback) return app.is_installed() def is_available(self, pkg_match): + def _ui_redraw_callback(*args): + if const_debug_enabled(): + const_debug_write(__name__, + "_ui_redraw_callback()") + self.emit("redraw-request", pkg_match) + app = Application(self._entropy, self._entropy_ws, pkg_match, - redraw_callback=self._ui_redraw_callback) + redraw_callback=_ui_redraw_callback) return app.is_available() def get_markup(self, pkg_match): + def _ui_redraw_callback(*args): + if const_debug_enabled(): + const_debug_write(__name__, + "_ui_redraw_callback()") + self.emit("redraw-request", pkg_match) + app = Application(self._entropy, self._entropy_ws, pkg_match, - redraw_callback=self._ui_redraw_callback) + redraw_callback=_ui_redraw_callback) return app.get_markup() def get_review_stats(self, pkg_match): + def _ui_redraw_callback(*args): + if const_debug_enabled(): + const_debug_write(__name__, + "_ui_redraw_callback()") + self.emit("redraw-request", pkg_match) + app = Application(self._entropy, self._entropy_ws, pkg_match, - redraw_callback=self._ui_redraw_callback) + redraw_callback=_ui_redraw_callback) return app.get_review_stats() def get_application(self, pkg_match): + def _ui_redraw_callback(*args): + if const_debug_enabled(): + const_debug_write(__name__, + "_ui_redraw_callback()") + self.emit("redraw-request", pkg_match) + app = Application(self._entropy, self._entropy_ws, pkg_match, - redraw_callback=self._ui_redraw_callback) + redraw_callback=_ui_redraw_callback) return app def get_transaction_progress(self, pkg_match): diff --git a/rigo/rigo/ui/gtk3/utils.py b/rigo/rigo/ui/gtk3/utils.py index 244fdf276..ef5ecf56a 100644 --- a/rigo/rigo/ui/gtk3/utils.py +++ b/rigo/rigo/ui/gtk3/utils.py @@ -48,12 +48,12 @@ def init_sc_css_provider(toplevel, settings, screen, datadir): # munge css path for theme-name css_path = os.path.join(datadir, - "ui/gtk3/css/softwarecenter.%s.css" % \ + "ui/gtk3/css/rigo.%s.css" % \ theme_name) # if no css for theme-name try fallback css if not os.path.exists(css_path): - css_path = os.path.join(datadir, "ui/gtk3/css/softwarecenter.css") + css_path = os.path.join(datadir, "ui/gtk3/css/rigo.css") if not os.path.exists(css_path): # check fallback exists as well... if not return None but warn @@ -64,7 +64,7 @@ def init_sc_css_provider(toplevel, settings, screen, datadir): LOG.warn(msg % css_path) return None - # things seem ok, now set the css provider for softwarecenter + # things seem ok, now set the css provider for Rigo msg = "Rigo style provider for %s Gtk theme: %s" LOG.info(msg % (theme_name, css_path)) diff --git a/rigo/rigo/ui/gtk3/widgets/apptreeview.py b/rigo/rigo/ui/gtk3/widgets/apptreeview.py index 81229391b..565ab039d 100644 --- a/rigo/rigo/ui/gtk3/widgets/apptreeview.py +++ b/rigo/rigo/ui/gtk3/widgets/apptreeview.py @@ -8,7 +8,6 @@ from cellrenderers import CellRendererAppView, CellButtonRenderer, \ CellButtonIDs from rigo.em import em, StockEms -from rigo.utils import ExecutionTime from rigo.enums import Icons from rigo.models.application import CategoryRowReference @@ -544,24 +543,18 @@ def on_entry_changed(widget, data): new_text = widget.get_text() (view, enquirer) = data - with ExecutionTime("total time"): - with ExecutionTime("enquire.set_query()"): - # FIXME lxnay - enquirer.set_query(get_query_from_search_entry(new_text), - limit=100*1000, - nonapps_visible=NonAppVisibility.ALWAYS_VISIBLE) + # FIXME lxnay + enquirer.set_query(get_query_from_search_entry(new_text), + limit=100*1000, + nonapps_visible=NonAppVisibility.ALWAYS_VISIBLE) - store = view.tree_view.get_model() - with ExecutionTime("store.clear()"): - store.clear() + store = view.tree_view.get_model() + store.clear() - with ExecutionTime("store.set_documents()"): - store.set_from_matches(enquirer.matches) + store.set_from_matches(enquirer.matches) - with ExecutionTime("model settle (size=%s)" % len(store)): - while Gtk.events_pending(): - Gtk.main_iteration() - return + while Gtk.events_pending(): + Gtk.main_iteration() if widget.stamp: GObject.source_remove(widget.stamp) diff --git a/rigo/rigo/utils.py b/rigo/rigo/utils.py deleted file mode 100644 index 3af9b9fc1..000000000 --- a/rigo/rigo/utils.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (C) 2009 Canonical -# -# Authors: -# Michael Vogt -# -# 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; version 3. -# -# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA - -import logging -import time - -class ExecutionTime(object): - """ - Helper that can be used in with statements to have a simple - measure of the timming of a particular block of code, e.g. - with ExecutinTime("db flush"): - db.flush() - """ - def __init__(self, info=""): - self.info = info - - def __enter__(self): - self.now = time.time() - - def __exit__(self, type, value, stack): - logger = logging.getLogger("softwarecenter.performance") - logger.debug("%s: %s" % (self.info, time.time() - self.now)) diff --git a/rigo/rigo_app.py b/rigo/rigo_app.py index 03e31cbd7..2b41c1878 100644 --- a/rigo/rigo_app.py +++ b/rigo/rigo_app.py @@ -2,6 +2,7 @@ import os import sys import copy import tempfile +import time from threading import Lock sys.path.insert(0, "../lib") @@ -12,7 +13,7 @@ sys.path.insert(4, "/usr/lib/entropy/client") sys.path.insert(5, "/usr/lib/entropy/rigo") -from gi.repository import Gtk, Gdk, Gio, GLib, GObject, GdkPixbuf +from gi.repository import Gtk, Gdk, Gio, GLib, GObject from rigo.paths import DATA_DIR from rigo.enums import Icons @@ -22,6 +23,7 @@ from rigo.ui.gtk3.widgets.apptreeview import AppTreeView from rigo.ui.gtk3.widgets.notifications import NotificationBox, \ RepositoriesUpdateNotificationBox, UpdatesNotificationBox from rigo.ui.gtk3.widgets.welcome import WelcomeBox +from rigo.ui.gtk3.widgets.stars import ReactiveStar from rigo.ui.gtk3.models.appliststore import AppListStore from rigo.ui.gtk3.utils import init_sc_css_provider, get_sc_icon_theme @@ -312,11 +314,22 @@ class ApplicationViewController(GObject.Object): self._entropy = entropy_client self._entropy_ws = entropy_ws self._app_store = None + self._last_pkg_match = None self._image = self._builder.get_object("appViewImage") self._app_name_lbl = self._builder.get_object("appViewNameLabel") self._app_download_lbl = self._builder.get_object( "appViewDownloadedLabel") + self._stars_container = self._builder.get_object("appViewStarsSelVbox") + + self._stars = ReactiveStar() + self._stars_alignment = Gtk.Alignment.new(0.0, 0.5, 1.0, 1.0) + self._stars_alignment.set_padding(0, 5, 0, 0) + self._stars_alignment.add(self._stars) + self._stars.set_size_as_pixel_value(24) + + self._stars_container.pack_start(self._stars_alignment, False, False, 0) + def set_store(self, store): """ @@ -326,6 +339,7 @@ class ApplicationViewController(GObject.Object): def setup(self): self.connect("application-activated", self._on_application_activated) + self._app_store.connect("redraw-request", self._on_redraw_request) def _on_application_activated(self, avc, app): """ @@ -333,11 +347,22 @@ class ApplicationViewController(GObject.Object): information. Once we're done loading the shit, we just emit 'application-show' and let others do the UI switch. """ + self._last_pkg_match = app.get_details().pkg task = ParallelTask(self.__application_activate, app) task.name = "ApplicationActivate" task.daemon = True task.start() + def _on_redraw_request(self, widget, pkg_match): + """ + Redraw request received from AppListStore for given package match. + We are required to update rating, number of downloads, icon. + """ + if pkg_match == self._last_pkg_match: + stats = self._app_store.get_review_stats(pkg_match) + icon = self._app_store.get_icon(pkg_match) + self._setup_application_stats(stats, icon) + def __application_activate(self, app): """ Collect data from app, then call the UI setup in the main loop. @@ -346,9 +371,28 @@ class ApplicationViewController(GObject.Object): metadata = {} metadata['name'] = app.get_markup() metadata['stats'] = app.get_review_stats() - metadata['icon'] = self._app_store.get_icon(details.pkg, app=app) + # using app store here because we cache the icon pixbuf + metadata['icon'] = self._app_store.get_icon(details.pkg) GLib.idle_add(self._setup_application_info, app, metadata) + def _setup_application_stats(self, stats, icon): + """ + Setup widgets related to Application statistics (and icon). + """ + total_downloads = stats.downloads_total + if not total_downloads: + down_msg = _("Never downloaded") + else: + down_msg = ngettext("%d download", + "%d downloads", + total_downloads) + down_msg = down_msg % (total_downloads,) + self._app_download_lbl.set_markup(down_msg) + if icon: + self._image.set_from_pixbuf(icon) + self._stars.set_rating(stats.ratings_average - 1) + self._stars_alignment.show_all() + def _setup_application_info(self, app, metadata): """ Setup the actual UI widgets content and emit 'application-show' @@ -356,18 +400,12 @@ class ApplicationViewController(GObject.Object): # FIXME, lxnay complete self._app_name_lbl.set_markup(metadata['name']) stats = metadata['stats'] - total_downloads = stats.downloads_total - if not total_downloads: - down_msg = _("Never downloaded") - else: - down_msg = ngettext("%d download", - "%d downloads", - total_downloads) - self._app_download_lbl.set_markup(down_msg % (total_downloads,)) - self._image.set_from_pixbuf(metadata['icon']) + icon = metadata['icon'] + self._setup_application_stats(stats, icon) self.emit("application-show", app) + class Rigo(Gtk.Application): class RigoHandler: @@ -395,6 +433,7 @@ class Rigo(Gtk.Application): self._builder.add_from_file(os.path.join(DATA_DIR, "ui/gtk3/rigo.ui")) self._builder.connect_signals(Rigo.RigoHandler()) self._window = self._builder.get_object("rigoWindow") + self._window.set_name("rigoWindow") self._apps_view = self._builder.get_object("appsViewVbox") self._scrolled_view = self._builder.get_object("appsViewScrolledWindow") self._app_view = self._builder.get_object("appViewVbox") @@ -412,6 +451,10 @@ class Rigo(Gtk.Application): self._app_store = AppListStore( self._entropy, self._entropy_ws, self._view, icons) + def _queue_draw(*args): + self._view.queue_draw() + self._app_store.connect("redraw-request", _queue_draw) + self._app_view_c.set_store(self._app_store) self._app_view_c.connect("application-show", self._on_application_show)