Merge lp:~lottanzb/lottanzb/appindicator into lp:lottanzb/0.5

Proposed by Severin H
Status: Merged
Merged at revision: not available
Proposed branch: lp:~lottanzb/lottanzb/appindicator
Merge into: lp:lottanzb/0.5
Diff against target: 604 lines (+268/-151)
7 files modified
NEWS (+4/-0)
data/ui/plugin_panel_menu.ui (+60/-48)
lottanzb/config.py (+9/-1)
lottanzb/gui/main.py (+19/-16)
lottanzb/gui/prefs.py (+6/-0)
lottanzb/plugins/panel_menu/__init__.py (+168/-84)
lottanzb/plugins/start_minimized/__init__.py (+2/-2)
To merge this branch: bzr merge lp:~lottanzb/lottanzb/appindicator
Reviewer Review Type Date Requested Status
LottaNZB Development Team Pending
Review via email: mp+17794@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Severin H (severinh) wrote :

Tested it both on Ubuntu 10.04 and 9.10. I hope everyone is fine with the arranged panel menu entries. Both checkboxes are at the top now, which seems fairly logical to me. The problem is that otherwise, it will look odd on Ubuntu 10.04.

python-appindicator is not introduced as a dependency. If the "appindicator" module isn't found, LottaNZB will just fall back to a regular gtk.StatusIcon. We therefore don't need to mention it neither in the README nor in debian/control. Right?

lp:~lottanzb/lottanzb/appindicator updated
893. By Severin H

Also display an entry "Preferences" at the bottom of the panel menu,
as stated in https://wiki.ubuntu.com/CustomStatusMenuDesignGuidelines.

For that purpose, a new method `set_current_tab` has been added to `gui.prefs.Window`
and a new method `show_preferences_window` has been added to `gui.main.Window`.

