Merge lp:~onox/awn-extras/common-folder-applet into lp:awn-extras

Proposed by onox
Status: Merged
Merged at revision: 1399
Proposed branch: lp:~onox/awn-extras/common-folder-applet
Merge into: lp:awn-extras
Diff against target: 673 lines (+398/-56)
9 files modified
applets/Makefile.am (+1/-0)
applets/maintained/common-folder/Makefile.am (+3/-0)
applets/maintained/common-folder/common-folder.desktop.in (+10/-0)
applets/maintained/common-folder/common-folder.py (+176/-0)
applets/maintained/hardware-sensors/hardware-sensors.py (+2/-2)
applets/maintained/mail/mail.py (+3/-3)
applets/maintained/weather/weather.py (+1/-1)
configure.ac (+1/-0)
shared/python/awnlib.py (+201/-50)
To merge this branch: bzr merge lp:~onox/awn-extras/common-folder-applet
Reviewer Review Type Date Requested Status
Michal Hruby (community) Abstain
Review via email: mp+35612@code.launchpad.net

Description of the change

New applet called Common Folder Launcher. Currently it displays an icon that links to the Home Folder, to Desktop, and to all bookmarks. The appropriate icon is used for XDG directories. I wrote this applet because file-browser-launcher didn't show the appropriate icons for XDG folders, is too much code and requires configuration. Although this applet has less features, it has much less code (98 SLOC compared to f-b-l's 1255 SLOC) and needs no configuration.

- Currently assumes ~/Desktop exists (should treat it as an XDG folder)
- Also does not handle custom icons (people rarely use this, and nautilus does not handle it either in the side pane, only in the main pane) I could add support for it
- If the folder of a local location is created/removed the whole applet gets rebuild instead of inserting/removing the specific icon. This is a minor thing, and the rebuilding process looks OK already. Changing it requires keeping track of positions of icons in the icon box in awnlib, so it could be high cost for little gain if this gets added. The applet also gets rebuild completely when ~/.gtk-bookmarks is modified, and both events rarely occur

To post a comment you must log in.
Revision history for this message
Alberto Aldegheri (albyrock87) wrote :

- class Icons is an awnlib's feature, so (imho) it should implement a "remove(icon)" method

- class Icons: add(self, icon_name, tooltip_text) should be add(self, icon_name, tooltip_text, connect_applet_context_menu = True), for example IndicatorApplet has multiple icons, but different popups menu.

Revision history for this message
Gabor Karsay (gabor-karsay) wrote :

I'm not in the position to review python code, just installed it and it works fine. There is an error though on command line:

/usr/local/lib/python2.6/dist-packages/awn/extras/awnlib.py:1478: Warning: g_object_unref: assertion `G_IS_OBJECT (object)' failed
  gtk.main()

Is this the reason for the TODO you added in awnlibs init_start (line 1468)?

Is this applet meant as a replacement for the Places applet? Just asking, because a while ago I did some experiments with Cairo Menu that allow a standalone Places menu (haven't uploaded it yet).

Revision history for this message
Gabor Karsay (gabor-karsay) wrote :

One more thing: Clicking on remote bookmarks (ftp in this case) gives me:
Error when opening: The specified location is not mounted

Revision history for this message
onox (onox) wrote :

> - class Icons is an awnlib's feature, so (imho) it should implement a
> "remove(icon)" method
>
> - class Icons: add(self, icon_name, tooltip_text) should be add(self,
> icon_name, tooltip_text, connect_applet_context_menu = True), for example
> IndicatorApplet has multiple icons, but different popups menu.

Good point, I'll see about adding it.

> I'm not in the position to review python code, just installed it and it works
> fine. There is an error though on command line:
>
> /usr/local/lib/python2.6/dist-packages/awn/extras/awnlib.py:1478: Warning:
> g_object_unref: assertion `G_IS_OBJECT (object)' failed
> gtk.main()

I don't know what's causing that warning. Seems to be generated somewhere deep in C code.

> Is this the reason for the TODO you added in awnlibs init_start (line 1468)?

No, the TODO is about that for single icon applets you can set the icon to the name "dialog-error". With multiple-icons applets, this is not so simple, perhaps I could remove all icons in the icon box and then add a new icon with the name "dialog-error"

