Merge lp:~mmcg069/software-center/viewswitcher2 into lp:software-center
- viewswitcher2
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Vogt | Needs Information | ||
Matthew Paul Thomas | design | Approve | |
Review via email: mp+32867@code.launchpad.net |
Commit message
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...
Matthew Paul Thomas (mpt) wrote : | # |
Also, the lozenge currently has a mouseover effect and should not.
- 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.
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.
softwarecenter/
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:/
> You are the owner of lp:~mmcg069/software-center/viewswitcher2.
>
--
From the mind of me!
Matthew Paul Thomas (mpt) wrote : | # |
Nicely done, thanks. Design approved.
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:/
> 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.
Michael Vogt (mvo) wrote : | # |
With the new gtk3 UI this branch is no longer relevant, right? Or will it need porting?
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:/
> You are the owner of lp:~mmcg069/software-center/viewswitcher2.
>
--
From the mind of me!
Michael Vogt (mvo) wrote : | # |
Thanks! I set it to rejected to clean the merge overview page now.
And HAPPY NEW YEAR to you :)
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:/
> 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
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: |
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.