Merge lp:~mvo/software-center/treeview-keep-state-on-db-cache-change into lp:software-center

Proposed by Michael Vogt
Status: Merged
Merged at revision: 2933
Proposed branch: lp:~mvo/software-center/treeview-keep-state-on-db-cache-change
Merge into: lp:software-center
Diff against target: 283 lines (+84/-28)
5 files modified
softwarecenter/ui/gtk3/models/appstore2.py (+10/-1)
softwarecenter/ui/gtk3/panes/installedpane.py (+54/-6)
softwarecenter/ui/gtk3/panes/softwarepane.py (+0/-9)
softwarecenter/ui/gtk3/views/appview.py (+12/-2)
softwarecenter/ui/gtk3/widgets/apptreeview.py (+8/-10)
To merge this branch: bzr merge lp:~mvo/software-center/treeview-keep-state-on-db-cache-change
Reviewer Review Type Date Requested Status
Gary Lasker (community) Approve
Review via email: mp+99946@code.launchpad.net

Description of the change

This branch depends on the other branches:
lp:~mvo/software-center/lp964433
lp:~mvo/software-center/lp966879
lp:~mvo/software-center/lp846204

and adds the ability to save/restore the state of the installedpane treeview when the db or the cache changes.
This means that if you e.g. uninstal a application when the view refreshes the expanded nodes and scrollposition
is saved.

To test, go to the installed pane, expand some rows and run:
$ dbus-send --print-reply --dest=com.ubuntu.Softwarecenter /com/ubuntu/Softwarecenter com.ubuntu.SoftwarecenterIFace.triggerCacheReload

or simply remove a application there.

To post a comment you must log in.
Revision history for this message
Gary Lasker (gary-lasker) wrote :

This is really nice! And definitely a welcome improvement over the complete collapse of the tree after a refresh of the treeview.

At some point we should look at removing the multiple refreshes (and the showing the spinner) that we get on an install/remove.

Thanks, Michael!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'softwarecenter/ui/gtk3/models/appstore2.py'
--- softwarecenter/ui/gtk3/models/appstore2.py 2012-03-16 10:44:05 +0000
+++ softwarecenter/ui/gtk3/models/appstore2.py 2012-03-29 14:01:25 +0000
@@ -61,6 +61,9 @@
61 self.pkg_count = pkg_count61 self.pkg_count = pkg_count
62 self.vis_count = pkg_count62 self.vis_count = pkg_count
6363
64 def __repr__(self):
65 return "[CategoryRowReference: name=%s]" % self.untranslated_name
66
6467
65class UncategorisedRowRef(CategoryRowReference):68class UncategorisedRowRef(CategoryRowReference):
6669
@@ -75,6 +78,9 @@
75 display_name,78 display_name,
76 None, pkg_count)79 None, pkg_count)
7780
81 def __repr__(self):
82 return "[UncategorizedRowReference: name=%s]" % self.untranslated_name
83
7884
79class AppPropertiesHelper(GObject.GObject):85class AppPropertiesHelper(GObject.GObject):
80 """ Baseclass that contains common functions for our86 """ Baseclass that contains common functions for our
@@ -409,6 +415,7 @@
409 """ set the content of the liststore based on a list of415 """ set the content of the liststore based on a list of
410 xapian.MSetItems416 xapian.MSetItems
411 """417 """
418 LOG.debug("set_from_matches len(matches)='%s'" % len(matches))
412 self.current_matches = matches419 self.current_matches = matches
413 n_matches = len(matches)420 n_matches = len(matches)
414 if n_matches == 0:421 if n_matches == 0:
@@ -432,6 +439,7 @@
432 self.buffer_icons()439 self.buffer_icons()
433440
434 def load_range(self, indices, step):441 def load_range(self, indices, step):
442 LOG.debug("load_range: %s %s" % (indices, step))
435 db = self.db.xapiandb443 db = self.db.xapiandb
436 matches = self.current_matches444 matches = self.current_matches
437445
@@ -446,7 +454,8 @@
446 for i in range(start, end):454 for i in range(start, end):
447 try:455 try:
448 row_content = self[(i,)][0]456 row_content = self[(i,)][0]
449 except IndexError:457 except IndexError as e:
458 LOG.warn("failed to load rows: '%s'" % e)
450 break459 break
451460
452 if row_content:461 if row_content:
453462
=== modified file 'softwarecenter/ui/gtk3/panes/installedpane.py'
--- softwarecenter/ui/gtk3/panes/installedpane.py 2012-03-27 22:55:49 +0000
+++ softwarecenter/ui/gtk3/panes/installedpane.py 2012-03-29 14:01:25 +0000
@@ -325,8 +325,25 @@
325 self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE325 self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE
326 self.refresh_apps()326 self.refresh_apps()
327327
328 def _save_treeview_state(self):
329 # store the state
330 expanded_rows = []
331 self.app_view.tree_view.map_expanded_rows(
332 lambda view, path, data: expanded_rows.append(path.to_string()),
333 None)
334 vadj = self.app_view.tree_view_scroll.get_vadjustment().get_value()
335 return expanded_rows, vadj
336
337 def _restore_treeview_state(self, state):
338 expanded_rows, vadj = state
339 for ind in expanded_rows:
340 path = Gtk.TreePath.new_from_string(ind)
341 self.app_view.tree_view.expand_row(path, False)
342 self.app_view.tree_view_scroll.get_vadjustment().set_lower(vadj)
343 self.app_view.tree_view_scroll.get_vadjustment().set_value(vadj)
344
328 #~ @interrupt_build_and_wait345 #~ @interrupt_build_and_wait
329 def _build_categorised_installedview(self):346 def _build_categorised_installedview(self, keep_state=False):
330 LOG.debug('Rebuilding categorised installedview...')347 LOG.debug('Rebuilding categorised installedview...')
331348
332 # display the busy cursor and a local spinner while we build the view349 # display the busy cursor and a local spinner while we build the view
@@ -335,6 +352,13 @@
335 window.set_cursor(self.busy_cursor)352 window.set_cursor(self.busy_cursor)
336 self.show_installed_view_spinner()353 self.show_installed_view_spinner()
337354
355 if keep_state:
356 treeview_state = self._save_treeview_state()
357
358 # disconnect the model to avoid e.g. updates of "cursor-changed"
359 # AppTreeView.expand_path while the model is in rebuild-flux
360 self.app_view.set_model(None)
361
338 model = self.base_model # base model not treefilter362 model = self.base_model # base model not treefilter
339 model.clear()363 model.clear()
340364
@@ -410,6 +434,10 @@
410 self.app_view._append_appcount(self.installed_count,434 self.app_view._append_appcount(self.installed_count,
411 mode=AppView.INSTALLED_MODE)435 mode=AppView.INSTALLED_MODE)
412436
437 self.app_view.set_model(self.treefilter)
438 if keep_state:
439 self._restore_treeview_state(treeview_state)
440
413 # hide the local spinner441 # hide the local spinner
414 self.hide_installed_view_spinner()442 self.hide_installed_view_spinner()
415443
@@ -425,7 +453,7 @@
425453
426 GObject.idle_add(profiled_rebuild_categorised_view)454 GObject.idle_add(profiled_rebuild_categorised_view)
427455
428 def _build_oneconfview(self):456 def _build_oneconfview(self, keep_state=False):
429 LOG.debug('Rebuilding oneconfview for %s...' % self.current_hostid)457 LOG.debug('Rebuilding oneconfview for %s...' % self.current_hostid)
430458
431 # display the busy cursor and the local spinner while we build the view459 # display the busy cursor and the local spinner while we build the view
@@ -434,6 +462,13 @@
434 window.set_cursor(self.busy_cursor)462 window.set_cursor(self.busy_cursor)
435 self.show_installed_view_spinner()463 self.show_installed_view_spinner()
436464
465 if keep_state:
466 treeview_state = self._save_treeview_state()
467
468 # disconnect the model to avoid e.g. updates of "cursor-changed"
469 # AppTreeView.expand_path while the model is in rebuild-flux
470 self.app_view.set_model(None)
471
437 model = self.base_model # base model not treefilter472 model = self.base_model # base model not treefilter
438 model.clear()473 model.clear()
439474
@@ -518,6 +553,10 @@
518 self.app_view._append_appcount(self.installed_count,553 self.app_view._append_appcount(self.installed_count,
519 mode=AppView.DIFF_MODE)554 mode=AppView.DIFF_MODE)
520555
556 self.app_view.set_model(self.treefilter)
557 if keep_state:
558 self._restore_treeview_state(treeview_state)
559
521 # hide the local spinner560 # hide the local spinner
522 self.hide_installed_view_spinner()561 self.hide_installed_view_spinner()
523562
@@ -599,10 +638,11 @@
599 def refresh_apps(self, *args, **kwargs):638 def refresh_apps(self, *args, **kwargs):
600 """refresh the applist and update the navigation bar """639 """refresh the applist and update the navigation bar """
601 logging.debug("installedpane refresh_apps")640 logging.debug("installedpane refresh_apps")
641 keep_state = kwargs.get("keep_state", False)
602 if self.current_hostid:642 if self.current_hostid:
603 self._build_oneconfview()643 self._build_oneconfview(keep_state)
604 else:644 else:
605 self._build_categorised_installedview()645 self._build_categorised_installedview(keep_state)
606646
607 def _clear_search(self):647 def _clear_search(self):
608 # remove the details and clear the search648 # remove the details and clear the search
@@ -629,9 +669,17 @@
629 self.app_view._append_appcount(appcount, mode=AppView.DIFF_MODE)669 self.app_view._append_appcount(appcount, mode=AppView.DIFF_MODE)
630 return vis_cats670 return vis_cats
631671
672 def _refresh_on_cache_or_db_change(self):
673 self.refresh_apps(keep_state=True)
674 self.app_details_view.refresh_app()
675
632 def on_db_reopen(self, db):676 def on_db_reopen(self, db):
633 self.refresh_apps(rebuild=True)677 LOG.debug("on_db_reopen")
634 self.app_details_view.refresh_app()678 self._refresh_on_cache_or_db_change()
679
680 def on_cache_ready(self, cache):
681 LOG.debug("on_cache_ready")
682 self._refresh_on_cache_or_db_change()
635683
636 def on_application_selected(self, appview, app):684 def on_application_selected(self, appview, app):
637 """callback when an app is selected"""685 """callback when an app is selected"""
638686
=== modified file 'softwarecenter/ui/gtk3/panes/softwarepane.py'
--- softwarecenter/ui/gtk3/panes/softwarepane.py 2012-03-20 08:23:24 +0000
+++ softwarecenter/ui/gtk3/panes/softwarepane.py 2012-03-29 14:01:25 +0000
@@ -254,15 +254,6 @@
254 def on_cache_ready(self, cache):254 def on_cache_ready(self, cache):
255 " refresh the application list when the cache is re-opened "255 " refresh the application list when the cache is re-opened "
256 LOG.debug("on_cache_ready")256 LOG.debug("on_cache_ready")
257 # it only makes sense to refresh if there is something to
258 # refresh, otherwise we create a bunch of (not yet needed)
259 # AppStore objects on startup when the cache sends its
260 # initial "cache-ready" signal
261 model = self.app_view.tree_view.get_model()
262 if model is None:
263 return
264 # FIXME: preserve selection too
265 self.refresh_apps()
266257
267 @wait_for_apt_cache_ready258 @wait_for_apt_cache_ready
268 def on_application_activated(self, appview, app):259 def on_application_activated(self, appview, app):
269260
=== modified file 'softwarecenter/ui/gtk3/views/appview.py'
--- softwarecenter/ui/gtk3/views/appview.py 2012-03-14 16:34:56 +0000
+++ softwarecenter/ui/gtk3/views/appview.py 2012-03-29 14:01:25 +0000
@@ -176,13 +176,16 @@
176 def set_model(self, model):176 def set_model(self, model):
177 self.tree_view.set_model(model)177 self.tree_view.set_model(model)
178178
179 def get_model(self):
180 return self.tree_view.appmodel
181
179 def display_matches(self, matches, is_search=False):182 def display_matches(self, matches, is_search=False):
180 # FIXME: installedpane handles display of the trees intimately,183 # FIXME: installedpane handles display of the trees intimately,
181 # so for the time being lets just return None in the case of our184 # so for the time being lets just return None in the case of our
182 # TreeView displaying an AppTreeStore ... ;(185 # TreeView displaying an AppTreeStore ... ;(
183 # ... also we dont currently support user sorting in the186 # ... also we dont currently support user sorting in the
184 # installedview, so issue is somewhat moot for the time being...187 # installedview, so issue is somewhat moot for the time being...
185 if isinstance(self.tree_view.appmodel, AppTreeStore):188 if isinstance(self.get_model(), AppTreeStore):
186 LOG.debug("display_matches called on AppTreeStore, ignoring")189 LOG.debug("display_matches called on AppTreeStore, ignoring")
187 return190 return
188191
@@ -197,9 +200,16 @@
197 not self.user_defined_sort_method):200 not self.user_defined_sort_method):
198 self.set_sort_method_with_no_signal(self._SORT_BY_TOP_RATED)201 self.set_sort_method_with_no_signal(self._SORT_BY_TOP_RATED)
199202
200 model = self.tree_view.appmodel203 model = self.get_model()
204 # disconnect the model from the view before running
205 # set_from_matches to ensure that the _cell_data_func_cb is not
206 # run when the placeholder items are set, otherwise the purpose
207 # of the "load-on-demand" is gone and it leads to bugs like
208 # LP: #964433
209 self.set_model(None)
201 if model:210 if model:
202 model.set_from_matches(matches)211 model.set_from_matches(matches)
212 self.set_model(model)
203 self.user_defined_sort_method = False213 self.user_defined_sort_method = False
204214
205 self.tree_view_scroll.get_vadjustment().set_lower(self.vadj)215 self.tree_view_scroll.get_vadjustment().set_lower(self.vadj)
206216
=== modified file 'softwarecenter/ui/gtk3/widgets/apptreeview.py'
--- softwarecenter/ui/gtk3/widgets/apptreeview.py 2012-03-22 23:32:06 +0000
+++ softwarecenter/ui/gtk3/widgets/apptreeview.py 2012-03-29 14:01:25 +0000
@@ -20,6 +20,9 @@
20 AppGenericStore, CategoryRowReference)20 AppGenericStore, CategoryRowReference)
2121
2222
23LOG = logging.getLogger(__name__)
24
25
23class AppTreeView(Gtk.TreeView):26class AppTreeView(Gtk.TreeView):
2427
25 """Treeview based view component that takes a AppStore and displays it"""28 """Treeview based view component that takes a AppStore and displays it"""
@@ -144,16 +147,16 @@
144147
145 if old is not None:148 if old is not None:
146 start, end = self.get_visible_range() or (None, None)149 start, end = self.get_visible_range() or (None, None)
147 if (start and start.compare(old) != -1) or \150 if ((start and start.compare(old) != -1) or
148 (end and end.compare(old) != 1):151 (end and end.compare(old) != 1)):
149 self._needs_collapse.append(old)152 self._needs_collapse.append(old)
150 else:153 else:
151 try: # try... a lazy solution to Bug #846204154 try: # try... a lazy solution to Bug #846204
152 model.row_changed(old, model.get_iter(old))155 model.row_changed(old, model.get_iter(old))
153 except:156 except:
154 msg = ("apptreeview.expand_path: Supplied 'old' "157 LOG.exception(
155 "path is an invalid tree path: '%s'" % old)158 "apptreeview.expand_path: Supplied 'old' "
156 logging.debug(msg)159 "path is an invalid tree path: '%s'" % old)
157160
158 if path == None:161 if path == None:
159 return162 return
@@ -485,13 +488,8 @@
485 path)488 path)
486489
487 def _cell_data_func_cb(self, col, cell, model, it, user_data):490 def _cell_data_func_cb(self, col, cell, model, it, user_data):
488
489 path = model.get_path(it)491 path = model.get_path(it)
490492
491 # this will give us the right underlying model regardless if its
492 # a TreeModelFilter, a AppTreeStore or a AppListStore
493 model = self.appmodel
494
495 # this will pre-load data *only* on a AppListStore, it has493 # this will pre-load data *only* on a AppListStore, it has
496 # no effect with a AppTreeStore494 # no effect with a AppTreeStore
497 if model[path][0] is None:495 if model[path][0] is None:

Subscribers

People subscribed via source and target branches