[rigo] rework some code paths to be more signal-centric, more work on the app details pane

This commit is contained in:
Fabio Erculiani
2012-02-19 23:07:34 +01:00
parent 74149c79c1
commit ac8de69970
10 changed files with 176 additions and 224 deletions

View File

@@ -73,3 +73,8 @@ GtkViewport {
border-width: 0;
padding: 0;
}
#rigoWindow {
background-color: white;
/* color: black; */
}

View File

@@ -10,6 +10,8 @@
<object class="GtkWindow" id="rigoWindow">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Rigo Browser</property>
<property name="window_position">center</property>
<property name="icon_name">system-software-update</property>
<signal name="delete-event" handler="onDeleteWindow" swapped="no"/>
<child>
<object class="GtkVBox" id="appVbox">
@@ -123,148 +125,11 @@
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="appViewInnerStatsVbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkEventBox" id="starsEvent">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox36">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkEventBox" id="starEvent1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="vote1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">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</property>
<property name="stock">gtk-missing-image</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="starEvent2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="vote2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">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</property>
<property name="stock">gtk-missing-image</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="starEvent3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="vote3">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">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</property>
<property name="stock">gtk-missing-image</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="starEvent4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="vote4">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">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</property>
<property name="stock">gtk-missing-image</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="starEvent5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="vote5">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events"></property>
<property name="stock">gtk-missing-image</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="appViewDownloadedLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">8</property>
<property name="position">0</property>
</packing>
</child>
@@ -275,11 +140,54 @@
<property name="xalign">0.019999999552965164</property>
<property name="yalign">0.019999999552965164</property>
<property name="label" translatable="yes">Application markup</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="appViewInnerStatsVbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="appViewStarsSelVbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="appViewDownloadedLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">0.98000001907348633</property>
<property name="label" translatable="yes">Downloaded times</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
<property name="padding">5</property>
<property name="position">2</property>
</packing>
</child>
</object>

View File

@@ -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<small><i>%s</i></small>" % (
text = "<b>%s</b> %s\n<small><i>%s</i></small>" % (
GObject.markup_escape_text(name),
GObject.markup_escape_text(version),
GObject.markup_escape_text(description))

View File

@@ -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):

View File

@@ -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))

View File

@@ -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)

View File

@@ -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))

View File

@@ -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("<small><b>%d</b> download</small>",
"<small><b>%d</b> downloads</small>",
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("<b>%d</b> download",
"<b>%d</b> 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)