Merge lp:~mmcg069/software-center/viewswitcher2 into lp:software-center

Proposed by Matthew McGowan
Status: Rejected
Rejected by: Michael Vogt
Proposed branch: lp:~mmcg069/software-center/viewswitcher2
Merge into: lp:software-center
Diff against target: 1832 lines (+951/-371)
15 files modified
softwarecenter/app.py (+30/-11)
softwarecenter/apt/aptcache.py (+5/-2)
softwarecenter/enums.py (+3/-2)
softwarecenter/view/appview.py (+16/-2)
softwarecenter/view/availablepane.py (+8/-2)
softwarecenter/view/basepane.py (+20/-0)
softwarecenter/view/catview_gtk.py (+34/-10)
softwarecenter/view/historypane.py (+10/-0)
softwarecenter/view/installedpane.py (+4/-1)
softwarecenter/view/pendingview.py (+6/-1)
softwarecenter/view/softwarepane.py (+25/-1)
softwarecenter/view/viewmanager.py (+38/-9)
softwarecenter/view/viewswitcher.py (+745/-328)
softwarecenter/view/widgets/animatedimage.py (+5/-1)
softwarecenter/view/widgets/mkit.py (+2/-1)
To merge this branch: bzr merge lp:~mmcg069/software-center/viewswitcher2
Reviewer Review Type Date Requested Status
Michael Vogt Needs Information
Matthew Paul Thomas design Approve
Review via email: mp+32867@code.launchpad.net

Description of the change

In this branch Viewswitcher is essentially a frakenstein of vbox & treeview, enabling all vboxes nice padding and spacing attributes while offering node navigation simialr to a treeview+treestore.

Moreover, a new cellrenderer and api is provided which allows the setting of the "action count" on a node, which subsequently displays an 'action_count' bubble in the 'treeview' for the appropriate node.

As well; misc bug fixes.

Note: please pay close attention to the touches on viewmanager.py, as viewmanager seems closely aligned with the plugin architecture, not sure if my changes are desirable...

To post a comment you must log in.
Revision history for this message
Matthew Paul Thomas (mpt) wrote :

This is a good start, and I'd love to see this fix in Maverick. But as shown in <http://imgur.com/vJe6F>, it needs a little work. In that screenshot, the red, lime, and purple stripes should all be the same length.

review: Needs Fixing (design)
Revision history for this message
Matthew Paul Thomas (mpt) wrote :

Also, the lozenge currently has a mouseover effect and should not.

review: Needs Fixing (design)
931. By Matthew McGowan

make spacing and padding uniform.

932. By Matthew McGowan

stop lozenge from changing colour when selecting the row or mousing over the row.

933. By Matthew McGowan

some small tweaks.

Revision history for this message
Matthew McGowan (mmcg069) wrote :

Hi,

The spacing should now be fixed. The red, lime, and purple lines
should all be 6 pixels. This value is easily tweakable by setting the
value of ViewSwitcher.ITEM_SPACING in
softwarecenter/view/viewswitcher.py.

Also, the lozenge no longer has a moueover effect.
.

On Wed, Aug 18, 2010 at 2:21 AM, Matthew Paul Thomas <email address hidden> wrote:
> Review: Needs Fixing design
> Also, the lozenge currently has a mouseover effect and should not.
> --
> https://code.launchpad.net/~mmcg069/software-center/viewswitcher2/+merge/32867
> You are the owner of lp:~mmcg069/software-center/viewswitcher2.
>

--
From the mind of me!

Revision history for this message
Matthew Paul Thomas (mpt) wrote :

Nicely done, thanks. Design approved.

review: Approve (design)
Revision history for this message
Matthew McGowan (mmcg069) wrote :

Thanks :)

On Wed, Aug 18, 2010 at 10:49 PM, Matthew Paul Thomas <email address hidden> wrote:
> Review: Approve design
> Nicely done, thanks. Design approved.
> --
> https://code.launchpad.net/~mmcg069/software-center/viewswitcher2/+merge/32867
> You are the owner of lp:~mmcg069/software-center/viewswitcher2.
>

--
From the mind of me!

934. By Matthew McGowan

merge w trunk.

935. By Matthew McGowan

merge w trunk.

936. By Matthew McGowan

merge w trunk.

937. By Matthew McGowan

merge w trunk and resolve.

938. By Matthew McGowan

merge w trunk and a few tweaks.

939. By Matthew McGowan

remove some stuff that existsed for testing only.

940. By Matthew McGowan

merge w trunk and resolve.

941. By Matthew McGowan

stuff

942. By Matthew McGowan

merge

943. By Matthew McGowan

misc changes

944. By Matthew McGowan

merge w lp branch of viewswitcher2.

Revision history for this message
Michael Vogt (mvo) wrote :

With the new gtk3 UI this branch is no longer relevant, right? Or will it need porting?

review: Needs Information
Revision history for this message
Matthew McGowan (mmcg069) wrote :

i think this can die.

On Thu, Jan 5, 2012 at 10:03 PM, Michael Vogt <email address hidden>wrote:

> Review: Needs Information
>
> With the new gtk3 UI this branch is no longer relevant, right? Or will it
> need porting?
> --
>
> https://code.launchpad.net/~mmcg069/software-center/viewswitcher2/+merge/32867
> You are the owner of lp:~mmcg069/software-center/viewswitcher2.
>

--
From the mind of me!

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks! I set it to rejected to clean the merge overview page now.

And HAPPY NEW YEAR to you :)

Revision history for this message
Matthew McGowan (mmcg069) wrote :

HAPPY NEW YEAR :D

See ya on IRC once i stop being lazy :)

On Fri, Jan 6, 2012 at 7:54 AM, Michael Vogt <email address hidden>wrote:

> Thanks! I set it to rejected to clean the merge overview page now.
>
> And HAPPY NEW YEAR to you :)
> --
>
> https://code.launchpad.net/~mmcg069/software-center/viewswitcher2/+merge/32867
> You are the owner of lp:~mmcg069/software-center/viewswitcher2.
>

--
From the mind of me!

Unmerged revisions

944. By Matthew McGowan

merge w lp branch of viewswitcher2.

943. By Matthew McGowan

misc changes

942. By Matthew McGowan

merge

941. By Matthew McGowan

stuff

940. By Matthew McGowan

merge w trunk and resolve.

939. By Matthew McGowan

remove some stuff that existsed for testing only.

938. By Matthew McGowan

merge w trunk and a few tweaks.

937. By Matthew McGowan

merge w trunk and resolve.

936. By Matthew McGowan

merge w trunk.

935. By Matthew McGowan

merge w trunk.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'softwarecenter/app.py'
2--- softwarecenter/app.py 2010-11-24 22:30:43 +0000
3+++ softwarecenter/app.py 2010-11-26 21:34:16 +0000
4@@ -43,7 +43,7 @@
5 from softwarecenter.view.widgets.mkit import floats_from_string
6
7 import view.dialogs
8-from view.viewswitcher import ViewSwitcher, ViewSwitcherList
9+from view.viewswitcher import ViewSwitcher, ViewReference
10 from view.pendingview import PendingView
11 from view.installedpane import InstalledPane
12 from view.channelpane import ChannelPane
13@@ -63,6 +63,9 @@
14 from apt.aptcache import AptCache
15 from gettext import gettext as _
16
17+#from softwarecenter.widgets.animatedimage import AnimatedImage
18+
19+
20 class SoftwarecenterDbusController(dbus.service.Object):
21 """
22 This is a helper to provide the SoftwarecenterIFace
23@@ -91,7 +94,7 @@
24 self.parent.cache.emit("cache-ready")
25
26 class SoftwareCenterApp(SimpleGtkbuilderApp):
27-
28+
29 WEBLINK_URL = "http://apt.ubuntu.com/p/%s"
30
31 # the size of the icon for dialogs
32@@ -224,7 +227,7 @@
33 self.available_pane.connect("app-list-changed",
34 self.on_app_list_changed,
35 VIEW_PAGE_AVAILABLE)
36- self.view_manager.register(self.available_pane, VIEW_PAGE_AVAILABLE)
37+ self.view_manager.register_primary(self.available_pane, VIEW_PAGE_AVAILABLE)
38
39 # channel pane
40 self.channel_pane = ChannelPane(self.cache,
41@@ -248,7 +251,7 @@
42 self.channel_pane.connect("app-list-changed",
43 self.on_app_list_changed,
44 VIEW_PAGE_CHANNEL)
45- self.view_manager.register(self.channel_pane, VIEW_PAGE_CHANNEL)
46+ self.view_manager.register_secondary(self.channel_pane, VIEW_PAGE_CHANNEL)
47
48 # installed pane
49 self.installed_pane = InstalledPane(self.cache,
50@@ -272,7 +275,8 @@
51 self.installed_pane.connect("app-list-changed",
52 self.on_app_list_changed,
53 VIEW_PAGE_INSTALLED)
54- self.view_manager.register(self.installed_pane, VIEW_PAGE_INSTALLED)
55+ self.view_manager.register_primary(self.installed_pane,
56+ VIEW_PAGE_INSTALLED)
57
58 # history pane (not fully loaded at this point)
59 self.history_pane = HistoryPane(self.cache,
60@@ -280,24 +284,32 @@
61 self.distro,
62 self.icons,
63 datadir)
64+
65 self.history_pane.connect("app-list-changed",
66 self.on_app_list_changed,
67 VIEW_PAGE_HISTORY)
68- self.view_manager.register(self.history_pane, VIEW_PAGE_HISTORY)
69+ self.view_manager.register_primary(self.history_pane,
70+ VIEW_PAGE_HISTORY)
71
72 # pending view
73 self.pending_view = PendingView(self.icons)
74- self.view_manager.register(self.pending_view, VIEW_PAGE_PENDING)
75+ self.view_manager.register_primary(self.pending_view, VIEW_PAGE_PENDING)
76
77 # keep track of the current active pane
78 self.active_pane = self.available_pane
79
80 # view switcher
81 self.view_switcher = ViewSwitcher(self.view_manager, datadir, self.db, self.cache, self.icons)
82+ self.view_switcher.show()
83+ self.view_switcher.build_primary_views()
84+
85+ # increase the padding around the Pending item
86+ from softwarecenter.view.widgets.mkit import EM
87+ #self.view_switcher.set_item_padding(VIEW_PAGE_PENDING, int(1.75*EM))
88 self.scrolledwindow_viewswitcher.add(self.view_switcher)
89- self.view_switcher.show()
90 self.view_switcher.connect("view-changed",
91 self.on_view_switcher_changed)
92+
93 self.view_switcher.width = self.scrolledwindow_viewswitcher.get_property('width-request')
94 self.view_switcher.connect('size-allocate', self.on_viewswitcher_resized)
95 self.view_switcher.set_view(VIEW_PAGE_AVAILABLE)
96@@ -428,7 +440,7 @@
97 self.glaunchpad.shutdown()
98 self.save_state()
99 gtk.main_quit()
100-
101+
102 def on_window_main_key_press_event(self, widget, event):
103 if (event.keyval == gtk.gdk.keyval_from_name("BackSpace") and
104 self.active_pane and
105@@ -444,6 +456,7 @@
106 # set menu sensitve
107 self.menuitem_view_supported_only.set_sensitive(self.active_pane != None)
108 self.menuitem_view_all.set_sensitive(self.active_pane != None)
109+
110 # set menu state
111 if self.active_pane:
112 self._block_menuitem_view = True
113@@ -455,6 +468,7 @@
114 else:
115 self.menuitem_view_all.activate()
116 self._block_menuitem_view = False
117+
118 if view_id == VIEW_PAGE_AVAILABLE:
119 back_action = self.available_pane.nav_history.navhistory_back_action
120 forward_action = self.available_pane.nav_history.navhistory_forward_action
121@@ -542,7 +556,7 @@
122 action_func(app.pkgname, app.appname, appdetails.icon, addons_install=addons_install, addons_remove=addons_remove)
123 else:
124 logging.error("Not a valid action in AptdaemonBackend: '%s'" % action)
125-
126+
127 def get_icon_filename(self, iconname, iconsize):
128 iconinfo = self.icons.lookup_icon(iconname, iconsize, 0)
129 if not iconinfo:
130@@ -819,6 +833,11 @@
131 (or a cache reload) has finished
132 """
133 self.cache.open()
134+ self.update_app_status_menu()
135+
136+ def _on_transaction_stopped(self, *args, **kwargs):
137+ """ callback when an application install/remove transaction has stopped """
138+ self.update_app_status_menu()
139
140 def on_channels_changed(self, backend, res):
141 """ callback when the set of software channels has changed """
142@@ -854,7 +873,7 @@
143 return
144 if channel:
145 self.channel_pane.set_channel(channel)
146- self.active_pane.refresh_apps()
147+ self.channel_pane.refresh_apps()
148 self.active_pane.update_app_view()
149
150 def _on_database_rebuilding_handler(self, is_rebuilding):
151
152=== modified file 'softwarecenter/apt/aptcache.py'
153--- softwarecenter/apt/aptcache.py 2010-11-23 14:56:54 +0000
154+++ softwarecenter/apt/aptcache.py 2010-11-26 21:34:16 +0000
155@@ -79,7 +79,8 @@
156 self._ready = False
157 self._timeout_id = None
158 # async open cache
159- glib.timeout_add(10, self.open)
160+ #glib.timeout_add(10, self.open)
161+ self.open()
162 # setup monitor watch for install/remove changes
163 self.apt_finished_stamp=gio.File(self.APT_FINISHED_STAMP)
164 self.apt_finished_monitor = self.apt_finished_stamp.monitor_file(
165@@ -126,7 +127,9 @@
166 def __iter__(self):
167 return self._cache.__iter__()
168 def __contains__(self, k):
169- return self._cache.__contains__(k)
170+ if self._cache:
171+ return self._cache.__contains__(k)
172+ return False
173 def _get_rdepends_by_type(self, pkg, type, onlyInstalled):
174 rdeps = set()
175 for rdep in pkg._pkg.rev_depends_list:
176
177=== modified file 'softwarecenter/enums.py'
178--- softwarecenter/enums.py 2010-11-24 10:54:29 +0000
179+++ softwarecenter/enums.py 2010-11-26 21:34:16 +0000
180@@ -54,11 +54,12 @@
181 # items considered "permanent", that is, if a item disappears
182 # (e.g. progress) then switch back to the previous on in permanent
183 # views (LP: #431907)
184-PERMANENT_VIEWS = [VIEW_PAGE_AVAILABLE,
185+PERMANENT_VIEWS = (VIEW_PAGE_AVAILABLE,
186 VIEW_PAGE_INSTALLED,
187+ #VIEW_PAGE_CHANNEL,
188 VIEW_PAGE_CHANNEL,
189 VIEW_PAGE_HISTORY,
190- ]
191+ )
192
193 # icons
194 MISSING_APP_ICON = "applications-other"
195
196=== modified file 'softwarecenter/view/appview.py'
197--- softwarecenter/view/appview.py 2010-11-23 14:56:54 +0000
198+++ softwarecenter/view/appview.py 2010-11-26 21:34:16 +0000
199@@ -1178,6 +1178,8 @@
200
201 def __init__(self, show_ratings, store=None):
202 gtk.TreeView.__init__(self)
203+ #self.set_rules_hint(True)
204+
205 self._logger = logging.getLogger("softwarecenter.view.appview")
206 #self.buttons = {}
207 self.pressed = False
208@@ -1571,8 +1573,20 @@
209
210 def _on_transaction_finished(self, backend, result, tr):
211 """ callback when an application install/remove transaction has finished """
212- # need to send a cursor-changed so the row button is properly updated
213- self.emit("cursor-changed")
214+
215+ # If this item has just been removed...
216+ try:
217+ pkgname = result.meta_data["sc_pkgname"]
218+ except KeyError:
219+ return
220+ appname = result.meta_data.get("sc_appname", "")
221+ db = self.get_model().db
222+ appdetails = Application(appname, pkgname).get_details(db)
223+ # ...then manually emit "cursor-changed" as an item has
224+ # just been removed and so everything else needs to update
225+ if appdetails.pkg_state == PKG_STATE_UNINSTALLED:
226+ self.emit("cursor-changed")
227+
228 # remove pkg from the block list
229 self._check_remove_pkg_from_blocklist(result.pkgname)
230
231
232=== modified file 'softwarecenter/view/availablepane.py'
233--- softwarecenter/view/availablepane.py 2010-11-24 14:49:46 +0000
234+++ softwarecenter/view/availablepane.py 2010-11-26 21:34:16 +0000
235@@ -64,7 +64,7 @@
236 # constant for use in action bar (see _update_action_bar)
237 _INSTALL_BTN_ID = 0
238
239- def __init__(self,
240+ def __init__(self,
241 cache,
242 db,
243 distro,
244@@ -72,9 +72,14 @@
245 datadir,
246 navhistory_back_action,
247 navhistory_forward_action):
248- # parent
249+
250 SoftwarePane.__init__(self, cache, db, distro, icons, datadir)
251 self._logger = logging.getLogger(__name__)
252+
253+ # available pane display name and icon
254+ self.pane_name = _('Get Free Software')
255+ self.pane_icon = self._get_viewswitcher_icon("softwarecenter")
256+
257 # navigation history actions
258 self.navhistory_back_action = navhistory_back_action
259 self.navhistory_forward_action = navhistory_forward_action
260@@ -599,6 +604,7 @@
261 self.action_bar.clear()
262 self.searchentry.show()
263 self.cat_view.stop_carousels()
264+ self.action_bar.clear()
265 return
266
267 def display_details(self):
268
269=== modified file 'softwarecenter/view/basepane.py'
270--- softwarecenter/view/basepane.py 2010-11-19 23:03:15 +0000
271+++ softwarecenter/view/basepane.py 2010-11-26 21:34:16 +0000
272@@ -17,11 +17,31 @@
273 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
274
275
276+import gtk
277+from widgets.animatedimage import AnimatedImage
278+
279+
280 class BasePane(object):
281 """ Base for all the View widgets that can be registered in a
282 ViewManager
283 """
284
285+ VIEW_SWITCHER_ICON_SIZE = 24
286+
287+ def _get_viewswitcher_icon(self, icon_name):
288+ size = self.VIEW_SWITCHER_ICON_SIZE
289+ if self.icons.lookup_icon(icon_name, size, 0):
290+ path = self.icons.lookup_icon(icon_name, size, 0).get_filename()
291+ pb = gtk.gdk.pixbuf_new_from_file_at_size(path,
292+ size,
293+ size)
294+ icon = AnimatedImage(pb)
295+ else:
296+ # icon not present in theme, probably because running uninstalled
297+ icon = AnimatedImage(self.icons.load_icon("gtk-missing-image",
298+ size, 0))
299+ return icon
300+
301 def __init__(self):
302 # stuff that is queried by app.py
303 self.apps_filter = None
304
305=== modified file 'softwarecenter/view/catview_gtk.py'
306--- softwarecenter/view/catview_gtk.py 2010-11-22 16:45:17 +0000
307+++ softwarecenter/view/catview_gtk.py 2010-11-26 21:34:16 +0000
308@@ -391,7 +391,7 @@
309 self.hbox_inner.set_homogeneous(True)
310
311 featured_cat = get_category_by_name(self.categories,
312- 'Featured') # untranslated name
313+ 'Featured Applications') # untranslated name
314
315 # the spec says the carousel icons should be 4em
316 # however, by not using a stock icon size, icons sometimes dont
317@@ -420,7 +420,7 @@
318
319 # create new-apps widget
320 new_cat = get_category_by_name(self.categories,
321- u"What\u2019s New")
322+ "New Applications")
323 if new_cat:
324 new_apps = AppStore(self.cache,
325 self.db,
326@@ -432,6 +432,7 @@
327 icon_size=best_stock_size,
328 global_icon_cache=False,
329 nonapps_visible=AppStore.NONAPPS_MAYBE_VISIBLE)
330+
331 self.newapps_carousel = CarouselView(
332 new_apps, _(u"What\u2019s New"), self.icons,
333 start_random=False)
334@@ -458,8 +459,8 @@
335 sorted_cats = categories_sorted_by_name(self.categories)
336
337 for cat in sorted_cats:
338- if cat.untranslated_name not in ('Featured',
339- u"What\u2019s New"):
340+ if cat.untranslated_name not in ('Featured Applications',
341+ 'New Applications'):
342 #enquirer.set_query(cat.query)
343 ## limiting the size here does not make it faster
344 #matches = enquirer.get_mset(0, len(self.db))
345@@ -900,8 +901,6 @@
346
347 def draw(self, cr, a, expose_area):
348 if mkit.not_overlapping(a, expose_area): return
349- #mkit.FramedSection.draw(self, cr, a, expose_area)
350-
351 cr.save()
352
353 lin = cairo.LinearGradient(a.x, a.y+80, a.x, a.y+a.height)
354@@ -931,9 +930,35 @@
355
356 cr.restore()
357
358- r,g,b = mkit.floats_from_gdkcolor(self.style.mid[0])
359- rr = mkit.ShapeRoundedRectangle()
360-
361+ #r,g,b = mkit.floats_from_gdkcolor(self.style.mid[0])
362+ #rr = mkit.ShapeRoundedRectangle()
363+
364+ #rr.layout(cr, a.x, a.y, a.x+a.width, a.y+a.height, radius=5)
365+ #cr.clip()
366+ #cr.set_source_rgba(r,g,b,0.35)
367+ #cr.mask(lin)
368+
369+ #cairo.Context.reset_clip(cr)
370+ #cr.save()
371+ #cr.translate(0.5,0.5)
372+ #cr.set_line_width(1)
373+
374+ #r,g,b = mkit.floats_from_gdkcolor(self.style.light[0])
375+ #rr.layout(cr, a.x, a.y, a.x+a.width-1, a.y+a.height, radius=5)
376+ #lin = cairo.LinearGradient(a.x, a.y+80, a.x, a.y+a.height)
377+ #lin.add_color_stop_rgba(0,r,g,b,1)
378+ #lin.add_color_stop_rgba(1,r,g,b,0)
379+ #cr.set_source(lin)
380+ #cr.stroke()
381+
382+ #r,g,b = mkit.floats_from_gdkcolor(self.style.dark[0])
383+ #rr.layout(cr, a.x, a.y, a.x+a.width, a.y+a.height, radius=5)
384+ #lin = cairo.LinearGradient(a.x, a.y+80, a.x, a.y+a.height)
385+ #lin.add_color_stop_rgba(0,r,g,b,1)
386+ #lin.add_color_stop_rgba(1,r,g,b,0)
387+ #cr.set_source(lin)
388+ #cr.stroke()
389+ #cr.restore()
390 cr.restore()
391
392 self.more_btn.draw(cr, self.more_btn.allocation, expose_area)
393@@ -992,7 +1017,6 @@
394
395 def __init__(self, markup='None', icon_name='None', icon_size=48, icons=None):
396 mkit.VLinkButton.__init__(self, markup, icon_name, icon_size, icons)
397-
398 self.set_border_width(mkit.BORDER_WIDTH_LARGE)
399 self.set_internal_spacing(mkit.SPACING_SMALL)
400
401
402=== modified file 'softwarecenter/view/historypane.py'
403--- softwarecenter/view/historypane.py 2010-11-24 14:49:46 +0000
404+++ softwarecenter/view/historypane.py 2010-11-26 21:34:16 +0000
405@@ -62,12 +62,16 @@
406
407 def __init__(self, cache, db, distro, icons, datadir):
408 gtk.VBox.__init__(self)
409+
410 self.cache = cache
411 self.db = db
412 self.distro = distro
413 self.icons = icons
414 self.datadir = datadir
415
416+ self.pane_name = _('History')
417+ self.pane_icon = self._get_viewswitcher_icon('clock')
418+
419 self.apps_filter = None
420
421 # Icon cache, invalidated upon icon theme changes
422@@ -258,6 +262,12 @@
423
424 self.emit('app-list-changed', self.visible_changes)
425
426+ def set_pane_name(self, name):
427+ self.pane_name = name
428+
429+ def set_pane_icon(self, icon):
430+ self.pane_icon = icon
431+
432 def _row_matches(self, store, iter):
433 # Whether a child row matches the current filter and the search entry
434 pkg = store.get_value(iter, self.COL_PKG) or ''
435
436=== modified file 'softwarecenter/view/installedpane.py'
437--- softwarecenter/view/installedpane.py 2010-11-16 23:59:07 +0000
438+++ softwarecenter/view/installedpane.py 2010-11-26 21:34:16 +0000
439@@ -41,8 +41,11 @@
440 PAGE_APP_DETAILS) = range(2)
441
442 def __init__(self, cache, db, distro, icons, datadir):
443- # parent
444 SoftwarePane.__init__(self, cache, db, distro, icons, datadir, show_ratings=False)
445+
446+ self.pane_name = _('Installed Software')
447+ self.pane_icon = self._get_viewswitcher_icon('computer')
448+
449 # state
450 self.apps_filter = AppViewFilter(db, cache)
451 self.apps_filter.set_installed_only(True)
452
453=== modified file 'softwarecenter/view/pendingview.py'
454--- softwarecenter/view/pendingview.py 2010-09-15 23:37:35 +0000
455+++ softwarecenter/view/pendingview.py 2010-11-26 21:34:16 +0000
456@@ -35,6 +35,7 @@
457 from softwarecenter.backend.transactionswatcher import TransactionsWatcher
458 from softwarecenter.view.basepane import BasePane
459
460+from widgets.animatedimage import AnimatedImage
461 from gettext import gettext as _
462
463 class PendingStore(gtk.ListStore, TransactionsWatcher):
464@@ -208,12 +209,16 @@
465
466
467 class PendingView(gtk.TreeView, BasePane):
468-
469+
470+ ANIMATION_PATH = "/usr/share/icons/hicolor/24x24/status/softwarecenter-progress.png"
471 CANCEL_XPAD = 6
472 CANCEL_YPAD = 6
473
474 def __init__(self, icons):
475 gtk.TreeView.__init__(self)
476+ self.pane_name = _('In Progress...')
477+ self.pane_icon = AnimatedImage(self.ANIMATION_PATH)
478+ # softwarepane compat
479 BasePane.__init__(self)
480 # customization
481 self.set_headers_visible(False)
482
483=== modified file 'softwarecenter/view/softwarepane.py'
484--- softwarecenter/view/softwarepane.py 2010-11-24 14:49:46 +0000
485+++ softwarecenter/view/softwarepane.py 2010-11-26 21:34:16 +0000
486@@ -38,8 +38,14 @@
487 from softwarecenter.backend import get_install_backend
488 from softwarecenter.view.basepane import BasePane
489
490+from widgets.searchentry import SearchEntry
491+
492+#from widgets.actionbar2 import ActionBar
493+from widgets.actionbar import ActionBar
494+
495 from appview import AppView, AppStore
496
497+
498 if "SOFTWARE_CENTER_APPDETAILS_WEBKIT" in os.environ:
499 from appdetailsview_webkit import AppDetailsViewWebkit as AppDetailsView
500 else:
501@@ -130,6 +136,7 @@
502 (int, ),
503 ),
504 }
505+
506 PADDING = 6
507
508 (PAGE_APPVIEW,
509@@ -137,6 +144,9 @@
510
511 def __init__(self, cache, db, distro, icons, datadir, show_ratings=False):
512 gtk.VBox.__init__(self)
513+ self.pane_name = None
514+ self.pane_icon = None
515+
516 BasePane.__init__(self)
517 # other classes we need
518 self.cache = cache
519@@ -258,7 +268,21 @@
520 index = model.app_index_map.get(current_app)
521 LOG.debug("found app: %s at index %s" % (current_app.pkgname, index))
522 self.app_view.set_cursor(index)
523-
524+
525+ def set_pane_name(self, name):
526+ self.pane_name = name
527+
528+ def set_pane_icon(self, icon):
529+ self.pane_icon = icon
530+
531+ def set_section_image(self, image_id, surf):
532+ self.app_details.set_section_image(image_id, surf)
533+ return
534+
535+ def set_section_color(self, color):
536+ self.app_details.set_section_color(color)
537+ return
538+
539 def show_appview_spinner(self):
540 """ display the spinner in the appview panel """
541 self.action_bar.clear()
542
543=== modified file 'softwarecenter/view/viewmanager.py'
544--- softwarecenter/view/viewmanager.py 2010-11-18 18:39:41 +0000
545+++ softwarecenter/view/viewmanager.py 2010-11-26 21:34:16 +0000
546@@ -22,13 +22,40 @@
547
548 def __init__(self, notebook_view):
549 self.notebook_view = notebook_view
550+ self._primary_order = []
551+ self._secondary_order = []
552+
553 self.all_views = {}
554- self.view_to_pane = {}
555- def register(self, view_widget, view_id):
556- page_id = self.notebook_view.append_page(
557- view_widget, gtk.Label(view_id)) # label is for debugging only
558- self.all_views[view_id] = page_id
559- self.view_to_pane[view_id] = view_widget
560+ self.primary_to_pane = {}
561+ self.secondary_to_pane = {}
562+
563+ def register_primary(self, view_widget, view_id):
564+ self._primary_order.append(view_id)
565+ page_id = self.notebook_view.append_page(
566+ view_widget, gtk.Label(view_id)) # label is for debugging only
567+ self.all_views[view_id] = page_id
568+ self.primary_to_pane[view_id] = view_widget
569+
570+ def register_secondary(self, view_widget, view_id):
571+ self._secondary_order.append(view_id)
572+ page_id = self.notebook_view.append_page(
573+ view_widget, gtk.Label(view_id)) # label is for debugging only
574+ self.all_views[view_id] = page_id
575+ self.secondary_to_pane[view_id] = view_widget
576+
577+ def primary_views(self):
578+ views = []
579+ for view_id in self._primary_order:
580+ vw = self.primary_to_pane[view_id]
581+ views.append((view_id, vw))
582+ return views
583+
584+ def secondary_views(self):
585+ views = []
586+ for view_id in self._secondary_order:
587+ vw = self.secondary_to_pane[view_id]
588+ views.append((view_id, vw))
589+ return views
590
591 def set_active_view(self, view_id):
592 if not view_id == "view-page-separator-1":
593@@ -45,7 +72,9 @@
594
595 def get_notebook_page_from_view_id(self, view_id):
596 return self.all_views[view_id]
597-
598+
599 def get_view_widget(self, view_id):
600- if not view_id == "view-page-separator-1":
601- return self.view_to_pane[view_id]
602+ if self.primary_to_pane.has_key(view_id):
603+ return self.primary_to_pane[view_id]
604+ return self.secondary_to_pane[view_id]
605+
606
607=== modified file 'softwarecenter/view/viewswitcher.py'
608--- softwarecenter/view/viewswitcher.py 2010-10-19 14:50:11 +0000
609+++ softwarecenter/view/viewswitcher.py 2010-11-26 21:34:16 +0000
610@@ -26,6 +26,7 @@
611 import os
612 import time
613 import xapian
614+import cairo
615
616 import aptdaemon.client
617 from gettext import gettext as _
618@@ -37,323 +38,565 @@
619 from softwarecenter.enums import *
620
621 from widgets.animatedimage import CellRendererAnimatedImage, AnimatedImage
622+from widgets.mkit import SPACING_SMALL, SPACING_MED, ShapeRoundedRectangle, floats_from_gdkcolor
623+
624+
625+class ViewItemCellRenderer(gtk.CellRendererText):
626+
627+ """ A cell renderer that displays text and an action count bubble that
628+ displays if the item has an action occuring.
629+ """
630+
631+ __gproperties__ = {
632+
633+ 'bubble_text': (str, 'Bubble text',
634+ 'Text to be label inside row bubble',
635+ '', gobject.PARAM_READWRITE),
636+ }
637+
638+ def __init__(self):
639+ gtk.CellRendererText.__init__(self)
640+ self._rr = ShapeRoundedRectangle()
641+ self._layout = None
642+ return
643+
644+ def do_set_property(self, pspec, value):
645+ setattr(self, pspec.name, value)
646+
647+ def do_get_property(self, pspec):
648+ return getattr(self, pspec.name)
649+
650+ def do_render(self, window, widget, background_area, cell_area,
651+ expose_area, flags):
652+
653+ # important! ensures correct text rendering, esp. when using hicolor theme
654+ if (flags & gtk.CELL_RENDERER_SELECTED) != 0:
655+ # this follows the behaviour that gtk+ uses for states in treeviews
656+ if widget.has_focus():
657+ state = gtk.STATE_SELECTED
658+ else:
659+ state = gtk.STATE_ACTIVE
660+ else:
661+ state = gtk.STATE_NORMAL
662+
663+ text = self.get_property('bubble_text')
664+ draw_bubble = text and text != '0'
665+
666+ if draw_bubble:
667+
668+ if not self._layout:
669+ self._layout = widget.create_pango_layout('')
670+
671+ # setup layout and determine layout extents
672+ color = widget.style.black.to_string()
673+ self._layout.set_markup('<span color="%s"><small><b>%s</b></small></span>' % (color, text))
674+ lw, lh = self._layout.get_pixel_extents()[1][2:]
675+
676+ w = max(16, lw+8)
677+ h = min(cell_area.height-8, lh+8)
678+
679+ # shrink text area width to make room for the bubble
680+ area = gtk.gdk.Rectangle(cell_area.x, cell_area.y,
681+ cell_area.width-w-9,
682+ cell_area.height)
683+ else:
684+ area = cell_area
685+
686+ # draw text
687+ gtk.CellRendererText.do_render(self,
688+ window,
689+ widget,
690+ background_area,
691+ area,
692+ expose_area,
693+ state)
694+
695+ # draw bubble
696+ if not draw_bubble: return
697+
698+
699+ cr = window.cairo_create()
700+
701+ # draw action bubble background
702+ x = max(3, cell_area.x + cell_area.width - w)
703+ y = cell_area.y + (cell_area.height-h)/2
704+
705+ self._rr.layout(cr, x, y, x+w, y+h, radius=7)
706+ cr.set_source_rgb(*floats_from_gdkcolor(widget.style.dark[state]))
707+ cr.fill_preserve()
708+
709+ lin = cairo.LinearGradient(0, y, 0, y+h)
710+ lin.add_color_stop_rgba(0.0, 0, 0, 0, 0.0)
711+ lin.add_color_stop_rgba(1.0, 0, 0, 0, 0.25)
712+ cr.set_source(lin)
713+ cr.fill_preserve()
714+
715+ x, y = int(x+(w-lw)*0.5+0.5), int(y+(h-lh)*0.5+0.5),
716+
717+
718+ # bubble number shadow
719+ widget.style.paint_layout(window,
720+ gtk.STATE_NORMAL,
721+ False,
722+ cell_area,
723+ widget,
724+ None,
725+ x, y+1,
726+ self._layout)
727+
728+
729+ # bubble number
730+ color = widget.style.white.to_string()
731+ self._layout.set_markup('<span color="%s"><small><b>%s</b></small></span>' % (color, text))
732+
733+ widget.style.paint_layout(window,
734+ gtk.STATE_NORMAL,
735+ False,
736+ cell_area,
737+ widget,
738+ None,
739+ x, y,
740+ self._layout)
741+
742+ del cr
743+ return
744
745 LOG = logging.getLogger(__name__)
746
747-class ViewSwitcher(gtk.TreeView):
748-
749- __gsignals__ = {
750- "view-changed" : (gobject.SIGNAL_RUN_LAST,
751- gobject.TYPE_NONE,
752- (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT),
753- ),
754- }
755-
756-
757- def __init__(self, view_manager, datadir, db, cache, icons, store=None):
758- super(ViewSwitcher, self).__init__()
759- self.view_manager = view_manager
760- self.datadir = datadir
761- self.icons = icons
762- if not store:
763- store = ViewSwitcherList(view_manager, datadir, db, cache, icons)
764- # FIXME: this is just set here for app.py, make the
765- # transactions-changed signal part of the view api
766- # instead of the model
767- self.model = store
768- self.set_model(store)
769+
770+class ViewItem(gtk.TreeView):
771+
772+ """ A TreeView with convenience methods """
773+
774+ # columns
775+ (COL_ICON,
776+ COL_NAME,
777+ COL_ACTION,
778+ COL_CHANNEL,
779+ COL_BUBBLE_TEXT) = range(5)
780+
781+ def __init__(self, root_name, root_icon, root_action_id):
782 gtk.TreeView.__init__(self)
783-
784+ self.set_headers_visible(False)
785+ self.set_level_indentation(4)
786+ self.set_enable_search(False)
787+ #self.child_focus(gtk.DIR_RIGHT)
788+
789+ # must match columns above
790+ self.store = gtk.TreeStore(AnimatedImage,
791+ str,
792+ str,
793+ gobject.TYPE_PYOBJECT,
794+ str)
795+
796+ self.set_model(self.store)
797+
798 tp = CellRendererAnimatedImage()
799 column = gtk.TreeViewColumn("Icon")
800 column.pack_start(tp, expand=False)
801- column.set_attributes(tp, image=store.COL_ICON)
802- tr = gtk.CellRendererText()
803+ column.set_attributes(tp, image=self.COL_ICON)
804+
805+ tr = ViewItemCellRenderer()
806 tr.set_property("ellipsize", pango.ELLIPSIZE_END)
807 column.pack_start(tr, expand=True)
808- column.set_attributes(tr, markup=store.COL_NAME)
809+ column.set_attributes(tr, markup=self.COL_NAME,
810+ bubble_text=self.COL_BUBBLE_TEXT)
811+
812 self.append_column(column)
813-
814+ self.column = column
815+
816+ # root name/icon/action_id
817+ self.root_name = root_name
818+ self.root_icon = root_icon
819+ self.root_action_id = root_action_id
820+
821+
822+ self.root_iter = self.store.append(None,
823+ (root_icon, root_name,
824+ root_action_id, None, 0))
825+ return
826+
827+ def append_subitem(self, name, icon, action_id, channel=None, bubble_text=''):
828+ """ Append a row to a primary view as specified by action_id.
829+ A name and icon must be supplied.
830+ """
831+ self.store.append(self.root_iter,
832+ (icon, name, action_id, channel, bubble_text))
833+ return
834+
835+ def select_root(self):
836+ """ Selects root row and sets cursor """
837+ root = self.store.get_iter_root()
838+ sel = self.get_selection()
839+ sel.select_iter(root)
840+ self.set_cursor(0)
841+ self.grab_focus()
842+ return
843+
844+ def select_tail(self):
845+ """ Selects final row and sets the cursor on it """
846+ root = self.store.get_iter_root()
847+ n_children = self.store.iter_n_children(root)
848+ path = (0, n_children-1)
849+
850+ tail = self.store.get_iter(path)
851+
852+ sel = self.get_selection()
853+ sel.select_iter(tail)
854+ self.set_cursor(path)
855+ self.grab_focus()
856+ return
857+
858+ def is_expanded(self):
859+ """ Returns True if the root node is expanded """
860+ return self.row_expanded(0)
861+
862+ def try_expander_toggle(self, iter=None):
863+ """ Expands, collapses or does nothing depending on the row """
864+
865+ it = iter or self.store.get_iter(0)
866+ if self.is_expanded():
867+ self.collapse_row(self.store.get_path(it))
868+ else:
869+ self.expand_row(self.store.get_path(it), True)
870+ return
871+
872+ def expand(self, iter=None):
873+ """ Expands a node if possible """
874+ it = iter or self.store.get_iter(0)
875+ self.expand_row(self.store.get_path(it), True)
876+ return
877+
878+ def collapse(self, iter=None):
879+ """ Collapses a node if possible """
880+ it = iter or self.store.get_iter(0)
881+ self.collapse_row(self.store.get_path(it))
882+ return
883+
884+ def set_bubble_text(self, bubble_text, subitem=-1):
885+ """ Sets the action count, if subitem is specified the action
886+ count is set to the corresponding child of the parent view.
887+ """
888+ if subitem >= 0:
889+ path = (0,subitem)
890+ else:
891+ path = 0
892+
893+ row = self.store[path]
894+ self.store.set_value(row.iter, self.COL_BUBBLE_TEXT, str(bubble_text))
895+ self.store.row_changed(path, self.store.get_iter(path))
896+ return
897+
898+ def get_path(self):
899+ return self.get_cursor()[0]
900+
901+
902+class ViewReference:
903+
904+ """ Stores the item and path, everything we need to represent a row """
905+
906+ def __init__(self, item, path):
907+ self.item = item
908+ self.path = path
909+ return
910+
911+ def __repr__(self):
912+ return 'ViewRef: %s, path:%s' % (self.item.root_name, self.path)
913+
914+ def select(self):
915+ item = self.item
916+ path = self.path
917+ sel = item.get_selection()
918+ if sel:
919+ sel.select_path(path)
920+ if path:
921+ item.set_cursor(path)
922+ item.grab_focus()
923+ return
924+
925+
926+class ViewSwitcher(gtk.Viewport):
927+
928+ """
929+ A container in which ViewItem's can be packed, enabling all the
930+ spacing and padding capabilities a VBox offers.
931+ """
932+
933+ ICON_SIZE = 24
934+ ITEM_SPACING = 6
935+
936+ __gsignals__ = {
937+ "view-changed" : (gobject.SIGNAL_RUN_LAST,
938+ gobject.TYPE_NONE,
939+ (str, gobject.TYPE_PYOBJECT)),
940+ }
941+
942+
943+ def __init__(self, view_manager, datadir, db, cache, icons, store=None):
944+ gtk.Viewport.__init__(self)
945+ self.set_shadow_type(gtk.SHADOW_NONE)
946+ self.alignment = gtk.Alignment(xscale=1.0, yscale=1.0)
947+ self.add(self.alignment)
948+ self.vbox = gtk.VBox(spacing=self.ITEM_SPACING)
949+ self.alignment.add(self.vbox)
950+ self.alignment.set_padding(self.ITEM_SPACING, 0,
951+ 0, 0)
952+ self.show_all()
953+
954+ self.view_manager = view_manager
955+ self._id_to_item = {}
956+
957+ self.datadir = datadir
958+ self.icons = icons
959+ self.db = db
960+ self.cache = cache
961+
962+ self.backend = get_install_backend()
963+ self.backend.connect("transactions-changed", self._on_transactions_changed)
964+ self.backend.connect("transaction-finished", self._on_transaction_finished)
965+ self.backend.connect("channels-changed", self._on_channels_changed)
966+
967+ self._block_set_cursor_signals = False
968+
969+ self._has_focus = [False, False, False, False]
970+ self._active_ref = None
971 # Remember the previously selected permanent view
972- self._permanent_views = PERMANENT_VIEWS
973- self._previous_permanent_view = None
974+ self._last_permaview = None
975
976 # set sensible atk name
977 atk_desc = self.get_accessible()
978 atk_desc.set_name(_("Software sources"))
979-
980- self.set_model(store)
981- self.set_headers_visible(False)
982- self.get_selection().set_select_function(self.on_treeview_selected)
983- self.set_level_indentation(4)
984- self.set_enable_search(False)
985
986+ self.channel_manager = ChannelsManager(db, icons)
987 self.selected_channel_name = None
988 self.selected_channel_installed_only = False
989-
990- self.connect("row-expanded", self.on_treeview_row_expanded)
991- self.connect("row-collapsed", self.on_treeview_row_collapsed)
992- self.connect("cursor-changed", self.on_cursor_changed)
993- self.connect("key-release-event", self.on_key_release_event)
994-
995- self.get_model().connect("channels-refreshed", self._on_channels_refreshed)
996- self.get_model().connect("row-deleted", self._on_row_deleted)
997- # channels changed
998- self.backend = get_install_backend()
999- self.backend.connect("channels-changed", self.on_channels_changed)
1000- self._block_set_cursor_signals = False
1001-
1002- def on_channels_changed(self, backend, res):
1003- LOG.debug("on_channels_changed %s" % res)
1004- if not res:
1005- return
1006- # update channel list, but block signals so that the cursor
1007- # does not jump around
1008- self._block_set_cursor_signals = True
1009- model = self.get_model()
1010- if model:
1011- model._update_channel_list()
1012- self._block_set_cursor_signals = False
1013-
1014- def on_treeview_row_expanded(self, widget, iter, path):
1015- # do nothing on a node expansion
1016- pass
1017-
1018- def on_treeview_row_collapsed(self, widget, iter, path):
1019- # on a node collapse, select the node
1020- self.set_cursor(path)
1021-
1022- def on_treeview_selected(self, path):
1023- model = self.get_model()
1024- iter_ = model.get_iter(path)
1025- id_ = model.get_value(iter_, 2)
1026- if id_ == VIEW_PAGE_SEPARATOR_1:
1027- return False
1028- return True
1029-
1030- def on_cursor_changed(self, widget):
1031+
1032+ self.alignment.connect('expose-event', self._on_vbox_expose)
1033+ return
1034+
1035+ def _on_realize(self, widget):
1036+ # check if there are any live transactions on startup """
1037+ self._update_pending_item(self.backend.pending_transactions)
1038+ # set the default startup view to the Available pane
1039+ self.set_view(VIEW_PAGE_AVAILABLE)
1040+ return
1041+
1042+ def _on_focus_in(self, item, event):
1043+ self._active_ref.select()
1044+ return
1045+
1046+ def _on_vbox_expose(self, widget, event):
1047+ children = self.vbox.get_children()
1048+ if children and isinstance(children[0], gtk.TreeView):
1049+ treeview = children[0]
1050+ treeview.style.paint_flat_box(widget.window,
1051+ widget.state,
1052+ gtk.SHADOW_NONE,
1053+ widget.allocation,
1054+ treeview,
1055+ 'cell_even',
1056+ widget.allocation.x,
1057+ widget.allocation.y,
1058+ widget.allocation.width,
1059+ widget.allocation.height)
1060+ else: # backup draw method
1061+ cr = widget.window.cairo_create()
1062+ cr.rectangle(event.area)
1063+ cr.clip()
1064+
1065+ cr.set_source_rgb(*floats_from_gdkcolor(self.style.base[self.state]))
1066+ cr.rectangle(widget.allocation)
1067+ cr.fill()
1068+ return True
1069+
1070+ def _on_item_keynav(self, item, event):
1071+ """ We handle keynav ourselves so we can skip through items when an
1072+ item extent is reached
1073+ """
1074+ kv = event.keyval
1075+
1076+ sel = item.get_selection()
1077+ model, siter = sel.get_selected() # current selected iter
1078+
1079+ if not siter: return
1080+
1081+ if kv == gtk.keysyms.Tab or kv == gtk.keysyms.ISO_Left_Tab: # tab/shift-tab
1082+ return # allow regular keynav handling
1083+ elif kv == gtk.keysyms.space or kv == gtk.keysyms.Return:
1084+ item.try_expander_toggle(siter)
1085+ elif kv == gtk.keysyms.Right: # try expand only
1086+ item.expand(siter)
1087+ elif kv == gtk.keysyms.Left: # try collapse only
1088+ item.collapse(siter)
1089+ elif kv == gtk.keysyms.Up:
1090+ self._keynav_up(item, model, sel, siter)
1091+ elif kv == gtk.keysyms.Down:
1092+ self._keynav_down(item, model, sel, siter)
1093+ return True
1094+
1095+ def _on_available_item_focus_in(self, item, event):
1096+ if item != self._active_ref.item:
1097+ sel = self._active_ref.item.get_selection()
1098+ sel.unselect_all()
1099+
1100+ sel = item.get_selection()
1101+ sel.select_path((0,))
1102+ item.set_cursor((0,))
1103+ return
1104+
1105+ def _on_cursor_changed(self, item):
1106 if self._block_set_cursor_signals:
1107 return
1108- (path, column) = self.get_cursor()
1109- if not path:
1110- return
1111- model = self.get_model()
1112- if not model:
1113- return
1114- action = model[path][ViewSwitcherList.COL_ACTION]
1115- channel = model[path][ViewSwitcherList.COL_CHANNEL]
1116- if action in self._permanent_views:
1117- self._previous_permanent_view = path
1118- self.selected_channel_name = model[path][ViewSwitcherList.COL_NAME]
1119+
1120+ sel = item.get_selection()
1121+ # this handles the case where the initial expand or collapse
1122+ # of a node triggers a cursor change but does not update selection
1123+ if not sel.get_selected()[1]:
1124+ self._active_ref.select()
1125+ return
1126+
1127+ if self._active_ref and self._active_ref.item != item:
1128+ sel = self._active_ref.item.get_selection()
1129+ sel.unselect_all()
1130+
1131+
1132+ path = item.get_path()
1133+ view_ref = ViewReference(item, path)
1134+ self._active_ref = view_ref
1135+
1136+ model = item.get_model()
1137+ action = model[path][ViewItem.COL_ACTION]
1138+ channel = model[path][ViewItem.COL_CHANNEL]
1139+ self.selected_channel_name = model[path][ViewItem.COL_NAME]
1140+
1141+ if item.root_action_id in PERMANENT_VIEWS:
1142+ self._last_permaview = view_ref
1143+
1144 if channel:
1145 self.selected_channel_installed_only = channel.installed_only
1146
1147- view_page = action
1148- self.emit("view-changed", view_page, channel)
1149-
1150- def on_key_release_event(self, widget, event):
1151- # Get the toplevel node of the currently selected row
1152- toplevel = self.get_toplevel_node(self.get_cursor())
1153- if toplevel is None:
1154- return
1155- toplevel_path = (toplevel,)
1156-
1157- # Expand the toplevel node if the right arrow key is clicked
1158- if event.keyval == gtk.keysyms.Right:
1159- if not self.row_expanded(toplevel_path):
1160- self.expand_row(toplevel_path, False)
1161- # Collapse the toplevel node if the left arrow key is clicked
1162- elif event.keyval == gtk.keysyms.Left:
1163- if self.row_expanded(toplevel_path):
1164- self.collapse_row(toplevel_path)
1165+ gobject.idle_add(self._idle_emit_view_changed, action, channel)
1166+ return
1167+
1168+ #def _on_row_deleted(self, widget, deleted_path):
1169+ #(path, column) = widget.get_cursor()
1170+ #if path is None:
1171+ #LOG.debug("path from _on_row_deleted no longer available")
1172+ ## The view that was selected has been deleted, switch back to
1173+ ## the previously selected permanent view.
1174+ #if self._previous_permanent_view is not None:
1175+ #self.set_view_ref(self._last_permaview)
1176+
1177+ def _on_transactions_changed(self, backend, total_transactions):
1178+ self._update_pending_item(total_transactions)
1179+ return
1180+
1181+ def _on_transaction_finished(self, backend, result):
1182+ if result.success:
1183+ self._update_channel_list_installed_view()
1184+ self._restore_previous_selection()
1185+ return
1186+
1187+ def _on_treeview_row_expanded(self, item, iter, path):
1188+ return
1189+
1190+ def _on_treeview_row_collapsed(self, item, iter, path):
1191+ if self._active_ref and item == self._active_ref.item:
1192+ self._active_ref = ViewReference(item, path)
1193+ self._active_ref.select()
1194+ return
1195+
1196+ def _on_treeview_row_activated(self, item, path, col):
1197+ item.try_expander_toggle()
1198+ return
1199+
1200+ def _on_channels_changed(self, backend, res):
1201+ LOG.debug("on_channels_changed %s" % res)
1202+ self._block_set_cursor_signals = True
1203+ if res:
1204+ self.db.open()
1205+ self._update_channel_list()
1206+
1207+ self._block_set_cursor_signals = False
1208+ return
1209+
1210+ def _keynav_up(self, item, model, sel, siter):
1211+ niter = self._up_iter(model, siter)
1212+
1213+ # do a normal up down keynav within an item
1214+ if niter:
1215+ sel.select_iter(niter)
1216+ item.set_cursor(model.get_path(niter))
1217+ item.grab_focus()
1218+
1219+ # we have reached the end of the road within an item,
1220+ # so we need to unselect current item and jump to a next item
1221+ # and select the appropriate TreeView path
1222+ else:
1223+ next_item = self.item_prev(item)
1224+ if not next_item: return
1225+
1226+ sel.unselect_iter(siter)
1227+
1228+ if next_item.is_expanded():
1229+ next_item.select_tail()
1230+ else:
1231+ next_item.select_root()
1232+
1233+ self._active_ref = ViewReference(next_item,
1234+ next_item.get_path())
1235+ return
1236+
1237+ def _keynav_down(self, item, model, sel, siter):
1238+ niter = model.iter_next(siter)
1239+ # do a normal up down keynav within an item
1240+ if niter:
1241+ sel.select_iter(niter)
1242+ item.set_cursor(model.get_path(niter))
1243+ item.grab_focus()
1244+
1245+ # there is a child row we can jump to
1246+ elif model.iter_has_child(siter) and item.is_expanded():
1247+ niter = model.iter_nth_child(siter, 0)
1248+ sel.select_iter(niter)
1249+ item.set_cursor(model.get_path(niter))
1250+ item.grab_focus()
1251+
1252+ # we have reached the end of the road within an item,
1253+ # so we need to unselect current item and jump to a next item
1254+ # and select the appropriate TreeView path
1255+ else:
1256+ next_item = self.item_next(item)
1257+ if not next_item: return
1258+
1259+ sel.unselect_iter(siter)
1260+
1261+ next_item.select_root()
1262+ self._active_item = ViewReference(next_item,
1263+ next_item.get_path())
1264+ return
1265+
1266+ def _idle_emit_view_changed(self, action, channel):
1267+ self.emit("view-changed", action, channel)
1268 return False
1269-
1270- def get_view(self):
1271- """return the current activated view number or None if no
1272- view is activated (this can happen when a pending view
1273- disappeared). Views are:
1274-
1275- VIEW_PAGE_AVAILABLE
1276- VIEW_PAGE_CHANNEL
1277- VIEW_PAGE_INSTALLED
1278- VIEW_PAGE_HISTORY
1279- VIEW_PAGE_PENDING
1280- """
1281- (path, column) = self.get_cursor()
1282- if not path:
1283- return None
1284- return path[0]
1285-
1286- def get_toplevel_node(self, cursor):
1287- """Returns the toplevel node of a selected row"""
1288- (path, column) = cursor
1289- if not path:
1290- return None
1291- return path[0]
1292-
1293- def set_view(self, view_page):
1294- notebook_page_id = self.view_manager.get_notebook_page_from_view_id(view_page)
1295- # FIXME: This isn't really the cleanest way to do this, but afaics it is the only way to achieve this with the current view_manager
1296- if view_page == 'view-page-available':
1297- self.set_cursor((notebook_page_id,))
1298- else:
1299- self.set_cursor((notebook_page_id - 1,))
1300- self.emit("view-changed", view_page, None)
1301-
1302- def on_motion_notify_event(self, widget, event):
1303- #print "on_motion_notify_event: ", event
1304- path = self.get_path_at_pos(int(event.x), int(event.y))
1305- if path is None:
1306- self.window.set_cursor(None)
1307- else:
1308- self.window.set_cursor(self.cursor_hand)
1309-
1310- def expand_available_node(self):
1311- """ expand the available pane node in the viewswitcher pane """
1312- model = self.get_model()
1313- if model:
1314- self.expand_row(model.get_path(model.available_iter), False)
1315-
1316- def is_available_node_expanded(self):
1317- """ return True if the available pane node in the viewswitcher pane is expanded """
1318- model = self.get_model()
1319- expanded = False
1320- if model:
1321- expanded = self.row_expanded(model.get_path(model.available_iter))
1322- return expanded
1323-
1324- def expand_installed_node(self):
1325- """ expand the installed pane node in the viewswitcher pane """
1326- model = self.get_model()
1327- if model:
1328- self.expand_row(model.get_path(model.installed_iter), False)
1329-
1330- def is_installed_node_expanded(self):
1331- """ return True if the installed pane node in the viewswitcher pane is expanded """
1332- model = self.get_model()
1333- expanded = False
1334- if model:
1335- expanded = self.row_expanded(model.get_path(model.installed_iter))
1336- return expanded
1337-
1338- def select_channel_node(self, channel_name, installed_only):
1339- """ select the specified channel node """
1340- model = self.get_model()
1341- if model:
1342- channel_iter_to_select = model.get_channel_iter_for_name(channel_name,
1343- installed_only)
1344- if channel_iter_to_select:
1345- self.set_cursor(model.get_path(channel_iter_to_select))
1346-
1347- def _on_channels_refreshed(self, model):
1348- """
1349- when channels are refreshed, the viewswitcher channel is unselected so
1350- we need to reselect it
1351- """
1352- model = self.get_model()
1353- if model:
1354- channel_iter_to_select = model.get_channel_iter_for_name(
1355- self.selected_channel_name,
1356- self.selected_channel_installed_only)
1357- if channel_iter_to_select:
1358- self._block_set_cursor_signals = True
1359- self.set_cursor(model.get_path(channel_iter_to_select))
1360- self._block_set_cursor_signals = False
1361-
1362- def _on_row_deleted(self, widget, deleted_path):
1363- (path, column) = self.get_cursor()
1364- if path is None:
1365- LOG.debug("path from _on_row_deleted no longer available")
1366- # The view that was selected has been deleted, switch back to
1367- # the previously selected permanent view.
1368- if self._previous_permanent_view is not None:
1369- self.set_cursor(self._previous_permanent_view)
1370-
1371-class ViewSwitcherList(gtk.TreeStore):
1372-
1373- # columns
1374- (COL_ICON,
1375- COL_NAME,
1376- COL_ACTION,
1377- COL_CHANNEL,
1378- ) = range(4)
1379-
1380- ICON_SIZE = 24
1381-
1382- ANIMATION_PATH = "/usr/share/icons/hicolor/24x24/status/softwarecenter-progress.png"
1383-
1384- __gsignals__ = {'channels-refreshed':(gobject.SIGNAL_RUN_FIRST,
1385- gobject.TYPE_NONE,
1386- ())}
1387-
1388-
1389- def __init__(self, view_manager, datadir, db, cache, icons):
1390- gtk.TreeStore.__init__(self,
1391- AnimatedImage,
1392- str,
1393- gobject.TYPE_PYOBJECT,
1394- gobject.TYPE_PYOBJECT,
1395- ) # must match columns above
1396- self.view_manager = view_manager
1397- self.icons = icons
1398- self.datadir = datadir
1399- self.backend = get_install_backend()
1400- self.backend.connect("transactions-changed", self.on_transactions_changed)
1401- self.backend.connect("transaction-finished", self.on_transaction_finished)
1402- self.db = db
1403- self.cache = cache
1404- self.distro = get_distro()
1405- # pending transactions
1406- self._pending = 0
1407- # setup the normal stuff
1408-
1409- # first, the availablepane items
1410- available_icon = self._get_icon("softwarecenter")
1411- self.available_iter = self.append(None, [available_icon, _("Get Software"), VIEW_PAGE_AVAILABLE, None])
1412-
1413- # the installedpane items
1414- icon = AnimatedImage(self.icons.load_icon("computer", self.ICON_SIZE, 0))
1415- self.installed_iter = self.append(None, [icon, _("Installed Software"), VIEW_PAGE_INSTALLED, None])
1416-
1417- # the channelpane
1418- self.channel_manager = ChannelsManager(db, icons)
1419- # do initial channel list update
1420- self._update_channel_list()
1421-
1422- # the historypane item
1423- icon = self._get_icon("clock")
1424- history_iter = self.append(None, [icon, _("History"), VIEW_PAGE_HISTORY, None])
1425- icon = AnimatedImage(None)
1426- self.append(None, [icon, "<span size='1'> </span>", VIEW_PAGE_SEPARATOR_1, None])
1427-
1428- # the progress pane is build on demand
1429-
1430- # emit a transactions-changed signal to ensure that we display any
1431- # pending transactions
1432- self.backend.emit("transactions-changed", self.backend.pending_transactions)
1433-
1434- def on_transactions_changed(self, backend, total_transactions):
1435+
1436+ def _update_pending_item(self, total_transactions):
1437 LOG.debug("on_transactions_changed '%s'" % total_transactions)
1438 pending = len(total_transactions)
1439+ pending_item = self.get_item_by_id(VIEW_PAGE_PENDING)
1440 if pending > 0:
1441- for row in self:
1442- if row[self.COL_ACTION] == VIEW_PAGE_PENDING:
1443- row[self.COL_NAME] = _("In Progress (%i)") % pending
1444- break
1445- else:
1446- icon = AnimatedImage(self.ANIMATION_PATH)
1447- icon.start()
1448- self.append(None, [icon, _("In Progress (%i)") % pending,
1449- VIEW_PAGE_PENDING, None])
1450+ pending_item.set_bubble_text(pending)
1451+ pending_item.show()
1452+ if not pending_item.root_icon.is_playing():
1453+ pending_item.root_icon.start() # start the icon animating
1454 else:
1455- for (i, row) in enumerate(self):
1456- if row[self.COL_ACTION] == VIEW_PAGE_PENDING:
1457- del self[(i,)]
1458-
1459- def on_transaction_finished(self, backend, result):
1460- if result.success:
1461- self._update_channel_list_installed_view()
1462- self.emit("channels-refreshed")
1463+ if self._active_ref and self._active_ref.item == pending_item:
1464+ self.set_view_ref(self._last_permaview)
1465+
1466+ pending_item.set_bubble_text(0)
1467+ pending_item.hide()
1468+ pending_item.root_icon.stop() # stop the icon animating
1469+ return
1470
1471 def get_channel_iter_for_name(self, channel_name, installed_only):
1472 """ get the liststore iterator for the given name, consider
1473@@ -361,48 +604,39 @@
1474 """
1475 LOG.debug("get_channel_iter_for_name %s %s" % (channel_name,
1476 installed_only))
1477- def _get_iter_for_channel_name(it):
1478+ def _get_iter_for_channel_name(it, item):
1479 """ internal helper """
1480 while it:
1481- if self.get_value(it, self.COL_NAME) == channel_name:
1482+ if item.get_value(it, self.COL_NAME) == channel_name:
1483 return it
1484- it = self.iter_next(it)
1485+ it = item.iter_next(it)
1486 return None
1487
1488 # check root iter first
1489- channel_iter_for_name = _get_iter_for_channel_name(self.get_iter_root())
1490+ item = self.get_item_by_id(VIEW_PAGE_AVAILABLE)
1491+ channel_iter_for_name = _get_iter_for_channel_name(item.get_iter_root(), item)
1492 if channel_iter_for_name:
1493 LOG.debug("found '%s' on root level" % channel_name)
1494 return channel_iter_for_name
1495
1496 # check children
1497 if installed_only:
1498- parent_iter = self.installed_iter
1499+ item = self.get_item_by_id(VIEW_PAGE_INSTALLED)
1500+ parent_iter = item.root_iter
1501 else:
1502- parent_iter = self.available_iter
1503- LOG.debug("looking at path '%s'" % self.get_path(parent_iter))
1504- child = self.iter_children(parent_iter)
1505+ parent_iter = item.root_iter
1506+
1507+ LOG.debug("looking at path '%s'" % self.get_path(parent_iter), item)
1508+ child = item.iter_children(parent_iter)
1509 channel_iter_for_name = _get_iter_for_channel_name(child)
1510 return channel_iter_for_name
1511-
1512- def _get_icon(self, icon_name):
1513- if self.icons.lookup_icon(icon_name, self.ICON_SIZE, 0):
1514- icon = AnimatedImage(self.icons.load_icon(icon_name, self.ICON_SIZE, 0))
1515- else:
1516- # icon not present in theme, probably because running uninstalled
1517- icon = AnimatedImage(self.icons.load_icon("gtk-missing-image",
1518- self.ICON_SIZE, 0))
1519- return icon
1520
1521 def _update_channel_list(self):
1522 self._update_channel_list_available_view()
1523 self._update_channel_list_installed_view()
1524- self.emit("channels-refreshed")
1525-
1526- # FIXME: this way of updating is really not ideal because it
1527- # will trigger set_cursor signals and that causes the
1528- # UI to behave funny if the user is in a channel view
1529- # and the backend sends a channels-changed signal
1530+ self._restore_previous_selection()
1531+ return
1532+
1533 def _update_channel_list_available_view(self):
1534 # check what needs to be cleared. we need to append first, kill
1535 # afterward because otherwise a row without children is collapsed
1536@@ -411,29 +645,34 @@
1537 # normally GtkTreeIters have a limited life-cycle and are no
1538 # longer valid after the model changed, fortunately with the
1539 # gtk.TreeStore (that we use) they are persisent
1540- child = self.iter_children(self.available_iter)
1541+ a = self.get_item_by_id(VIEW_PAGE_AVAILABLE)
1542+ child = a.store.iter_children(a.root_iter)
1543 iters_to_kill = set()
1544 while child:
1545 iters_to_kill.add(child)
1546- child = self.iter_next(child)
1547+ child = a.store.iter_next(child)
1548+
1549 # iterate the channels and add as subnodes of the available node
1550 for channel in self.channel_manager.channels:
1551- self.append(self.available_iter, [
1552- channel.icon,
1553- channel.display_name,
1554- VIEW_PAGE_CHANNEL,
1555- channel])
1556+ a.append_subitem(channel.display_name,
1557+ channel.icon,
1558+ VIEW_PAGE_CHANNEL,
1559+ channel)
1560+
1561 # delete the old ones
1562 for child in iters_to_kill:
1563- self.remove(child)
1564+ a.store.remove(child)
1565+ return
1566
1567 def _update_channel_list_installed_view(self):
1568 # see comments for _update_channel_list_available_view() method above
1569- child = self.iter_children(self.installed_iter)
1570+ i = self.get_item_by_id(VIEW_PAGE_INSTALLED)
1571+ child = i.store.iter_children(i.root_iter)
1572 iters_to_kill = set()
1573 while child:
1574 iters_to_kill.add(child)
1575- child = self.iter_next(child)
1576+ child = i.store.iter_next(child)
1577+
1578 # iterate the channels and add as subnodes of the installed node
1579 for channel in self.channel_manager.channels_installed_only:
1580 # check for no installed items for each channel and do not
1581@@ -453,15 +692,193 @@
1582 self.cache[pkgname].is_installed):
1583 add_channel_item = True
1584 break
1585+
1586 if add_channel_item:
1587- self.append(self.installed_iter, [
1588- channel.icon,
1589- channel.display_name,
1590- VIEW_PAGE_CHANNEL,
1591- channel])
1592+ i.append_subitem(channel.display_name,
1593+ channel.icon,
1594+ VIEW_PAGE_CHANNEL,
1595+ channel)
1596+
1597 # delete the old ones
1598 for child in iters_to_kill:
1599- self.remove(child)
1600+ i.store.remove(child)
1601+ return
1602+
1603+ def _restore_previous_selection(self):
1604+ """
1605+ when channels are refreshed, the viewswitcher channel is unselected so
1606+ we need to reselect it
1607+ """
1608+ if not self._active_ref: return
1609+ ref = self._active_ref
1610+ self._active_ref = None
1611+ self.set_view_ref(ref)
1612+ return
1613+
1614+ def _get_icon(self, icon_name):
1615+ if self.icons.lookup_icon(icon_name, self.ICON_SIZE, 0):
1616+ path = self.icons.lookup_icon(icon_name, self.ICON_SIZE, 0).get_filename()
1617+ pb = gtk.gdk.pixbuf_new_from_file_at_size(path,
1618+ self.ICON_SIZE,
1619+ self.ICON_SIZE)
1620+ icon = AnimatedImage(pb)
1621+ else:
1622+ # icon not present in theme, probably because running uninstalled
1623+ icon = AnimatedImage(self.icons.load_icon("gtk-missing-image",
1624+ self.ICON_SIZE, 0))
1625+ return icon
1626+
1627+ def _up_iter(self, model, siter):
1628+ path = model.get_path(siter)
1629+ if path[-1]: # not == 0
1630+ path = (0, path[-1]-1)
1631+ niter = model.get_iter(path)
1632+ elif len(path) > 1:
1633+ niter = model.get_iter((0,))
1634+ else:
1635+ niter = None
1636+ return niter
1637+
1638+ def _item_connect_signals(self, item):
1639+ item.connect('key-press-event', self._on_item_keynav)
1640+# item.connect('focus-out-event', self._on_focus_out)
1641+ item.connect("cursor-changed", self._on_cursor_changed)
1642+ item.connect("row-expanded", self._on_treeview_row_expanded)
1643+ item.connect("row-collapsed", self._on_treeview_row_collapsed)
1644+ item.connect("row-activated", self._on_treeview_row_activated)
1645+
1646+ #item.get_model().connect("row-deleted", self._on_row_deleted)
1647+ return
1648+
1649+ def set_item_padding(self, view_id, padding):
1650+ item = self.get_item_by_id(view_id)
1651+ self.vbox.set_child_packing(item, False, False, padding, gtk.PACK_START)
1652+ return
1653+
1654+ def get_item_by_id(self, id):
1655+ return self._id_to_item[id]
1656+
1657+ def get_view(self):
1658+ """return the current activated view ViewReference"""
1659+ return self._active_ref
1660+
1661+ def set_view(self, view_id, path=(0,)):
1662+ item = self.get_item_by_id(view_id)
1663+ view_ref = ViewReference(item, path)
1664+
1665+ if self._active_ref and item == self._active_ref.item: return
1666+
1667+ # unselect prev active item
1668+ if self._active_ref:
1669+ sel = self._active_ref.item.get_selection()
1670+ sel.unselect_all()
1671+
1672+ view_ref.select()
1673+ self._active_ref = view_ref
1674+ self.emit("view-changed", item.root_action_id, None)
1675+ return
1676+
1677+ # new api
1678+ def set_view_ref(self, view_ref):
1679+ if self._active_ref and view_ref.item == self._active_ref.item: return
1680+
1681+ # unselect prev active item
1682+ if self._active_ref:
1683+ sel = self._active_ref.item.get_selection()
1684+ sel.unselect_all()
1685+
1686+ view_ref.select()
1687+ self._active_ref = view_ref
1688+ self.emit("view-changed", view_ref.item.root_action_id, None)
1689+ return
1690+
1691+ def build_primary_views(self):
1692+ """ pack viewmanager registered views into viewswitcher """
1693+
1694+ fallback_icon = self._get_icon("softwarecenter")
1695+ for view_id, view_widget in self.view_manager.primary_views():
1696+ display_name = view_widget.pane_name or view_id
1697+ display_icon = view_widget.pane_icon or fallback_icon
1698+ item = ViewItem(display_name,
1699+ display_icon,
1700+ view_id)
1701+
1702+ self._item_connect_signals(item)
1703+ self._id_to_item[view_id] = item
1704+ self.pack_start(item, False)
1705+
1706+ available = self.get_item_by_id(VIEW_PAGE_AVAILABLE)
1707+ available.set_bubble_text('20 New!')
1708+
1709+ self.set_focus_chain((available,))
1710+ available.connect('focus-in-event', self._on_focus_in)
1711+
1712+ # do initial channel list update
1713+ self._update_channel_list()
1714+
1715+ self.show_all()
1716+ self.connect('realize', self._on_realize)
1717+ return
1718+
1719+ def item_next(self, item):
1720+ items = self.vbox.get_children()
1721+ for i, it in enumerate(items):
1722+ if it.root_name == item.root_name:
1723+ break
1724+
1725+ if i+1 < len(items):
1726+ it = items[i+1]
1727+ if it.get_property('visible'):
1728+ return it
1729+ return None
1730+
1731+ def item_prev(self, item):
1732+ items = self.vbox.get_children()
1733+ for i, it in enumerate(items):
1734+ if it.root_name == item.root_name:
1735+ break
1736+
1737+ if i-1 >= 0:
1738+ it = items[i-1]
1739+ if it.get_property('visible'):
1740+ return it
1741+ return None
1742+
1743+ def pack_start(self, *args, **kwargs):
1744+ gtk.VBox.pack_start(self.vbox, *args, **kwargs)
1745+ return
1746+
1747+ def pack_end(self, *args, **kwargs):
1748+ gtk.VBox.pack_end(self.vbox, *args, **kwargs)
1749+ return
1750+
1751+ def expand_available_node(self):
1752+ """ expand the available pane node in the viewswitcher pane """
1753+ a = self.get_item_by_id(VIEW_PAGE_AVAILABLE)
1754+ a.expand_row((0,), True)
1755+ return
1756+
1757+ def is_available_node_expanded(self):
1758+ """ return True if the available pane node in the viewswitcher pane is expanded """
1759+ a = self.get_item_by_id(VIEW_PAGE_AVAILABLE)
1760+ return a.is_expanded()
1761+
1762+ def expand_installed_node(self):
1763+ """ expand the installed pane node in the viewswitcher pane """
1764+ i = self.get_item_by_id(VIEW_PAGE_INSTALLED)
1765+ i.expand_row((0,), True)
1766+ return
1767+
1768+ def is_installed_node_expanded(self):
1769+ """ return True if the installed pane node in the viewswitcher pane is expanded """
1770+ i = self.get_item_by_id(VIEW_PAGE_INSTALLED)
1771+ return i.is_expanded()
1772+
1773+ # treeview stubination
1774+ def set_cursor(self, *args, **kwargs):
1775+ logging.warn('ViewSwitcher.set_cursor: Please use set_view instead.')
1776+ return
1777+
1778
1779 if __name__ == "__main__":
1780 logging.basicConfig(level=logging.DEBUG)
1781
1782=== modified file 'softwarecenter/view/widgets/animatedimage.py'
1783--- softwarecenter/view/widgets/animatedimage.py 2010-08-26 13:44:44 +0000
1784+++ softwarecenter/view/widgets/animatedimage.py 2010-11-26 21:34:16 +0000
1785@@ -41,6 +41,7 @@
1786 """
1787 super(AnimatedImage, self).__init__()
1788 self._progressN = 0
1789+ self._run = False
1790 if icon is None:
1791 icon = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 1, 1)
1792 icon.fill(0)
1793@@ -75,7 +76,7 @@
1794 self.connect("show", self.start)
1795 self.connect("hide", self.stop)
1796 else:
1797- raise IOError, "need a str, list or a pixbuf"
1798+ raise IOError, "need a str, list or a pixbuf, got:", icon
1799
1800 def start(self, w=None):
1801 source_id = gobject.timeout_add(int(1000/self.FPS),
1802@@ -98,6 +99,9 @@
1803 self.set_from_pixbuf(self.get_current_pixbuf())
1804 return self._run
1805
1806+ def is_playing(self):
1807+ return self._run
1808+
1809 class CellRendererAnimatedImage(gtk.CellRendererPixbuf):
1810
1811 __gproperties__ = {
1812
1813=== modified file 'softwarecenter/view/widgets/mkit.py'
1814--- softwarecenter/view/widgets/mkit.py 2010-10-03 05:17:08 +0000
1815+++ softwarecenter/view/widgets/mkit.py 2010-11-26 21:34:16 +0000
1816@@ -904,7 +904,6 @@
1817 widest_w = max(widest_w, btn.calc_width())
1818
1819 # determine number of columns to display
1820-# width -= 100 # fudge number
1821 n_columns = width / widest_w
1822 n_columns = (width - n_columns*self.column_hbox.get_spacing()) / widest_w
1823
1824@@ -1152,6 +1151,8 @@
1825 else:
1826 col = self.style.dark[gtk.STATE_NORMAL].to_string()
1827
1828+ text = gobject.markup_escape_text(self.label.get_text())
1829+
1830 if self._use_underline:
1831 self.label.set_markup('<span color="%s"><u>%s</u></span>' % (col, self._markup))
1832 else: