Merge lp:~gtg-user/gtg/backends-window into lp:~gtg/gtg/old-trunk
- backends-window
- Merge into old-trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~gtg-user/gtg/backends-window |
Merge into: | lp:~gtg/gtg/old-trunk |
Diff against target: |
3065 lines (+2530/-139) 24 files modified
CHANGELOG (+1/-0) GTG/backends/backendsignals.py (+0/-127) GTG/core/requester.py (+3/-0) GTG/gtk/__init__.py (+3/-1) GTG/gtk/backends_dialog.glade (+166/-0) GTG/gtk/backends_dialog/__init__.py (+294/-0) GTG/gtk/backends_dialog/addpanel.py (+214/-0) GTG/gtk/backends_dialog/backendscombo.py (+92/-0) GTG/gtk/backends_dialog/backendstree.py (+252/-0) GTG/gtk/backends_dialog/configurepanel.py (+298/-0) GTG/gtk/backends_dialog/parameters_ui/__init__.py (+149/-0) GTG/gtk/backends_dialog/parameters_ui/checkboxui.py (+72/-0) GTG/gtk/backends_dialog/parameters_ui/importtagsui.py (+131/-0) GTG/gtk/backends_dialog/parameters_ui/passwordui.py (+84/-0) GTG/gtk/backends_dialog/parameters_ui/pathui.py (+111/-0) GTG/gtk/backends_dialog/parameters_ui/periodui.py (+88/-0) GTG/gtk/backends_dialog/parameters_ui/textui.py (+77/-0) GTG/gtk/browser/browser.py (+103/-4) GTG/gtk/browser/custominfobar.py (+210/-0) GTG/gtk/browser/taskbrowser.glade (+11/-0) GTG/gtk/colors.py (+27/-1) GTG/gtk/manager.py (+18/-6) GTG/tests/test_interruptible.py (+69/-0) GTG/tools/networkmanager.py (+57/-0) |
To merge this branch: | bzr merge lp:~gtg-user/gtg/backends-window |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gtg developers | Pending | ||
Review via email: mp+32005@code.launchpad.net |
This proposal has been superseded by a proposal from 2010-08-13.
Commit message
Description of the change
This merge contains all the code relative to the window used to add, remove and edit backends.
All the functions should be documented.
- 868. By Luca Invernizzi
-
Small fix
- 869. By Luca Invernizzi
-
a
- 870. By Luca Invernizzi
-
Uri support will be given in a different merge request
- 871. By Luca Invernizzi
-
merge with trunk
- 872. By Luca Invernizzi
-
merge with trunk
- 873. By Luca Invernizzi
-
updated changelog
- 874. By Luca Invernizzi
-
merge with trunk
- 875. By Luca Invernizzi
-
small fix for marko kevec comment
- 876. By Luca Invernizzi
-
small fix in the backends tree
- 877. By Luca Invernizzi
-
cherrypicking from my development branch
- 878. By Luca Invernizzi
-
cherrypicking from my development branch
- 879. By Luca Invernizzi
-
merge w/ trunk
- 880. By Luca Invernizzi
-
Workaround for arch linux, as for bug #lp 624204
- 881. By Luca Invernizzi
-
Bugfix for bug lp #624298 by Andrew Starr-Bochicchio
New backends window should have close button not quit - 882. By Luca Invernizzi
-
cherrypicking from my development branch
- 883. By Luca Invernizzi
-
cherrypicking from my development branch
- 884. By Luca Invernizzi
-
cherrypicking from my development branch
Unmerged revisions
Preview Diff
1 | === modified file 'CHANGELOG' |
2 | --- CHANGELOG 2010-08-04 00:30:22 +0000 |
3 | +++ CHANGELOG 2010-08-13 23:43:07 +0000 |
4 | @@ -4,6 +4,7 @@ |
5 | * Fixed bug with data consistency #579189, by Marko Kevac |
6 | * Added samba bugzilla to the bugzilla plugin, by Jelmer Vernoij |
7 | * Fixed bug #532392, a start date is later than a due date, by Volodymyr Floreskul |
8 | + * Added a window to add/delete/edit backends by Luca Invernizzi |
9 | |
10 | 2010-03-01 Getting Things GNOME! 0.2.2 |
11 | * Autostart on login, by Luca Invernizzi |
12 | |
13 | === added file 'GTG/backends/backendsignals.py' |
14 | --- GTG/backends/backendsignals.py 1970-01-01 00:00:00 +0000 |
15 | +++ GTG/backends/backendsignals.py 2010-08-13 23:43:07 +0000 |
16 | @@ -0,0 +1,148 @@ |
17 | +# -*- coding: utf-8 -*- |
18 | +# ----------------------------------------------------------------------------- |
19 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
20 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
21 | +# |
22 | +# This program is free software: you can redistribute it and/or modify it under |
23 | +# the terms of the GNU General Public License as published by the Free Software |
24 | +# Foundation, either version 3 of the License, or (at your option) any later |
25 | +# version. |
26 | +# |
27 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
28 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
29 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
30 | +# details. |
31 | +# |
32 | +# You should have received a copy of the GNU General Public License along with |
33 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
34 | +# ----------------------------------------------------------------------------- |
35 | + |
36 | +import gobject |
37 | + |
38 | +from GTG.tools.borg import Borg |
39 | + |
40 | + |
41 | + |
42 | +class BackendSignals(Borg): |
43 | + ''' |
44 | + This class handles the signals that involve backends. |
45 | + In particular, it's a wrapper Borg class around a _BackendSignalsGObject |
46 | + class, and all method of the wrapped class can be used as if they were part |
47 | + of this class |
48 | + ''' |
49 | + |
50 | + #error codes to send along with the BACKEND_FAILED signal |
51 | + ERRNO_AUTHENTICATION = "authentication failed" |
52 | + ERRNO_NETWORK = "network is down" |
53 | + ERRNO_DBUS = "Dbus interface cannot be connected" |
54 | + |
55 | + def __init__(self): |
56 | + '''Checks that this is the only instance, and instantiates the |
57 | + gobject''' |
58 | + super(BackendSignals, self).__init__() |
59 | + if hasattr(self, "_gobject"): |
60 | + return |
61 | + self._gobject = _BackendSignalsGObject() |
62 | + |
63 | + def __getattr__(self, attr): |
64 | + ''' |
65 | + From outside the class, there should be no difference between self's |
66 | + attributes and self._gobject's attributes. |
67 | + ''' |
68 | + if attr == "_gobject" and not "_gobject" in self.__dict__: |
69 | + raise AttributeError |
70 | + return getattr(self._gobject, attr) |
71 | + |
72 | + |
73 | +def signal_type_factory(*args): |
74 | + ''' |
75 | + Simply returns a gobject signal type |
76 | + |
77 | + @returns tuple |
78 | + ''' |
79 | + return (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, args) |
80 | + |
81 | + |
82 | + |
83 | +class _BackendSignalsGObject(gobject.GObject): |
84 | + |
85 | + #signal name constants |
86 | + BACKEND_STATE_TOGGLED = 'backend-state-toggled' #emitted when a |
87 | + #backend is |
88 | + #enabled or disabled |
89 | + BACKEND_RENAMED = 'backend-renamed' #emitted when a backend is renamed |
90 | + BACKEND_ADDED = 'backend-added' |
91 | + BACKEND_REMOVED = 'backend-added' #when a backend is deleted |
92 | + DEFAULT_BACKEND_LOADED = 'default-backend-loaded' #emitted after all |
93 | + # tasks have been |
94 | + # loaded from the |
95 | + # default backend |
96 | + BACKEND_FAILED = 'backend-failed' #something went wrong with a backend |
97 | + BACKEND_SYNC_STARTED = 'backend-sync-started' |
98 | + BACKEND_SYNC_ENDED = 'backend-sync-ended' |
99 | + INTERACTION_REQUESTED = 'user-interaction-requested' |
100 | + |
101 | + INTERACTION_CONFIRM = 'confirm' |
102 | + INTERACTION_TEXT = 'text' |
103 | + |
104 | + __gsignals__ = {BACKEND_STATE_TOGGLED : signal_type_factory(str), \ |
105 | + BACKEND_RENAMED : signal_type_factory(str), \ |
106 | + BACKEND_ADDED : signal_type_factory(str), \ |
107 | + BACKEND_REMOVED : signal_type_factory(str), \ |
108 | + BACKEND_SYNC_STARTED : signal_type_factory(str), \ |
109 | + BACKEND_SYNC_ENDED : signal_type_factory(str), \ |
110 | + DEFAULT_BACKEND_LOADED: signal_type_factory(), \ |
111 | + BACKEND_FAILED : signal_type_factory(str, str), \ |
112 | + INTERACTION_REQUESTED : signal_type_factory(str, str, \ |
113 | + str, str)} |
114 | + |
115 | + def __init__(self): |
116 | + super(_BackendSignalsGObject, self).__init__() |
117 | + self.backends_currently_syncing = [] |
118 | + |
119 | + ############# Signals ######### |
120 | + #connecting to signals is fine, but keep an eye if you should emit them. |
121 | + #As a general rule, signals should only be emitted in the GenericBackend |
122 | + #class |
123 | + |
124 | + def _emit_signal(self, signal, backend_id): |
125 | + gobject.idle_add(self.emit, signal, backend_id) |
126 | + |
127 | + def backend_state_changed(self, backend_id): |
128 | + self._emit_signal(self.BACKEND_STATE_TOGGLED, backend_id) |
129 | + |
130 | + def backend_renamed(self, backend_id): |
131 | + self._emit_signal(self.BACKEND_RENAMED, backend_id) |
132 | + |
133 | + def backend_added(self, backend_id): |
134 | + self._emit_signal(self.BACKEND_ADDED, backend_id) |
135 | + |
136 | + def backend_removed(self, backend_id): |
137 | + self._emit_signal(self.BACKEND_REMOVED, backend_id) |
138 | + |
139 | + def default_backend_loaded(self): |
140 | + gobject.idle_add(self.emit, self.DEFAULT_BACKEND_LOADED) |
141 | + |
142 | + def backend_failed(self, backend_id, error_code): |
143 | + gobject.idle_add(self.emit, self.BACKEND_FAILED, backend_id, \ |
144 | + error_code) |
145 | + |
146 | + def interaction_requested(self, backend_id, description, \ |
147 | + interaction_type, callback_str): |
148 | + gobject.idle_add(self.emit, self.INTERACTION_REQUESTED, \ |
149 | + backend_id, description, interaction_type, callback_str) |
150 | + |
151 | + def backend_sync_started(self, backend_id): |
152 | + self._emit_signal(self.BACKEND_SYNC_STARTED, backend_id) |
153 | + self.backends_currently_syncing.append(backend_id) |
154 | + |
155 | + def backend_sync_ended(self, backend_id): |
156 | + self._emit_signal(self.BACKEND_SYNC_ENDED, backend_id) |
157 | + try: |
158 | + self.backends_currently_syncing.remove(backend_id) |
159 | + except: |
160 | + pass |
161 | + |
162 | + def is_backend_syncing(self, backend_id): |
163 | + return backend_id in self.backends_currently_syncing |
164 | + |
165 | |
166 | === removed file 'GTG/backends/backendsignals.py' |
167 | --- GTG/backends/backendsignals.py 2010-06-23 12:49:28 +0000 |
168 | +++ GTG/backends/backendsignals.py 1970-01-01 00:00:00 +0000 |
169 | @@ -1,127 +0,0 @@ |
170 | -# -*- coding: utf-8 -*- |
171 | -# ----------------------------------------------------------------------------- |
172 | -# Getting Things Gnome! - a personal organizer for the GNOME desktop |
173 | -# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
174 | -# |
175 | -# This program is free software: you can redistribute it and/or modify it under |
176 | -# the terms of the GNU General Public License as published by the Free Software |
177 | -# Foundation, either version 3 of the License, or (at your option) any later |
178 | -# version. |
179 | -# |
180 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
181 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
182 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
183 | -# details. |
184 | -# |
185 | -# You should have received a copy of the GNU General Public License along with |
186 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
187 | -# ----------------------------------------------------------------------------- |
188 | - |
189 | -import gobject |
190 | - |
191 | -from GTG.tools.borg import Borg |
192 | - |
193 | - |
194 | - |
195 | -class BackendSignals(Borg): |
196 | - ''' |
197 | - This class handles the signals that involve backends. |
198 | - In particular, it's a wrapper Borg class around a _BackendSignalsGObject |
199 | - class, and all method of the wrapped class can be used as if they were part |
200 | - of this class |
201 | - ''' |
202 | - |
203 | - #error codes to send along with the BACKEND_FAILED signal |
204 | - ERRNO_AUTHENTICATION = "authentication failed" |
205 | - ERRNO_NETWORK = "network is down" |
206 | - ERRNO_DBUS = "Dbus interface cannot be connected" |
207 | - |
208 | - def __init__(self): |
209 | - super(BackendSignals, self).__init__() |
210 | - if hasattr(self, "_gobject"): |
211 | - return |
212 | - self._gobject = _BackendSignalsGObject() |
213 | - |
214 | - def __getattr__(self, attr): |
215 | - if attr == "_gobject" and not "_gobject" in self.__dict__: |
216 | - raise AttributeError |
217 | - return getattr(self._gobject, attr) |
218 | - |
219 | - |
220 | -class _BackendSignalsGObject(gobject.GObject): |
221 | - |
222 | - #signal name constants |
223 | - BACKEND_STATE_TOGGLED = 'backend-state-toggled' #emitted when a |
224 | - #backend is |
225 | - #enabled or disabled |
226 | - BACKEND_RENAMED = 'backend-renamed' #emitted when a backend is renamed |
227 | - BACKEND_ADDED = 'backend-added' |
228 | - BACKEND_REMOVED = 'backend-added' #when a backend is deleted |
229 | - DEFAULT_BACKEND_LOADED = 'default-backend-loaded' #emitted after all |
230 | - # tasks have been |
231 | - # loaded from the |
232 | - # default backend |
233 | - BACKEND_FAILED = 'backend-failed' #something went wrong with a backend |
234 | - BACKEND_SYNC_STARTED = 'backend-sync-started' |
235 | - BACKEND_SYNC_ENDED = 'backend-sync-ended' |
236 | - |
237 | - __string_signal__ = (gobject.SIGNAL_RUN_FIRST, \ |
238 | - gobject.TYPE_NONE, (str, )) |
239 | - __none_signal__ = (gobject.SIGNAL_RUN_FIRST, \ |
240 | - gobject.TYPE_NONE, ( )) |
241 | - __string_string_signal__ = (gobject.SIGNAL_RUN_FIRST, \ |
242 | - gobject.TYPE_NONE, (str, str, )) |
243 | - |
244 | - __gsignals__ = {BACKEND_STATE_TOGGLED : __string_signal__, \ |
245 | - BACKEND_RENAMED : __string_signal__, \ |
246 | - BACKEND_ADDED : __string_signal__, \ |
247 | - BACKEND_REMOVED : __string_signal__, \ |
248 | - BACKEND_SYNC_STARTED : __string_signal__, \ |
249 | - BACKEND_SYNC_ENDED : __string_signal__, \ |
250 | - DEFAULT_BACKEND_LOADED: __none_signal__, \ |
251 | - BACKEND_FAILED : __string_string_signal__} |
252 | - |
253 | - def __init__(self): |
254 | - super(_BackendSignalsGObject, self).__init__() |
255 | - self.backends_currently_syncing = [] |
256 | - |
257 | - ############# Signals ######### |
258 | - #connecting to signals is fine, but keep an eye if you should emit them. |
259 | - #As a general rule, signals should only be emitted in the GenericBackend |
260 | - #class |
261 | - |
262 | - def _emit_signal(self, signal, backend_id): |
263 | - gobject.idle_add(self.emit, signal, backend_id) |
264 | - |
265 | - def backend_state_changed(self, backend_id): |
266 | - self._emit_signal(self.BACKEND_STATE_TOGGLED, backend_id) |
267 | - |
268 | - def backend_renamed(self, backend_id): |
269 | - self._emit_signal(self.BACKEND_RENAMED, backend_id) |
270 | - |
271 | - def backend_added(self, backend_id): |
272 | - self._emit_signal(self.BACKEND_ADDED, backend_id) |
273 | - |
274 | - def backend_removed(self, backend_id): |
275 | - self._emit_signal(self.BACKEND_REMOVED, backend_id) |
276 | - |
277 | - def default_backend_loaded(self): |
278 | - gobject.idle_add(self.emit, self.DEFAULT_BACKEND_LOADED) |
279 | - |
280 | - def backend_failed(self, backend_id, error_code): |
281 | - gobject.idle_add(self.emit, self.BACKEND_FAILED, backend_id, \ |
282 | - error_code) |
283 | - |
284 | - def backend_sync_started(self, backend_id): |
285 | - self._emit_signal(self.BACKEND_SYNC_STARTED, backend_id) |
286 | - self.backends_currently_syncing.append(backend_id) |
287 | - |
288 | - def backend_sync_ended(self, backend_id): |
289 | - self._emit_signal(self.BACKEND_SYNC_ENDED, backend_id) |
290 | - try: |
291 | - self.backends_currently_syncing.remove(backend_id) |
292 | - except: |
293 | - pass |
294 | - |
295 | - def is_backend_syncing(self, backend_id): |
296 | - return backend_id in self.backends_currently_syncing |
297 | |
298 | === modified file 'GTG/core/requester.py' |
299 | --- GTG/core/requester.py 2010-06-22 19:55:15 +0000 |
300 | +++ GTG/core/requester.py 2010-08-13 23:43:07 +0000 |
301 | @@ -284,3 +284,6 @@ |
302 | |
303 | def backend_change_attached_tags(self, backend_id, tags): |
304 | return self.ds.backend_change_attached_tags(backend_id, tags) |
305 | + |
306 | + def save_datastore(self): |
307 | + return self.ds.save() |
308 | |
309 | === modified file 'GTG/gtk/__init__.py' |
310 | --- GTG/gtk/__init__.py 2010-06-02 18:12:23 +0000 |
311 | +++ GTG/gtk/__init__.py 2010-08-13 23:43:07 +0000 |
312 | @@ -28,7 +28,9 @@ |
313 | |
314 | |
315 | class ViewConfig: |
316 | + |
317 | + |
318 | current_rep = os.path.dirname(os.path.abspath(__file__)) |
319 | DELETE_GLADE_FILE = os.path.join(current_rep, "deletion.glade") |
320 | PREFERENCES_GLADE_FILE = os.path.join(current_rep, "preferences.glade") |
321 | - |
322 | + BACKENDS_GLADE_FILE = os.path.join(current_rep, "backends_dialog.glade") |
323 | |
324 | === added directory 'GTG/gtk/backends_dialog' |
325 | === added file 'GTG/gtk/backends_dialog.glade' |
326 | --- GTG/gtk/backends_dialog.glade 1970-01-01 00:00:00 +0000 |
327 | +++ GTG/gtk/backends_dialog.glade 2010-08-13 23:43:07 +0000 |
328 | @@ -0,0 +1,166 @@ |
329 | +<?xml version="1.0"?> |
330 | +<interface> |
331 | + <requires lib="gtk+" version="2.16"/> |
332 | + <!-- interface-naming-policy project-wide --> |
333 | + <object class="GtkWindow" id="backends_dialog"> |
334 | + <property name="window_position">mouse</property> |
335 | + <signal name="delete_event" handler="on_BackendsDialog_delete_event"/> |
336 | + <child> |
337 | + <object class="GtkAlignment" id="alignment1"> |
338 | + <property name="visible">True</property> |
339 | + <property name="top_padding">10</property> |
340 | + <property name="bottom_padding">10</property> |
341 | + <property name="left_padding">10</property> |
342 | + <property name="right_padding">10</property> |
343 | + <child> |
344 | + <object class="GtkVBox" id="vbox1"> |
345 | + <property name="visible">True</property> |
346 | + <property name="spacing">10</property> |
347 | + <child> |
348 | + <object class="GtkHBox" id="big_central_hbox"> |
349 | + <property name="visible">True</property> |
350 | + <property name="spacing">10</property> |
351 | + <child> |
352 | + <object class="GtkVBox" id="vbox2"> |
353 | + <property name="visible">True</property> |
354 | + <child> |
355 | + <object class="GtkAlignment" id="treeview_window"> |
356 | + <property name="height_request">400</property> |
357 | + <property name="visible">True</property> |
358 | + <child> |
359 | + <placeholder/> |
360 | + </child> |
361 | + </object> |
362 | + <packing> |
363 | + <property name="position">0</property> |
364 | + </packing> |
365 | + </child> |
366 | + <child> |
367 | + <object class="GtkAlignment" id="alignment2"> |
368 | + <property name="height_request">30</property> |
369 | + <property name="visible">True</property> |
370 | + <property name="yalign">1</property> |
371 | + <property name="top_padding">20</property> |
372 | + <property name="bottom_padding">10</property> |
373 | + <property name="left_padding">10</property> |
374 | + <property name="right_padding">10</property> |
375 | + <child> |
376 | + <object class="GtkHButtonBox" id="hbuttonbox3"> |
377 | + <property name="visible">True</property> |
378 | + <property name="spacing">10</property> |
379 | + <property name="homogeneous">True</property> |
380 | + <child> |
381 | + <object class="GtkButton" id="add_button"> |
382 | + <property name="label">gtk-add</property> |
383 | + <property name="visible">True</property> |
384 | + <property name="can_focus">True</property> |
385 | + <property name="receives_default">True</property> |
386 | + <property name="use_stock">True</property> |
387 | + <signal name="clicked" handler="on_add_button_clicked"/> |
388 | + </object> |
389 | + <packing> |
390 | + <property name="expand">False</property> |
391 | + <property name="fill">False</property> |
392 | + <property name="position">0</property> |
393 | + </packing> |
394 | + </child> |
395 | + <child> |
396 | + <object class="GtkButton" id="remove_button"> |
397 | + <property name="label">gtk-remove</property> |
398 | + <property name="visible">True</property> |
399 | + <property name="can_focus">True</property> |
400 | + <property name="receives_default">True</property> |
401 | + <property name="use_stock">True</property> |
402 | + <signal name="clicked" handler="on_remove_button_clicked"/> |
403 | + </object> |
404 | + <packing> |
405 | + <property name="expand">False</property> |
406 | + <property name="fill">False</property> |
407 | + <property name="position">1</property> |
408 | + </packing> |
409 | + </child> |
410 | + </object> |
411 | + </child> |
412 | + </object> |
413 | + <packing> |
414 | + <property name="expand">False</property> |
415 | + <property name="fill">False</property> |
416 | + <property name="position">1</property> |
417 | + </packing> |
418 | + </child> |
419 | + </object> |
420 | + <packing> |
421 | + <property name="position">0</property> |
422 | + </packing> |
423 | + </child> |
424 | + <child> |
425 | + <object class="GtkScrolledWindow" id="central_pane_window"> |
426 | + <property name="width_request">450</property> |
427 | + <property name="visible">True</property> |
428 | + <property name="can_focus">True</property> |
429 | + <property name="vadjustment">adjustment1</property> |
430 | + <property name="hscrollbar_policy">automatic</property> |
431 | + <property name="vscrollbar_policy">automatic</property> |
432 | + <child> |
433 | + <object class="GtkViewport" id="central_pane1"> |
434 | + <property name="visible">True</property> |
435 | + <property name="resize_mode">queue</property> |
436 | + <child> |
437 | + <object class="GtkAlignment" id="central_pane"> |
438 | + <property name="visible">True</property> |
439 | + <property name="left_padding">10</property> |
440 | + <property name="right_padding">10</property> |
441 | + <child> |
442 | + <placeholder/> |
443 | + </child> |
444 | + </object> |
445 | + </child> |
446 | + </object> |
447 | + </child> |
448 | + </object> |
449 | + <packing> |
450 | + <property name="position">1</property> |
451 | + </packing> |
452 | + </child> |
453 | + </object> |
454 | + <packing> |
455 | + <property name="position">0</property> |
456 | + </packing> |
457 | + </child> |
458 | + <child> |
459 | + <object class="GtkHButtonBox" id="hbuttonbox2"> |
460 | + <property name="visible">True</property> |
461 | + <property name="layout_style">end</property> |
462 | + <child> |
463 | + <object class="GtkButton" id="quit_button"> |
464 | + <property name="label">gtk-quit</property> |
465 | + <property name="visible">True</property> |
466 | + <property name="can_focus">True</property> |
467 | + <property name="receives_default">True</property> |
468 | + <property name="use_stock">True</property> |
469 | + <signal name="clicked" handler="on_quit_button_clicked"/> |
470 | + </object> |
471 | + <packing> |
472 | + <property name="expand">False</property> |
473 | + <property name="fill">False</property> |
474 | + <property name="position">0</property> |
475 | + </packing> |
476 | + </child> |
477 | + </object> |
478 | + <packing> |
479 | + <property name="expand">False</property> |
480 | + <property name="position">1</property> |
481 | + </packing> |
482 | + </child> |
483 | + </object> |
484 | + </child> |
485 | + </object> |
486 | + </child> |
487 | + </object> |
488 | + <object class="GtkAdjustment" id="adjustment1"> |
489 | + <property name="upper">100</property> |
490 | + <property name="step_increment">1</property> |
491 | + <property name="page_increment">10</property> |
492 | + <property name="page_size">10</property> |
493 | + </object> |
494 | +</interface> |
495 | |
496 | === added file 'GTG/gtk/backends_dialog/__init__.py' |
497 | --- GTG/gtk/backends_dialog/__init__.py 1970-01-01 00:00:00 +0000 |
498 | +++ GTG/gtk/backends_dialog/__init__.py 2010-08-13 23:43:07 +0000 |
499 | @@ -0,0 +1,294 @@ |
500 | +# -*- coding: utf-8 -*- |
501 | +# ----------------------------------------------------------------------------- |
502 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
503 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
504 | +# |
505 | +# This program is free software: you can redistribute it and/or modify it under |
506 | +# the terms of the GNU General Public License as published by the Free Software |
507 | +# Foundation, either version 3 of the License, or (at your option) any later |
508 | +# version. |
509 | +# |
510 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
511 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
512 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
513 | +# details. |
514 | +# |
515 | +# You should have received a copy of the GNU General Public License along with |
516 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
517 | +# ----------------------------------------------------------------------------- |
518 | + |
519 | +''' |
520 | +This file contains BackendsDialog, a class that manages the window that |
521 | +lets you add and configure backends. |
522 | +This window is divided in two: |
523 | + - a treeview of the currently loaded backends (the ones added by the user) |
524 | + - a big space, that can be filled by the configuration panel or the add |
525 | + panel (these are called also "views" in this class) |
526 | +''' |
527 | + |
528 | +import gtk |
529 | + |
530 | +from GTG.gtk import ViewConfig |
531 | +from GTG.core import CoreConfig |
532 | +from GTG.gtk.backends_dialog.backendstree import BackendsTree |
533 | +from GTG.gtk.backends_dialog.addpanel import AddPanel |
534 | +from GTG.gtk.backends_dialog.configurepanel import ConfigurePanel |
535 | +from GTG.backends import BackendFactory |
536 | +from GTG.tools.logger import Log |
537 | +from GTG import _ |
538 | +from GTG.backends.genericbackend import GenericBackend |
539 | + |
540 | + |
541 | + |
542 | +class BackendsDialog(object): |
543 | + ''' |
544 | + BackendsDialog manages a window that lets you manage and configure backends. |
545 | + It can display two "views", or "panels": |
546 | + - the backend configuration view |
547 | + - the backend adding view |
548 | + ''' |
549 | + |
550 | + |
551 | + def __init__(self, req): |
552 | + ''' |
553 | + Initializes the gtk objects and signals. |
554 | + @param req: a Requester object |
555 | + ''' |
556 | + self.req = req |
557 | + self._configure_icon_theme() |
558 | + builder = gtk.Builder() |
559 | + self._load_widgets_from_glade(builder) |
560 | + self._create_widgets_for_add_panel() |
561 | + self._create_widgets_for_configure_panel() |
562 | + self._setup_signal_connections(builder) |
563 | + self._create_widgets_for_backends_tree() |
564 | + |
565 | +######################################## |
566 | +### INTERFACE WITH THE VIEWMANAGER ##### |
567 | +######################################## |
568 | + |
569 | + def activate(self): |
570 | + '''Shows this window, refreshing the current view''' |
571 | + self.config_panel.set_hidden(False) |
572 | + self.dialog.show_all() |
573 | + self.backends_tv.refresh() |
574 | + self.backends_tv.select_backend() |
575 | + self.dialog.present() |
576 | + |
577 | + def on_close(self, widget, data = None): |
578 | + ''' |
579 | + Hides this window, saving the backends configuration. |
580 | + |
581 | + @param widget: not used, here only for using this as signal callback |
582 | + @param data: same as widget, disregard the content |
583 | + ''' |
584 | + self.dialog.hide() |
585 | + self.config_panel.set_hidden(True) |
586 | + self.req.save_datastore() |
587 | + |
588 | +######################################## |
589 | +### HELPER FUNCTIONS ################### |
590 | +######################################## |
591 | + |
592 | + def get_requester(self): |
593 | + ''' |
594 | + Helper function: returns the requester. |
595 | + It's used by the "views" displayed by this class (backend editing and |
596 | + adding views) to access the requester |
597 | + ''' |
598 | + return self.req |
599 | + |
600 | + def get_pixbuf_from_icon_name(self, name, height, width): |
601 | + ''' |
602 | + Helper function: returns a pixbuf of an icon given its name in the |
603 | + loaded icon theme |
604 | + |
605 | + @param name: the name of the icon |
606 | + @param height: the height of the returned pixbuf |
607 | + @param width: the width of the returned pixbuf |
608 | + |
609 | + @returns gtk.gdk.Pixbuf: a pixbuf containing the wanted icon, or None |
610 | + (if the icon is not present) |
611 | + ''' |
612 | + #NOTE: loading icons directly from the theme and scaling them results in |
613 | + # blurry icons. So, instead of doing that, I'm loading them |
614 | + # directly from file. |
615 | + icon_info = self.icon_theme.lookup_icon(name, gtk.ICON_SIZE_MENU, 0) |
616 | + if icon_info == None: |
617 | + return None |
618 | + pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.get_filename()) |
619 | + return pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR) |
620 | + |
621 | + def _show_panel(self, panel_name): |
622 | + ''' |
623 | + Helper function to switch between panels. |
624 | + |
625 | + @param panel_name: the name of the wanted panel. Choose between |
626 | + "configuration" or "add" |
627 | + ''' |
628 | + if panel_name == "configuration": |
629 | + panel_to_remove = self.add_panel |
630 | + panel_to_add = self.config_panel |
631 | + side_is_enabled = True |
632 | + elif panel_name == "add": |
633 | + panel_to_remove = self.config_panel |
634 | + panel_to_add = self.add_panel |
635 | + side_is_enabled = False |
636 | + else: |
637 | + Log.error("panel name unknown") |
638 | + return |
639 | + ##Central pane |
640 | + #NOTE: self.central_pane is the gtk.Container in which we load panels |
641 | + if panel_to_remove in self.central_pane: |
642 | + self.central_pane.remove(panel_to_remove) |
643 | + if not panel_to_add in self.central_pane: |
644 | + self.central_pane.add(panel_to_add) |
645 | + self.central_pane.show_all() |
646 | + #Side treeview |
647 | + # disabled if we're adding a new backend |
648 | + try: |
649 | + #when this is called upon initialization of this class, the |
650 | + # backends_tv object has not been created yet. |
651 | + self.add_button.set_sensitive(side_is_enabled) |
652 | + self.remove_button.set_sensitive(side_is_enabled) |
653 | + self.backends_tv.set_sensitive(side_is_enabled) |
654 | + except AttributeError: |
655 | + pass |
656 | + |
657 | +######################################## |
658 | +### WIDGETS AND SIGNALS ################ |
659 | +######################################## |
660 | + |
661 | + def _load_widgets_from_glade(self, builder): |
662 | + ''' |
663 | + Loads widgets from the glade file |
664 | + |
665 | + @param builder: a gtk.Builder |
666 | + ''' |
667 | + builder.add_from_file(ViewConfig.BACKENDS_GLADE_FILE) |
668 | + widgets = { |
669 | + 'dialog' : 'backends_dialog', |
670 | + 'treeview_window' : 'treeview_window', |
671 | + 'central_pane' : 'central_pane', |
672 | + 'add_button' : 'add_button', |
673 | + 'remove_button' : 'remove_button', |
674 | + } |
675 | + for attr, widget in widgets.iteritems(): |
676 | + setattr(self, attr, builder.get_object(widget)) |
677 | + |
678 | + def _setup_signal_connections(self, builder): |
679 | + ''' |
680 | + Creates some GTK signals connections |
681 | + |
682 | + @param builder: a gtk.Builder |
683 | + ''' |
684 | + signals = { |
685 | + 'on_add_button_clicked': self.on_add_button, |
686 | + 'on_BackendsDialog_delete_event': self.on_close, |
687 | + 'on_quit_button_clicked': self.on_close, |
688 | + 'on_remove_button_clicked': self.on_remove_button, |
689 | + } |
690 | + builder.connect_signals(signals) |
691 | + |
692 | + def _configure_icon_theme(self): |
693 | + ''' |
694 | + Inform gtk on the location of the backends icons (which is in |
695 | + the GTG directory tree, and not in the default location for icons |
696 | + ''' |
697 | + self.icon_theme = gtk.icon_theme_get_default() |
698 | + for directory in CoreConfig().get_icons_directories(): |
699 | + self.icon_theme.prepend_search_path(directory) |
700 | + |
701 | + def _create_widgets_for_backends_tree(self): |
702 | + ''' |
703 | + Creates the widgets for the lateral treeview displaying the |
704 | + backends the user has added |
705 | + ''' |
706 | + self.backends_tv = BackendsTree(self) |
707 | + self.treeview_window.add(self.backends_tv) |
708 | + |
709 | + def _create_widgets_for_configure_panel(self): |
710 | + '''simply creates the panel to configure backends''' |
711 | + self.config_panel = ConfigurePanel(self) |
712 | + |
713 | + def _create_widgets_for_add_panel(self): |
714 | + '''simply creates the panel to add backends''' |
715 | + self.add_panel = AddPanel(self) |
716 | + |
717 | +######################################## |
718 | +### EVENT HANDLING ##################### |
719 | +######################################## |
720 | + |
721 | + def on_backend_selected(self, backend_id): |
722 | + ''' |
723 | + When a backend in the treeview gets selected, show |
724 | + its configuration pane |
725 | + |
726 | + @param backend_id: the id of the selected backend |
727 | + ''' |
728 | + if backend_id: |
729 | + self._show_panel("configuration") |
730 | + self.config_panel.set_backend(backend_id) |
731 | + backend = self.req.get_backend(backend_id) |
732 | + self.remove_button.set_sensitive(not backend.is_default()) |
733 | + |
734 | + def on_add_button(self, widget = None, data = None): |
735 | + ''' |
736 | + When the add button is pressed, the add panel is shown |
737 | + |
738 | + @param widget: not used, here only for using this as signal callback |
739 | + @param data: same as widget, disregard the content |
740 | + ''' |
741 | + self._show_panel("add") |
742 | + self.add_panel.refresh_backends() |
743 | + |
744 | + def on_backend_added(self, backend_name): |
745 | + ''' |
746 | + When a backend is added, it is created and registered in the Datastore. |
747 | + Also, the configuration panel is shown. |
748 | + |
749 | + @param backend_name: the name of the type of the backend to add |
750 | + (identified as BACKEND_NAME in the Backend class) |
751 | + ''' |
752 | + backend_id = None |
753 | + #Create Backend |
754 | + backend_dic = BackendFactory().get_new_backend_dict(backend_name) |
755 | + if backend_dic: |
756 | + backend_id = backend_dic["backend"].get_id() |
757 | + backend_dic[GenericBackend.KEY_ENABLED] = False |
758 | + self.req.register_backend(backend_dic) |
759 | + #Restore UI |
760 | + self._show_panel("configuration") |
761 | + |
762 | + def show_config_for_backend(self, backend_id): |
763 | + ''' |
764 | + Selects a backend in the lateral treeview |
765 | + |
766 | + @param backend_id: the id of the backend that must be selected |
767 | + ''' |
768 | + self.backends_tv.select_backend(backend_id) |
769 | + |
770 | + def on_remove_button(self, widget = None, data = None): |
771 | + ''' |
772 | + When the remove button is pressed, a confirmation dialog is shown, |
773 | + and if the answer is positive, the backend is deleted. |
774 | + ''' |
775 | + backend_id = self.backends_tv.get_selected_backend_id() |
776 | + if backend_id == None: |
777 | + #no backend selected |
778 | + return |
779 | + backend = self.req.get_backend(backend_id) |
780 | + dialog = gtk.MessageDialog( \ |
781 | + parent = self.dialog, |
782 | + flags = gtk.DIALOG_DESTROY_WITH_PARENT, |
783 | + type = gtk.MESSAGE_QUESTION, |
784 | + buttons = gtk.BUTTONS_YES_NO, |
785 | + message_format = \ |
786 | + _("Do you really want to remove the backend '%s'?") % \ |
787 | + backend.get_human_name()) |
788 | + response = dialog.run() |
789 | + dialog.destroy() |
790 | + if response == gtk.RESPONSE_YES: |
791 | + #delete the backend and remove it from the lateral treeview |
792 | + self.req.remove_backend(backend_id) |
793 | + self.backends_tv.remove_backend(backend_id) |
794 | |
795 | === added file 'GTG/gtk/backends_dialog/addpanel.py' |
796 | --- GTG/gtk/backends_dialog/addpanel.py 1970-01-01 00:00:00 +0000 |
797 | +++ GTG/gtk/backends_dialog/addpanel.py 2010-08-13 23:43:07 +0000 |
798 | @@ -0,0 +1,214 @@ |
799 | +# -*- coding: utf-8 -*- |
800 | +# ----------------------------------------------------------------------------- |
801 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
802 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
803 | +# |
804 | +# This program is free software: you can redistribute it and/or modify it under |
805 | +# the terms of the GNU General Public License as published by the Free Software |
806 | +# Foundation, either version 3 of the License, or (at your option) any later |
807 | +# version. |
808 | +# |
809 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
810 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
811 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
812 | +# details. |
813 | +# |
814 | +# You should have received a copy of the GNU General Public License along with |
815 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
816 | +# ----------------------------------------------------------------------------- |
817 | + |
818 | +import gtk |
819 | + |
820 | +from GTG.gtk.backends_dialog.backendscombo import BackendsCombo |
821 | +from GTG.backends import BackendFactory |
822 | +from GTG import _, ngettext |
823 | + |
824 | +#The code for showing the required modules has been disabled since it |
825 | +# seems that backends will be packaged separately (as plugins). I'm |
826 | +# leaving this here in case we change that decision (invernizzi). |
827 | +#from GTG.tools.moduletopackage import ModuleToPackage |
828 | + |
829 | + |
830 | + |
831 | +class AddPanel(gtk.VBox): |
832 | + ''' |
833 | + A VBox filled with gtk widgets to let the user choose a new backend. |
834 | + ''' |
835 | + |
836 | + |
837 | + def __init__(self, backends_dialog): |
838 | + ''' |
839 | + Constructor, just initializes the gtk widgets |
840 | + |
841 | + @param backends_dialog: a reference to the dialog in which this is |
842 | + loaded |
843 | + ''' |
844 | + super(AddPanel, self).__init__() |
845 | + self.dialog = backends_dialog |
846 | + self._create_widgets() |
847 | + |
848 | + def _create_widgets(self): |
849 | + ''' |
850 | + gtk widgets initialization |
851 | + ''' |
852 | + #Division of the available space in three segments: |
853 | + # top, middle and bottom. |
854 | + top = gtk.HBox() |
855 | + middle = gtk.HBox() |
856 | + bottom = gtk.HBox() |
857 | + self._fill_top_hbox(top) |
858 | + self._fill_middle_hbox(middle) |
859 | + self._fill_bottom_hbox(bottom) |
860 | + self.pack_start(top, False) |
861 | + self.pack_start(middle, True) |
862 | + self.pack_start(bottom, True) |
863 | + |
864 | + def _fill_top_hbox(self, hbox): |
865 | + ''' |
866 | + Helper function to fill and hbox with a combobox that lists the |
867 | + available backends and a gtk.Label. |
868 | + |
869 | + @param hbox: the gtk.HBox to fill |
870 | + ''' |
871 | + label = gtk.Label("Select a backend") |
872 | + label.set_size_request(-1, 30) |
873 | + self.combo_types = BackendsCombo(self.dialog) |
874 | + self.combo_types.child.connect('changed', self.on_combo_changed) |
875 | + hbox.pack_start(label, True, True) |
876 | + hbox.pack_start(self.combo_types, False, True) |
877 | + |
878 | + def _fill_middle_hbox(self, hbox): |
879 | + ''' |
880 | + Helper function to fill an hbox with a label describing the backend |
881 | + and a gtk.Image (that loads the backend image) |
882 | + |
883 | + @param hbox: the gtk.HBox to fill |
884 | + ''' |
885 | + self.label_name = gtk.Label("name") |
886 | + self.label_name.set_alignment(xalign = 0.5, yalign = 1) |
887 | + self.label_description = gtk.Label() |
888 | + self.label_description.set_justify(gtk.JUSTIFY_FILL) |
889 | + self.label_description.set_line_wrap(True) |
890 | + self.label_description.set_size_request(300, -1) |
891 | + self.label_description.set_alignment(xalign = 0, yalign = 0.5) |
892 | + self.label_author = gtk.Label("") |
893 | + self.label_author.set_line_wrap(True) |
894 | + self.label_author.set_alignment(xalign = 0, yalign = 0) |
895 | + self.label_modules = gtk.Label("") |
896 | + self.label_modules.set_line_wrap(True) |
897 | + self.label_modules.set_alignment(xalign = 0, yalign = 0) |
898 | + self.image_icon = gtk.Image() |
899 | + self.image_icon.set_size_request(100, 100) |
900 | + align_image = gtk.Alignment(xalign = 1, yalign = 0) |
901 | + align_image.add(self.image_icon) |
902 | + labels_vbox = gtk.VBox() |
903 | + labels_vbox.pack_start(self.label_description, True, True) |
904 | + labels_vbox.pack_start(self.label_author, True, True) |
905 | + labels_vbox.pack_start(self.label_modules, True, True) |
906 | + low_hbox = gtk.HBox() |
907 | + low_hbox.pack_start(labels_vbox, True, True) |
908 | + low_hbox.pack_start(align_image, True, True) |
909 | + vbox = gtk.VBox() |
910 | + vbox.pack_start(self.label_name, True, True) |
911 | + vbox.pack_start(low_hbox, True, True) |
912 | + hbox.pack_start(vbox, True, True) |
913 | + |
914 | + def _fill_bottom_hbox(self, hbox): |
915 | + ''' |
916 | + Helper function to fill and hbox with a buttonbox, featuring |
917 | + and ok and cancel buttons. |
918 | + |
919 | + @param hbox: the gtk.HBox to fill |
920 | + ''' |
921 | + cancel_button = gtk.Button(stock = gtk.STOCK_CANCEL) |
922 | + cancel_button.connect('clicked', self.on_cancel) |
923 | + self.ok_button = gtk.Button(stock = gtk.STOCK_OK) |
924 | + self.ok_button.connect('clicked', self.on_confirm) |
925 | + align =gtk.Alignment(xalign = 0.5, \ |
926 | + yalign = 1, \ |
927 | + xscale = 1) |
928 | + align.set_padding(0, 10, 0, 0) |
929 | + buttonbox = gtk.HButtonBox() |
930 | + buttonbox.set_layout(gtk.BUTTONBOX_EDGE) |
931 | + buttonbox.add(cancel_button) |
932 | + buttonbox.set_child_secondary(cancel_button, False) |
933 | + buttonbox.add(self.ok_button) |
934 | + align.add(buttonbox) |
935 | + hbox.pack_start(align, True, True) |
936 | + |
937 | + def refresh_backends(self): |
938 | + '''Populates the combo box containing the available backends''' |
939 | + self.combo_types.refresh() |
940 | + |
941 | + def on_confirm(self, widget = None): |
942 | + ''' |
943 | + Notifies the dialog holding this VBox that a backend has been |
944 | + chosen |
945 | + |
946 | + @param widget: just to make this function usable as a signal callback. |
947 | + Not used. |
948 | + ''' |
949 | + backend_name = self.combo_types.get_selected() |
950 | + self.dialog.on_backend_added(backend_name) |
951 | + |
952 | + def on_cancel(self, widget = None): |
953 | + ''' |
954 | + Aborts the addition of a new backend. Shows the configuration panel |
955 | + previously loaded. |
956 | + |
957 | + @param widget: just to make this function usable as a signal callback. |
958 | + Not used. |
959 | + ''' |
960 | + self.dialog.show_config_for_backend(None) |
961 | + |
962 | + def on_combo_changed(self, widget = None): |
963 | + ''' |
964 | + Updates the backend description and icon. |
965 | + |
966 | + @param widget: just to make this function usable as a signal callback. |
967 | + Not used. |
968 | + ''' |
969 | + backend_name = self.combo_types.get_selected() |
970 | + if backend_name == None: |
971 | + return |
972 | + backend = BackendFactory().get_backend(backend_name) |
973 | + self.label_description.set_markup(backend.Backend.get_description()) |
974 | + |
975 | + label = _('Syncing is <span color="red">disabled</span>') |
976 | + markup = '<big><big><big><b>%s</b></big></big></big>' % \ |
977 | + backend.Backend.get_human_default_name() |
978 | + self.label_name.set_markup(markup) |
979 | + authors = backend.Backend.get_authors() |
980 | + author_txt = '<b>%s</b>:\n - %s' % \ |
981 | + (ngettext("Author", "Authors", len(authors)), |
982 | + reduce(lambda a, b: a + "\n" + " - " + b, authors)) |
983 | + self.label_author.set_markup(author_txt) |
984 | + #The code for showing the required modules has been disabled since it |
985 | + # seems that backends will be packaged separately (as plugins). I'm |
986 | + # leaving this here in case we change that decision (invernizzi). |
987 | + #self._build_module_list(backend.Backend) |
988 | + pixbuf = self.dialog.get_pixbuf_from_icon_name(backend_name, 100, 100) |
989 | + self.image_icon.set_from_pixbuf(pixbuf) |
990 | + self.show_all() |
991 | + |
992 | + #The code for showing the required modules has been disabled since it |
993 | + # seems that backends will be packaged separately (as plugins). I'm |
994 | + # leaving this here in case we change that decision (invernizzi). |
995 | +# def _build_module_list(self, backend): |
996 | +# missing_modules = [] |
997 | +# for module in backend.get_required_modules(): |
998 | +# try: |
999 | +# __import__(module) |
1000 | +# except ImportError: |
1001 | +# missing_modules.append(module) |
1002 | +# if missing_modules: |
1003 | +# text = "<b> Missing modules:</b>\n - " |
1004 | +# module2package = ModuleToPackage() |
1005 | +# missing_modules = map(lambda a: \ |
1006 | +# "<span color='red'>" + \ |
1007 | +# module2package.lookup(a) +\ |
1008 | +# "</span>", missing_modules) |
1009 | +# text += reduce(lambda a, b: a + "\n - " + b, missing_modules) |
1010 | +# self.label_modules.set_markup(text) |
1011 | +# self.ok_button.set_sensitive(missing_modules == []) |
1012 | + |
1013 | |
1014 | === added file 'GTG/gtk/backends_dialog/backendscombo.py' |
1015 | --- GTG/gtk/backends_dialog/backendscombo.py 1970-01-01 00:00:00 +0000 |
1016 | +++ GTG/gtk/backends_dialog/backendscombo.py 2010-08-13 23:43:07 +0000 |
1017 | @@ -0,0 +1,92 @@ |
1018 | +# -*- coding: utf-8 -*- |
1019 | +# ----------------------------------------------------------------------------- |
1020 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
1021 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
1022 | +# |
1023 | +# This program is free software: you can redistribute it and/or modify it under |
1024 | +# the terms of the GNU General Public License as published by the Free Software |
1025 | +# Foundation, either version 3 of the License, or (at your option) any later |
1026 | +# version. |
1027 | +# |
1028 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1029 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
1030 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
1031 | +# details. |
1032 | +# |
1033 | +# You should have received a copy of the GNU General Public License along with |
1034 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
1035 | +# ----------------------------------------------------------------------------- |
1036 | + |
1037 | +import gtk |
1038 | + |
1039 | +from GTG.backends import BackendFactory |
1040 | + |
1041 | + |
1042 | + |
1043 | +class BackendsCombo(gtk.ComboBoxEntry): |
1044 | + ''' |
1045 | + A combobox listing all the available backends types |
1046 | + ''' |
1047 | + |
1048 | + |
1049 | + COLUMN_NAME = 0 #unique name for the backend type. It's never |
1050 | + # displayed, it's used to find which backend has |
1051 | + # been selected |
1052 | + COLUMN_HUMAN_NAME = 1 #human friendly name (which is localized). |
1053 | + COLUMN_ICON = 2 |
1054 | + |
1055 | + def __init__(self, backends_dialog): |
1056 | + ''' |
1057 | + Constructor, itializes gtk widgets. |
1058 | + @param backends_dialog: reference to the dialog in which this combo is |
1059 | + loaded. |
1060 | + ''' |
1061 | + super(BackendsCombo, self).__init__() |
1062 | + self.dialog = backends_dialog |
1063 | + self._liststore_init() |
1064 | + self._renderers_init() |
1065 | + self.set_size_request(-1, 30) |
1066 | + self.show_all() |
1067 | + |
1068 | + def _liststore_init(self): |
1069 | + '''Setup the gtk.ListStore''' |
1070 | + self.liststore = gtk.ListStore(str, str, gtk.gdk.Pixbuf) |
1071 | + self.set_model(self.liststore) |
1072 | + |
1073 | + def _renderers_init(self): |
1074 | + '''Configure the cell renderers''' |
1075 | + #Text renderer |
1076 | + text_cell = gtk.CellRendererText() |
1077 | + self.pack_start(text_cell, False) |
1078 | + self.set_text_column(self.COLUMN_HUMAN_NAME) |
1079 | + #Icon renderer |
1080 | + pixbuf_cell = gtk.CellRendererPixbuf() |
1081 | + self.pack_start(pixbuf_cell, False) |
1082 | + self.add_attribute(pixbuf_cell, "pixbuf", self.COLUMN_ICON) |
1083 | + |
1084 | + def refresh(self): |
1085 | + ''' |
1086 | + Populates the combo box with the available backends |
1087 | + ''' |
1088 | + self.liststore.clear() |
1089 | + backend_types = BackendFactory().get_all_backends() |
1090 | + for name, module in backend_types.iteritems(): |
1091 | + pixbuf = self.dialog.get_pixbuf_from_icon_name(name, 16, 16) |
1092 | + self.liststore.append((name, \ |
1093 | + module.Backend.get_human_default_name(), \ |
1094 | + pixbuf)) |
1095 | + if backend_types: |
1096 | + #triggers a "changed" signal, which is used in the AddPanel to |
1097 | + #refresh the backend description and icon |
1098 | + self.set_active(0) |
1099 | + |
1100 | + def get_selected(self): |
1101 | + ''' |
1102 | + Returns the name of the selected backend, or None |
1103 | + ''' |
1104 | + selected_iter = self.get_active_iter() |
1105 | + if selected_iter: |
1106 | + return self.liststore.get_value(selected_iter, \ |
1107 | + BackendsCombo.COLUMN_NAME) |
1108 | + else: |
1109 | + return None |
1110 | |
1111 | === added file 'GTG/gtk/backends_dialog/backendstree.py' |
1112 | --- GTG/gtk/backends_dialog/backendstree.py 1970-01-01 00:00:00 +0000 |
1113 | +++ GTG/gtk/backends_dialog/backendstree.py 2010-08-13 23:43:07 +0000 |
1114 | @@ -0,0 +1,252 @@ |
1115 | +# -*- coding: utf-8 -*- |
1116 | +# ----------------------------------------------------------------------------- |
1117 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
1118 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
1119 | +# |
1120 | +# This program is free software: you can redistribute it and/or modify it under |
1121 | +# the terms of the GNU General Public License as published by the Free Software |
1122 | +# Foundation, either version 3 of the License, or (at your option) any later |
1123 | +# version. |
1124 | +# |
1125 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1126 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
1127 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
1128 | +# details. |
1129 | +# |
1130 | +# You should have received a copy of the GNU General Public License along with |
1131 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
1132 | +# ----------------------------------------------------------------------------- |
1133 | + |
1134 | +import gtk |
1135 | + |
1136 | +from GTG.gtk.colors import get_colored_tags_markup |
1137 | +from GTG.backends.genericbackend import GenericBackend |
1138 | +from GTG.backends.backendsignals import BackendSignals |
1139 | + |
1140 | + |
1141 | + |
1142 | +class BackendsTree(gtk.TreeView): |
1143 | + ''' |
1144 | + gtk.TreeView that shows the currently loaded backends. |
1145 | + ''' |
1146 | + |
1147 | + |
1148 | + COLUMN_BACKEND_ID = 0 #never shown, used for internal lookup. |
1149 | + COLUMN_ICON = 1 |
1150 | + COLUMN_TEXT = 2 # holds the backend "human-readable" name |
1151 | + COLUMN_TAGS = 3 |
1152 | + |
1153 | + def __init__(self, backendsdialog): |
1154 | + ''' |
1155 | + Constructor, just initializes the gtk widgets |
1156 | + |
1157 | + @param backends_dialog: a reference to the dialog in which this is |
1158 | + loaded |
1159 | + ''' |
1160 | + super(BackendsTree,self).__init__() |
1161 | + self.dialog = backendsdialog |
1162 | + self.req = backendsdialog.get_requester() |
1163 | + self._init_liststore() |
1164 | + self._init_renderers() |
1165 | + self._init_signals() |
1166 | + self.refresh() |
1167 | + |
1168 | + def refresh(self): |
1169 | + '''refreshes the gtk.Liststore''' |
1170 | + self.backendid_to_iter = {} |
1171 | + self.liststore.clear() |
1172 | + for backend in self.req.get_all_backends(disabled = True): |
1173 | + self.add_backend(backend) |
1174 | + self.on_backend_state_changed(None, backend.get_id()) |
1175 | + |
1176 | + def on_backend_added(self, sender, backend_id): |
1177 | + ''' |
1178 | + Signal callback executed when a new backend is loaded |
1179 | + |
1180 | + @param sender: not used, only here to let this function be used as a |
1181 | + callback |
1182 | + @param backend_id: the id of the backend to add |
1183 | + ''' |
1184 | + #Add |
1185 | + backend = self.req.get_backend(backend_id) |
1186 | + if not backend: |
1187 | + return |
1188 | + self.add_backend(backend) |
1189 | + #Select |
1190 | + self.select_backend(backend_id) |
1191 | + #Update it's enabled state |
1192 | + self.on_backend_state_changed(None, backend.get_id()) |
1193 | + |
1194 | + def add_backend(self, backend): |
1195 | + ''' |
1196 | + Adds a new backend to the list |
1197 | + |
1198 | + @param backend_id: the id of the backend to add |
1199 | + ''' |
1200 | + if backend: |
1201 | + backend_iter = self.liststore.append([ \ |
1202 | + backend.get_id(), \ |
1203 | + self.dialog.get_pixbuf_from_icon_name(backend.get_name(), \ |
1204 | + 16, 16), \ |
1205 | + backend.get_human_name(), \ |
1206 | + self._get_markup_for_tags(backend.get_attached_tags()), \ |
1207 | + ]) |
1208 | + self.backendid_to_iter[backend.get_id()] = backend_iter |
1209 | + |
1210 | + |
1211 | + def on_backend_state_changed(self, sender, backend_id): |
1212 | + ''' |
1213 | + Signal callback executed when a backend is enabled/disabled. |
1214 | + |
1215 | + @param sender: not used, only here to let this function be used as a |
1216 | + callback |
1217 | + @param backend_id: the id of the backend to add |
1218 | + ''' |
1219 | + if backend_id in self.backendid_to_iter: |
1220 | + style = self.get_style() |
1221 | + b_iter = self.backendid_to_iter[backend_id] |
1222 | + b_path = self.liststore.get_path(b_iter) |
1223 | + backend = self.req.get_backend(backend_id) |
1224 | + backend_name = backend.get_human_name() |
1225 | + if backend.is_enabled(): |
1226 | + text = backend_name |
1227 | + else: |
1228 | + color = str(style.text[gtk.STATE_INSENSITIVE]) |
1229 | + text = "<span color='%s'>%s</span>" % \ |
1230 | + (color, backend_name) |
1231 | + self.liststore[b_path][self.COLUMN_TEXT] = text |
1232 | + |
1233 | + def _get_markup_for_tags(self, tag_names): |
1234 | + '''Given a list of tags names, generates the pango markup to render that |
1235 | + list with the tag colors used in GTG |
1236 | + |
1237 | + @param tag_names: the list of the tags (strings) |
1238 | + @return str: the pango markup string |
1239 | + ''' |
1240 | + if GenericBackend.ALLTASKS_TAG in tag_names: |
1241 | + tags_txt = "" |
1242 | + else: |
1243 | + tags_txt = get_colored_tags_markup(self.req, tag_names) |
1244 | + return "<small>" + tags_txt + "</small>" |
1245 | + |
1246 | + |
1247 | + def remove_backend(self, backend_id): |
1248 | + ''' Removes a backend from the treeview, and selects the first (to show |
1249 | + something in the configuration panel |
1250 | + |
1251 | + @param backend_id: the id of the backend to remove |
1252 | + ''' |
1253 | + if backend_id in self.backendid_to_iter: |
1254 | + self.liststore.remove(self.backendid_to_iter[backend_id]) |
1255 | + del self.backendid_to_iter[backend_id] |
1256 | + self.select_backend() |
1257 | + |
1258 | + def _init_liststore(self): |
1259 | + '''Creates the liststore''' |
1260 | + self.liststore = gtk.ListStore(object, gtk.gdk.Pixbuf, str, str) |
1261 | + self.set_model(self.liststore) |
1262 | + |
1263 | + def _init_renderers(self): |
1264 | + '''Initializes the cell renderers''' |
1265 | + # We hide the columns headers |
1266 | + self.set_headers_visible(False) |
1267 | + # For the backend icon |
1268 | + pixbuf_cell = gtk.CellRendererPixbuf() |
1269 | + tvcolumn_pixbuf = gtk.TreeViewColumn('Icon', pixbuf_cell) |
1270 | + tvcolumn_pixbuf.add_attribute(pixbuf_cell, 'pixbuf', self.COLUMN_ICON) |
1271 | + self.append_column(tvcolumn_pixbuf) |
1272 | + # For the backend name |
1273 | + text_cell = gtk.CellRendererText() |
1274 | + tvcolumn_text = gtk.TreeViewColumn('Name', text_cell) |
1275 | + tvcolumn_text.add_attribute(text_cell, 'markup', self.COLUMN_TEXT) |
1276 | + self.append_column(tvcolumn_text) |
1277 | + text_cell.connect('edited', self.cell_edited_callback) |
1278 | + text_cell.set_property('editable', True) |
1279 | + # For the backend tags |
1280 | + tags_cell = gtk.CellRendererText() |
1281 | + tvcolumn_tags = gtk.TreeViewColumn('Tags', tags_cell) |
1282 | + tvcolumn_tags.add_attribute(tags_cell, 'markup', self.COLUMN_TAGS) |
1283 | + self.append_column(tvcolumn_tags) |
1284 | + |
1285 | + def cell_edited_callback(self, text_cell, path, new_text): |
1286 | + '''If a backend name is changed, it saves the changes in the Backend |
1287 | + |
1288 | + @param text_cell: not used. The gtk.CellRendererText that emitted the |
1289 | + signal. Only here because it's passed by the signal |
1290 | + @param path: the gtk.TreePath of the edited cell |
1291 | + @param new_text: the new name of the backend |
1292 | + ''' |
1293 | + #we strip everything not permitted in backend names |
1294 | + new_text = ''.join(c for c in new_text if (c.isalnum() or\ |
1295 | + c in [" ", "-", "_"])) |
1296 | + selected_iter = self.liststore.get_iter(path) |
1297 | + # update the backend name |
1298 | + backend_id = self.liststore.get_value(selected_iter, \ |
1299 | + self.COLUMN_BACKEND_ID) |
1300 | + backend = self.dialog.get_requester().get_backend(backend_id) |
1301 | + if backend: |
1302 | + backend.set_human_name(new_text) |
1303 | + # update the text in the liststore |
1304 | + self.liststore.set(selected_iter, self.COLUMN_TEXT, new_text) |
1305 | + |
1306 | + def _init_signals(self): |
1307 | + '''Initializes the backends and gtk signals ''' |
1308 | + self.connect("cursor-changed", self.on_select_row) |
1309 | + _signals = BackendSignals() |
1310 | + _signals.connect(_signals.BACKEND_ADDED, self.on_backend_added) |
1311 | + _signals.connect(_signals.BACKEND_STATE_TOGGLED, |
1312 | + self.on_backend_state_changed) |
1313 | + |
1314 | + def on_select_row(self, treeview = None): |
1315 | + '''When a row is selected, displays the corresponding editing panel |
1316 | + |
1317 | + @treeview: not used |
1318 | + ''' |
1319 | + self.dialog.on_backend_selected(self.get_selected_backend_id()) |
1320 | + |
1321 | + def _get_selected_path(self): |
1322 | + ''' |
1323 | + Helper function to get the selected path |
1324 | + |
1325 | + @return gtk.TreePath : returns exactly one path for the selected object or |
1326 | + None |
1327 | + ''' |
1328 | + selection = self.get_selection() |
1329 | + if selection: |
1330 | + model, selected_paths = self.get_selection().get_selected_rows() |
1331 | + if selected_paths: |
1332 | + return selected_paths[0] |
1333 | + return None |
1334 | + |
1335 | + def select_backend(self, backend_id = None): |
1336 | + ''' |
1337 | + Selects the backend corresponding to backend_id. |
1338 | + If backend_id is none, refreshes the current configuration panel. |
1339 | + |
1340 | + @param backend_id: the id of the backend to select |
1341 | + ''' |
1342 | + if backend_id in self.backendid_to_iter: |
1343 | + backend_iter = self.backendid_to_iter[backend_id] |
1344 | + selection = self.get_selection() |
1345 | + if selection: |
1346 | + selection.select_iter(backend_iter) |
1347 | + else: |
1348 | + if self._get_selected_path(): |
1349 | + #We just reselect the currently selected entry |
1350 | + self.on_select_row() |
1351 | + else: |
1352 | + #If nothing is selected, we select the first entry |
1353 | + self.get_selection().select_path("0") |
1354 | + self.dialog.on_backend_selected(self.get_selected_backend_id()) |
1355 | + |
1356 | + def get_selected_backend_id(self): |
1357 | + ''' |
1358 | + returns the selected backend id, or none |
1359 | + |
1360 | + @return string: the selected backend id (or None) |
1361 | + ''' |
1362 | + selected_path = self._get_selected_path() |
1363 | + if not selected_path: |
1364 | + return None |
1365 | + selected_iter = self.liststore.get_iter(selected_path) |
1366 | + return self.liststore.get_value(selected_iter, self.COLUMN_BACKEND_ID) |
1367 | |
1368 | === added file 'GTG/gtk/backends_dialog/configurepanel.py' |
1369 | --- GTG/gtk/backends_dialog/configurepanel.py 1970-01-01 00:00:00 +0000 |
1370 | +++ GTG/gtk/backends_dialog/configurepanel.py 2010-08-13 23:43:07 +0000 |
1371 | @@ -0,0 +1,298 @@ |
1372 | +# -*- coding: utf-8 -*- |
1373 | +# ----------------------------------------------------------------------------- |
1374 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
1375 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
1376 | +# |
1377 | +# This program is free software: you can redistribute it and/or modify it under |
1378 | +# the terms of the GNU General Public License as published by the Free Software |
1379 | +# Foundation, either version 3 of the License, or (at your option) any later |
1380 | +# version. |
1381 | +# |
1382 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1383 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
1384 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
1385 | +# details. |
1386 | +# |
1387 | +# You should have received a copy of the GNU General Public License along with |
1388 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
1389 | +# ----------------------------------------------------------------------------- |
1390 | + |
1391 | +import gtk |
1392 | + |
1393 | +from GTG.gtk.colors import get_colored_tags_markup |
1394 | +from GTG import _, ngettext |
1395 | +from GTG.backends.genericbackend import GenericBackend |
1396 | +from GTG.gtk.backends_dialog.parameters_ui import ParametersUI |
1397 | +from GTG.backends.backendsignals import BackendSignals |
1398 | + |
1399 | + |
1400 | +class ConfigurePanel(gtk.VBox): |
1401 | + ''' |
1402 | + A VBox that lets you configure a backend |
1403 | + ''' |
1404 | + |
1405 | + |
1406 | + def __init__(self, backends_dialog): |
1407 | + ''' |
1408 | + Constructor, creating all the gtk widgets |
1409 | + |
1410 | + @param backends_dialog: a reference to the dialog in which this is |
1411 | + loaded |
1412 | + ''' |
1413 | + super(ConfigurePanel, self).__init__() |
1414 | + self.dialog = backends_dialog |
1415 | + self.should_spinner_be_shown = False |
1416 | + self.task_deleted_handle = None |
1417 | + self.task_added_handle = None |
1418 | + self.req = backends_dialog.get_requester() |
1419 | + self._create_widgets() |
1420 | + self._connect_signals() |
1421 | + |
1422 | + def _connect_signals(self): |
1423 | + ''' Connects the backends generated signals ''' |
1424 | + _signals = BackendSignals() |
1425 | + _signals.connect(_signals.BACKEND_RENAMED, self.refresh_title) |
1426 | + _signals.connect(_signals.BACKEND_STATE_TOGGLED, \ |
1427 | + self.refresh_sync_status) |
1428 | + _signals.connect(_signals.BACKEND_SYNC_STARTED, self.on_sync_started) |
1429 | + _signals.connect(_signals.BACKEND_SYNC_ENDED, self.on_sync_ended) |
1430 | + |
1431 | + def _create_widgets(self): |
1432 | + ''' |
1433 | + This function fills this Vbox with widgets |
1434 | + ''' |
1435 | + #Division of the available space in three segments: |
1436 | + # top, middle and bottom |
1437 | + top = gtk.HBox() |
1438 | + middle = gtk.HBox() |
1439 | + self._fill_top_hbox(top) |
1440 | + self._fill_middle_hbox(middle) |
1441 | + self.pack_start(top, False) |
1442 | + self.pack_start(middle, False) |
1443 | + align = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1) |
1444 | + align.set_padding(10, 0, 0, 0) |
1445 | + self.parameters_ui = ParametersUI(self.req) |
1446 | + align.add(self.parameters_ui) |
1447 | + self.pack_start(align, False) |
1448 | + |
1449 | + def _fill_top_hbox(self, hbox): |
1450 | + ''' |
1451 | + Helper function to fill an hbox with an image, a spinner and |
1452 | + three labels |
1453 | + |
1454 | + @param hbox: the gtk.HBox to fill |
1455 | + ''' |
1456 | + hbox.set_spacing(10) |
1457 | + self.image_icon = gtk.Image() |
1458 | + self.image_icon.set_size_request(100, 100) |
1459 | + vbox = gtk.VBox() |
1460 | + hbox_top = gtk.HBox() |
1461 | + self.human_name_label = gtk.Label() |
1462 | + self.human_name_label.set_alignment(xalign = 0, yalign = 0.5) |
1463 | + self.spinner = gtk.Spinner() |
1464 | + self.spinner.set_size_request(32, 32) |
1465 | + self.spinner.connect("show", self.on_spinner_show) |
1466 | + align_spin = gtk.Alignment(xalign = 1, yalign = 0) |
1467 | + align_spin.add(self.spinner) |
1468 | + hbox_top.pack_start(self.human_name_label, True) |
1469 | + hbox_top.pack_start(align_spin, False) |
1470 | + self.sync_desc_label = gtk.Label() |
1471 | + self.sync_desc_label.set_alignment(xalign = 0, yalign = 1) |
1472 | + self.sync_desc_label.set_line_wrap(True) |
1473 | + vbox.pack_start(hbox_top, True) |
1474 | + vbox.pack_start(self.sync_desc_label, True) |
1475 | + hbox.pack_start(self.image_icon, False) |
1476 | + align_vbox = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1) |
1477 | + align_vbox.set_padding(10, 0, 20, 0) |
1478 | + align_vbox.add(vbox) |
1479 | + hbox.pack_start(align_vbox, True) |
1480 | + |
1481 | + def _fill_middle_hbox(self, hbox): |
1482 | + ''' |
1483 | + Helper function to fill an hbox with a label and a button |
1484 | + |
1485 | + @param hbox: the gtk.HBox to fill |
1486 | + ''' |
1487 | + self.sync_status_label = gtk.Label() |
1488 | + self.sync_status_label.set_alignment(xalign = 0.8, yalign = 0.5) |
1489 | + self.sync_button = gtk.Button() |
1490 | + self.sync_button.connect("clicked", self.on_sync_button_clicked) |
1491 | + hbox.pack_start(self.sync_status_label, True) |
1492 | + hbox.pack_start(self.sync_button, True) |
1493 | + |
1494 | + def set_backend(self, backend_id): |
1495 | + '''Changes the backend to configure, refreshing this view. |
1496 | + |
1497 | + @param backend_id: the id of the backend to configure |
1498 | + ''' |
1499 | + self.backend = self.dialog.get_requester().get_backend(backend_id) |
1500 | + self.refresh_title() |
1501 | + self.refresh_sync_status() |
1502 | + self.parameters_ui.refresh(self.backend) |
1503 | + self.image_icon.set_from_pixbuf(self.dialog.get_pixbuf_from_icon_name(\ |
1504 | + self.backend.get_name(), 80, 80)) |
1505 | + |
1506 | + def refresh_title(self, sender = None, data = None): |
1507 | + ''' |
1508 | + Callback for the signal that notifies backends name changes. It changes |
1509 | + the title of this view |
1510 | + |
1511 | + @param sender: not used, here only for signal callback compatibility |
1512 | + @param data: not used, here only for signal callback compatibility |
1513 | + ''' |
1514 | + markup = "<big><big><big><b>%s</b></big></big></big>" % \ |
1515 | + self.backend.get_human_name() |
1516 | + self.human_name_label.set_markup(markup) |
1517 | + |
1518 | + def refresh_number_of_tasks(self): |
1519 | + '''refreshes the number of synced tasks by this backend''' |
1520 | + #FIXME: disabled for now. I'm not sure that this is nice because the |
1521 | + # count is correct only after the backend has synced all the pending |
1522 | + # tasks, and this is quite misleading (invernizzi) |
1523 | + return |
1524 | + #This will have to be changed for import/export.. |
1525 | + tags = self.backend.get_attached_tags() |
1526 | + tasks_number = self.backend.get_number_of_tasks() |
1527 | + if GenericBackend.ALLTASKS_TAG in tags: |
1528 | + if tasks_number == 0: |
1529 | + markup = _("Ready to start syncing") |
1530 | + else: |
1531 | + markup = ngettext("Syncing your only task", \ |
1532 | + "Syncing all %d tasks" % tasks_number, tasks_number) |
1533 | + else: |
1534 | + tags_txt = get_colored_tags_markup(self.req, tags) |
1535 | + if tasks_number == 0: |
1536 | + markup = _("There's no task tagged %s") % tags_txt |
1537 | + else: |
1538 | + markup = ngettext("Syncing a task tagged %s" % tags_txt, \ |
1539 | + "Syncing %d tasks tagged %s" % (tasks_number, tags_txt), \ |
1540 | + tasks_number) |
1541 | + self.sync_desc_label.set_markup(markup) |
1542 | + |
1543 | + def refresh_sync_button(self): |
1544 | + ''' |
1545 | + Refreshes the state of the button that enables the backend |
1546 | + ''' |
1547 | + self.sync_button.set_sensitive(not self.backend.is_default()) |
1548 | + if self.backend.is_enabled(): |
1549 | + label = _("Disable syncing") |
1550 | + else: |
1551 | + label = _("Enable syncing") |
1552 | + self.sync_button.set_label(label) |
1553 | + |
1554 | + def refresh_sync_status_label(self): |
1555 | + ''' |
1556 | + Refreshes the gtk.Label that shows the current state of this backend |
1557 | + ''' |
1558 | + if self.backend.is_default(): |
1559 | + label = _("This is the default backend") |
1560 | + else: |
1561 | + if self.backend.is_enabled(): |
1562 | + label = _("Syncing is enabled") |
1563 | + else: |
1564 | + label = _('Syncing is <span color="red">disabled</span>') |
1565 | + self.sync_status_label.set_markup(label) |
1566 | + |
1567 | + def refresh_sync_status(self, sender = False, data = False): |
1568 | + '''Signal callback function, called when a backend state |
1569 | + (enabled/disabled) changes. Refreshes this view. |
1570 | + |
1571 | + @param sender: not used, here only for signal callback compatibility |
1572 | + @param data: not used, here only for signal callback compatibility |
1573 | + ''' |
1574 | + self.refresh_number_of_tasks() |
1575 | + self.refresh_sync_button() |
1576 | + self.refresh_sync_status_label() |
1577 | + |
1578 | + def set_hidden(self, is_hidden): |
1579 | + ''' |
1580 | + Notifies this pane if it's hidden or not. We disconnect signals when |
1581 | + hidden, since there is no need to keep the UI updated. |
1582 | + Hopefully, this should make GTG faster :) |
1583 | + |
1584 | + @param is_hidden: boolean, True if the window is not visible |
1585 | + ''' |
1586 | + #These is only needed to refresh the number of synced tasks. |
1587 | + #since that is disabled for now, there is no need for this |
1588 | + |
1589 | +# if is_hidden: |
1590 | +# if self.task_added_handle: |
1591 | +# self.req.disconnect(self.task_added_handle) |
1592 | +# self.task_added_handle = None |
1593 | +# if self.task_deleted_handle: |
1594 | +# self.req.disconnect(self.task_deleted_handle) |
1595 | +# self.task_deleted_handle = None |
1596 | +# else: |
1597 | +# self.task_added_handle = self.req.connect("task-added", \ |
1598 | +# self.__on_task_changed) |
1599 | +# self.task_added_handle = self.req.connect("task-modified", \ |
1600 | +# self.__on_task_changed) |
1601 | +# self.task_deleted_handle = self.req.connect("task-deleted", \ |
1602 | +# self.__on_task_changed) |
1603 | +# |
1604 | +# def __on_task_changed(self, sender, task_id): |
1605 | +# ''' |
1606 | +# If tasks are added, modified or removed, updates the number of |
1607 | +# tasks of the current backend |
1608 | +# ''' |
1609 | +# self.refresh_sync_status() |
1610 | + |
1611 | + def on_sync_button_clicked(self, sender): |
1612 | + ''' |
1613 | + Signal callback when a backend is enabled/disabled via the UI button |
1614 | + |
1615 | + @param sender: not used, here only for signal callback compatibility |
1616 | + ''' |
1617 | + self.parameters_ui.commit_changes() |
1618 | + self.req.set_backend_enabled(self.backend.get_id(), \ |
1619 | + not self.backend.is_enabled()) |
1620 | + |
1621 | + def on_sync_started(self, sender, backend_id): |
1622 | + ''' |
1623 | + If the backend has started syncing tasks, update the state of the |
1624 | + gtk.Spinner |
1625 | + |
1626 | + @param sender: not used, here only for signal callback compatibility |
1627 | + @param backend_id: the id of the backend that emitted this signal |
1628 | + ''' |
1629 | + if backend_id == self.backend.get_id(): |
1630 | + self.spinner_set_active(True) |
1631 | + |
1632 | + def on_sync_ended(self, sender, backend_id): |
1633 | + ''' |
1634 | + If the backend has stopped syncing tasks, update the state of the |
1635 | + gtk.Spinner |
1636 | + |
1637 | + @param sender: not used, here only for signal callback compatibility |
1638 | + @param backend_id: the id of the backend that emitted this signal |
1639 | + ''' |
1640 | + |
1641 | + if backend_id == self.backend.get_id(): |
1642 | + self.spinner_set_active(False) |
1643 | + |
1644 | + def on_spinner_show(self, sender): |
1645 | + '''This signal callback hides the spinner if it's not supposed to be |
1646 | + seen. It's a workaround to let us call show_all on the whole window |
1647 | + while keeping this hidden (it's the only widget that requires special |
1648 | + attention) |
1649 | + |
1650 | + @param sender: not used, here only for signal callback compatibility |
1651 | + ''' |
1652 | + if self.should_spinner_be_shown == False: |
1653 | + self.spinner.hide() |
1654 | + |
1655 | + def spinner_set_active(self, active): |
1656 | + ''' |
1657 | + Enables/disables the gtk.Spinner, while showing/hiding it at the same |
1658 | + time |
1659 | + |
1660 | + @param active: True if the spinner should spin |
1661 | + ''' |
1662 | + self.should_spinner_be_shown = active |
1663 | + if active: |
1664 | + self.spinner.start() |
1665 | + self.spinner.show() |
1666 | + else: |
1667 | + self.spinner.hide() |
1668 | + self.spinner.stop() |
1669 | + |
1670 | |
1671 | === added directory 'GTG/gtk/backends_dialog/parameters_ui' |
1672 | === added file 'GTG/gtk/backends_dialog/parameters_ui/__init__.py' |
1673 | --- GTG/gtk/backends_dialog/parameters_ui/__init__.py 1970-01-01 00:00:00 +0000 |
1674 | +++ GTG/gtk/backends_dialog/parameters_ui/__init__.py 2010-08-13 23:43:07 +0000 |
1675 | @@ -0,0 +1,149 @@ |
1676 | +# -*- coding: utf-8 -*- |
1677 | +# ----------------------------------------------------------------------------- |
1678 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
1679 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
1680 | +# |
1681 | +# This program is free software: you can redistribute it and/or modify it under |
1682 | +# the terms of the GNU General Public License as published by the Free Software |
1683 | +# Foundation, either version 3 of the License, or (at your option) any later |
1684 | +# version. |
1685 | +# |
1686 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1687 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
1688 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
1689 | +# details. |
1690 | +# |
1691 | +# You should have received a copy of the GNU General Public License along with |
1692 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
1693 | +# ----------------------------------------------------------------------------- |
1694 | +''' |
1695 | +This modules reads a bakcn configuration and generates a series of widgets to |
1696 | +let the user see the configuration and modify it. |
1697 | +In this manner, backends do not need to know anything about their UI since it's |
1698 | +built for them: it should play along the lines of the separation between GTG |
1699 | +server and client |
1700 | +''' |
1701 | + |
1702 | +#FIXME: all the parameters have one function in common (2 lines total). |
1703 | +# Evaluate if there is a clean way to avoid duplication of this code, |
1704 | +# without becoming too difficult to understand. |
1705 | +# (invernizzi) |
1706 | + |
1707 | +import gtk |
1708 | +import functools |
1709 | + |
1710 | +from GTG import _ |
1711 | +from GTG.backends.genericbackend import GenericBackend |
1712 | +from GTG.gtk.backends_dialog.parameters_ui.importtagsui import ImportTagsUI |
1713 | +from GTG.gtk.backends_dialog.parameters_ui.textui import TextUI |
1714 | +from GTG.gtk.backends_dialog.parameters_ui.passwordui import PasswordUI |
1715 | +from GTG.gtk.backends_dialog.parameters_ui.periodui import PeriodUI |
1716 | +from GTG.gtk.backends_dialog.parameters_ui.checkboxui import CheckBoxUI |
1717 | +from GTG.gtk.backends_dialog.parameters_ui.pathui import PathUI |
1718 | + |
1719 | + |
1720 | + |
1721 | +class ParametersUI(gtk.VBox): |
1722 | + ''' |
1723 | + Given a bakcend, this gtk.VBox populates itself with all the necessary |
1724 | + widgets to view and edit a backend configuration |
1725 | + ''' |
1726 | + |
1727 | + |
1728 | + COMMON_WIDTH = 150 |
1729 | + |
1730 | + def __init__(self, requester): |
1731 | + '''Constructs the list of the possible widgets. |
1732 | + |
1733 | + @param requester: a GTG.core.requester.Requester object |
1734 | + ''' |
1735 | + super(ParametersUI, self).__init__(False) |
1736 | + self.req = requester |
1737 | + self.set_spacing(10) |
1738 | + |
1739 | + #builds a list of widget generators. More precisely, it's a |
1740 | + # list of tuples: (backend_parameter_name, widget_generator) |
1741 | + self.parameter_widgets = ( \ |
1742 | + ("import-tags", self.UI_generator(ImportTagsUI, \ |
1743 | + {"title": _("Import tags"), \ |
1744 | + "anybox_text": _("All tags"), \ |
1745 | + "somebox_text": _("Just these tags"), \ |
1746 | + "parameter_name": "import-tags"}) \ |
1747 | + ),\ |
1748 | + ("attached-tags", self.UI_generator(ImportTagsUI, \ |
1749 | + {"title": _("Tags to sync"), \ |
1750 | + "anybox_text": _("All tasks"), \ |
1751 | + "somebox_text": _("Tasks with these tags"), \ |
1752 | + "parameter_name": "attached-tags"}) \ |
1753 | + ),\ |
1754 | + ("path", self.UI_generator(PathUI)), \ |
1755 | + ("username", self.UI_generator(TextUI, \ |
1756 | + {"description": _("Username"), |
1757 | + "parameter_name": "username"}) |
1758 | + ), \ |
1759 | + ("password" , self.UI_generator(PasswordUI)), \ |
1760 | + ("period" , self.UI_generator(PeriodUI)), \ |
1761 | + ("import-from-replies", self.UI_generator(CheckBoxUI, \ |
1762 | + {"text": _("Import tasks from @ replies " + \ |
1763 | + "directed to you"), \ |
1764 | + "parameter": "import-from-replies"}) \ |
1765 | + ),\ |
1766 | + ("import-from-direct-messages", self.UI_generator(CheckBoxUI, \ |
1767 | + {"text": _("Import tasks from direct messages"), \ |
1768 | + "parameter": "import-from-direct-messages"}) \ |
1769 | + ),\ |
1770 | + ("import-from-my-tweets", self.UI_generator(CheckBoxUI, \ |
1771 | + {"text": _("Import tasks from your tweets"), \ |
1772 | + "parameter": "import-from-my-tweets"}) \ |
1773 | + ),\ |
1774 | + ("import-bug-tags", self.UI_generator(CheckBoxUI, \ |
1775 | + {"text": _("Tag your tasks with the bug tags"), \ |
1776 | + "parameter": "import-bug-tags"}) \ |
1777 | + ),\ |
1778 | + ) |
1779 | + def UI_generator(self, param_type, special_arguments = {}): |
1780 | + '''A helper function to build a widget type from a template. |
1781 | + It passes to the created widget generator a series of common parameters, |
1782 | + plus the ones needed to specialize the given template |
1783 | + |
1784 | + @param param_type: the template to specialize |
1785 | + @param special_arguments: the arguments used for this particular widget |
1786 | + generator. |
1787 | + |
1788 | + @return function: return a widget generator, not a widget. the widget can |
1789 | + be obtained by calling widget_generator(backend) |
1790 | + ''' |
1791 | + return lambda backend: param_type(req = self.req, \ |
1792 | + backend = backend, \ |
1793 | + width = self.COMMON_WIDTH, \ |
1794 | + **special_arguments) |
1795 | + |
1796 | + def refresh(self, backend): |
1797 | + '''Builds the widgets necessary to configure the backend. If it doesn't |
1798 | + know how to render a widget, it simply skips it. |
1799 | + |
1800 | + @param backend: the backend that is being configured |
1801 | + ''' |
1802 | + #remove the old parameters UIs |
1803 | + def _remove_child(self, child): |
1804 | + self.remove(child) |
1805 | + self.foreach(functools.partial(_remove_child, self)) |
1806 | + #add new widgets |
1807 | + backend_parameters = backend.get_parameters() |
1808 | + if backend_parameters[GenericBackend.KEY_DEFAULT_BACKEND]: |
1809 | + #if it's the default backend, the user should not mess with it |
1810 | + return |
1811 | + for parameter_name, widget in self.parameter_widgets: |
1812 | + if parameter_name in backend_parameters: |
1813 | + self.pack_start(widget(backend), True) |
1814 | + self.show_all() |
1815 | + |
1816 | + def commit_changes(self): |
1817 | + ''' |
1818 | + Saves all the parameters at their current state (the user may have |
1819 | + modified them) |
1820 | + ''' |
1821 | + def _commit_changes(child): |
1822 | + child.commit_changes() |
1823 | + self.foreach(_commit_changes) |
1824 | + |
1825 | |
1826 | === added file 'GTG/gtk/backends_dialog/parameters_ui/checkboxui.py' |
1827 | --- GTG/gtk/backends_dialog/parameters_ui/checkboxui.py 1970-01-01 00:00:00 +0000 |
1828 | +++ GTG/gtk/backends_dialog/parameters_ui/checkboxui.py 2010-08-13 23:43:07 +0000 |
1829 | @@ -0,0 +1,72 @@ |
1830 | +# -*- coding: utf-8 -*- |
1831 | +# ----------------------------------------------------------------------------- |
1832 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
1833 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
1834 | +# |
1835 | +# This program is free software: you can redistribute it and/or modify it under |
1836 | +# the terms of the GNU General Public License as published by the Free Software |
1837 | +# Foundation, either version 3 of the License, or (at your option) any later |
1838 | +# version. |
1839 | +# |
1840 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1841 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
1842 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
1843 | +# details. |
1844 | +# |
1845 | +# You should have received a copy of the GNU General Public License along with |
1846 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
1847 | +# ----------------------------------------------------------------------------- |
1848 | + |
1849 | +import gtk |
1850 | + |
1851 | + |
1852 | + |
1853 | +class CheckBoxUI(gtk.HBox): |
1854 | + ''' |
1855 | + It's a widget displaying a simple checkbox, with some text to explain its |
1856 | + meaning |
1857 | + ''' |
1858 | + |
1859 | + |
1860 | + def __init__(self, req, backend, width, text, parameter): |
1861 | + ''' |
1862 | + Creates the checkbox and the related label. |
1863 | + |
1864 | + @param req: a Requester |
1865 | + @param backend: a backend object |
1866 | + @param width: the width of the gtk.Label object |
1867 | + @param parameter: the backend parameter this checkbox should display and |
1868 | + modify |
1869 | + ''' |
1870 | + super(CheckBoxUI, self).__init__() |
1871 | + self.backend = backend |
1872 | + self.req = req |
1873 | + self.text = text |
1874 | + self.parameter = parameter |
1875 | + self._populate_gtk(width) |
1876 | + |
1877 | + def _populate_gtk(self, width): |
1878 | + '''Creates the checkbox and the related label |
1879 | + |
1880 | + @param width: the width of the gtk.Label object |
1881 | + ''' |
1882 | + self.checkbutton =gtk.CheckButton(label = self.text) |
1883 | + self.checkbutton.set_active(self.backend.get_parameters()[self.parameter]) |
1884 | + self.checkbutton.connect("toggled", self.on_modified) |
1885 | + self.pack_start(self.checkbutton, False) |
1886 | + |
1887 | + def commit_changes(self): |
1888 | + '''Saves the changes to the backend parameter''' |
1889 | + self.backend.set_parameter(self.parameter,\ |
1890 | + self.checkbutton.get_active()) |
1891 | + |
1892 | + def on_modified(self, sender = None): |
1893 | + ''' Signal callback, executed when the user clicks on the checkbox. |
1894 | + Disables the backend. The user will re-enable it to confirm the changes |
1895 | + (s)he made. |
1896 | + |
1897 | + @param sender: not used, only here for signal compatibility |
1898 | + ''' |
1899 | + if self.backend.is_enabled() and not self.backend.is_default(): |
1900 | + self.req.set_backend_enabled(self.backend.get_id(), False) |
1901 | + |
1902 | |
1903 | === added file 'GTG/gtk/backends_dialog/parameters_ui/importtagsui.py' |
1904 | --- GTG/gtk/backends_dialog/parameters_ui/importtagsui.py 1970-01-01 00:00:00 +0000 |
1905 | +++ GTG/gtk/backends_dialog/parameters_ui/importtagsui.py 2010-08-13 23:43:07 +0000 |
1906 | @@ -0,0 +1,131 @@ |
1907 | +# -*- coding: utf-8 -*- |
1908 | +# ----------------------------------------------------------------------------- |
1909 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
1910 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
1911 | +# |
1912 | +# This program is free software: you can redistribute it and/or modify it under |
1913 | +# the terms of the GNU General Public License as published by the Free Software |
1914 | +# Foundation, either version 3 of the License, or (at your option) any later |
1915 | +# version. |
1916 | +# |
1917 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1918 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
1919 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
1920 | +# details. |
1921 | +# |
1922 | +# You should have received a copy of the GNU General Public License along with |
1923 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
1924 | +# ----------------------------------------------------------------------------- |
1925 | + |
1926 | +import gtk |
1927 | + |
1928 | +from GTG.backends.genericbackend import GenericBackend |
1929 | + |
1930 | + |
1931 | + |
1932 | +class ImportTagsUI(gtk.VBox): |
1933 | + ''' |
1934 | + It's a widget displaying a couple of radio buttons, a label and a textbox |
1935 | + to let the user change the attached tags (or imported) |
1936 | + ''' |
1937 | + |
1938 | + |
1939 | + def __init__(self, req, backend, width, title, anybox_text, somebox_text, \ |
1940 | + parameter_name): |
1941 | + '''Populates the widgets and refresh the tags to display |
1942 | + |
1943 | + @param req: a requester |
1944 | + @param backend: the backend to configure |
1945 | + @param width: the length of the radio buttons |
1946 | + @param title: the text for the label describing what this collection |
1947 | + of gtk widgets is used for |
1948 | + @param anybox_text: the text for the "Any tag matches" radio button |
1949 | + @param somebox_text: the text for the "only this set of tags matches" |
1950 | + radio button |
1951 | + @param parameter_name: the backend parameter this widget should modify |
1952 | + ''' |
1953 | + super(ImportTagsUI, self).__init__() |
1954 | + self.backend = backend |
1955 | + self.req = req |
1956 | + self.title = title |
1957 | + self.anybox_text = anybox_text |
1958 | + self.somebox_text = somebox_text |
1959 | + self.parameter_name = parameter_name |
1960 | + self._populate_gtk(width) |
1961 | + self._refresh_tags() |
1962 | + self._connect_signals() |
1963 | + |
1964 | + def _populate_gtk(self, width): |
1965 | + ''' |
1966 | + Populates the widgets |
1967 | + |
1968 | + @param width: the length of the radio buttons |
1969 | + ''' |
1970 | + title_label = gtk.Label() |
1971 | + title_label.set_alignment(xalign = 0, yalign = 0) |
1972 | + title_label.set_markup("<big><b>%s</b></big>" % self.title) |
1973 | + self.pack_start(title_label, True) |
1974 | + align = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1) |
1975 | + align.set_padding(0, 0, 10, 0) |
1976 | + self.pack_start(align, True) |
1977 | + vbox = gtk.VBox() |
1978 | + align.add(vbox) |
1979 | + self.all_tags_radio = gtk.RadioButton(group = None, \ |
1980 | + label = self.anybox_text) |
1981 | + vbox.pack_start(self.all_tags_radio, True) |
1982 | + self.some_tags_radio = gtk.RadioButton(group = self.all_tags_radio, |
1983 | + label = self.somebox_text) |
1984 | + self.some_tags_radio.set_size_request(width = width, height = -1) |
1985 | + hbox = gtk.HBox() |
1986 | + vbox.pack_start(hbox, True) |
1987 | + hbox.pack_start(self.some_tags_radio, False) |
1988 | + self.tags_entry = gtk.Entry() |
1989 | + hbox.pack_start(self.tags_entry, True) |
1990 | + |
1991 | + def on_changed(self, radio, data = None): |
1992 | + ''' Signal callback, executed when the user modifies something. |
1993 | + Disables the backend. The user will re-enable it to confirm the changes |
1994 | + (s)he made. |
1995 | + |
1996 | + @param sender: not used, only here for signal compatibility |
1997 | + @param data: not used, only here for signal compatibility |
1998 | + ''' |
1999 | + #every change in the config disables the backend |
2000 | + self.req.set_backend_enabled(self.backend.get_id(), False) |
2001 | + self._refresh_textbox_state() |
2002 | + |
2003 | + def commit_changes(self): |
2004 | + '''Saves the changes to the backend parameter''' |
2005 | + if self.all_tags_radio.get_active(): |
2006 | + tags = [GenericBackend.ALLTASKS_TAG] |
2007 | + else: |
2008 | + tags = self.tags_entry.get_text().split(",") |
2009 | + tags = filter(lambda t: t, tags) |
2010 | + self.backend.set_parameter(self.parameter_name, tags) |
2011 | + |
2012 | + def _refresh_textbox_state(self): |
2013 | + '''Refreshes the content of the textbox''' |
2014 | + self.tags_entry.set_sensitive(self.some_tags_radio.get_active()) |
2015 | + |
2016 | + def _refresh_tags(self): |
2017 | + ''' |
2018 | + Refreshes the list of tags to display in the textbox, and selects |
2019 | + the correct radio button |
2020 | + ''' |
2021 | + tags_list = self.backend.get_parameters()[self.parameter_name] |
2022 | + has_all_tasks = GenericBackend.ALLTASKS_TAG in tags_list |
2023 | + self.all_tags_radio.set_active(has_all_tasks) |
2024 | + self.some_tags_radio.set_active(not has_all_tasks) |
2025 | + self._refresh_textbox_state() |
2026 | + if not has_all_tasks: |
2027 | + tags_text = "" |
2028 | + if tags_list: |
2029 | + tags_text = reduce(lambda a, b: a + ", " + b, tags_list) |
2030 | + self.tags_entry.set_text(tags_text) |
2031 | + |
2032 | + def _connect_signals(self): |
2033 | + '''Connects the gtk signals''' |
2034 | + self.some_tags_radio.connect("toggled", self.on_changed) |
2035 | + self.all_tags_radio.connect("toggled", self.on_changed) |
2036 | + self.tags_entry.connect("changed", self.on_changed) |
2037 | + |
2038 | |
2039 | === added file 'GTG/gtk/backends_dialog/parameters_ui/passwordui.py' |
2040 | --- GTG/gtk/backends_dialog/parameters_ui/passwordui.py 1970-01-01 00:00:00 +0000 |
2041 | +++ GTG/gtk/backends_dialog/parameters_ui/passwordui.py 2010-08-13 23:43:07 +0000 |
2042 | @@ -0,0 +1,84 @@ |
2043 | +# -*- coding: utf-8 -*- |
2044 | +# ----------------------------------------------------------------------------- |
2045 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
2046 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
2047 | +# |
2048 | +# This program is free software: you can redistribute it and/or modify it under |
2049 | +# the terms of the GNU General Public License as published by the Free Software |
2050 | +# Foundation, either version 3 of the License, or (at your option) any later |
2051 | +# version. |
2052 | +# |
2053 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2054 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
2055 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
2056 | +# details. |
2057 | +# |
2058 | +# You should have received a copy of the GNU General Public License along with |
2059 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
2060 | +# ----------------------------------------------------------------------------- |
2061 | + |
2062 | +import gtk |
2063 | + |
2064 | +from GTG import _ |
2065 | + |
2066 | + |
2067 | + |
2068 | +class PasswordUI(gtk.HBox): |
2069 | + '''Widget displaying a gtk.Label and a textbox to input a password''' |
2070 | + |
2071 | + |
2072 | + def __init__(self, req, backend, width): |
2073 | + '''Creates the gtk widgets and loads the current password in the text |
2074 | + field |
2075 | + |
2076 | + @param req: a Requester |
2077 | + @param backend: a backend object |
2078 | + @param width: the width of the gtk.Label object |
2079 | + ''' |
2080 | + super(PasswordUI, self).__init__() |
2081 | + self.backend = backend |
2082 | + self.req = req |
2083 | + self._populate_gtk(width) |
2084 | + self._load_password() |
2085 | + self._connect_signals() |
2086 | + |
2087 | + def _populate_gtk(self, width): |
2088 | + '''Creates the text box and the related label |
2089 | + |
2090 | + @param width: the width of the gtk.Label object |
2091 | + ''' |
2092 | + password_label = gtk.Label(_("Password:")) |
2093 | + password_label.set_alignment(xalign = 0, yalign = 0.5) |
2094 | + password_label.set_size_request(width = width, height = -1) |
2095 | + self.pack_start(password_label, False) |
2096 | + align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1) |
2097 | + align.set_padding(0, 0, 10, 0) |
2098 | + self.pack_start(align, True) |
2099 | + self.password_textbox = gtk.Entry() |
2100 | + align.add(self.password_textbox) |
2101 | + |
2102 | + def _load_password(self): |
2103 | + '''Loads the password from the backend''' |
2104 | + password = self.backend.get_parameters()['password'] |
2105 | + self.password_textbox.set_invisible_char('*') |
2106 | + self.password_textbox.set_visibility(False) |
2107 | + self.password_textbox.set_text(password) |
2108 | + |
2109 | + def _connect_signals(self): |
2110 | + '''Connects the gtk signals''' |
2111 | + self.password_textbox.connect('changed', self.on_password_modified) |
2112 | + |
2113 | + def commit_changes(self): |
2114 | + '''Saves the changes to the backend parameter ('password')''' |
2115 | + self.backend.set_parameter('password', self.password_textbox.get_text()) |
2116 | + |
2117 | + def on_password_modified(self, sender): |
2118 | + ''' Signal callback, executed when the user edits the password. |
2119 | + Disables the backend. The user will re-enable it to confirm the changes |
2120 | + (s)he made. |
2121 | + |
2122 | + @param sender: not used, only here for signal compatibility |
2123 | + ''' |
2124 | + if self.backend.is_enabled() and not self.backend.is_default(): |
2125 | + self.req.set_backend_enabled(self.backend.get_id(), False) |
2126 | + |
2127 | |
2128 | === added file 'GTG/gtk/backends_dialog/parameters_ui/pathui.py' |
2129 | --- GTG/gtk/backends_dialog/parameters_ui/pathui.py 1970-01-01 00:00:00 +0000 |
2130 | +++ GTG/gtk/backends_dialog/parameters_ui/pathui.py 2010-08-13 23:43:07 +0000 |
2131 | @@ -0,0 +1,111 @@ |
2132 | +# -*- coding: utf-8 -*- |
2133 | +# ----------------------------------------------------------------------------- |
2134 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
2135 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
2136 | +# |
2137 | +# This program is free software: you can redistribute it and/or modify it under |
2138 | +# the terms of the GNU General Public License as published by the Free Software |
2139 | +# Foundation, either version 3 of the License, or (at your option) any later |
2140 | +# version. |
2141 | +# |
2142 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2143 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
2144 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
2145 | +# details. |
2146 | +# |
2147 | +# You should have received a copy of the GNU General Public License along with |
2148 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
2149 | +# ----------------------------------------------------------------------------- |
2150 | + |
2151 | +import gtk |
2152 | +import os.path |
2153 | + |
2154 | +from GTG import _ |
2155 | + |
2156 | + |
2157 | + |
2158 | + |
2159 | +class PathUI(gtk.HBox): |
2160 | + '''Gtk widgets to show a path in a textbox, and a button to bring up a |
2161 | + filesystem explorer to modify that path (also, a label to describe those) |
2162 | + ''' |
2163 | + |
2164 | + |
2165 | + def __init__(self, req, backend, width): |
2166 | + ''' |
2167 | + Creates the textbox, the button and loads the current path. |
2168 | + |
2169 | + @param req: a Requester |
2170 | + @param backend: a backend object |
2171 | + @param width: the width of the gtk.Label object |
2172 | + ''' |
2173 | + super(PathUI, self).__init__() |
2174 | + self.backend = backend |
2175 | + self.req = req |
2176 | + self._populate_gtk(width) |
2177 | + |
2178 | + def _populate_gtk(self, width): |
2179 | + '''Creates the gtk.Label, the textbox and the button |
2180 | + |
2181 | + @param width: the width of the gtk.Label object |
2182 | + ''' |
2183 | + label = gtk.Label(_("Filename:")) |
2184 | + label.set_alignment(xalign = 0, yalign = 0.5) |
2185 | + label.set_size_request(width = width, height = -1) |
2186 | + self.pack_start(label, False) |
2187 | + align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1) |
2188 | + align.set_padding(0, 0, 10, 0) |
2189 | + self.pack_start(align, True) |
2190 | + self.textbox = gtk.Entry() |
2191 | + self.textbox.set_text(self.backend.get_parameters()['path']) |
2192 | + self.textbox.connect('changed', self.on_path_modified) |
2193 | + align.add(self.textbox) |
2194 | + self.button = gtk.Button(stock = gtk.STOCK_EDIT) |
2195 | + self.button.connect('clicked', self.on_button_clicked) |
2196 | + self.pack_start(self.button, False) |
2197 | + |
2198 | + def commit_changes(self): |
2199 | + '''Saves the changes to the backend parameter''' |
2200 | + self.backend.set_parameter('path', self.textbox.get_text()) |
2201 | + |
2202 | + def on_path_modified(self, sender): |
2203 | + ''' Signal callback, executed when the user edits the path. |
2204 | + Disables the backend. The user will re-enable it to confirm the changes |
2205 | + (s)he made. |
2206 | + |
2207 | + @param sender: not used, only here for signal compatibility |
2208 | + ''' |
2209 | + if self.backend.is_enabled() and not self.backend.is_default(): |
2210 | + self.req.set_backend_enabled(self.backend.get_id(), False) |
2211 | + |
2212 | + def on_button_clicked(self, sender): |
2213 | + '''Shows the filesystem explorer to choose a new file |
2214 | + |
2215 | + @param sender: not used, only here for signal compatibility |
2216 | + ''' |
2217 | + self.chooser = gtk.FileChooserDialog( \ |
2218 | + title=None, |
2219 | + action=gtk.FILE_CHOOSER_ACTION_SAVE, |
2220 | + buttons=(gtk.STOCK_CANCEL, |
2221 | + gtk.RESPONSE_CANCEL, \ |
2222 | + gtk.STOCK_OK, \ |
2223 | + gtk.RESPONSE_OK)) |
2224 | + self.chooser.set_default_response(gtk.RESPONSE_OK) |
2225 | + #set default file as the current self.path |
2226 | + self.chooser.set_current_name(os.path.basename(self.textbox.get_text())) |
2227 | + self.chooser.set_current_folder(os.path.dirname(self.textbox.get_text())) |
2228 | + |
2229 | + #filter files |
2230 | + afilter = gtk.FileFilter() |
2231 | + afilter.set_name("All files") |
2232 | + afilter.add_pattern("*") |
2233 | + self.chooser.add_filter(afilter) |
2234 | + afilter = gtk.FileFilter() |
2235 | + afilter.set_name("XML files") |
2236 | + afilter.add_mime_type("text/plain") |
2237 | + afilter.add_pattern("*.xml") |
2238 | + self.chooser.add_filter(afilter) |
2239 | + response = self.chooser.run() |
2240 | + if response == gtk.RESPONSE_OK: |
2241 | + self.textbox.set_text(self.chooser.get_filename()) |
2242 | + self.chooser.destroy() |
2243 | |
2244 | === added file 'GTG/gtk/backends_dialog/parameters_ui/periodui.py' |
2245 | --- GTG/gtk/backends_dialog/parameters_ui/periodui.py 1970-01-01 00:00:00 +0000 |
2246 | +++ GTG/gtk/backends_dialog/parameters_ui/periodui.py 2010-08-13 23:43:07 +0000 |
2247 | @@ -0,0 +1,88 @@ |
2248 | +# -*- coding: utf-8 -*- |
2249 | +# ----------------------------------------------------------------------------- |
2250 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
2251 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
2252 | +# |
2253 | +# This program is free software: you can redistribute it and/or modify it under |
2254 | +# the terms of the GNU General Public License as published by the Free Software |
2255 | +# Foundation, either version 3 of the License, or (at your option) any later |
2256 | +# version. |
2257 | +# |
2258 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2259 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
2260 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
2261 | +# details. |
2262 | +# |
2263 | +# You should have received a copy of the GNU General Public License along with |
2264 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
2265 | +# ----------------------------------------------------------------------------- |
2266 | + |
2267 | +import gtk |
2268 | + |
2269 | +from GTG import _ |
2270 | + |
2271 | + |
2272 | + |
2273 | +class PeriodUI(gtk.HBox): |
2274 | + '''A widget to change the frequency of a backend synchronization |
2275 | + ''' |
2276 | + |
2277 | + |
2278 | + def __init__(self, req, backend, width): |
2279 | + ''' |
2280 | + Creates the gtk.Adjustment and the related label. Loads the current |
2281 | + period. |
2282 | + |
2283 | + @param req: a Requester |
2284 | + @param backend: a backend object |
2285 | + @param width: the width of the gtk.Label object |
2286 | + ''' |
2287 | + super(PeriodUI, self).__init__() |
2288 | + self.backend = backend |
2289 | + self.req = req |
2290 | + self._populate_gtk(width) |
2291 | + self._connect_signals() |
2292 | + |
2293 | + def _populate_gtk(self, width): |
2294 | + '''Creates the gtk widgets |
2295 | + |
2296 | + @param width: the width of the gtk.Label object |
2297 | + ''' |
2298 | + period_label = gtk.Label(_("Period:")) |
2299 | + period_label.set_alignment(xalign = 0, yalign = 0.5) |
2300 | + period_label.set_size_request(width = width, height = -1) |
2301 | + self.pack_start(period_label, False) |
2302 | + align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1) |
2303 | + align.set_padding(0, 0, 10, 0) |
2304 | + self.pack_start(align, False) |
2305 | + period = self.backend.get_parameters()['period'] |
2306 | + self.adjustment = gtk.Adjustment(value = period, |
2307 | + lower = 1, |
2308 | + upper = 120, |
2309 | + step_incr = 1, |
2310 | + page_incr = 0, |
2311 | + page_size = 0) |
2312 | + self.period_spin = gtk.SpinButton(adjustment = self.adjustment, |
2313 | + climb_rate = 0.3, |
2314 | + digits = 0) |
2315 | + align.add(self.period_spin) |
2316 | + self.show_all() |
2317 | + |
2318 | + def _connect_signals(self): |
2319 | + '''Connects the gtk signals''' |
2320 | + self.period_spin.connect('changed', self.on_spin_changed) |
2321 | + |
2322 | + def commit_changes(self): |
2323 | + '''Saves the changes to the backend parameter''' |
2324 | + self.backend.set_parameter('period', int(self.adjustment.get_value())) |
2325 | + |
2326 | + def on_spin_changed(self, sender): |
2327 | + ''' Signal callback, executed when the user changes the period. |
2328 | + Disables the backend. The user will re-enable it to confirm the changes |
2329 | + (s)he made. |
2330 | + |
2331 | + @param sender: not used, only here for signal compatibility |
2332 | + ''' |
2333 | + if self.backend.is_enabled() and not self.backend.is_default(): |
2334 | + self.req.set_backend_enabled(self.backend.get_id(), False) |
2335 | + |
2336 | |
2337 | === added file 'GTG/gtk/backends_dialog/parameters_ui/textui.py' |
2338 | --- GTG/gtk/backends_dialog/parameters_ui/textui.py 1970-01-01 00:00:00 +0000 |
2339 | +++ GTG/gtk/backends_dialog/parameters_ui/textui.py 2010-08-13 23:43:07 +0000 |
2340 | @@ -0,0 +1,77 @@ |
2341 | +# -*- coding: utf-8 -*- |
2342 | +# ----------------------------------------------------------------------------- |
2343 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
2344 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
2345 | +# |
2346 | +# This program is free software: you can redistribute it and/or modify it under |
2347 | +# the terms of the GNU General Public License as published by the Free Software |
2348 | +# Foundation, either version 3 of the License, or (at your option) any later |
2349 | +# version. |
2350 | +# |
2351 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2352 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
2353 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
2354 | +# details. |
2355 | +# |
2356 | +# You should have received a copy of the GNU General Public License along with |
2357 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
2358 | +# ----------------------------------------------------------------------------- |
2359 | + |
2360 | +import gtk |
2361 | + |
2362 | + |
2363 | + |
2364 | +class TextUI(gtk.HBox): |
2365 | + '''A widget to display a simple textbox and a label to describe its content |
2366 | + ''' |
2367 | + |
2368 | + |
2369 | + def __init__(self, req, backend, width, description, parameter_name): |
2370 | + ''' |
2371 | + Creates the textbox and the related label. Loads the current |
2372 | + content. |
2373 | + |
2374 | + @param req: a Requester |
2375 | + @param backend: a backend object |
2376 | + @param width: the width of the gtk.Label object |
2377 | + ''' |
2378 | + super(TextUI, self).__init__() |
2379 | + self.backend = backend |
2380 | + self.req = req |
2381 | + self.parameter_name = parameter_name |
2382 | + self.description = description |
2383 | + self._populate_gtk(width) |
2384 | + |
2385 | + def _populate_gtk(self, width): |
2386 | + '''Creates the gtk widgets |
2387 | + |
2388 | + @param width: the width of the gtk.Label object |
2389 | + ''' |
2390 | + label = gtk.Label("%s:" % self.description) |
2391 | + label.set_alignment(xalign = 0, yalign = 0.5) |
2392 | + label.set_size_request(width = width, height = -1) |
2393 | + self.pack_start(label, False) |
2394 | + align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1) |
2395 | + align.set_padding(0, 0, 10, 0) |
2396 | + self.pack_start(align, True) |
2397 | + self.textbox = gtk.Entry() |
2398 | + self.textbox.set_text(\ |
2399 | + self.backend.get_parameters()[self.parameter_name]) |
2400 | + self.textbox.connect('changed', self.on_text_modified) |
2401 | + align.add(self.textbox) |
2402 | + |
2403 | + def commit_changes(self): |
2404 | + '''Saves the changes to the backend parameter''' |
2405 | + self.backend.set_parameter(self.parameter_name,\ |
2406 | + self.textbox.get_text()) |
2407 | + |
2408 | + def on_text_modified(self, sender): |
2409 | + ''' Signal callback, executed when the user changes the text. |
2410 | + Disables the backend. The user will re-enable it to confirm the changes |
2411 | + (s)he made. |
2412 | + |
2413 | + @param sender: not used, only here for signal compatibility |
2414 | + ''' |
2415 | + if self.backend.is_enabled() and not self.backend.is_default(): |
2416 | + self.req.set_backend_enabled(self.backend.get_id(), False) |
2417 | + |
2418 | |
2419 | === modified file 'GTG/gtk/browser/browser.py' |
2420 | --- GTG/gtk/browser/browser.py 2010-08-10 17:30:24 +0000 |
2421 | +++ GTG/gtk/browser/browser.py 2010-08-13 23:43:07 +0000 |
2422 | @@ -34,7 +34,9 @@ |
2423 | |
2424 | #our own imports |
2425 | import GTG |
2426 | -from GTG.core import CoreConfig |
2427 | +from GTG.backends.backendsignals import BackendSignals |
2428 | +from GTG.gtk.browser.custominfobar import CustomInfoBar |
2429 | +from GTG.core import CoreConfig |
2430 | from GTG import _, info, ngettext |
2431 | from GTG.core.task import Task |
2432 | from GTG.gtk.browser import GnomeConfig, tasktree, tagtree |
2433 | @@ -206,6 +208,7 @@ |
2434 | self.sidebar_notebook = self.builder.get_object("sidebar_notebook") |
2435 | self.main_notebook = self.builder.get_object("main_notebook") |
2436 | self.accessory_notebook = self.builder.get_object("accessory_notebook") |
2437 | + self.vbox_toolbars = self.builder.get_object("vbox_toolbars") |
2438 | |
2439 | self.closed_pane = None |
2440 | |
2441 | @@ -313,6 +316,8 @@ |
2442 | self.on_nonworkviewtag_toggled, |
2443 | "on_preferences_activate": |
2444 | self.open_preferences, |
2445 | + "on_edit_backends_activate": |
2446 | + self.open_edit_backends, |
2447 | } |
2448 | self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC) |
2449 | |
2450 | @@ -346,6 +351,16 @@ |
2451 | # Connect requester signals to TreeModels |
2452 | self.req.connect("task-added", self.on_task_added) |
2453 | self.req.connect("task-deleted", self.on_task_deleted) |
2454 | + #this causes changed be shouwn only on save |
2455 | + #tree = self.task_tree_model.get_tree() |
2456 | + #tree.connect("task-added-inview", self.on_task_added) |
2457 | + #tree.connect("task-deleted-inview", self.on_task_deleted) |
2458 | + b_signals = BackendSignals() |
2459 | + b_signals.connect(b_signals.BACKEND_FAILED, self.on_backend_failed) |
2460 | + b_signals.connect(b_signals.BACKEND_STATE_TOGGLED, \ |
2461 | + self.remove_backend_infobar) |
2462 | + b_signals.connect(b_signals.INTERACTION_REQUESTED, \ |
2463 | + self.on_backend_needing_interaction) |
2464 | |
2465 | # Connect signals from models |
2466 | self.task_modelsort.connect("row-has-child-toggled",\ |
2467 | @@ -425,9 +440,12 @@ |
2468 | |
2469 | ### HELPER FUNCTIONS ######################################################## |
2470 | |
2471 | - def open_preferences(self,widget): |
2472 | + def open_preferences(self, widget): |
2473 | self.vmanager.open_preferences(self.priv) |
2474 | |
2475 | + def open_edit_backends(self, widget): |
2476 | + self.vmanager.open_edit_backends() |
2477 | + |
2478 | def quit(self,widget=None): |
2479 | self.vmanager.close_browser() |
2480 | |
2481 | @@ -522,7 +540,7 @@ |
2482 | col_id,\ |
2483 | self.priv["tasklist"]["sort_order"]) |
2484 | except: |
2485 | - print "Invalid configuration for sorting columns" |
2486 | + Log.error("Invalid configuration for sorting columns") |
2487 | |
2488 | if "view" in self.config["browser"]: |
2489 | view = self.config["browser"]["view"] |
2490 | @@ -953,7 +971,9 @@ |
2491 | text = \ |
2492 | text.replace("%s%s:%s" % (spaces, attribute, args), "") |
2493 | # Create the new task |
2494 | - task = self.req.new_task(tags=[t.get_name() for t in tags], newtask=True) |
2495 | + task = self.req.new_task( newtask=True) |
2496 | + for tag in tags: |
2497 | + task.add_tag(tag.get_name()) |
2498 | if text != "": |
2499 | task.set_title(text.strip()) |
2500 | task.set_to_keep() |
2501 | @@ -1516,3 +1536,82 @@ |
2502 | """ Returns true if window is the currently active window """ |
2503 | return self.window.get_property("is-active") |
2504 | |
2505 | +## BACKENDS RELATED METHODS ################################################## |
2506 | + |
2507 | + def on_backend_failed(self, sender, backend_id, error_code): |
2508 | + ''' |
2509 | + Signal callback. |
2510 | + When a backend fails to work, loads a gtk.Infobar to alert the user |
2511 | + |
2512 | + @param sender: not used, only here for signal compatibility |
2513 | + @param backend_id: the id of the failing backend |
2514 | + @param error_code: a backend error code, as specified in BackendsSignals |
2515 | + ''' |
2516 | + infobar = self._new_infobar(backend_id) |
2517 | + infobar.set_error_code(error_code) |
2518 | + |
2519 | + def on_backend_needing_interaction(self, sender, backend_id, description, \ |
2520 | + interaction_type, callback): |
2521 | + ''' |
2522 | + Signal callback. |
2523 | + When a backend needs some kind of feedback from the user, |
2524 | + loads a gtk.Infobar to alert the user. |
2525 | + This is used, for example, to request confirmation after authenticating |
2526 | + via OAuth. |
2527 | + |
2528 | + @param sender: not used, only here for signal compatibility |
2529 | + @param backend_id: the id of the failing backend |
2530 | + @param description: a string describing the interaction needed |
2531 | + @param interaction_type: a string describing the type of interaction |
2532 | + (yes/no, only confirm, ok/cancel...) |
2533 | + @param callback: the function to call when the user provides the |
2534 | + feedback |
2535 | + ''' |
2536 | + infobar = self._new_infobar(backend_id) |
2537 | + infobar.set_interaction_request(description, interaction_type, callback) |
2538 | + |
2539 | + |
2540 | + def __remove_backend_infobar(self, child, backend_id): |
2541 | + ''' |
2542 | + Helper function to remove an gtk.Infobar related to a backend |
2543 | + |
2544 | + @param child: a gtk.Infobar |
2545 | + @param backend_id: the id of the backend which gtk.Infobar should be |
2546 | + removed. |
2547 | + ''' |
2548 | + if isinstance(child, CustomInfoBar) and\ |
2549 | + child.get_backend_id() == backend_id: |
2550 | + if self.vbox_toolbars: |
2551 | + self.vbox_toolbars.remove(child) |
2552 | + |
2553 | + def remove_backend_infobar(self, sender, backend_id): |
2554 | + ''' |
2555 | + Signal callback. |
2556 | + Deletes the gtk.Infobars related to a backend |
2557 | + |
2558 | + @param sender: not used, only here for signal compatibility |
2559 | + @param backend_id: the id of the backend which gtk.Infobar should be |
2560 | + removed. |
2561 | + ''' |
2562 | + backend = self.req.get_backend(backend_id) |
2563 | + if not backend or (backend and backend.is_enabled()): |
2564 | + #remove old infobar related to backend_id, if any |
2565 | + if self.vbox_toolbars: |
2566 | + self.vbox_toolbars.foreach(self.__remove_backend_infobar, \ |
2567 | + backend_id) |
2568 | + |
2569 | + def _new_infobar(self, backend_id): |
2570 | + ''' |
2571 | + Helper function to create a new infobar for a backend |
2572 | + |
2573 | + @param backend_id: the backend for which we're creating the infobar |
2574 | + @returns gtk.Infobar: the created infobar |
2575 | + ''' |
2576 | + #remove old infobar related to backend_id, if any |
2577 | + if not self.vbox_toolbars: |
2578 | + return |
2579 | + self.vbox_toolbars.foreach(self.__remove_backend_infobar, backend_id) |
2580 | + #add a new one |
2581 | + infobar = CustomInfoBar(self.req, self, self.vmanager, backend_id) |
2582 | + self.vbox_toolbars.pack_start(infobar, True) |
2583 | + return infobar |
2584 | |
2585 | === added file 'GTG/gtk/browser/custominfobar.py' |
2586 | --- GTG/gtk/browser/custominfobar.py 1970-01-01 00:00:00 +0000 |
2587 | +++ GTG/gtk/browser/custominfobar.py 2010-08-13 23:43:07 +0000 |
2588 | @@ -0,0 +1,210 @@ |
2589 | +# -*- coding: utf-8 -*- |
2590 | +# ----------------------------------------------------------------------------- |
2591 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
2592 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
2593 | +# |
2594 | +# This program is free software: you can redistribute it and/or modify it under |
2595 | +# the terms of the GNU General Public License as published by the Free Software |
2596 | +# Foundation, either version 3 of the License, or (at your option) any later |
2597 | +# version. |
2598 | +# |
2599 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2600 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
2601 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
2602 | +# details. |
2603 | +# |
2604 | +# You should have received a copy of the GNU General Public License along with |
2605 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
2606 | +# ----------------------------------------------------------------------------- |
2607 | + |
2608 | +import gtk |
2609 | +import threading |
2610 | + |
2611 | +from GTG import _ |
2612 | +from GTG.backends.backendsignals import BackendSignals |
2613 | +from GTG.tools.networkmanager import is_connection_up |
2614 | + |
2615 | + |
2616 | + |
2617 | +class CustomInfoBar(gtk.InfoBar): |
2618 | + ''' |
2619 | + A gtk.InfoBar specialized for displaying errors and requests for |
2620 | + interaction coming from the backends |
2621 | + ''' |
2622 | + |
2623 | + |
2624 | + AUTHENTICATION_MESSAGE = _("The <b>%s</b> backend cannot login with the " |
2625 | + "supplied authentication data and has been" |
2626 | + " disabled. To retry the login, re-enable the backend.") |
2627 | + |
2628 | + NETWORK_MESSAGE = _("Due to a network problem, I cannot contact " |
2629 | + "the <b>%s</b> backend.") |
2630 | + |
2631 | + DBUS_MESSAGE = _("Cannot connect to DBUS, I've disabled " |
2632 | + "the <b>%s</b> backend.") |
2633 | + |
2634 | + def __init__(self, req, browser, vmanager, backend_id): |
2635 | + ''' |
2636 | + Constructor, Prepares the infobar. |
2637 | + |
2638 | + @param req: a Requester object |
2639 | + @param browser: a TaskBrowser object |
2640 | + @param vmanager: a ViewManager object |
2641 | + @param backend_id: the id of the backend linked to the infobar |
2642 | + ''' |
2643 | + super(CustomInfoBar, self).__init__() |
2644 | + self.req = req |
2645 | + self.browser = browser |
2646 | + self.vmanager = vmanager |
2647 | + self.backend_id = backend_id |
2648 | + self.backend = self.req.get_backend(backend_id) |
2649 | + |
2650 | + def get_backend_id(self): |
2651 | + ''' |
2652 | + Getter function to return the id of the backend for which this |
2653 | + gtk.InfoBar was created |
2654 | + ''' |
2655 | + return self.backend_id |
2656 | + |
2657 | + def _populate(self): |
2658 | + '''Setting up gtk widgets''' |
2659 | + content_hbox = self.get_content_area() |
2660 | + content_hbox.set_homogeneous(False) |
2661 | + self.label = gtk.Label() |
2662 | + self.label.set_line_wrap(True) |
2663 | + self.label.set_alignment(0.5, 0.5) |
2664 | + self.label.set_justify(gtk.JUSTIFY_FILL) |
2665 | + content_hbox.pack_start(self.label, True, True) |
2666 | + |
2667 | + def _on_error_response(self, widget, event): |
2668 | + ''' |
2669 | + Signal callback executed when the user acknowledges the error displayed |
2670 | + in the infobar |
2671 | + |
2672 | + @param widget: not used, here for compatibility with signals callbacks |
2673 | + @param event: the code of the gtk response |
2674 | + ''' |
2675 | + self.hide() |
2676 | + if event == gtk.RESPONSE_ACCEPT: |
2677 | + self.vmanager.configure_backend(backend_id = self.backend_id) |
2678 | + |
2679 | + def set_error_code(self, error_code): |
2680 | + ''' |
2681 | + Sets this infobar to show an error to the user |
2682 | + |
2683 | + @param error_code: the code of the error to show. Error codes are listed |
2684 | + in BackendSignals |
2685 | + ''' |
2686 | + self._populate() |
2687 | + self.connect("response", self._on_error_response) |
2688 | + backend_name = self.backend.get_human_name() |
2689 | + |
2690 | + if error_code == BackendSignals.ERRNO_AUTHENTICATION: |
2691 | + self.set_message_type(gtk.MESSAGE_ERROR) |
2692 | + self.label.set_markup(self.AUTHENTICATION_MESSAGE % backend_name) |
2693 | + self.add_button(_('Configure backend'), gtk.RESPONSE_ACCEPT) |
2694 | + self.add_button(_('Ignore'), gtk.RESPONSE_CLOSE) |
2695 | + |
2696 | + elif error_code == BackendSignals.ERRNO_NETWORK: |
2697 | + if not is_connection_up(): |
2698 | + return |
2699 | + self.set_message_type(gtk.MESSAGE_WARNING) |
2700 | + self.label.set_markup(self.NETWORK_MESSAGE % backend_name) |
2701 | + #FIXME: use gtk stock button instead |
2702 | + self.add_button(_('Ok'), gtk.RESPONSE_CLOSE) |
2703 | + |
2704 | + elif error_code == BackendSignals.ERRNO_DBUS: |
2705 | + self.set_message_type(gtk.MESSAGE_WARNING) |
2706 | + self.label.set_markup(self.DBUS_MESSAGE % backend_name) |
2707 | + self.add_button(_('Ok'), gtk.RESPONSE_CLOSE) |
2708 | + |
2709 | + self.show_all() |
2710 | + |
2711 | + def set_interaction_request(self, description, interaction_type, callback): |
2712 | + ''' |
2713 | + Sets this infobar to request an interaction from the user |
2714 | + |
2715 | + @param description: a string describing the interaction needed |
2716 | + @param interaction_type: a string describing the type of interaction |
2717 | + (yes/no, only confirm, ok/cancel...) |
2718 | + @param callback: the function to call when the user provides the |
2719 | + feedback |
2720 | + ''' |
2721 | + self._populate() |
2722 | + self.callback = callback |
2723 | + self.set_message_type(gtk.MESSAGE_INFO) |
2724 | + self.label.set_markup(description) |
2725 | + self.connect("response", self._on_interaction_response) |
2726 | + self.interaction_type = interaction_type |
2727 | + if interaction_type == BackendSignals().INTERACTION_CONFIRM: |
2728 | + self.add_button(_('Confirm'), gtk.RESPONSE_ACCEPT) |
2729 | + elif interaction_type == BackendSignals().INTERACTION_TEXT: |
2730 | + self.add_button(_('Continue'), gtk.RESPONSE_ACCEPT) |
2731 | + self.show_all() |
2732 | + |
2733 | + def _on_interaction_response(self, widget, event): |
2734 | + ''' |
2735 | + Signal callback executed when the user gives the feedback for a |
2736 | + requested interaction |
2737 | + |
2738 | + @param widget: not used, here for compatibility with signals callbacks |
2739 | + @param event: the code of the gtk response |
2740 | + ''' |
2741 | + if event == gtk.RESPONSE_ACCEPT: |
2742 | + if self.interaction_type == BackendSignals().INTERACTION_TEXT: |
2743 | + self._prepare_textual_interaction() |
2744 | + print "done" |
2745 | + elif self.interaction_type == BackendSignals().INTERACTION_CONFIRM: |
2746 | + self.hide() |
2747 | + threading.Thread(target = getattr(self.backend, |
2748 | + self.callback)).start() |
2749 | + |
2750 | + def _prepare_textual_interaction(self): |
2751 | + ''' |
2752 | + Helper function. gtk calls to populate the infobar in the case of |
2753 | + interaction request |
2754 | + ''' |
2755 | + title, description\ |
2756 | + = getattr(self.backend, self.callback)("get_title") |
2757 | + self.dialog = gtk.Window()#type = gtk.WINDOW_POPUP) |
2758 | + self.dialog.set_title(title) |
2759 | + self.dialog.set_transient_for(self.browser.window) |
2760 | + self.dialog.set_destroy_with_parent(True) |
2761 | + self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) |
2762 | + self.dialog.set_modal(True) |
2763 | + # self.dialog.set_size_request(300,170) |
2764 | + vbox = gtk.VBox() |
2765 | + self.dialog.add(vbox) |
2766 | + description_label = gtk.Label() |
2767 | + description_label.set_justify(gtk.JUSTIFY_FILL) |
2768 | + description_label.set_line_wrap(True) |
2769 | + description_label.set_markup(description) |
2770 | + align = gtk.Alignment(0.5, 0.5, 1, 1) |
2771 | + align.set_padding(10, 0, 20, 20) |
2772 | + align.add(description_label) |
2773 | + vbox.pack_start(align) |
2774 | + self.text_box = gtk.Entry() |
2775 | + self.text_box.set_size_request(-1, 40) |
2776 | + align = gtk.Alignment(0.5, 0.5, 1, 1) |
2777 | + align.set_padding(20, 20, 20, 20) |
2778 | + align.add(self.text_box) |
2779 | + vbox.pack_start(align) |
2780 | + button = gtk.Button(stock = gtk.STOCK_OK) |
2781 | + button.connect("clicked", self._on_text_confirmed) |
2782 | + button.set_size_request(-1, 40) |
2783 | + vbox.pack_start(button, False) |
2784 | + self.dialog.show_all() |
2785 | + self.hide() |
2786 | + |
2787 | + def _on_text_confirmed(self, widget): |
2788 | + ''' |
2789 | + Signal callback, used when the interaction needs a textual input to be |
2790 | + completed (e.g, the twitter OAuth, requesting a pin) |
2791 | + |
2792 | + @param widget: not used, here for signal callback compatibility |
2793 | + ''' |
2794 | + text = self.text_box.get_text() |
2795 | + self.dialog.destroy() |
2796 | + threading.Thread(target = getattr(self.backend, self.callback), |
2797 | + args = ("set_text", text)).start() |
2798 | + |
2799 | |
2800 | === modified file 'GTG/gtk/browser/taskbrowser.glade' |
2801 | --- GTG/gtk/browser/taskbrowser.glade 2010-05-22 22:41:44 +0000 |
2802 | +++ GTG/gtk/browser/taskbrowser.glade 2010-08-13 23:43:07 +0000 |
2803 | @@ -154,6 +154,17 @@ |
2804 | <signal name="activate" handler="on_preferences_activate"/> |
2805 | </object> |
2806 | </child> |
2807 | + <child> |
2808 | + <object class="GtkImageMenuItem" id="backends_mi"> |
2809 | + <property name="label">_Backends</property> |
2810 | + <property name="visible">True</property> |
2811 | + <property name="use_underline">True</property> |
2812 | + <property name="image">image4</property> |
2813 | + <property name="use_stock">False</property> |
2814 | + <property name="accel_group">accelgroup1</property> |
2815 | + <signal name="activate" handler="on_edit_backends_activate"/> |
2816 | + </object> |
2817 | + </child> |
2818 | </object> |
2819 | </child> |
2820 | </object> |
2821 | |
2822 | === modified file 'GTG/gtk/colors.py' |
2823 | --- GTG/gtk/colors.py 2010-06-07 21:14:45 +0000 |
2824 | +++ GTG/gtk/colors.py 2010-08-13 23:43:07 +0000 |
2825 | @@ -20,7 +20,7 @@ |
2826 | |
2827 | #Take list of Tags and give the background color that should be applied |
2828 | #The returned color might be None (in which case, the default is used) |
2829 | -def background_color(tags, bgcolor=None): |
2830 | +def background_color(tags, bgcolor = None): |
2831 | if not bgcolor: |
2832 | bgcolor = gtk.gdk.color_parse("#FFFFFF") |
2833 | # Compute color |
2834 | @@ -52,3 +52,29 @@ |
2835 | my_color = gtk.gdk.Color(red, green, blue).to_string() |
2836 | return my_color |
2837 | |
2838 | +def get_colored_tag_markup(req, tag_name): |
2839 | + ''' |
2840 | + Given a tag name, returns a string containing the markup to color the |
2841 | + tag name |
2842 | + ''' |
2843 | + tag = req.get_tag(tag_name) |
2844 | + if tag is None: |
2845 | + #no task loaded with that tag, color cannot be taken |
2846 | + return tag_name |
2847 | + else: |
2848 | + tag_color = tag.get_attribute("color") |
2849 | + if tag_color: |
2850 | + return '<span color="%s">%s</span>' % (tag_color, tag_name) |
2851 | + else: |
2852 | + return tag_name |
2853 | + |
2854 | +def get_colored_tags_markup(req, tag_names): |
2855 | + ''' |
2856 | + Calls get_colored_tag_markup for each tag_name in tag_names |
2857 | + ''' |
2858 | + tag_markups = map(lambda t: get_colored_tag_markup(req, t), tag_names) |
2859 | + tags_txt = "" |
2860 | + if tag_markups: |
2861 | + #reduce crashes if applied to an empty list |
2862 | + tags_txt = reduce(lambda a, b: a + ", " + b, tag_markups) |
2863 | + return tags_txt |
2864 | |
2865 | === modified file 'GTG/gtk/manager.py' |
2866 | --- GTG/gtk/manager.py 2010-08-03 17:07:31 +0000 |
2867 | +++ GTG/gtk/manager.py 2010-08-13 23:43:07 +0000 |
2868 | @@ -39,10 +39,11 @@ |
2869 | from GTG.core.plugins.engine import PluginEngine |
2870 | from GTG.core.plugins.api import PluginAPI |
2871 | from GTG.tools.logger import Log |
2872 | - |
2873 | - |
2874 | - |
2875 | -class Manager: |
2876 | +from GTG.gtk.backends_dialog import BackendsDialog |
2877 | + |
2878 | + |
2879 | + |
2880 | +class Manager(object): |
2881 | |
2882 | |
2883 | ############## init ##################################################### |
2884 | @@ -80,6 +81,7 @@ |
2885 | #Preferences and Backends windows |
2886 | # Initialize dialogs |
2887 | self.preferences_dialog = None |
2888 | + self.edit_backends_dialog = None |
2889 | |
2890 | #DBus |
2891 | DBusTaskWrapper(self.req, self) |
2892 | @@ -196,6 +198,16 @@ |
2893 | |
2894 | ################ Others dialog ############################################ |
2895 | |
2896 | + def open_edit_backends(self, sender = None, backend_id = None): |
2897 | + if not self.edit_backends_dialog: |
2898 | + self.edit_backends_dialog = BackendsDialog(self.req) |
2899 | + self.edit_backends_dialog.activate() |
2900 | + if backend_id != None: |
2901 | + self.edit_backends_dialog.show_config_for_backend(backend_id) |
2902 | + |
2903 | + def configure_backend(self, backend_id): |
2904 | + self.open_edit_backends(None, backend_id) |
2905 | + |
2906 | def open_preferences(self, config_priv, sender=None): |
2907 | if not hasattr(self, "preferences"): |
2908 | self.preferences = PreferencesDialog(self.pengine, self.p_apis, \ |
2909 | @@ -211,7 +223,8 @@ |
2910 | self.close_task(t) |
2911 | |
2912 | ### MAIN ################################################################### |
2913 | - def main(self, once_thru=False): |
2914 | + |
2915 | + def main(self, once_thru = False): |
2916 | gobject.threads_init() |
2917 | if once_thru: |
2918 | gtk.main_iteration() |
2919 | @@ -219,7 +232,6 @@ |
2920 | gtk.main() |
2921 | return 0 |
2922 | |
2923 | - |
2924 | def quit(self,sender=None): |
2925 | gtk.main_quit() |
2926 | #save opened tasks and their positions. |
2927 | |
2928 | === added file 'GTG/tests/test_interruptible.py' |
2929 | --- GTG/tests/test_interruptible.py 1970-01-01 00:00:00 +0000 |
2930 | +++ GTG/tests/test_interruptible.py 2010-08-13 23:43:07 +0000 |
2931 | @@ -0,0 +1,69 @@ |
2932 | +# -*- coding: utf-8 -*- |
2933 | +# ----------------------------------------------------------------------------- |
2934 | +# Gettings Things Gnome! - a personal organizer for the GNOME desktop |
2935 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
2936 | +# |
2937 | +# This program is free software: you can redistribute it and/or modify it under |
2938 | +# the terms of the GNU General Public License as published by the Free Software |
2939 | +# Foundation, either version 3 of the License, or (at your option) any later |
2940 | +# version. |
2941 | +# |
2942 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2943 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
2944 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
2945 | +# details. |
2946 | +# |
2947 | +# You should have received a copy of the GNU General Public License along with |
2948 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
2949 | +# ----------------------------------------------------------------------------- |
2950 | + |
2951 | +''' |
2952 | +Tests for interrupting cooperative threads |
2953 | +''' |
2954 | + |
2955 | +import unittest |
2956 | +import time |
2957 | +from threading import Thread, Event |
2958 | + |
2959 | +from GTG.tools.interruptible import interruptible, _cancellation_point |
2960 | + |
2961 | + |
2962 | +class TestInterruptible(unittest.TestCase): |
2963 | + ''' |
2964 | + Tests for interrupting cooperative threads |
2965 | + ''' |
2966 | + |
2967 | + def test_interruptible_decorator(self): |
2968 | + self.quit_condition = False |
2969 | + cancellation_point = lambda: _cancellation_point(\ |
2970 | + lambda: self.quit_condition) |
2971 | + self.thread_started = Event() |
2972 | + @interruptible |
2973 | + def never_ending(cancellation_point): |
2974 | + self.thread_started.set() |
2975 | + while True: |
2976 | + time.sleep(0.1) |
2977 | + cancellation_point() |
2978 | + thread = Thread(target = never_ending, args = (cancellation_point, )) |
2979 | + thread.start() |
2980 | + self.thread_started.wait() |
2981 | + self.quit_condition = True |
2982 | + countdown = 10 |
2983 | + while thread.is_alive() and countdown > 0: |
2984 | + time.sleep(0.1) |
2985 | + countdown -= 1 |
2986 | + self.assertFalse(thread.is_alive()) |
2987 | + |
2988 | + |
2989 | + |
2990 | + |
2991 | + |
2992 | + |
2993 | + |
2994 | + |
2995 | + |
2996 | + |
2997 | + |
2998 | +def test_suite(): |
2999 | + return unittest.TestLoader().loadTestsFromTestCase(TestInterruptible) |
3000 | + |
3001 | |
3002 | === added file 'GTG/tools/networkmanager.py' |
3003 | --- GTG/tools/networkmanager.py 1970-01-01 00:00:00 +0000 |
3004 | +++ GTG/tools/networkmanager.py 2010-08-13 23:43:07 +0000 |
3005 | @@ -0,0 +1,57 @@ |
3006 | +#!/bin/env python |
3007 | +# |
3008 | +# This program is free software; you can redistribute it and/or modify |
3009 | +# it under the terms of the GNU General Public License as published by |
3010 | +# the Free Software Foundation; either version 2 of the License, or |
3011 | +# (at your option) any later version. |
3012 | +# |
3013 | +# This program is distributed in the hope that it will be useful, |
3014 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3015 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3016 | +# GNU General Public License for more details. |
3017 | +# |
3018 | +# You should have received a copy of the GNU General Public License along |
3019 | +# with this program; if not, write to the Free Software Foundation, Inc., |
3020 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
3021 | +# |
3022 | +# Copyright (C) 2010 Red Hat, Inc. |
3023 | +# |
3024 | + |
3025 | +import dbus |
3026 | + |
3027 | + |
3028 | +def is_connection_up(): |
3029 | + ''' |
3030 | + Returns True if network-manager reports that at least one connection is up |
3031 | + |
3032 | + @returns bool |
3033 | + ''' |
3034 | + state = False |
3035 | + bus = dbus.SystemBus() |
3036 | + |
3037 | + proxy = bus.get_object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager") |
3038 | + manager = dbus.Interface(proxy, "org.freedesktop.NetworkManager") |
3039 | + |
3040 | + manager_prop_iface = dbus.Interface(proxy, "org.freedesktop.DBus.Properties") |
3041 | + active = manager_prop_iface.Get("org.freedesktop.NetworkManager", "ActiveConnections") |
3042 | + for a in active: |
3043 | + ac_proxy = bus.get_object("org.freedesktop.NetworkManager", a) |
3044 | + prop_iface = dbus.Interface(ac_proxy, "org.freedesktop.DBus.Properties") |
3045 | + state = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "State") |
3046 | + |
3047 | + # Connections in NM are a collection of settings that describe everything |
3048 | + # needed to connect to a specific network. Lets get those details so we |
3049 | + # can find the user-readable name of the connection. |
3050 | + con_path = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "Connection") |
3051 | + con_service = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "ServiceName") |
3052 | + |
3053 | + # ask the provider of the connection for its details |
3054 | + service_proxy = bus.get_object(con_service, con_path) |
3055 | + con_iface = dbus.Interface(service_proxy, "org.freedesktop.NetworkManagerSettings.Connection") |
3056 | + con_details = con_iface.GetSettings() |
3057 | + con_name = con_details['connection']['id'] |
3058 | + |
3059 | + if state == 2: # activated |
3060 | + state = True |
3061 | + return state |
3062 | + |
3063 | |
3064 | === added file 'data/icons/hicolor/scalable/apps/backend_localfile.png' |
3065 | Binary files data/icons/hicolor/scalable/apps/backend_localfile.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_localfile.png 2010-08-13 23:43:07 +0000 differ |