One might want to rethink this as these guidelines still seem to be a work in progress.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-01-20 22:47:39 +0000
3+++ NEWS 2010-01-21 11:18:11 +0000
4@@ -1,6 +1,10 @@
5 LottaNZB 0.5.3 - Development
6 ============================
7
8+- Use the new application indicator approach to be introduced in Ubuntu 10.04.
9+ The "Notification area" plug-in has therefore been renamed to "Panel menu".
10+ Users of other distributions or older Ubuntu versions will only notice a
11+ slight change in the order of panel menu items.
12 - Fix crash on machines with GTK 2.19 or later (e.g. Ubuntu 10.04).
13 (LP: #507739)
14 - Add 64x64 application icon to fix scaling issue in Ubuntu's Software Center.
15
16=== renamed file 'data/ui/plugin_notification_area_menu.ui' => 'data/ui/plugin_panel_menu.ui'
17--- data/ui/plugin_notification_area_menu.ui 2009-09-08 18:37:03 +0000
18+++ data/ui/plugin_panel_menu.ui 2010-01-21 11:18:11 +0000
19@@ -2,54 +2,7 @@
20 <interface>
21 <requires lib="gtk+" version="2.16"/>
22 <!-- interface-naming-policy toplevel-contextual -->
23- <object class="GtkMenu" id="menu">
24- <property name="visible">True</property>
25- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
26- <child>
27- <object class="GtkCheckMenuItem" id="menuitem3">
28- <property name="visible">True</property>
29- <property name="related_action">open</property>
30- <property name="use_action_appearance">True</property>
31- <property name="use_underline">True</property>
32- </object>
33- </child>
34- <child>
35- <object class="GtkSeparatorMenuItem" id="menuitem5">
36- <property name="visible">True</property>
37- </object>
38- </child>
39- <child>
40- <object class="GtkImageMenuItem" id="menuitem1">
41- <property name="visible">True</property>
42- <property name="related_action">add</property>
43- <property name="use_action_appearance">True</property>
44- <property name="use_underline">True</property>
45- <property name="use_stock">True</property>
46- </object>
47- </child>
48- <child>
49- <object class="GtkCheckMenuItem" id="menuitem4">
50- <property name="visible">True</property>
51- <property name="related_action">pause</property>
52- <property name="use_action_appearance">True</property>
53- </object>
54- </child>
55- <child>
56- <object class="GtkSeparatorMenuItem" id="menuitem6">
57- <property name="visible">True</property>
58- </object>
59- </child>
60- <child>
61- <object class="GtkImageMenuItem" id="menuitem2">
62- <property name="visible">True</property>
63- <property name="related_action">quit</property>
64- <property name="use_action_appearance">True</property>
65- <property name="use_underline">True</property>
66- <property name="use_stock">True</property>
67- </object>
68- </child>
69- </object>
70- <object class="GtkWindow" id="plugin_notification_area_menu">
71+ <object class="GtkWindow" id="plugin_panel_menu">
72 <child>
73 <object class="GtkHBox" id="hbox1">
74 <property name="visible">True</property>
75@@ -72,4 +25,63 @@
76 <property name="label" translatable="yes">Pause all</property>
77 <property name="stock_id">gtk-media-pause</property>
78 </object>
79+ <object class="GtkAction" id="show_preferences">
80+ <property name="stock_id">gtk-preferences</property>
81+ </object>
82+ <object class="GtkMenu" id="menu">
83+ <property name="visible">True</property>
84+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
85+ <child>
86+ <object class="GtkCheckMenuItem" id="menuitem3">
87+ <property name="visible">True</property>
88+ <property name="use_action_appearance">True</property>
89+ <property name="related_action">open</property>
90+ <property name="use_underline">True</property>
91+ </object>
92+ </child>
93+ <child>
94+ <object class="GtkCheckMenuItem" id="menuitem4">
95+ <property name="visible">True</property>
96+ <property name="use_action_appearance">True</property>
97+ <property name="related_action">pause</property>
98+ </object>
99+ </child>
100+ <child>
101+ <object class="GtkSeparatorMenuItem" id="menuitem5">
102+ <property name="visible">True</property>
103+ </object>
104+ </child>
105+ <child>
106+ <object class="GtkImageMenuItem" id="menuitem1">
107+ <property name="visible">True</property>
108+ <property name="use_action_appearance">True</property>
109+ <property name="related_action">add</property>
110+ <property name="use_underline">True</property>
111+ <property name="use_stock">True</property>
112+ </object>
113+ </child>
114+ <child>
115+ <object class="GtkImageMenuItem" id="menuitem2">
116+ <property name="visible">True</property>
117+ <property name="use_action_appearance">True</property>
118+ <property name="related_action">quit</property>
119+ <property name="use_underline">True</property>
120+ <property name="use_stock">True</property>
121+ </object>
122+ </child>
123+ <child>
124+ <object class="GtkSeparatorMenuItem" id="menuitem6">
125+ <property name="visible">True</property>
126+ </object>
127+ </child>
128+ <child>
129+ <object class="GtkImageMenuItem" id="menuitem7">
130+ <property name="visible">True</property>
131+ <property name="use_action_appearance">True</property>
132+ <property name="related_action">show_preferences</property>
133+ <property name="use_stock">True</property>
134+ <property name="use_underline">True</property>
135+ </object>
136+ </child>
137+ </object>
138 </interface>
139
140=== modified file 'lottanzb/config.py'
141--- lottanzb/config.py 2010-01-16 09:22:53 +0000
142+++ lottanzb/config.py 2010-01-21 11:18:11 +0000
143@@ -811,7 +811,7 @@
144 backend = gproperty(type=object)
145 plugins = gproperty(type=object)
146
147- revision = gproperty(type=int, default=2)
148+ revision = gproperty(type=int, default=3)
149
150 def load(self):
151 """
152@@ -893,6 +893,14 @@
153 ["gui", "start_minimized"],
154 ["plugins", "start_minimized", "enabled"]
155 )
156+
157+ def upgrade_2_to_3(self):
158+ """The "notification_area" plug-in has been renamed to "panel_menu"."""
159+
160+ self.try_to_move_option(
161+ ["plugins", "notification_area"],
162+ ["plugins", "panel_menu"]
163+ )
164
165 def convert_xml_config(self):
166 """
167
168=== modified file 'lottanzb/gui/main.py'
169--- lottanzb/gui/main.py 2010-01-16 09:22:53 +0000
170+++ lottanzb/gui/main.py 2010-01-21 11:18:11 +0000
171@@ -126,6 +126,24 @@
172
173 self.emit("add-file-dialog-opened", dialog)
174
175+ def show_preferences_window(self):
176+ """
177+ Displays the preferences window. If it has already been opened, bring
178+ it to the front.
179+ """
180+
181+ if self.prefs_window:
182+ self.prefs_window.toplevel.present()
183+ else:
184+ def on_prefs_window_destroy(*args):
185+ self.prefs_window = None
186+
187+ self.prefs_window = prefs.Window()
188+ self.prefs_window.show(self)
189+
190+ self.prefs_window.toplevel.connect("destroy",
191+ on_prefs_window_destroy)
192+
193 def remove_download(self, download):
194 q = _("Are you sure you want to cancel this download?")
195
196@@ -236,22 +254,7 @@
197 return False
198
199 def on_edit_preferences__activate(self, widget):
200- """
201- Displays the preferences window. If it has already been opened, bring
202- it to the front.
203- """
204-
205- if self.prefs_window:
206- self.prefs_window.toplevel.present()
207- else:
208- def on_prefs_window_destroy(*args):
209- self.prefs_window = None
210-
211- self.prefs_window = prefs.Window()
212- self.prefs_window.show(self)
213-
214- self.prefs_window.toplevel.connect("destroy",
215- on_prefs_window_destroy)
216+ self.show_preferences_window()
217
218 def on_select_mode__activate(self, widget):
219 dialog = mode_selection.Dialog()
220
221=== modified file 'lottanzb/gui/prefs.py'
222--- lottanzb/gui/prefs.py 2010-01-16 09:22:53 +0000
223+++ lottanzb/gui/prefs.py 2010-01-21 11:18:11 +0000
224@@ -153,6 +153,12 @@
225 self.detach_slave(name)
226 self.notebook.remove_page(self.notebook.page_num(eventbox))
227
228+ def set_current_tab(self, tab):
229+ eventbox = tab.toplevel.parent
230+ page_num = self.notebook.page_num(eventbox)
231+
232+ self.notebook.set_current_page(page_num)
233+
234 def _compare_tabs(self, tab, other_tab):
235 """
236 Can be used to check if a tab has a higher priority than an other tab.
237
238=== renamed directory 'lottanzb/plugins/notification_area' => 'lottanzb/plugins/panel_menu'
239=== modified file 'lottanzb/plugins/panel_menu/__init__.py'
240--- lottanzb/plugins/notification_area/__init__.py 2010-01-16 09:22:53 +0000
241+++ lottanzb/plugins/panel_menu/__init__.py 2010-01-21 11:18:11 +0000
242@@ -20,110 +20,85 @@
243 from lottanzb.plugins import PluginBase, PluginConfig, PluginPrefsTabBase
244 from lottanzb.util import gproperty, _
245
246+try:
247+ import appindicator
248+except ImportError:
249+ appindicator = None
250+
251 class Config(PluginConfig):
252 """The plug-in is enabled by default."""
253
254 enabled = gproperty(type=bool, default=True)
255
256+
257 class Plugin(PluginBase):
258- title = _("Notification area icon")
259- description = _("Displays an icon in the notification area and allows "
260- "LottaNZB to be controlled through it.")
261+ title = _("Panel menu")
262+ description = _("Control LottaNZB using a menu in the panel.")
263 author = _("LottaNZB Development Team")
264
265- # The gtk.StatusIcon object that represents the icon in the
266- # notification area.
267- icon = None
268+ # Contains a `MenuContainer` object that represents the actual icon in
269+ # the panel. On Ubuntu 10.04 or later, this comes in the form of an
270+ # application indicator. On other systems, a simple gtk.StatusIcon is
271+ # used instead.
272+ menu_container = None
273
274+ # The `GladeDelegate` object holding the `gtk.Menu` as a property `menu`.
275 menu_delegate = None
276+
277 main_window = None
278
279 def refresh(self):
280 """
281- Makes sure that the application is not entirely shut down if the main
282+ Make sure that the application is not entirely shut down if the main
283 window is closed while the plug-in is enabled.
284 """
285
286- if self.main_window:
287+ if self.main_window is not None:
288 self.main_window.quit_on_close = not self.enabled
289
290 def load(self):
291 """
292- Creates the status icon if necessary and makes sure that it's visible.
293+ Create the `menu_container` if necessary and make sure that it's
294+ visible.
295 """
296
297- if self.icon:
298- def show_icon():
299- self.icon.set_visible(True)
300-
301- self.do_with_rgb_colormap(show_icon)
302- elif self.main_window:
303- def create_icon():
304- """
305- Helper method that creates the notification area icon and
306- registers all event handlers.
307- """
308+ if self.menu_container is None:
309+ if self.main_window is not None:
310+ # Choose the right `MenuContainer`.
311+ menu_container_classes = (IndicatorMenuContainer,
312+ StatusIconMenuContainer)
313
314- self.icon = gtk.StatusIcon()
315- self.icon.set_from_icon_name("lottanzb")
316- self.icon.set_tooltip("LottaNZB")
317- self.icon.connect("popup-menu", self.show_menu)
318- self.icon.connect("activate", self.toggle_main_window)
319-
320- self.do_with_rgb_colormap(create_icon)
321+ for menu_container_class in menu_container_classes:
322+ try:
323+ self.menu_container = menu_container_class("lottanzb",
324+ self.menu_delegate.menu, self.main_window)
325+ except:
326+ pass
327+ else:
328+ break
329+
330+ # Left-clicking on the `gtk.StatusIcon` should cause the main
331+ # window to be toggled. For the new-fasioned application
332+ # indicators, it will just open the associated menu.
333+ # TODO: This unelegant code breaks the otherwise okay-ish
334+ # abstraction of `MenuContainer`, but it works.
335+ if isinstance(self.menu_container, StatusIconMenuContainer):
336+ self.menu_container._icon.connect("activate",
337+ self.toggle_main_window)
338+ else:
339+ self.menu_container.set_visible(True)
340
341 PluginBase.load(self)
342
343 def unload(self):
344- """Hides the status menu if necessary."""
345+ """Hide the `menu_container`."""
346
347- if self.icon:
348- self.icon.set_visible(False)
349+ if self.menu_container:
350+ self.menu_container.set_visible(False)
351
352 PluginBase.unload(self)
353
354- def do_with_rgb_colormap(self, function):
355- """
356- Workaround for bug http://bugzilla.gnome.org/show_bug.cgi?id=501842
357-
358- This method ensures that the default RGB color map is active while the
359- passed function is being executed and returns to the previous color
360- map afterwards.
361- """
362-
363- # We cannot do it without the gtk.Screen provided by the main window.
364- if self.main_window:
365- screen = self.main_window.toplevel.get_screen()
366- current_colormap = screen.get_default_colormap()
367-
368- try:
369- assert current_colormap == screen.get_rgba_colormap()
370- except (AttributeError, AssertionError):
371- rgba_support = False
372- else:
373- screen.set_default_colormap(screen.get_rgb_colormap())
374- rgba_support = True
375-
376- function()
377-
378- # Restore the RGBA color map if necessary.
379- if rgba_support:
380- screen.set_default_colormap(current_colormap)
381-
382- def show_menu(self, icon, button, activate_time):
383- """Display the status icon menu."""
384-
385- position = gtk.status_icon_position_menu
386-
387- self.menu_delegate.menu.popup(None, None, position, button, \
388- activate_time, icon)
389-
390 def toggle_main_window(self, *args):
391- """
392- Does what the method name says. ;-) It just changes the visibility of
393- the main window.
394- """
395-
396 if self.main_window_visible:
397 self.main_window.hide()
398 else:
399@@ -132,10 +107,7 @@
400 return True
401
402 def on_main_window_ready(self, main_window):
403- """
404- Internal method that registers the 'Close' file menu entry as well
405- as the notification area icon menu.
406- """
407+ """Register the 'Close' file menu entry as well as the panel menu."""
408
409 self.main_window = main_window
410
411@@ -156,17 +128,22 @@
412 self.menu_delegate.add.connect("activate", self.on_add_activated)
413 self.menu_delegate.pause.connect("toggled", self.on_pause_toggled)
414 self.menu_delegate.quit.connect("activate", self.on_quit_activated)
415+ self.menu_delegate.show_preferences.connect("activate",
416+ self.on_show_preferences_activated)
417
418 self.main_window.toplevel.connect("show", self.update_open_checkbox)
419 self.main_window.toplevel.connect("hide", self.update_open_checkbox)
420 self.main_window.pause.connect("activate", self.update_pause_checkbox)
421
422+ self.update_open_checkbox()
423+ self.update_pause_checkbox()
424+
425 if self.enabled:
426 self.load()
427
428 def on_open_toggled(self, widget):
429 """
430- Makes sure that the main window's visibility matches the state of the
431+ Make sure that the main window's visibility matches the state of the
432 'Show main window' checkbox.
433 """
434
435@@ -198,22 +175,40 @@
436
437 self.app.quit()
438
439- def update_open_checkbox(self, window):
440+ def on_show_preferences_activated(self, *args):
441+ """
442+ Show the preferences window, but also jump to the "Plug-ins" tab and
443+ select the "Panel menu" item.
444+
445+ This behaviour is meant to make it easy for a user to hide the panel
446+ item again and is recommended by
447+ https://wiki.ubuntu.com/CustomStatusMenuDesignGuidelines
448+ """
449+
450+ if self.main_window is not None:
451+ self.main_window.show_preferences_window()
452+
453+ prefs_window = self.main_window.prefs_window
454+ prefs_window.set_current_tab(prefs_window.plugins_tab)
455+ prefs_window.plugins_tab.plugin_list.select(self)
456+
457+ def update_open_checkbox(self, *args):
458 """
459 If the main window is visibility of the main window is changed by the
460 user by closing the window etc, this method changes the 'Show main
461- window' checkbox in the notification area icon menu accordingly.
462+ window' checkbox in the panel menu accordingly.
463 """
464
465- self.menu_delegate.open.set_active(window.get_property("visible"))
466+ self.menu_delegate.open.set_active(self.main_window_visible)
467
468- def update_pause_checkbox(self, widget):
469+ def update_pause_checkbox(self, *args):
470 """
471 If the downloads are paused from the main window, also update the
472- checkbox in the notification area menu.
473+ checkbox in the panel menu.
474 """
475
476- self.menu_delegate.pause.set_active(widget.get_active())
477+ paused = self.main_window.pause.get_active()
478+ self.menu_delegate.pause.set_active(paused)
479
480 @property
481 def main_window_visible(self):
482@@ -224,11 +219,100 @@
483
484 return self.main_window.toplevel.get_property("visible")
485
486+
487 class MenuDelegate(GladeDelegate):
488 """
489- This glade file contains a nearly empty gtk.Window, which isn't actually
490+ This UI file contains a nearly empty gtk.Window, which isn't actually
491 used, but necessary because Kiwi doesn't allow delegates with a gtk.Menu
492 root object.
493 """
494
495- gladefile = "plugin_notification_area_menu"
496+ gladefile = "plugin_panel_menu"
497+
498+
499+class MenuContainer(object):
500+ def __init__(self, application, menu, main_window):
501+ self.application = application
502+ self.menu = menu
503+ self.main_window = main_window
504+
505+ def set_visible(self, visible):
506+ raise NotImplementedError
507+
508+
509+class IndicatorMenuContainer(MenuContainer):
510+ def __init__(self, application, menu, main_window):
511+ MenuContainer.__init__(self, application, menu, main_window)
512+
513+ self._indicator = appindicator.Indicator(self.application,
514+ self.application, appindicator.CATEGORY_APPLICATION_STATUS)
515+
516+ self._indicator.set_status(appindicator.STATUS_ACTIVE)
517+ self._indicator.set_menu(self.menu)
518+
519+ def set_visible(self, visible):
520+ if visible:
521+ status = appindicator.STATUS_ACTIVE
522+ else:
523+ status = appindicator.STATUS_PASSIVE
524+
525+ self._indicator.set_status(status)
526+
527+
528+class StatusIconMenuContainer(MenuContainer):
529+ def __init__(self, application, menu, main_window):
530+ MenuContainer.__init__(self, application, menu, main_window)
531+
532+ self._icon = None
533+ self._do_with_rgb_colormap(self._create_icon)
534+
535+ def set_visible(self, visible):
536+ def show_icon():
537+ self._icon.set_visible(visible)
538+
539+ self._do_with_rgb_colormap(show_icon)
540+
541+ def _do_with_rgb_colormap(self, function):
542+ """
543+ Workaround for bug http://bugzilla.gnome.org/show_bug.cgi?id=501842
544+
545+ This method ensures that the default RGB color map is active while the
546+ passed function is being executed and returns to the previous color
547+ map afterwards.
548+ """
549+
550+ # We cannot do it without the gtk.Screen provided by the main window.
551+ screen = self.main_window.toplevel.get_screen()
552+ current_colormap = screen.get_default_colormap()
553+
554+ try:
555+ assert current_colormap == screen.get_rgba_colormap()
556+ except (AttributeError, AssertionError):
557+ rgba_support = False
558+ else:
559+ screen.set_default_colormap(screen.get_rgb_colormap())
560+ rgba_support = True
561+
562+ function()
563+
564+ # Restore the RGBA color map if necessary.
565+ if rgba_support:
566+ screen.set_default_colormap(current_colormap)
567+
568+ def _show_menu(self, icon, button, activate_time):
569+ """Display the status icon menu."""
570+
571+ position = gtk.status_icon_position_menu
572+
573+ self.menu.popup(None, None, position, button, \
574+ activate_time, icon)
575+
576+ def _create_icon(self):
577+ """
578+ Helper method that creates the `gtk.StatusIcon` and
579+ registers all event handlers.
580+ """
581+
582+ self._icon = gtk.StatusIcon()
583+ self._icon.set_from_icon_name(self.application)
584+ self._icon.connect("popup-menu", self._show_menu)
585
586=== modified file 'lottanzb/plugins/start_minimized/__init__.py'
587--- lottanzb/plugins/start_minimized/__init__.py 2010-01-16 09:22:53 +0000
588+++ lottanzb/plugins/start_minimized/__init__.py 2010-01-21 11:18:11 +0000
589@@ -20,7 +20,7 @@
590 title = _("Start minimized")
591 description = _("Only display the notification area icon when starting.")
592 author = _("LottaNZB Development Team")
593- requires = ["notification_area"]
594+ requires = ["panel_menu"]
595
596 def __init__(self, app, config):
597 PluginBase.__init__(self, app, config)
598@@ -28,4 +28,4 @@
599 # Prevent the main window from being displayed if the plug-in is
600 # enabled.
601 if self.enabled:
602- self.app.silent_start = True
603\ No newline at end of file
604+ self.app.silent_start = True

Subscribers

People subscribed via source and target branches

to all changes: