Merge lp:~onox/awn-extras/common-folder-applet into lp:awn-extras
- common-folder-applet
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michal Hruby (community) | Abstain | ||
Review via email: mp+35612@code.launchpad.net |
Commit message
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-
- 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
Alberto Aldegheri (albyrock87) wrote : | # |
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/
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).
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
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_
> 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/
> 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.
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..
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...
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.
Preview Diff
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() |
- 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.