> Is this applet meant as a replacement for the Places applet? Just asking,
> because a while ago I did some experiments with Cairo Menu that allow a
> standalone Places menu (haven't uploaded it yet).

You could see it as something similar to Places. Places shows one icon here though (and an ugly pop-up menu if you click on the icon), and an ugly preferences window.

> One more thing: Clicking on remote bookmarks (ftp in this case) gives me:
> Error when opening: The specified location is not mounted

This is the same as with yama, because lda doesn't mount automatically like pyxdg did (which yama used to use). mhr3 has a potential fix for the gio vfs backend of lda.

Revision history for this message
Alberto Aldegheri (albyrock87) wrote :

> With multiple-icons applets, this is not so simple,
> perhaps I could remove all icons in the icon box and then add a new icon with
> the name "dialog-error"
Yes, this is a good idea! :D

> You could see it as something similar to Places. Places shows one icon here
> though (and an ugly pop-up menu if you click on the icon), and an ugly
> preferences window.
I like f-b-l!!!!
I'll switch to "Common Folders" only if it'll have the possibility to choose which icons to show..

Revision history for this message
Michal Hruby (mhr3) wrote :

As always with applets that duplicate functionality of other applets I don't like it. As you alone said, currently the applet doesn't really provide any benefits to f-b-l, besides that it doesn't do configuration (which is a questionable "benefit"). IMO it'd be much better to fix fbl issues, than to write completely new applet, and when you do write completely new applet, it should try to replace said applet, ie. provide feature-parity or at least other significant benefits (like being faster/more mem efficient etc)...

But ultimately this has been practised for quite some time in extras, so I'll leave the merging up to you...

review: Abstain
1392. By onox

Merge from trunk

1393. By onox

awnlib: add utility function is_required_version

1394. By onox

common-folder: display XDG user special folders (if python glib version is at least 2.18.0)

Revision history for this message
onox (onox) wrote :

The applet now displays the XDG user special folders if they exist. Desktop folder is now also treated as an XDG special folder.

> > You could see it as something similar to Places. Places shows one icon here
> > though (and an ugly pop-up menu if you click on the icon), and an ugly
> > preferences window.
> I like f-b-l!!!!
> I'll switch to "Common Folders" only if it'll have the possibility to choose
> which icons to show..

It's purpose is to show icons for the XDG user special folders and the bookmarks.

1395. By onox

awnlib: add documentation to is_required_version

1396. By onox

awnlib: add remove procedure in class Icons and add context_menu parameter to add procedure

1397. By onox

Merge from trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'applets/Makefile.am'
2--- applets/Makefile.am 2010-07-04 19:43:41 +0000
3+++ applets/Makefile.am 2010-09-17 17:39:43 +0000
4@@ -65,6 +65,7 @@
5 maintained/battery \
6 maintained/cairo-clock \
7 maintained/comics \
8+ maintained/common-folder \
9 maintained/cpufreq \
10 maintained/dialect \
11 maintained/digital-clock \
12
13=== added directory 'applets/maintained/common-folder'
14=== added file 'applets/maintained/common-folder/Makefile.am'
15--- applets/maintained/common-folder/Makefile.am 1970-01-01 00:00:00 +0000
16+++ applets/maintained/common-folder/Makefile.am 2010-09-17 17:39:43 +0000
17@@ -0,0 +1,3 @@
18+APPLET_NAME = common-folder
19+APPLET_MAIN_FILE = common-folder.py
20+include $(top_srcdir)/Makefile.python-applet
21
22=== added file 'applets/maintained/common-folder/common-folder.desktop.in'
23--- applets/maintained/common-folder/common-folder.desktop.in 1970-01-01 00:00:00 +0000
24+++ applets/maintained/common-folder/common-folder.desktop.in 2010-09-17 17:39:43 +0000
25@@ -0,0 +1,10 @@
26+[Desktop Entry]
27+Type=Application
28+X-AWN-Type=Applet
29+X-AWN-AppletType=Python
30+_Name=Common Folder Launcher
31+_Comment=Launcher for common folders and bookmarks
32+Exec=awn-applet -p %k
33+X-AWN-AppletExec=common-folder/common-folder.py
34+Icon=folder
35+X-AWN-AppletCategory=Utility
36
37=== added file 'applets/maintained/common-folder/common-folder.py'
38--- applets/maintained/common-folder/common-folder.py 1970-01-01 00:00:00 +0000
39+++ applets/maintained/common-folder/common-folder.py 2010-09-17 17:39:43 +0000
40@@ -0,0 +1,176 @@
41+#!/usr/bin/python
42+# Copyright (C) 2010 onox <denkpadje@gmail.com>
43+#
44+# This program is free software: you can redistribute it and/or modify
45+# it under the terms of the GNU General Public License as published by
46+# the Free Software Foundation, version 3 of the License.
47+#
48+# This program is distributed in the hope that it will be useful,
49+# but WITHOUT ANY WARRANTY; without even the implied warranty of
50+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51+# GNU General Public License for more details.
52+#
53+# You should have received a copy of the GNU General Public License
54+# along with this program. If not, see <http://www.gnu.org/licenses/>.
55+
56+from __future__ import with_statement
57+
58+import os
59+import re
60+from urllib import unquote
61+
62+import pygtk
63+pygtk.require("2.0")
64+import gtk
65+
66+from awn.extras import _, awnlib, __version__
67+from desktopagnostic import fdo, vfs
68+
69+import gio
70+import glib
71+
72+applet_name = _("Common Folder Launcher")
73+applet_description = _("Applet to launch common folders and bookmarks")
74+
75+# Applet's themed icon, also shown in the GTK About dialog
76+applet_logo = "folder"
77+
78+# Describes the pattern used to try to decode URLs
79+url_pattern = re.compile("^[a-z]+://(?:[^@]+@)?([^/]+)/(.*)$")
80+
81+user_path = os.path.expanduser("~/")
82+bookmarks_file = os.path.expanduser("~/.gtk-bookmarks")
83+
84+pyglib_ok = awnlib.is_required_version(glib.pyglib_version, (2, 18, 0))
85+
86+if pyglib_ok:
87+ # Ordered sequence of XDG user special folders
88+ user_dirs = [glib.USER_DIRECTORY_DOCUMENTS,
89+ glib.USER_DIRECTORY_MUSIC,
90+ glib.USER_DIRECTORY_PICTURES,
91+ glib.USER_DIRECTORY_VIDEOS,
92+ glib.USER_DIRECTORY_DOWNLOAD,
93+ glib.USER_DIRECTORY_PUBLIC_SHARE,
94+ glib.USER_DIRECTORY_TEMPLATES]
95+ xdg_user_uris = ["file://%s" % glib.get_user_special_dir(dir) for dir in user_dirs]
96+else:
97+ xdg_user_uris = []
98+
99+
100+class CommonFolderApplet:
101+
102+ """Applet to launch common folders and bookmarks.
103+
104+ """
105+
106+ def __init__(self, applet):
107+ self.applet = applet
108+
109+ self.__monitors = []
110+
111+ self.icon_theme = gtk.icon_theme_get_default()
112+
113+ # Monitor bookmarks file for changes
114+ self.__bookmarks_monitor = gio.File(bookmarks_file).monitor_file() # keep a reference to avoid getting it garbage collected
115+ def bookmarks_changed_cb(monitor, file, other_file, event):
116+ if event == gio.FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
117+ self.rebuild_icons()
118+ self.__bookmarks_monitor.connect("changed", bookmarks_changed_cb)
119+
120+ self.rebuild_icons()
121+
122+ def rebuild_icons(self):
123+ glib.idle_add(self.add_folders_and_bookmarks)
124+
125+ def add_folders_and_bookmarks(self):
126+ self.applet.icons.destroy_all()
127+
128+ # Destroy all current local bookmark monitors
129+ for monitor in self.__monitors:
130+ monitor.cancel()
131+ self.__monitors = []
132+
133+ self.add_folder_icon(_("Home Folder"), "user-home", "file://%s" % user_path)
134+
135+ if pyglib_ok:
136+ # Add Desktop
137+ desktop_path = glib.get_user_special_dir(glib.USER_DIRECTORY_DESKTOP)
138+ if desktop_path != user_path:
139+ self.add_folder_icon(glib.filename_display_basename(desktop_path), "user-desktop", "file://%s" % desktop_path)
140+
141+ # Add XDG user special folders
142+ for uri in xdg_user_uris:
143+ self.add_url_name(uri)
144+
145+ # Add bookmarks
146+ if os.path.isfile(bookmarks_file):
147+ with open(bookmarks_file) as f:
148+ for url_name in (i.rstrip().split(" ", 1) for i in f):
149+ if url_name[0] not in xdg_user_uris:
150+ self.add_url_name(*url_name)
151+
152+ def add_url_name(self, uri, name=None):
153+ if name is None:
154+ match = url_pattern.match(uri)
155+ if match is not None:
156+ name = "/%s on %s" % (match.group(2), match.group(1))
157+ else:
158+ basename = glib.filename_display_basename(uri)
159+ name = unquote(str(basename))
160+
161+ if uri.startswith("file://"):
162+ file = vfs.File.for_uri(uri)
163+ monitor = file.monitor()
164+ self.__monitors.append(monitor)
165+ monitor.connect("changed", self.file_changed_cb)
166+
167+ if not file.exists():
168+ return
169+
170+ file = gio.File(uri)
171+ info = file.query_info(gio.FILE_ATTRIBUTE_STANDARD_ICON, gio.FILE_QUERY_INFO_NONE)
172+ icon = self.get_icon_name(info.get_attribute_object(gio.FILE_ATTRIBUTE_STANDARD_ICON))
173+ else:
174+ icon = "folder-remote"
175+
176+ self.add_folder_icon(name, icon, uri)
177+
178+ def add_folder_icon(self, label, icon_name, uri):
179+ icon = self.applet.icons.add(icon_name, label)
180+ icon.connect("clicked", self.icon_clicked_cb, uri)
181+
182+ def icon_clicked_cb(self, widget, uri):
183+ file = vfs.File.for_uri(uri)
184+
185+ if file is not None and (not uri.startswith("file://") or file.exists()):
186+ try:
187+ file.launch()
188+ except glib.GError, e:
189+ print "Error when opening: %s" % e
190+ else:
191+ print "File at URI not found (%s)" % uri
192+
193+ def get_icon_name(self, icon):
194+ if gio.pygio_version >= (2, 17, 0) and isinstance(icon, gio.EmblemedIcon):
195+ icon = icon.get_icon()
196+
197+ if isinstance(icon, gio.ThemedIcon):
198+ return filter(self.icon_theme.has_icon, icon.get_names())[0]
199+ elif isinstance(icon, gio.FileIcon):
200+ return icon.get_file().get_path()
201+
202+ def file_changed_cb(self, monitor, file, other_file, event):
203+ if event in (vfs.FILE_MONITOR_EVENT_CREATED, vfs.FILE_MONITOR_EVENT_DELETED):
204+ self.rebuild_icons()
205+
206+
207+if __name__ == "__main__":
208+ awnlib.init_start(CommonFolderApplet, {"name": applet_name,
209+ "short": "common-folder",
210+ "version": __version__,
211+ "description": applet_description,
212+ "theme": applet_logo,
213+ "author": "onox",
214+ "copyright-year": 2010,
215+ "authors": ["onox <denkpadje@gmail.com>"]},
216+ ["multiple-icons"])
217
218=== modified file 'applets/maintained/hardware-sensors/hardware-sensors.py'
219--- applets/maintained/hardware-sensors/hardware-sensors.py 2010-08-19 20:27:00 +0000
220+++ applets/maintained/hardware-sensors/hardware-sensors.py 2010-09-17 17:39:43 +0000
221@@ -88,7 +88,7 @@
222 print subject + ".", message
223
224 # Show massage with awn notify
225- self.applet.notify.send(subject=subject, body=message, icon=applet_logo)
226+ self.applet.notification.send(subject=subject, body=message, icon=applet_logo)
227 # Show "no sensors found" icon
228 self.applet.icon.file(no_sensors_icon, size=applet.get_size())
229 self.applet.tooltip.set(message)
230@@ -606,7 +606,7 @@
231 # === Event handlers === #
232 def alarm_cb(self, sensor, message):
233 """Show alarm message with awn notify."""
234- self.applet.notify.send(subject=None, body=message, icon=applet_logo)
235+ self.applet.notification.send(subject=None, body=message, icon=applet_logo)
236
237 def height_changed_cb(self):
238 """Update the applet's icon to reflect the new height."""
239
240=== modified file 'applets/maintained/mail/mail.py'
241--- applets/maintained/mail/mail.py 2010-09-15 19:35:16 +0000
242+++ applets/maintained/mail/mail.py 2010-09-17 17:39:43 +0000
243@@ -135,7 +135,7 @@
244 else:
245 self.awn.dialog.toggle("main", "hide")
246
247- self.awn.notify.send(_("Mail Applet"),
248+ self.awn.notification.send(_("Mail Applet"),
249 _("Logging in as %s") % key.attrs["username"],
250 self.__getIconPath("login"))
251
252@@ -157,7 +157,7 @@
253 self.awn.theme.icon("error")
254
255 if self.awn.settings["show-network-errors"]:
256- self.awn.notify.send(_("Network error - Mail Applet"), str(e), "")
257+ self.awn.notification.send(_("Network error - Mail Applet"), str(e), "")
258 return
259
260 diffSubjects = [i for i in self.mail.subjects if i not in oldSubjects]
261@@ -166,7 +166,7 @@
262 msg = strMailMessages(len(diffSubjects)) + ":\n" + \
263 "\n".join(diffSubjects)
264
265- self.awn.notify.send(_("New Mail - Mail Applet"), msg,
266+ self.awn.notification.send(_("New Mail - Mail Applet"), msg,
267 self.__getIconPath("mail-unread"))
268
269 self.awn.tooltip.set(strMessages(len(self.mail.subjects)))
270
271=== modified file 'applets/maintained/weather/weather.py'
272--- applets/maintained/weather/weather.py 2010-08-22 20:52:26 +0000
273+++ applets/maintained/weather/weather.py 2010-09-17 17:39:43 +0000
274@@ -202,7 +202,7 @@
275 self.reset_weather_data()
276
277 self.network_handler = self.NetworkHandler()
278- self.notification = applet.notify.create_notification(_("Network error in Weather"), network_error_message, "dialog-warning", 20)
279+ self.notification = applet.notification.create(_("Network error in Weather"), network_error_message, "dialog-warning", 20)
280
281 self.setup_context_menu()
282
283
284=== modified file 'configure.ac'
285--- configure.ac 2010-09-14 23:02:26 +0000
286+++ configure.ac 2010-09-17 17:39:43 +0000
287@@ -319,6 +319,7 @@
288 applets/maintained/cairo-menu/cairo-menu.desktop.in
289 applets/maintained/calendar/Makefile
290 applets/maintained/comics/Makefile
291+applets/maintained/common-folder/Makefile
292 applets/maintained/cpufreq/Makefile
293 applets/maintained/dialect/Makefile
294 applets/maintained/digital-clock/Makefile
295
296=== modified file 'shared/python/awnlib.py'
297--- shared/python/awnlib.py 2010-09-15 19:32:37 +0000
298+++ shared/python/awnlib.py 2010-09-17 17:39:43 +0000
299@@ -73,6 +73,19 @@
300 combobox.add_attribute(text, "text", 0)
301
302
303+def is_required_version(version, required_version):
304+ """Return True if version is higher than or equal to
305+ required_version, False otherwise.
306+
307+ """
308+ for i, j in zip(version, required_version):
309+ if i > j:
310+ return True
311+ elif i < j:
312+ return False
313+ return True
314+
315+
316 class KeyRingError:
317
318 def __init__(self, str):
319@@ -114,6 +127,7 @@
320 self.menu.append(about_item)
321 about_item.connect("activate", lambda w: self.toggle("about"))
322
323+ def connect_signals(self, parent):
324 def popup_menu_cb(widget, event):
325 self.toggle("menu", once=True, event=event)
326 parent.connect("context-menu-popup", popup_menu_cb)
327@@ -223,8 +237,7 @@
328 assert dialog in self.__register, "Dialog '%s' must be registered" % dialog
329
330 if dialog == "menu":
331- self.__register["menu"].show_all()
332- self.__parent.popup_gtk_menu(self.__register["menu"], event.button, event.time)
333+ self.show_menu(self.__parent, event)
334 elif dialog == "about":
335 self.__register["about"].show()
336 self.__register["about"].deiconify()
337@@ -253,6 +266,10 @@
338 if dialog == "preferences":
339 self.__register[dialog].deiconify()
340
341+ def show_menu(self, parent, event):
342+ self.__register["menu"].show_all()
343+ parent.popup_gtk_menu(self.__register["menu"], event.button, event.time)
344+
345 def hide(self):
346 """Hide the currently visible dialog.
347
348@@ -283,8 +300,16 @@
349 self.update_logo_icon()
350 parent.connect_size_changed(self.update_logo_icon)
351 elif "theme" in parent.meta:
352- self.update_theme_icon()
353- parent.connect_size_changed(self.update_theme_icon)
354+ if parent.meta.has_option("multiple-icons"):
355+ self._logo_icon = awn.ThemedIcon()
356+ self._logo_icon.set_info_simple(parent.meta["short"], \
357+ parent.get_uid(), parent.meta["theme"])
358+
359+ self.update_theme_icons()
360+ parent.connect_size_changed(self.update_theme_icons)
361+ else:
362+ self.update_theme_icon()
363+ parent.connect_size_changed(self.update_theme_icon)
364
365 # Connect some signals to be able to hide the window
366 self.connect("response", self.response_event)
367@@ -312,6 +337,13 @@
368 self.set_icon(self.__parent.get_icon() \
369 .get_icon_at_size(self.__parent.get_size()))
370
371+ def update_theme_icons(self):
372+ """Updates the logo to be of the same height as the panel.
373+
374+ """
375+ self.set_icon(self._logo_icon \
376+ .get_icon_at_size(self.__parent.get_size()))
377+
378 class AboutDialog(BaseDialog, gtk.AboutDialog):
379
380 """Applet's About dialog.
381@@ -345,7 +377,10 @@
382 elif "theme" in parent.meta:
383 # It is assumed that the C{awn.Icons}
384 # object has been set via set_awn_icon() in C{Icon}
385- self.set_logo(parent.get_icon().get_icon_at_size(48))
386+ if parent.meta.has_option("multiple-icons"):
387+ self.set_logo(self._logo_icon.get_icon_at_size(48))
388+ else:
389+ self.set_logo(parent.get_icon().get_icon_at_size(48))
390
391 class PreferencesDialog(BaseDialog, gtk.Dialog):
392
393@@ -407,7 +442,7 @@
394 def set(self, text):
395 """Set the applet tooltip.
396
397- @param text: The new tooltip text. Defaults to "".
398+ @param text: The new tooltip text.
399 @type text: C{string}
400
401 """
402@@ -435,7 +470,6 @@
403
404 # Set the themed icon to set the C{awn.Icons} object
405 if "theme" in parent.meta:
406- # TODO does not handle multiple icons yet
407 self.theme(parent.meta["theme"])
408
409 def file(self, file, set=True, size=None):
410@@ -532,6 +566,85 @@
411 self.__parent.get_icon().override_gtk_theme(theme)
412
413
414+class Icons:
415+
416+ def __init__(self, parent):
417+ """Create a new Icons object.
418+
419+ @param parent: The parent applet of the icons instance.
420+ @type parent: L{Applet}
421+
422+ """
423+ self.__parent = parent
424+
425+ self.__icon_box = awn.IconBox(parent)
426+ parent.add(self.__icon_box)
427+
428+ def update_size():
429+ size = self.__parent.get_size()
430+ for icon in self.__icon_box.get_children():
431+ icon.set_size(size)
432+
433+ parent.connect_size_changed(update_size)
434+
435+ def add(self, icon_name, tooltip_text, context_menu=None):
436+ """Set an icon from the default icon theme and set the applet
437+ tooltip. Optionally provide a context menu that should be
438+ displayed instead of the applet's standard context menu. The
439+ resultant themed icon will be returned.
440+
441+ @param icon_name: The name of the theme icon.
442+ @type icon_name: C{string}
443+ @param tooltip_text: The new tooltip text.
444+ @type tooltip_text: C{string}
445+ @param context_menu: Optional context menu.
446+ @type context_menu: C{gtk.Menu} or C{None}
447+ @return: The resultant themed icon
448+ @rtype: C{awn.ThemedIcon}
449+
450+ """
451+ icon = awn.ThemedIcon()
452+ icon.set_info_simple(self.__parent.meta["short"], self.__parent.get_uid(), icon_name)
453+ icon.set_tooltip_text(tooltip_text)
454+ icon.set_size(self.__parent.get_size())
455+
456+ # Callback context menu
457+ if context_menu is None:
458+ def popup_menu_cb(widget, event):
459+ self.__parent.dialog.show_menu(widget, event)
460+ icon.connect("context-menu-popup", popup_menu_cb)
461+ else:
462+ assert isinstance(context_menu, gtk.Menu)
463+
464+ def popup_menu_cb(widget, event, menu):
465+ menu.show_all()
466+ widget.popup_gtk_menu(menu, event.button, event.time)
467+ icon.connect("context-menu-popup", popup_menu_cb, context_menu)
468+
469+ icon.show_all()
470+ self.__icon_box.add(icon)
471+ return icon
472+
473+ def remove(self, icon):
474+ """Remove the specified icon from the applet. The icon will not
475+ be destroyed.
476+
477+ @param icon: The icon to be removed.
478+ @type icon: C{awn.ThemedIcon}
479+
480+ """
481+ assert isinstance(icon, awn.ThemedIcon)
482+
483+ self.__icon_box.remove(icon)
484+
485+ def destroy_all(self):
486+ """Remove and destroy all icons in the applet.
487+
488+ """
489+ for icon in self.__icon_box.get_children():
490+ icon.destroy()
491+
492+
493 class Errors:
494
495 def __init__(self, parent):
496@@ -694,7 +807,7 @@
497
498 """
499 type_parent = type(parent)
500- if type_parent in (Applet, config.Client):
501+ if type_parent in (AppletSimple, AppletMultiple, config.Client):
502 self.__folder = config.GROUP_DEFAULT
503 elif type_parent is str:
504 self.__folder = parent
505@@ -788,7 +901,7 @@
506 type_client = type(client)
507 if client is None:
508 self.__client = awn.config_get_default(awn.PANEL_ID_DEFAULT)
509- elif type_client is Applet:
510+ elif type_client is AppletSimple or type_client is AppletMultiple:
511 self.__client = awn.config_get_default_for_applet(client)
512
513 def applet_deleted_cb(applet):
514@@ -1220,7 +1333,7 @@
515 notification = self.Notification(self.__parent, *args, **kwargs)
516 notification.show()
517
518- def create_notification(self, *args, **kwargs):
519+ def create(self, *args, **kwargs):
520 """Return a notification that can be shown via show().
521
522 @param subject: The subject of your message. If blank, "Message from
523@@ -1310,56 +1423,87 @@
524 return key in self.__info
525
526
527-class Applet(awn.AppletSimple, object):
528-
529- def __init__(self, uid, panel_id, meta={}, options=[]):
530+def _getmodule(module):
531+ """Return a getter that lazy-loads a module, represented by a
532+ single instantiated class.
533+
534+ @param module: The class of the module to initialize and get
535+ @type module: C{class}
536+
537+ """
538+ instance = {}
539+
540+ def getter(self):
541+ key = (self, module)
542+ if key not in instance:
543+ instance[key] = module(self)
544+ return instance[key]
545+ return property(getter)
546+
547+
548+class Applet(object):
549+
550+ def __init__(self, meta, options):
551 """Create a new instance of the Applet object.
552
553- @param uid: The unique identifier of the applet
554- @type uid: C{string}
555- @param orient: The orientation of the applet. 0 means that the AWN bar
556- is on the bottom of the screen.
557- @type orient: C{int}
558- @param height: The height of the applet.
559- @type height: C{int}
560 @param meta: The meta information to be passed to the Meta constructor
561 @type meta: C{dict}
562
563 """
564+ # Create all required child-objects, others will be lazy-loaded
565+ self.meta = Meta(self, meta, options)
566+
567+ def connect_size_changed(self, callback):
568+ self.connect("size-changed", lambda w, e: callback())
569+
570+ settings = _getmodule(Settings)
571+ timing = _getmodule(Timing)
572+ keyring = _getmodule(Keyring)
573+ notification = _getmodule(Notify)
574+
575+
576+class AppletSimple(awn.AppletSimple, Applet):
577+
578+ def __init__(self, uid, panel_id, meta={}, options=[]):
579+ """Create a new instance of the AppletSimple object.
580+
581+ @param uid: The unique identifier of the applet
582+ @type uid: C{string}
583+ @param panel_id: Identifier of the panel in which the applet resides.
584+ @type panel_id: C{int}
585+
586+ """
587 awn.AppletSimple.__init__(self, meta["short"], uid, panel_id)
588+ Applet.__init__(self, meta, options)
589
590 # Create all required child-objects, others will be lazy-loaded
591- self.meta = Meta(self, meta, options)
592 self.icon = Icon(self)
593 self.tooltip = Tooltip(self)
594 self.dialog = Dialogs(self)
595
596- def connect_size_changed(self, callback):
597- self.connect("size-changed", lambda w, e: callback())
598-
599- def __getmodule(module):
600- """Return a getter that lazy-loads a module, represented by a
601- single instantiated class.
602-
603- @param module: The class of the module to initialize and get
604- @type module: C{class}
605+ self.dialog.connect_signals(self)
606+
607+ theme = _getmodule(Theme)
608+ errors = _getmodule(Errors)
609+
610+
611+class AppletMultiple(awn.Applet, Applet):
612+
613+ def __init__(self, uid, panel_id, meta={}, options=[]):
614+ """Create a new instance of the AppletMultiple object.
615+
616+ @param uid: The unique identifier of the applet
617+ @type uid: C{string}
618+ @param panel_id: Identifier of the panel in which the applet resides.
619+ @type panel_id: C{int}
620
621 """
622- instance = {}
623-
624- def getter(self):
625- key = (self, module)
626- if key not in instance:
627- instance[key] = module(self)
628- return instance[key]
629- return property(getter)
630-
631- settings = __getmodule(Settings)
632- theme = __getmodule(Theme)
633- timing = __getmodule(Timing)
634- errors = __getmodule(Errors)
635- keyring = __getmodule(Keyring)
636- notify = __getmodule(Notify)
637+ awn.Applet.__init__(self, meta["short"], uid, panel_id)
638+ Applet.__init__(self, meta, options)
639+
640+ # Create all required child-objects, others will be lazy-loaded
641+ self.icons = Icons(self)
642+ self.dialog = Dialogs(self)
643
644
645 def init_start(applet_class, meta={}, options=[]):
646@@ -1385,15 +1529,22 @@
647 gobject.threads_init()
648
649 awn.init(sys.argv[1:])
650- applet = Applet(awn.uid, awn.panel_id, meta, options)
651+ if "multiple-icons" in options:
652+ applet = AppletMultiple(awn.uid, awn.panel_id, meta, options)
653+ else:
654+ applet = AppletSimple(awn.uid, awn.panel_id, meta, options)
655
656 try:
657 applet_class(applet)
658 except Exception, e:
659- applet.errors.set_error_icon_and_click_to_restart()
660- import traceback
661- traceback = traceback.format_exception(type(e), e, sys.exc_traceback)
662- applet.errors.general(e, traceback=traceback, callback=gtk.main_quit)
663+ # TODO don't know what to do for multiple-icons applets
664+ if "multiple-icons" not in options:
665+ applet.errors.set_error_icon_and_click_to_restart()
666+ import traceback
667+ traceback = traceback.format_exception(type(e), e, sys.exc_traceback)
668+ applet.errors.general(e, traceback=traceback, callback=gtk.main_quit)
669+ else:
670+ raise
671
672 awn.embed_applet(applet)
673 gtk.main()

Subscribers

People subscribed via source and target branches