GTG

Merge lp:~gtg-user/gtg/backends-window into lp:~gtg/gtg/old-trunk

Proposed by Luca Invernizzi
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
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.

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.

To post a comment you must log in.
lp:~gtg-user/gtg/backends-window updated
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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'
3065Binary 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

Subscribers

People subscribed via source and target branches

to status/vote changes: