GTG

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

Proposed by Luca Invernizzi
Status: Merged
Merged at revision: 880
Proposed branch: lp:~gtg-user/gtg/backends-window
Merge into: lp:~gtg/gtg/old-trunk
Diff against target: 3479 lines (+2700/-285)
25 files modified
AUTHORS (+1/-0)
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 (+253/-0)
GTG/gtk/backends_dialog/configurepanel.py (+304/-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 (+135/-0)
GTG/gtk/backends_dialog/parameters_ui/passwordui.py (+84/-0)
GTG/gtk/backends_dialog/parameters_ui/pathui.py (+112/-0)
GTG/gtk/backends_dialog/parameters_ui/periodui.py (+97/-0)
GTG/gtk/backends_dialog/parameters_ui/textui.py (+78/-0)
GTG/gtk/browser/browser.py (+100/-3)
GTG/gtk/browser/custominfobar.py (+210/-0)
GTG/gtk/browser/taskbrowser.glade (+161/-147)
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+32647@code.launchpad.net

This proposal supersedes 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.
Revision history for this message
Marko Kevac (mkevac) wrote :

Backend window works fine. One existing backend (local file) can be added and deleted.

This warning is printed in console after starting GTG:

/home/marko/Projects/gtg/backends-window/GTG/gtk/browser/browser.py:109: GtkWarning: No object called: image4
  self.builder.add_from_file(GnomeConfig.GLADE_FILE)

Should that be fixed?

Revision history for this message
Luca Invernizzi (invernizzi) wrote :

Fixed

lp:~gtg-user/gtg/backends-window updated
875. By Luca Invernizzi

small fix for marko kevec comment

876. By Luca Invernizzi

small fix in the backends tree

Revision history for this message
Luca Invernizzi (invernizzi) wrote :

Maybe the "Quit" button to dismiss the window could be better named. Suggestions welcome!

lp:~gtg-user/gtg/backends-window updated
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

Preview Diff

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

Subscribers

People subscribed via source and target branches

to status/vote changes: