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
=== modified file 'AUTHORS'
--- AUTHORS 2010-08-15 19:12:35 +0000
+++ AUTHORS 2010-09-04 17:54:43 +0000
@@ -70,3 +70,4 @@
70* Volodymyr Floreskul <exufer@gmail.com>70* Volodymyr Floreskul <exufer@gmail.com>
71* Jeff Oliver <kaiserfro@gmail.com>71* Jeff Oliver <kaiserfro@gmail.com>
72* Thibault Fevry <https://edge.launchpad.net/~thibaultfevry> (no email provided)72* Thibault Fevry <https://edge.launchpad.net/~thibaultfevry> (no email provided)
73* Andrew Starr-Bochicchio <andrewsomething@ubuntu.com>
7374
=== modified file 'CHANGELOG'
--- CHANGELOG 2010-08-04 00:30:22 +0000
+++ CHANGELOG 2010-09-04 17:54:43 +0000
@@ -4,6 +4,7 @@
4 * Fixed bug with data consistency #579189, by Marko Kevac4 * Fixed bug with data consistency #579189, by Marko Kevac
5 * Added samba bugzilla to the bugzilla plugin, by Jelmer Vernoij5 * Added samba bugzilla to the bugzilla plugin, by Jelmer Vernoij
6 * Fixed bug #532392, a start date is later than a due date, by Volodymyr Floreskul6 * Fixed bug #532392, a start date is later than a due date, by Volodymyr Floreskul
7 * Added a window to add/delete/edit backends by Luca Invernizzi
78
82010-03-01 Getting Things GNOME! 0.2.292010-03-01 Getting Things GNOME! 0.2.2
9 * Autostart on login, by Luca Invernizzi10 * Autostart on login, by Luca Invernizzi
1011
=== added file 'GTG/backends/backendsignals.py'
--- GTG/backends/backendsignals.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/backendsignals.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,148 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gobject
21
22from GTG.tools.borg import Borg
23
24
25
26class BackendSignals(Borg):
27 '''
28 This class handles the signals that involve backends.
29 In particular, it's a wrapper Borg class around a _BackendSignalsGObject
30 class, and all method of the wrapped class can be used as if they were part
31 of this class
32 '''
33
34 #error codes to send along with the BACKEND_FAILED signal
35 ERRNO_AUTHENTICATION = "authentication failed"
36 ERRNO_NETWORK = "network is down"
37 ERRNO_DBUS = "Dbus interface cannot be connected"
38
39 def __init__(self):
40 '''Checks that this is the only instance, and instantiates the
41 gobject'''
42 super(BackendSignals, self).__init__()
43 if hasattr(self, "_gobject"):
44 return
45 self._gobject = _BackendSignalsGObject()
46
47 def __getattr__(self, attr):
48 '''
49 From outside the class, there should be no difference between self's
50 attributes and self._gobject's attributes.
51 '''
52 if attr == "_gobject" and not "_gobject" in self.__dict__:
53 raise AttributeError
54 return getattr(self._gobject, attr)
55
56
57def signal_type_factory(*args):
58 '''
59 Simply returns a gobject signal type
60
61 @returns tuple
62 '''
63 return (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, args)
64
65
66
67class _BackendSignalsGObject(gobject.GObject):
68
69 #signal name constants
70 BACKEND_STATE_TOGGLED = 'backend-state-toggled' #emitted when a
71 #backend is
72 #enabled or disabled
73 BACKEND_RENAMED = 'backend-renamed' #emitted when a backend is renamed
74 BACKEND_ADDED = 'backend-added'
75 BACKEND_REMOVED = 'backend-added' #when a backend is deleted
76 DEFAULT_BACKEND_LOADED = 'default-backend-loaded' #emitted after all
77 # tasks have been
78 # loaded from the
79 # default backend
80 BACKEND_FAILED = 'backend-failed' #something went wrong with a backend
81 BACKEND_SYNC_STARTED = 'backend-sync-started'
82 BACKEND_SYNC_ENDED = 'backend-sync-ended'
83 INTERACTION_REQUESTED = 'user-interaction-requested'
84
85 INTERACTION_CONFIRM = 'confirm'
86 INTERACTION_TEXT = 'text'
87
88 __gsignals__ = {BACKEND_STATE_TOGGLED : signal_type_factory(str), \
89 BACKEND_RENAMED : signal_type_factory(str), \
90 BACKEND_ADDED : signal_type_factory(str), \
91 BACKEND_REMOVED : signal_type_factory(str), \
92 BACKEND_SYNC_STARTED : signal_type_factory(str), \
93 BACKEND_SYNC_ENDED : signal_type_factory(str), \
94 DEFAULT_BACKEND_LOADED: signal_type_factory(), \
95 BACKEND_FAILED : signal_type_factory(str, str), \
96 INTERACTION_REQUESTED : signal_type_factory(str, str, \
97 str, str)}
98
99 def __init__(self):
100 super(_BackendSignalsGObject, self).__init__()
101 self.backends_currently_syncing = []
102
103 ############# Signals #########
104 #connecting to signals is fine, but keep an eye if you should emit them.
105 #As a general rule, signals should only be emitted in the GenericBackend
106 #class
107
108 def _emit_signal(self, signal, backend_id):
109 gobject.idle_add(self.emit, signal, backend_id)
110
111 def backend_state_changed(self, backend_id):
112 self._emit_signal(self.BACKEND_STATE_TOGGLED, backend_id)
113
114 def backend_renamed(self, backend_id):
115 self._emit_signal(self.BACKEND_RENAMED, backend_id)
116
117 def backend_added(self, backend_id):
118 self._emit_signal(self.BACKEND_ADDED, backend_id)
119
120 def backend_removed(self, backend_id):
121 self._emit_signal(self.BACKEND_REMOVED, backend_id)
122
123 def default_backend_loaded(self):
124 gobject.idle_add(self.emit, self.DEFAULT_BACKEND_LOADED)
125
126 def backend_failed(self, backend_id, error_code):
127 gobject.idle_add(self.emit, self.BACKEND_FAILED, backend_id, \
128 error_code)
129
130 def interaction_requested(self, backend_id, description, \
131 interaction_type, callback_str):
132 gobject.idle_add(self.emit, self.INTERACTION_REQUESTED, \
133 backend_id, description, interaction_type, callback_str)
134
135 def backend_sync_started(self, backend_id):
136 self._emit_signal(self.BACKEND_SYNC_STARTED, backend_id)
137 self.backends_currently_syncing.append(backend_id)
138
139 def backend_sync_ended(self, backend_id):
140 self._emit_signal(self.BACKEND_SYNC_ENDED, backend_id)
141 try:
142 self.backends_currently_syncing.remove(backend_id)
143 except:
144 pass
145
146 def is_backend_syncing(self, backend_id):
147 return backend_id in self.backends_currently_syncing
148
0149
=== removed file 'GTG/backends/backendsignals.py'
--- GTG/backends/backendsignals.py 2010-06-23 12:49:28 +0000
+++ GTG/backends/backendsignals.py 1970-01-01 00:00:00 +0000
@@ -1,127 +0,0 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gobject
21
22from GTG.tools.borg import Borg
23
24
25
26class BackendSignals(Borg):
27 '''
28 This class handles the signals that involve backends.
29 In particular, it's a wrapper Borg class around a _BackendSignalsGObject
30 class, and all method of the wrapped class can be used as if they were part
31 of this class
32 '''
33
34 #error codes to send along with the BACKEND_FAILED signal
35 ERRNO_AUTHENTICATION = "authentication failed"
36 ERRNO_NETWORK = "network is down"
37 ERRNO_DBUS = "Dbus interface cannot be connected"
38
39 def __init__(self):
40 super(BackendSignals, self).__init__()
41 if hasattr(self, "_gobject"):
42 return
43 self._gobject = _BackendSignalsGObject()
44
45 def __getattr__(self, attr):
46 if attr == "_gobject" and not "_gobject" in self.__dict__:
47 raise AttributeError
48 return getattr(self._gobject, attr)
49
50
51class _BackendSignalsGObject(gobject.GObject):
52
53 #signal name constants
54 BACKEND_STATE_TOGGLED = 'backend-state-toggled' #emitted when a
55 #backend is
56 #enabled or disabled
57 BACKEND_RENAMED = 'backend-renamed' #emitted when a backend is renamed
58 BACKEND_ADDED = 'backend-added'
59 BACKEND_REMOVED = 'backend-added' #when a backend is deleted
60 DEFAULT_BACKEND_LOADED = 'default-backend-loaded' #emitted after all
61 # tasks have been
62 # loaded from the
63 # default backend
64 BACKEND_FAILED = 'backend-failed' #something went wrong with a backend
65 BACKEND_SYNC_STARTED = 'backend-sync-started'
66 BACKEND_SYNC_ENDED = 'backend-sync-ended'
67
68 __string_signal__ = (gobject.SIGNAL_RUN_FIRST, \
69 gobject.TYPE_NONE, (str, ))
70 __none_signal__ = (gobject.SIGNAL_RUN_FIRST, \
71 gobject.TYPE_NONE, ( ))
72 __string_string_signal__ = (gobject.SIGNAL_RUN_FIRST, \
73 gobject.TYPE_NONE, (str, str, ))
74
75 __gsignals__ = {BACKEND_STATE_TOGGLED : __string_signal__, \
76 BACKEND_RENAMED : __string_signal__, \
77 BACKEND_ADDED : __string_signal__, \
78 BACKEND_REMOVED : __string_signal__, \
79 BACKEND_SYNC_STARTED : __string_signal__, \
80 BACKEND_SYNC_ENDED : __string_signal__, \
81 DEFAULT_BACKEND_LOADED: __none_signal__, \
82 BACKEND_FAILED : __string_string_signal__}
83
84 def __init__(self):
85 super(_BackendSignalsGObject, self).__init__()
86 self.backends_currently_syncing = []
87
88 ############# Signals #########
89 #connecting to signals is fine, but keep an eye if you should emit them.
90 #As a general rule, signals should only be emitted in the GenericBackend
91 #class
92
93 def _emit_signal(self, signal, backend_id):
94 gobject.idle_add(self.emit, signal, backend_id)
95
96 def backend_state_changed(self, backend_id):
97 self._emit_signal(self.BACKEND_STATE_TOGGLED, backend_id)
98
99 def backend_renamed(self, backend_id):
100 self._emit_signal(self.BACKEND_RENAMED, backend_id)
101
102 def backend_added(self, backend_id):
103 self._emit_signal(self.BACKEND_ADDED, backend_id)
104
105 def backend_removed(self, backend_id):
106 self._emit_signal(self.BACKEND_REMOVED, backend_id)
107
108 def default_backend_loaded(self):
109 gobject.idle_add(self.emit, self.DEFAULT_BACKEND_LOADED)
110
111 def backend_failed(self, backend_id, error_code):
112 gobject.idle_add(self.emit, self.BACKEND_FAILED, backend_id, \
113 error_code)
114
115 def backend_sync_started(self, backend_id):
116 self._emit_signal(self.BACKEND_SYNC_STARTED, backend_id)
117 self.backends_currently_syncing.append(backend_id)
118
119 def backend_sync_ended(self, backend_id):
120 self._emit_signal(self.BACKEND_SYNC_ENDED, backend_id)
121 try:
122 self.backends_currently_syncing.remove(backend_id)
123 except:
124 pass
125
126 def is_backend_syncing(self, backend_id):
127 return backend_id in self.backends_currently_syncing
1280
=== modified file 'GTG/core/requester.py'
--- GTG/core/requester.py 2010-06-22 19:55:15 +0000
+++ GTG/core/requester.py 2010-09-04 17:54:43 +0000
@@ -284,3 +284,6 @@
284284
285 def backend_change_attached_tags(self, backend_id, tags):285 def backend_change_attached_tags(self, backend_id, tags):
286 return self.ds.backend_change_attached_tags(backend_id, tags)286 return self.ds.backend_change_attached_tags(backend_id, tags)
287
288 def save_datastore(self):
289 return self.ds.save()
287290
=== modified file 'GTG/gtk/__init__.py'
--- GTG/gtk/__init__.py 2010-06-02 18:12:23 +0000
+++ GTG/gtk/__init__.py 2010-09-04 17:54:43 +0000
@@ -28,7 +28,9 @@
2828
2929
30class ViewConfig:30class ViewConfig:
31
32
31 current_rep = os.path.dirname(os.path.abspath(__file__))33 current_rep = os.path.dirname(os.path.abspath(__file__))
32 DELETE_GLADE_FILE = os.path.join(current_rep, "deletion.glade")34 DELETE_GLADE_FILE = os.path.join(current_rep, "deletion.glade")
33 PREFERENCES_GLADE_FILE = os.path.join(current_rep, "preferences.glade")35 PREFERENCES_GLADE_FILE = os.path.join(current_rep, "preferences.glade")
3436 BACKENDS_GLADE_FILE = os.path.join(current_rep, "backends_dialog.glade")
3537
=== added directory 'GTG/gtk/backends_dialog'
=== added file 'GTG/gtk/backends_dialog.glade'
--- GTG/gtk/backends_dialog.glade 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog.glade 2010-09-04 17:54:43 +0000
@@ -0,0 +1,166 @@
1<?xml version="1.0"?>
2<interface>
3 <requires lib="gtk+" version="2.16"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkWindow" id="backends_dialog">
6 <property name="window_position">mouse</property>
7 <signal name="delete_event" handler="on_BackendsDialog_delete_event"/>
8 <child>
9 <object class="GtkAlignment" id="alignment1">
10 <property name="visible">True</property>
11 <property name="top_padding">10</property>
12 <property name="bottom_padding">10</property>
13 <property name="left_padding">10</property>
14 <property name="right_padding">10</property>
15 <child>
16 <object class="GtkVBox" id="vbox1">
17 <property name="visible">True</property>
18 <property name="spacing">10</property>
19 <child>
20 <object class="GtkHBox" id="big_central_hbox">
21 <property name="visible">True</property>
22 <property name="spacing">10</property>
23 <child>
24 <object class="GtkVBox" id="vbox2">
25 <property name="visible">True</property>
26 <child>
27 <object class="GtkAlignment" id="treeview_window">
28 <property name="height_request">400</property>
29 <property name="visible">True</property>
30 <child>
31 <placeholder/>
32 </child>
33 </object>
34 <packing>
35 <property name="position">0</property>
36 </packing>
37 </child>
38 <child>
39 <object class="GtkAlignment" id="alignment2">
40 <property name="height_request">30</property>
41 <property name="visible">True</property>
42 <property name="yalign">1</property>
43 <property name="top_padding">20</property>
44 <property name="bottom_padding">10</property>
45 <property name="left_padding">10</property>
46 <property name="right_padding">10</property>
47 <child>
48 <object class="GtkHButtonBox" id="hbuttonbox3">
49 <property name="visible">True</property>
50 <property name="spacing">10</property>
51 <property name="homogeneous">True</property>
52 <child>
53 <object class="GtkButton" id="add_button">
54 <property name="label">gtk-add</property>
55 <property name="visible">True</property>
56 <property name="can_focus">True</property>
57 <property name="receives_default">True</property>
58 <property name="use_stock">True</property>
59 <signal name="clicked" handler="on_add_button_clicked"/>
60 </object>
61 <packing>
62 <property name="expand">False</property>
63 <property name="fill">False</property>
64 <property name="position">0</property>
65 </packing>
66 </child>
67 <child>
68 <object class="GtkButton" id="remove_button">
69 <property name="label">gtk-remove</property>
70 <property name="visible">True</property>
71 <property name="can_focus">True</property>
72 <property name="receives_default">True</property>
73 <property name="use_stock">True</property>
74 <signal name="clicked" handler="on_remove_button_clicked"/>
75 </object>
76 <packing>
77 <property name="expand">False</property>
78 <property name="fill">False</property>
79 <property name="position">1</property>
80 </packing>
81 </child>
82 </object>
83 </child>
84 </object>
85 <packing>
86 <property name="expand">False</property>
87 <property name="fill">False</property>
88 <property name="position">1</property>
89 </packing>
90 </child>
91 </object>
92 <packing>
93 <property name="position">0</property>
94 </packing>
95 </child>
96 <child>
97 <object class="GtkScrolledWindow" id="central_pane_window">
98 <property name="width_request">450</property>
99 <property name="visible">True</property>
100 <property name="can_focus">True</property>
101 <property name="vadjustment">adjustment1</property>
102 <property name="hscrollbar_policy">automatic</property>
103 <property name="vscrollbar_policy">automatic</property>
104 <child>
105 <object class="GtkViewport" id="central_pane1">
106 <property name="visible">True</property>
107 <property name="resize_mode">queue</property>
108 <child>
109 <object class="GtkAlignment" id="central_pane">
110 <property name="visible">True</property>
111 <property name="left_padding">10</property>
112 <property name="right_padding">10</property>
113 <child>
114 <placeholder/>
115 </child>
116 </object>
117 </child>
118 </object>
119 </child>
120 </object>
121 <packing>
122 <property name="position">1</property>
123 </packing>
124 </child>
125 </object>
126 <packing>
127 <property name="position">0</property>
128 </packing>
129 </child>
130 <child>
131 <object class="GtkHButtonBox" id="hbuttonbox2">
132 <property name="visible">True</property>
133 <property name="layout_style">end</property>
134 <child>
135 <object class="GtkButton" id="close_button">
136 <property name="label">gtk-close</property>
137 <property name="visible">True</property>
138 <property name="can_focus">True</property>
139 <property name="receives_default">True</property>
140 <property name="use_stock">True</property>
141 <signal name="clicked" handler="on_close_button_clicked"/>
142 </object>
143 <packing>
144 <property name="expand">False</property>
145 <property name="fill">False</property>
146 <property name="position">0</property>
147 </packing>
148 </child>
149 </object>
150 <packing>
151 <property name="expand">False</property>
152 <property name="position">1</property>
153 </packing>
154 </child>
155 </object>
156 </child>
157 </object>
158 </child>
159 </object>
160 <object class="GtkAdjustment" id="adjustment1">
161 <property name="upper">100</property>
162 <property name="step_increment">1</property>
163 <property name="page_increment">10</property>
164 <property name="page_size">10</property>
165 </object>
166</interface>
0167
=== added file 'GTG/gtk/backends_dialog/__init__.py'
--- GTG/gtk/backends_dialog/__init__.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/__init__.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,294 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20'''
21This file contains BackendsDialog, a class that manages the window that
22lets you add and configure backends.
23This window is divided in two:
24 - a treeview of the currently loaded backends (the ones added by the user)
25 - a big space, that can be filled by the configuration panel or the add
26 panel (these are called also "views" in this class)
27'''
28
29import gtk
30
31from GTG.gtk import ViewConfig
32from GTG.core import CoreConfig
33from GTG.gtk.backends_dialog.backendstree import BackendsTree
34from GTG.gtk.backends_dialog.addpanel import AddPanel
35from GTG.gtk.backends_dialog.configurepanel import ConfigurePanel
36from GTG.backends import BackendFactory
37from GTG.tools.logger import Log
38from GTG import _
39from GTG.backends.genericbackend import GenericBackend
40
41
42
43class BackendsDialog(object):
44 '''
45 BackendsDialog manages a window that lets you manage and configure backends.
46 It can display two "views", or "panels":
47 - the backend configuration view
48 - the backend adding view
49 '''
50
51
52 def __init__(self, req):
53 '''
54 Initializes the gtk objects and signals.
55 @param req: a Requester object
56 '''
57 self.req = req
58 self._configure_icon_theme()
59 builder = gtk.Builder()
60 self._load_widgets_from_glade(builder)
61 self._create_widgets_for_add_panel()
62 self._create_widgets_for_configure_panel()
63 self._setup_signal_connections(builder)
64 self._create_widgets_for_backends_tree()
65
66########################################
67### INTERFACE WITH THE VIEWMANAGER #####
68########################################
69
70 def activate(self):
71 '''Shows this window, refreshing the current view'''
72 self.config_panel.set_hidden(False)
73 self.dialog.show_all()
74 self.backends_tv.refresh()
75 self.backends_tv.select_backend()
76 self.dialog.present()
77
78 def on_close(self, widget, data = None):
79 '''
80 Hides this window, saving the backends configuration.
81
82 @param widget: not used, here only for using this as signal callback
83 @param data: same as widget, disregard the content
84 '''
85 self.dialog.hide()
86 self.config_panel.set_hidden(True)
87 self.req.save_datastore()
88
89########################################
90### HELPER FUNCTIONS ###################
91########################################
92
93 def get_requester(self):
94 '''
95 Helper function: returns the requester.
96 It's used by the "views" displayed by this class (backend editing and
97 adding views) to access the requester
98 '''
99 return self.req
100
101 def get_pixbuf_from_icon_name(self, name, height, width):
102 '''
103 Helper function: returns a pixbuf of an icon given its name in the
104 loaded icon theme
105
106 @param name: the name of the icon
107 @param height: the height of the returned pixbuf
108 @param width: the width of the returned pixbuf
109
110 @returns gtk.gdk.Pixbuf: a pixbuf containing the wanted icon, or None
111 (if the icon is not present)
112 '''
113 #NOTE: loading icons directly from the theme and scaling them results in
114 # blurry icons. So, instead of doing that, I'm loading them
115 # directly from file.
116 icon_info = self.icon_theme.lookup_icon(name, gtk.ICON_SIZE_MENU, 0)
117 if icon_info == None:
118 return None
119 pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.get_filename())
120 return pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
121
122 def _show_panel(self, panel_name):
123 '''
124 Helper function to switch between panels.
125
126 @param panel_name: the name of the wanted panel. Choose between
127 "configuration" or "add"
128 '''
129 if panel_name == "configuration":
130 panel_to_remove = self.add_panel
131 panel_to_add = self.config_panel
132 side_is_enabled = True
133 elif panel_name == "add":
134 panel_to_remove = self.config_panel
135 panel_to_add = self.add_panel
136 side_is_enabled = False
137 else:
138 Log.error("panel name unknown")
139 return
140 ##Central pane
141 #NOTE: self.central_pane is the gtk.Container in which we load panels
142 if panel_to_remove in self.central_pane:
143 self.central_pane.remove(panel_to_remove)
144 if not panel_to_add in self.central_pane:
145 self.central_pane.add(panel_to_add)
146 self.central_pane.show_all()
147 #Side treeview
148 # disabled if we're adding a new backend
149 try:
150 #when this is called upon initialization of this class, the
151 # backends_tv object has not been created yet.
152 self.add_button.set_sensitive(side_is_enabled)
153 self.remove_button.set_sensitive(side_is_enabled)
154 self.backends_tv.set_sensitive(side_is_enabled)
155 except AttributeError:
156 pass
157
158########################################
159### WIDGETS AND SIGNALS ################
160########################################
161
162 def _load_widgets_from_glade(self, builder):
163 '''
164 Loads widgets from the glade file
165
166 @param builder: a gtk.Builder
167 '''
168 builder.add_from_file(ViewConfig.BACKENDS_GLADE_FILE)
169 widgets = {
170 'dialog' : 'backends_dialog',
171 'treeview_window' : 'treeview_window',
172 'central_pane' : 'central_pane',
173 'add_button' : 'add_button',
174 'remove_button' : 'remove_button',
175 }
176 for attr, widget in widgets.iteritems():
177 setattr(self, attr, builder.get_object(widget))
178
179 def _setup_signal_connections(self, builder):
180 '''
181 Creates some GTK signals connections
182
183 @param builder: a gtk.Builder
184 '''
185 signals = {
186 'on_add_button_clicked': self.on_add_button,
187 'on_BackendsDialog_delete_event': self.on_close,
188 'on_close_button_clicked': self.on_close,
189 'on_remove_button_clicked': self.on_remove_button,
190 }
191 builder.connect_signals(signals)
192
193 def _configure_icon_theme(self):
194 '''
195 Inform gtk on the location of the backends icons (which is in
196 the GTG directory tree, and not in the default location for icons
197 '''
198 self.icon_theme = gtk.icon_theme_get_default()
199 for directory in CoreConfig().get_icons_directories():
200 self.icon_theme.prepend_search_path(directory)
201
202 def _create_widgets_for_backends_tree(self):
203 '''
204 Creates the widgets for the lateral treeview displaying the
205 backends the user has added
206 '''
207 self.backends_tv = BackendsTree(self)
208 self.treeview_window.add(self.backends_tv)
209
210 def _create_widgets_for_configure_panel(self):
211 '''simply creates the panel to configure backends'''
212 self.config_panel = ConfigurePanel(self)
213
214 def _create_widgets_for_add_panel(self):
215 '''simply creates the panel to add backends'''
216 self.add_panel = AddPanel(self)
217
218########################################
219### EVENT HANDLING #####################
220########################################
221
222 def on_backend_selected(self, backend_id):
223 '''
224 When a backend in the treeview gets selected, show
225 its configuration pane
226
227 @param backend_id: the id of the selected backend
228 '''
229 if backend_id:
230 self._show_panel("configuration")
231 self.config_panel.set_backend(backend_id)
232 backend = self.req.get_backend(backend_id)
233 self.remove_button.set_sensitive(not backend.is_default())
234
235 def on_add_button(self, widget = None, data = None):
236 '''
237 When the add button is pressed, the add panel is shown
238
239 @param widget: not used, here only for using this as signal callback
240 @param data: same as widget, disregard the content
241 '''
242 self._show_panel("add")
243 self.add_panel.refresh_backends()
244
245 def on_backend_added(self, backend_name):
246 '''
247 When a backend is added, it is created and registered in the Datastore.
248 Also, the configuration panel is shown.
249
250 @param backend_name: the name of the type of the backend to add
251 (identified as BACKEND_NAME in the Backend class)
252 '''
253 backend_id = None
254 #Create Backend
255 backend_dic = BackendFactory().get_new_backend_dict(backend_name)
256 if backend_dic:
257 backend_id = backend_dic["backend"].get_id()
258 backend_dic[GenericBackend.KEY_ENABLED] = False
259 self.req.register_backend(backend_dic)
260 #Restore UI
261 self._show_panel("configuration")
262
263 def show_config_for_backend(self, backend_id):
264 '''
265 Selects a backend in the lateral treeview
266
267 @param backend_id: the id of the backend that must be selected
268 '''
269 self.backends_tv.select_backend(backend_id)
270
271 def on_remove_button(self, widget = None, data = None):
272 '''
273 When the remove button is pressed, a confirmation dialog is shown,
274 and if the answer is positive, the backend is deleted.
275 '''
276 backend_id = self.backends_tv.get_selected_backend_id()
277 if backend_id == None:
278 #no backend selected
279 return
280 backend = self.req.get_backend(backend_id)
281 dialog = gtk.MessageDialog( \
282 parent = self.dialog,
283 flags = gtk.DIALOG_DESTROY_WITH_PARENT,
284 type = gtk.MESSAGE_QUESTION,
285 buttons = gtk.BUTTONS_YES_NO,
286 message_format = \
287 _("Do you really want to remove the backend '%s'?") % \
288 backend.get_human_name())
289 response = dialog.run()
290 dialog.destroy()
291 if response == gtk.RESPONSE_YES:
292 #delete the backend and remove it from the lateral treeview
293 self.req.remove_backend(backend_id)
294 self.backends_tv.remove_backend(backend_id)
0295
=== added file 'GTG/gtk/backends_dialog/addpanel.py'
--- GTG/gtk/backends_dialog/addpanel.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/addpanel.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,214 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22from GTG.gtk.backends_dialog.backendscombo import BackendsCombo
23from GTG.backends import BackendFactory
24from GTG import _, ngettext
25
26#The code for showing the required modules has been disabled since it
27# seems that backends will be packaged separately (as plugins). I'm
28# leaving this here in case we change that decision (invernizzi).
29#from GTG.tools.moduletopackage import ModuleToPackage
30
31
32
33class AddPanel(gtk.VBox):
34 '''
35 A VBox filled with gtk widgets to let the user choose a new backend.
36 '''
37
38
39 def __init__(self, backends_dialog):
40 '''
41 Constructor, just initializes the gtk widgets
42
43 @param backends_dialog: a reference to the dialog in which this is
44 loaded
45 '''
46 super(AddPanel, self).__init__()
47 self.dialog = backends_dialog
48 self._create_widgets()
49
50 def _create_widgets(self):
51 '''
52 gtk widgets initialization
53 '''
54 #Division of the available space in three segments:
55 # top, middle and bottom.
56 top = gtk.HBox()
57 middle = gtk.HBox()
58 bottom = gtk.HBox()
59 self._fill_top_hbox(top)
60 self._fill_middle_hbox(middle)
61 self._fill_bottom_hbox(bottom)
62 self.pack_start(top, False)
63 self.pack_start(middle, True)
64 self.pack_start(bottom, True)
65
66 def _fill_top_hbox(self, hbox):
67 '''
68 Helper function to fill and hbox with a combobox that lists the
69 available backends and a gtk.Label.
70
71 @param hbox: the gtk.HBox to fill
72 '''
73 label = gtk.Label("Select a backend")
74 label.set_size_request(-1, 30)
75 self.combo_types = BackendsCombo(self.dialog)
76 self.combo_types.child.connect('changed', self.on_combo_changed)
77 hbox.pack_start(label, True, True)
78 hbox.pack_start(self.combo_types, False, True)
79
80 def _fill_middle_hbox(self, hbox):
81 '''
82 Helper function to fill an hbox with a label describing the backend
83 and a gtk.Image (that loads the backend image)
84
85 @param hbox: the gtk.HBox to fill
86 '''
87 self.label_name = gtk.Label("name")
88 self.label_name.set_alignment(xalign = 0.5, yalign = 1)
89 self.label_description = gtk.Label()
90 self.label_description.set_justify(gtk.JUSTIFY_FILL)
91 self.label_description.set_line_wrap(True)
92 self.label_description.set_size_request(300, -1)
93 self.label_description.set_alignment(xalign = 0, yalign = 0.5)
94 self.label_author = gtk.Label("")
95 self.label_author.set_line_wrap(True)
96 self.label_author.set_alignment(xalign = 0, yalign = 0)
97 self.label_modules = gtk.Label("")
98 self.label_modules.set_line_wrap(True)
99 self.label_modules.set_alignment(xalign = 0, yalign = 0)
100 self.image_icon = gtk.Image()
101 self.image_icon.set_size_request(100, 100)
102 align_image = gtk.Alignment(xalign = 1, yalign = 0)
103 align_image.add(self.image_icon)
104 labels_vbox = gtk.VBox()
105 labels_vbox.pack_start(self.label_description, True, True)
106 labels_vbox.pack_start(self.label_author, True, True)
107 labels_vbox.pack_start(self.label_modules, True, True)
108 low_hbox = gtk.HBox()
109 low_hbox.pack_start(labels_vbox, True, True)
110 low_hbox.pack_start(align_image, True, True)
111 vbox = gtk.VBox()
112 vbox.pack_start(self.label_name, True, True)
113 vbox.pack_start(low_hbox, True, True)
114 hbox.pack_start(vbox, True, True)
115
116 def _fill_bottom_hbox(self, hbox):
117 '''
118 Helper function to fill and hbox with a buttonbox, featuring
119 and ok and cancel buttons.
120
121 @param hbox: the gtk.HBox to fill
122 '''
123 cancel_button = gtk.Button(stock = gtk.STOCK_CANCEL)
124 cancel_button.connect('clicked', self.on_cancel)
125 self.ok_button = gtk.Button(stock = gtk.STOCK_OK)
126 self.ok_button.connect('clicked', self.on_confirm)
127 align =gtk.Alignment(xalign = 0.5, \
128 yalign = 1, \
129 xscale = 1)
130 align.set_padding(0, 10, 0, 0)
131 buttonbox = gtk.HButtonBox()
132 buttonbox.set_layout(gtk.BUTTONBOX_EDGE)
133 buttonbox.add(cancel_button)
134 buttonbox.set_child_secondary(cancel_button, False)
135 buttonbox.add(self.ok_button)
136 align.add(buttonbox)
137 hbox.pack_start(align, True, True)
138
139 def refresh_backends(self):
140 '''Populates the combo box containing the available backends'''
141 self.combo_types.refresh()
142
143 def on_confirm(self, widget = None):
144 '''
145 Notifies the dialog holding this VBox that a backend has been
146 chosen
147
148 @param widget: just to make this function usable as a signal callback.
149 Not used.
150 '''
151 backend_name = self.combo_types.get_selected()
152 self.dialog.on_backend_added(backend_name)
153
154 def on_cancel(self, widget = None):
155 '''
156 Aborts the addition of a new backend. Shows the configuration panel
157 previously loaded.
158
159 @param widget: just to make this function usable as a signal callback.
160 Not used.
161 '''
162 self.dialog.show_config_for_backend(None)
163
164 def on_combo_changed(self, widget = None):
165 '''
166 Updates the backend description and icon.
167
168 @param widget: just to make this function usable as a signal callback.
169 Not used.
170 '''
171 backend_name = self.combo_types.get_selected()
172 if backend_name == None:
173 return
174 backend = BackendFactory().get_backend(backend_name)
175 self.label_description.set_markup(backend.Backend.get_description())
176
177 label = _('Syncing is <span color="red">disabled</span>')
178 markup = '<big><big><big><b>%s</b></big></big></big>' % \
179 backend.Backend.get_human_default_name()
180 self.label_name.set_markup(markup)
181 authors = backend.Backend.get_authors()
182 author_txt = '<b>%s</b>:\n - %s' % \
183 (ngettext("Author", "Authors", len(authors)),
184 reduce(lambda a, b: a + "\n" + " - " + b, authors))
185 self.label_author.set_markup(author_txt)
186 #The code for showing the required modules has been disabled since it
187 # seems that backends will be packaged separately (as plugins). I'm
188 # leaving this here in case we change that decision (invernizzi).
189 #self._build_module_list(backend.Backend)
190 pixbuf = self.dialog.get_pixbuf_from_icon_name(backend_name, 100, 100)
191 self.image_icon.set_from_pixbuf(pixbuf)
192 self.show_all()
193
194 #The code for showing the required modules has been disabled since it
195 # seems that backends will be packaged separately (as plugins). I'm
196 # leaving this here in case we change that decision (invernizzi).
197# def _build_module_list(self, backend):
198# missing_modules = []
199# for module in backend.get_required_modules():
200# try:
201# __import__(module)
202# except ImportError:
203# missing_modules.append(module)
204# if missing_modules:
205# text = "<b> Missing modules:</b>\n - "
206# module2package = ModuleToPackage()
207# missing_modules = map(lambda a: \
208# "<span color='red'>" + \
209# module2package.lookup(a) +\
210# "</span>", missing_modules)
211# text += reduce(lambda a, b: a + "\n - " + b, missing_modules)
212# self.label_modules.set_markup(text)
213# self.ok_button.set_sensitive(missing_modules == [])
214
0215
=== added file 'GTG/gtk/backends_dialog/backendscombo.py'
--- GTG/gtk/backends_dialog/backendscombo.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/backendscombo.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,92 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22from GTG.backends import BackendFactory
23
24
25
26class BackendsCombo(gtk.ComboBoxEntry):
27 '''
28 A combobox listing all the available backends types
29 '''
30
31
32 COLUMN_NAME = 0 #unique name for the backend type. It's never
33 # displayed, it's used to find which backend has
34 # been selected
35 COLUMN_HUMAN_NAME = 1 #human friendly name (which is localized).
36 COLUMN_ICON = 2
37
38 def __init__(self, backends_dialog):
39 '''
40 Constructor, itializes gtk widgets.
41 @param backends_dialog: reference to the dialog in which this combo is
42 loaded.
43 '''
44 super(BackendsCombo, self).__init__()
45 self.dialog = backends_dialog
46 self._liststore_init()
47 self._renderers_init()
48 self.set_size_request(-1, 30)
49 self.show_all()
50
51 def _liststore_init(self):
52 '''Setup the gtk.ListStore'''
53 self.liststore = gtk.ListStore(str, str, gtk.gdk.Pixbuf)
54 self.set_model(self.liststore)
55
56 def _renderers_init(self):
57 '''Configure the cell renderers'''
58 #Text renderer
59 text_cell = gtk.CellRendererText()
60 self.pack_start(text_cell, False)
61 self.set_text_column(self.COLUMN_HUMAN_NAME)
62 #Icon renderer
63 pixbuf_cell = gtk.CellRendererPixbuf()
64 self.pack_start(pixbuf_cell, False)
65 self.add_attribute(pixbuf_cell, "pixbuf", self.COLUMN_ICON)
66
67 def refresh(self):
68 '''
69 Populates the combo box with the available backends
70 '''
71 self.liststore.clear()
72 backend_types = BackendFactory().get_all_backends()
73 for name, module in backend_types.iteritems():
74 pixbuf = self.dialog.get_pixbuf_from_icon_name(name, 16, 16)
75 self.liststore.append((name, \
76 module.Backend.get_human_default_name(), \
77 pixbuf))
78 if backend_types:
79 #triggers a "changed" signal, which is used in the AddPanel to
80 #refresh the backend description and icon
81 self.set_active(0)
82
83 def get_selected(self):
84 '''
85 Returns the name of the selected backend, or None
86 '''
87 selected_iter = self.get_active_iter()
88 if selected_iter:
89 return self.liststore.get_value(selected_iter, \
90 BackendsCombo.COLUMN_NAME)
91 else:
92 return None
093
=== added file 'GTG/gtk/backends_dialog/backendstree.py'
--- GTG/gtk/backends_dialog/backendstree.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/backendstree.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,253 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22from GTG.gtk.colors import get_colored_tags_markup
23from GTG.backends.genericbackend import GenericBackend
24from GTG.backends.backendsignals import BackendSignals
25
26
27
28class BackendsTree(gtk.TreeView):
29 '''
30 gtk.TreeView that shows the currently loaded backends.
31 '''
32
33
34 COLUMN_BACKEND_ID = 0 #never shown, used for internal lookup.
35 COLUMN_ICON = 1
36 COLUMN_TEXT = 2 # holds the backend "human-readable" name
37 COLUMN_TAGS = 3
38
39 def __init__(self, backendsdialog):
40 '''
41 Constructor, just initializes the gtk widgets
42
43 @param backends_dialog: a reference to the dialog in which this is
44 loaded
45 '''
46 super(BackendsTree,self).__init__()
47 self.dialog = backendsdialog
48 self.req = backendsdialog.get_requester()
49 self._init_liststore()
50 self._init_renderers()
51 self._init_signals()
52 self.refresh()
53
54 def refresh(self):
55 '''refreshes the gtk.Liststore'''
56 self.backendid_to_iter = {}
57 self.liststore.clear()
58 for backend in self.req.get_all_backends(disabled = True):
59 self.add_backend(backend)
60 self.on_backend_state_changed(None, backend.get_id())
61
62 def on_backend_added(self, sender, backend_id):
63 '''
64 Signal callback executed when a new backend is loaded
65
66 @param sender: not used, only here to let this function be used as a
67 callback
68 @param backend_id: the id of the backend to add
69 '''
70 #Add
71 backend = self.req.get_backend(backend_id)
72 if not backend:
73 return
74 self.add_backend(backend)
75 #Select
76 self.select_backend(backend_id)
77 #Update it's enabled state
78 self.on_backend_state_changed(None, backend.get_id())
79
80 def add_backend(self, backend):
81 '''
82 Adds a new backend to the list
83
84 @param backend_id: the id of the backend to add
85 '''
86 if backend:
87 backend_iter = self.liststore.append([ \
88 backend.get_id(), \
89 self.dialog.get_pixbuf_from_icon_name(backend.get_name(), \
90 16, 16), \
91 backend.get_human_name(), \
92 self._get_markup_for_tags(backend.get_attached_tags()), \
93 ])
94 self.backendid_to_iter[backend.get_id()] = backend_iter
95
96
97 def on_backend_state_changed(self, sender, backend_id):
98 '''
99 Signal callback executed when a backend is enabled/disabled.
100
101 @param sender: not used, only here to let this function be used as a
102 callback
103 @param backend_id: the id of the backend to add
104 '''
105 if backend_id in self.backendid_to_iter:
106 style = self.get_style()
107 b_iter = self.backendid_to_iter[backend_id]
108 b_path = self.liststore.get_path(b_iter)
109 backend = self.req.get_backend(backend_id)
110 backend_name = backend.get_human_name()
111 if backend.is_enabled():
112 text = backend_name
113 else:
114 color = str(style.text[gtk.STATE_INSENSITIVE])
115 text = "<span color='%s'>%s</span>" % \
116 (color, backend_name)
117 self.liststore[b_path][self.COLUMN_TEXT] = text
118
119 def _get_markup_for_tags(self, tag_names):
120 '''Given a list of tags names, generates the pango markup to render that
121 list with the tag colors used in GTG
122
123 @param tag_names: the list of the tags (strings)
124 @return str: the pango markup string
125 '''
126 if GenericBackend.ALLTASKS_TAG in tag_names:
127 tags_txt = ""
128 else:
129 tags_txt = get_colored_tags_markup(self.req, tag_names)
130 return "<small>" + tags_txt + "</small>"
131
132
133 def remove_backend(self, backend_id):
134 ''' Removes a backend from the treeview, and selects the first (to show
135 something in the configuration panel
136
137 @param backend_id: the id of the backend to remove
138 '''
139 if backend_id in self.backendid_to_iter:
140 self.liststore.remove(self.backendid_to_iter[backend_id])
141 del self.backendid_to_iter[backend_id]
142 self.select_backend()
143
144 def _init_liststore(self):
145 '''Creates the liststore'''
146 self.liststore = gtk.ListStore(object, gtk.gdk.Pixbuf, str, str)
147 self.set_model(self.liststore)
148
149 def _init_renderers(self):
150 '''Initializes the cell renderers'''
151 # We hide the columns headers
152 self.set_headers_visible(False)
153 # For the backend icon
154 pixbuf_cell = gtk.CellRendererPixbuf()
155 tvcolumn_pixbuf = gtk.TreeViewColumn('Icon', pixbuf_cell)
156 tvcolumn_pixbuf.add_attribute(pixbuf_cell, 'pixbuf', self.COLUMN_ICON)
157 self.append_column(tvcolumn_pixbuf)
158 # For the backend name
159 text_cell = gtk.CellRendererText()
160 tvcolumn_text = gtk.TreeViewColumn('Name', text_cell)
161 tvcolumn_text.add_attribute(text_cell, 'markup', self.COLUMN_TEXT)
162 self.append_column(tvcolumn_text)
163 text_cell.connect('edited', self.cell_edited_callback)
164 text_cell.set_property('editable', True)
165 # For the backend tags
166 tags_cell = gtk.CellRendererText()
167 tvcolumn_tags = gtk.TreeViewColumn('Tags', tags_cell)
168 tvcolumn_tags.add_attribute(tags_cell, 'markup', self.COLUMN_TAGS)
169 self.append_column(tvcolumn_tags)
170
171 def cell_edited_callback(self, text_cell, path, new_text):
172 '''If a backend name is changed, it saves the changes in the Backend
173
174 @param text_cell: not used. The gtk.CellRendererText that emitted the
175 signal. Only here because it's passed by the signal
176 @param path: the gtk.TreePath of the edited cell
177 @param new_text: the new name of the backend
178 '''
179 #we strip everything not permitted in backend names
180 new_text = ''.join(c for c in new_text if (c.isalnum() or\
181 c in [" ", "-", "_"]))
182 selected_iter = self.liststore.get_iter(path)
183 # update the backend name
184 backend_id = self.liststore.get_value(selected_iter, \
185 self.COLUMN_BACKEND_ID)
186 backend = self.dialog.get_requester().get_backend(backend_id)
187 if backend:
188 backend.set_human_name(new_text)
189 # update the text in the liststore
190 self.liststore.set(selected_iter, self.COLUMN_TEXT, new_text)
191
192 def _init_signals(self):
193 '''Initializes the backends and gtk signals '''
194 self.connect("cursor-changed", self.on_select_row)
195 _signals = BackendSignals()
196 _signals.connect(_signals.BACKEND_ADDED, self.on_backend_added)
197 _signals.connect(_signals.BACKEND_STATE_TOGGLED,
198 self.on_backend_state_changed)
199
200 def on_select_row(self, treeview = None):
201 '''When a row is selected, displays the corresponding editing panel
202
203 @treeview: not used
204 '''
205 self.dialog.on_backend_selected(self.get_selected_backend_id())
206
207 def _get_selected_path(self):
208 '''
209 Helper function to get the selected path
210
211 @return gtk.TreePath : returns exactly one path for the selected object or
212 None
213 '''
214 selection = self.get_selection()
215 if selection:
216 model, selected_paths = self.get_selection().get_selected_rows()
217 if selected_paths:
218 return selected_paths[0]
219 return None
220
221 def select_backend(self, backend_id = None):
222 '''
223 Selects the backend corresponding to backend_id.
224 If backend_id is none, refreshes the current configuration panel.
225
226 @param backend_id: the id of the backend to select
227 '''
228 selection = self.get_selection()
229 if backend_id in self.backendid_to_iter:
230 backend_iter = self.backendid_to_iter[backend_id]
231 if selection:
232 selection.select_iter(backend_iter)
233 else:
234 if self._get_selected_path():
235 #We just reselect the currently selected entry
236 self.on_select_row()
237 else:
238 #If nothing is selected, we select the first entry
239 if selection:
240 selection.select_path("0")
241 self.dialog.on_backend_selected(self.get_selected_backend_id())
242
243 def get_selected_backend_id(self):
244 '''
245 returns the selected backend id, or none
246
247 @return string: the selected backend id (or None)
248 '''
249 selected_path = self._get_selected_path()
250 if not selected_path:
251 return None
252 selected_iter = self.liststore.get_iter(selected_path)
253 return self.liststore.get_value(selected_iter, self.COLUMN_BACKEND_ID)
0254
=== added file 'GTG/gtk/backends_dialog/configurepanel.py'
--- GTG/gtk/backends_dialog/configurepanel.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/configurepanel.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,304 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22from GTG.gtk.colors import get_colored_tags_markup
23from GTG import _, ngettext
24from GTG.backends.genericbackend import GenericBackend
25from GTG.gtk.backends_dialog.parameters_ui import ParametersUI
26from GTG.backends.backendsignals import BackendSignals
27
28
29class ConfigurePanel(gtk.VBox):
30 '''
31 A VBox that lets you configure a backend
32 '''
33
34
35 def __init__(self, backends_dialog):
36 '''
37 Constructor, creating all the gtk widgets
38
39 @param backends_dialog: a reference to the dialog in which this is
40 loaded
41 '''
42 super(ConfigurePanel, self).__init__()
43 self.dialog = backends_dialog
44 self.should_spinner_be_shown = False
45 self.task_deleted_handle = None
46 self.task_added_handle = None
47 self.req = backends_dialog.get_requester()
48 self._create_widgets()
49 self._connect_signals()
50
51 def _connect_signals(self):
52 ''' Connects the backends generated signals '''
53 _signals = BackendSignals()
54 _signals.connect(_signals.BACKEND_RENAMED, self.refresh_title)
55 _signals.connect(_signals.BACKEND_STATE_TOGGLED, \
56 self.refresh_sync_status)
57 _signals.connect(_signals.BACKEND_SYNC_STARTED, self.on_sync_started)
58 _signals.connect(_signals.BACKEND_SYNC_ENDED, self.on_sync_ended)
59
60 def _create_widgets(self):
61 '''
62 This function fills this Vbox with widgets
63 '''
64 #Division of the available space in three segments:
65 # top, middle and bottom
66 top = gtk.HBox()
67 middle = gtk.HBox()
68 self._fill_top_hbox(top)
69 self._fill_middle_hbox(middle)
70 self.pack_start(top, False)
71 self.pack_start(middle, False)
72 align = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1)
73 align.set_padding(10, 0, 0, 0)
74 self.parameters_ui = ParametersUI(self.req)
75 align.add(self.parameters_ui)
76 self.pack_start(align, False)
77
78 def _fill_top_hbox(self, hbox):
79 '''
80 Helper function to fill an hbox with an image, a spinner and
81 three labels
82
83 @param hbox: the gtk.HBox to fill
84 '''
85 hbox.set_spacing(10)
86 self.image_icon = gtk.Image()
87 self.image_icon.set_size_request(100, 100)
88 vbox = gtk.VBox()
89 hbox_top = gtk.HBox()
90 self.human_name_label = gtk.Label()
91 self.human_name_label.set_alignment(xalign = 0, yalign = 0.5)
92 try:
93 self.spinner = gtk.Spinner()
94 except AttributeError:
95 #worarkound for archlinux: bug #624204
96 self.spinner = gtk.HBox()
97 self.spinner.connect("show", self.on_spinner_show)
98 self.spinner.set_size_request(32, 32)
99 align_spin = gtk.Alignment(xalign = 1, yalign = 0)
100 align_spin.add(self.spinner)
101 hbox_top.pack_start(self.human_name_label, True)
102 hbox_top.pack_start(align_spin, False)
103 self.sync_desc_label = gtk.Label()
104 self.sync_desc_label.set_alignment(xalign = 0, yalign = 1)
105 self.sync_desc_label.set_line_wrap(True)
106 vbox.pack_start(hbox_top, True)
107 vbox.pack_start(self.sync_desc_label, True)
108 hbox.pack_start(self.image_icon, False)
109 align_vbox = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1)
110 align_vbox.set_padding(10, 0, 20, 0)
111 align_vbox.add(vbox)
112 hbox.pack_start(align_vbox, True)
113
114 def _fill_middle_hbox(self, hbox):
115 '''
116 Helper function to fill an hbox with a label and a button
117
118 @param hbox: the gtk.HBox to fill
119 '''
120 self.sync_status_label = gtk.Label()
121 self.sync_status_label.set_alignment(xalign = 0.8, yalign = 0.5)
122 self.sync_button = gtk.Button()
123 self.sync_button.connect("clicked", self.on_sync_button_clicked)
124 hbox.pack_start(self.sync_status_label, True)
125 hbox.pack_start(self.sync_button, True)
126
127 def set_backend(self, backend_id):
128 '''Changes the backend to configure, refreshing this view.
129
130 @param backend_id: the id of the backend to configure
131 '''
132 self.backend = self.dialog.get_requester().get_backend(backend_id)
133 self.refresh_title()
134 self.refresh_sync_status()
135 self.parameters_ui.refresh(self.backend)
136 self.image_icon.set_from_pixbuf(self.dialog.get_pixbuf_from_icon_name(\
137 self.backend.get_name(), 80, 80))
138
139 def refresh_title(self, sender = None, data = None):
140 '''
141 Callback for the signal that notifies backends name changes. It changes
142 the title of this view
143
144 @param sender: not used, here only for signal callback compatibility
145 @param data: not used, here only for signal callback compatibility
146 '''
147 markup = "<big><big><big><b>%s</b></big></big></big>" % \
148 self.backend.get_human_name()
149 self.human_name_label.set_markup(markup)
150
151 def refresh_number_of_tasks(self):
152 '''refreshes the number of synced tasks by this backend'''
153 #FIXME: disabled for now. I'm not sure that this is nice because the
154 # count is correct only after the backend has synced all the pending
155 # tasks, and this is quite misleading (invernizzi)
156 return
157 #This will have to be changed for import/export..
158 tags = self.backend.get_attached_tags()
159 tasks_number = self.backend.get_number_of_tasks()
160 if GenericBackend.ALLTASKS_TAG in tags:
161 if tasks_number == 0:
162 markup = _("Ready to start syncing")
163 else:
164 markup = ngettext("Syncing your only task", \
165 "Syncing all %d tasks" % tasks_number, tasks_number)
166 else:
167 tags_txt = get_colored_tags_markup(self.req, tags)
168 if tasks_number == 0:
169 markup = _("There's no task tagged %s") % tags_txt
170 else:
171 markup = ngettext("Syncing a task tagged %s" % tags_txt, \
172 "Syncing %d tasks tagged %s" % (tasks_number, tags_txt), \
173 tasks_number)
174 self.sync_desc_label.set_markup(markup)
175
176 def refresh_sync_button(self):
177 '''
178 Refreshes the state of the button that enables the backend
179 '''
180 self.sync_button.set_sensitive(not self.backend.is_default())
181 if self.backend.is_enabled():
182 label = _("Disable syncing")
183 else:
184 label = _("Enable syncing")
185 self.sync_button.set_label(label)
186
187 def refresh_sync_status_label(self):
188 '''
189 Refreshes the gtk.Label that shows the current state of this backend
190 '''
191 if self.backend.is_default():
192 label = _("This is the default backend")
193 else:
194 if self.backend.is_enabled():
195 label = _("Syncing is enabled")
196 else:
197 label = _('Syncing is <span color="red">disabled</span>')
198 self.sync_status_label.set_markup(label)
199
200 def refresh_sync_status(self, sender = False, data = False):
201 '''Signal callback function, called when a backend state
202 (enabled/disabled) changes. Refreshes this view.
203
204 @param sender: not used, here only for signal callback compatibility
205 @param data: not used, here only for signal callback compatibility
206 '''
207 self.refresh_number_of_tasks()
208 self.refresh_sync_button()
209 self.refresh_sync_status_label()
210
211 def set_hidden(self, is_hidden):
212 '''
213 Notifies this pane if it's hidden or not. We disconnect signals when
214 hidden, since there is no need to keep the UI updated.
215 Hopefully, this should make GTG faster :)
216
217 @param is_hidden: boolean, True if the window is not visible
218 '''
219 #These is only needed to refresh the number of synced tasks.
220 #since that is disabled for now, there is no need for this
221
222# if is_hidden:
223# if self.task_added_handle:
224# self.req.disconnect(self.task_added_handle)
225# self.task_added_handle = None
226# if self.task_deleted_handle:
227# self.req.disconnect(self.task_deleted_handle)
228# self.task_deleted_handle = None
229# else:
230# self.task_added_handle = self.req.connect("task-added", \
231# self.__on_task_changed)
232# self.task_added_handle = self.req.connect("task-modified", \
233# self.__on_task_changed)
234# self.task_deleted_handle = self.req.connect("task-deleted", \
235# self.__on_task_changed)
236#
237# def __on_task_changed(self, sender, task_id):
238# '''
239# If tasks are added, modified or removed, updates the number of
240# tasks of the current backend
241# '''
242# self.refresh_sync_status()
243
244 def on_sync_button_clicked(self, sender):
245 '''
246 Signal callback when a backend is enabled/disabled via the UI button
247
248 @param sender: not used, here only for signal callback compatibility
249 '''
250 self.parameters_ui.commit_changes()
251 self.req.set_backend_enabled(self.backend.get_id(), \
252 not self.backend.is_enabled())
253
254 def on_sync_started(self, sender, backend_id):
255 '''
256 If the backend has started syncing tasks, update the state of the
257 gtk.Spinner
258
259 @param sender: not used, here only for signal callback compatibility
260 @param backend_id: the id of the backend that emitted this signal
261 '''
262 if backend_id == self.backend.get_id():
263 self.spinner_set_active(True)
264
265 def on_sync_ended(self, sender, backend_id):
266 '''
267 If the backend has stopped syncing tasks, update the state of the
268 gtk.Spinner
269
270 @param sender: not used, here only for signal callback compatibility
271 @param backend_id: the id of the backend that emitted this signal
272 '''
273
274 if backend_id == self.backend.get_id():
275 self.spinner_set_active(False)
276
277 def on_spinner_show(self, sender):
278 '''This signal callback hides the spinner if it's not supposed to be
279 seen. It's a workaround to let us call show_all on the whole window
280 while keeping this hidden (it's the only widget that requires special
281 attention)
282
283 @param sender: not used, here only for signal callback compatibility
284 '''
285 if self.should_spinner_be_shown == False:
286 self.spinner.hide()
287
288 def spinner_set_active(self, active):
289 '''
290 Enables/disables the gtk.Spinner, while showing/hiding it at the same
291 time
292
293 @param active: True if the spinner should spin
294 '''
295 self.should_spinner_be_shown = active
296 if active:
297 if isinstance(self.spinner, gtk.Spinner):
298 self.spinner.start()
299 self.spinner.show()
300 else:
301 self.spinner.hide()
302 if isinstance(self.spinner, gtk.Spinner):
303 self.spinner.stop()
304
0305
=== added directory 'GTG/gtk/backends_dialog/parameters_ui'
=== added file 'GTG/gtk/backends_dialog/parameters_ui/__init__.py'
--- GTG/gtk/backends_dialog/parameters_ui/__init__.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/__init__.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,149 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19'''
20This modules reads a bakcn configuration and generates a series of widgets to
21let the user see the configuration and modify it.
22In this manner, backends do not need to know anything about their UI since it's
23built for them: it should play along the lines of the separation between GTG
24server and client
25'''
26
27import gtk
28import functools
29
30from GTG import _
31from GTG.backends.genericbackend import GenericBackend
32from GTG.gtk.backends_dialog.parameters_ui.importtagsui import ImportTagsUI
33from GTG.gtk.backends_dialog.parameters_ui.textui import TextUI
34from GTG.gtk.backends_dialog.parameters_ui.passwordui import PasswordUI
35from GTG.gtk.backends_dialog.parameters_ui.periodui import PeriodUI
36from GTG.gtk.backends_dialog.parameters_ui.checkboxui import CheckBoxUI
37from GTG.gtk.backends_dialog.parameters_ui.pathui import PathUI
38
39
40
41class ParametersUI(gtk.VBox):
42 '''
43 Given a bakcend, this gtk.VBox populates itself with all the necessary
44 widgets to view and edit a backend configuration
45 '''
46
47
48 COMMON_WIDTH = 170
49
50 def __init__(self, requester):
51 '''Constructs the list of the possible widgets.
52
53 @param requester: a GTG.core.requester.Requester object
54 '''
55 super(ParametersUI, self).__init__(False)
56 self.req = requester
57 self.set_spacing(10)
58
59 #builds a list of widget generators. More precisely, it's a
60 # list of tuples: (backend_parameter_name, widget_generator)
61 self.parameter_widgets = ( \
62 ("import-tags", self.UI_generator(ImportTagsUI, \
63 {"title": _("Import tags"), \
64 "anybox_text": _("All tags"), \
65 "somebox_text": _("Just these tags"), \
66 "parameter_name": "import-tags"}) \
67 ),\
68 ("attached-tags", self.UI_generator(ImportTagsUI, \
69 {"title": _("Tags to sync"), \
70 "anybox_text": _("All tasks"), \
71 "somebox_text": _("Tasks with these tags"), \
72 "parameter_name": "attached-tags"}) \
73 ),\
74 ("path", self.UI_generator(PathUI)), \
75 ("username", self.UI_generator(TextUI, \
76 {"description": _("Username"),
77 "parameter_name": "username"})
78 ), \
79 ("password" , self.UI_generator(PasswordUI)), \
80 ("period" , self.UI_generator(PeriodUI)), \
81 ("import-from-replies", self.UI_generator(CheckBoxUI, \
82 {"text": _("Import tasks from @ replies " + \
83 "directed to you"), \
84 "parameter": "import-from-replies"}) \
85 ),\
86 ("import-from-direct-messages", self.UI_generator(CheckBoxUI, \
87 {"text": _("Import tasks from direct messages"), \
88 "parameter": "import-from-direct-messages"}) \
89 ),\
90 ("import-from-my-tweets", self.UI_generator(CheckBoxUI, \
91 {"text": _("Import tasks from your tweets"), \
92 "parameter": "import-from-my-tweets"}) \
93 ),\
94 ("import-bug-tags", self.UI_generator(CheckBoxUI, \
95 {"text": _("Tag your GTG tasks with the bug tags"), \
96 "parameter": "import-bug-tags"}) \
97 ),\
98 ("tag-with-project-name", self.UI_generator(CheckBoxUI, \
99 {"text": _("Tag your GTG tasks with the project "
100 "targeted by the bug"), \
101 "parameter": "tag-with-project-name"}) \
102 ),\
103 )
104 def UI_generator(self, param_type, special_arguments = {}):
105 '''A helper function to build a widget type from a template.
106 It passes to the created widget generator a series of common parameters,
107 plus the ones needed to specialize the given template
108
109 @param param_type: the template to specialize
110 @param special_arguments: the arguments used for this particular widget
111 generator.
112
113 @return function: return a widget generator, not a widget. the widget can
114 be obtained by calling widget_generator(backend)
115 '''
116 return lambda backend: param_type(req = self.req, \
117 backend = backend, \
118 width = self.COMMON_WIDTH, \
119 **special_arguments)
120
121 def refresh(self, backend):
122 '''Builds the widgets necessary to configure the backend. If it doesn't
123 know how to render a widget, it simply skips it.
124
125 @param backend: the backend that is being configured
126 '''
127 #remove the old parameters UIs
128 def _remove_child(self, child):
129 self.remove(child)
130 self.foreach(functools.partial(_remove_child, self))
131 #add new widgets
132 backend_parameters = backend.get_parameters()
133 if backend_parameters[GenericBackend.KEY_DEFAULT_BACKEND]:
134 #if it's the default backend, the user should not mess with it
135 return
136 for parameter_name, widget in self.parameter_widgets:
137 if parameter_name in backend_parameters:
138 self.pack_start(widget(backend), True)
139 self.show_all()
140
141 def commit_changes(self):
142 '''
143 Saves all the parameters at their current state (the user may have
144 modified them)
145 '''
146 def _commit_changes(child):
147 child.commit_changes()
148 self.foreach(_commit_changes)
149
0150
=== added file 'GTG/gtk/backends_dialog/parameters_ui/checkboxui.py'
--- GTG/gtk/backends_dialog/parameters_ui/checkboxui.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/checkboxui.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,72 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22
23
24class CheckBoxUI(gtk.HBox):
25 '''
26 It's a widget displaying a simple checkbox, with some text to explain its
27 meaning
28 '''
29
30
31 def __init__(self, req, backend, width, text, parameter):
32 '''
33 Creates the checkbox and the related label.
34
35 @param req: a Requester
36 @param backend: a backend object
37 @param width: the width of the gtk.Label object
38 @param parameter: the backend parameter this checkbox should display and
39 modify
40 '''
41 super(CheckBoxUI, self).__init__()
42 self.backend = backend
43 self.req = req
44 self.text = text
45 self.parameter = parameter
46 self._populate_gtk(width)
47
48 def _populate_gtk(self, width):
49 '''Creates the checkbox and the related label
50
51 @param width: the width of the gtk.Label object
52 '''
53 self.checkbutton =gtk.CheckButton(label = self.text)
54 self.checkbutton.set_active(self.backend.get_parameters()[self.parameter])
55 self.checkbutton.connect("toggled", self.on_modified)
56 self.pack_start(self.checkbutton, False)
57
58 def commit_changes(self):
59 '''Saves the changes to the backend parameter'''
60 self.backend.set_parameter(self.parameter,\
61 self.checkbutton.get_active())
62
63 def on_modified(self, sender = None):
64 ''' Signal callback, executed when the user clicks on the checkbox.
65 Disables the backend. The user will re-enable it to confirm the changes
66 (s)he made.
67
68 @param sender: not used, only here for signal compatibility
69 '''
70 if self.backend.is_enabled() and not self.backend.is_default():
71 self.req.set_backend_enabled(self.backend.get_id(), False)
72
073
=== added file 'GTG/gtk/backends_dialog/parameters_ui/importtagsui.py'
--- GTG/gtk/backends_dialog/parameters_ui/importtagsui.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/importtagsui.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,135 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22from GTG.backends.genericbackend import GenericBackend
23
24
25
26class ImportTagsUI(gtk.VBox):
27 '''
28 It's a widget displaying a couple of radio buttons, a label and a textbox
29 to let the user change the attached tags (or imported)
30 '''
31
32
33 def __init__(self, req, backend, width, title, anybox_text, somebox_text, \
34 parameter_name):
35 '''Populates the widgets and refresh the tags to display
36
37 @param req: a requester
38 @param backend: the backend to configure
39 @param width: the length of the radio buttons
40 @param title: the text for the label describing what this collection
41 of gtk widgets is used for
42 @param anybox_text: the text for the "Any tag matches" radio button
43 @param somebox_text: the text for the "only this set of tags matches"
44 radio button
45 @param parameter_name: the backend parameter this widget should modify
46 '''
47 super(ImportTagsUI, self).__init__()
48 self.backend = backend
49 self.req = req
50 self.title = title
51 self.anybox_text = anybox_text
52 self.somebox_text = somebox_text
53 self.parameter_name = parameter_name
54 self._populate_gtk(width)
55 self._refresh_tags()
56 self._connect_signals()
57
58 def _populate_gtk(self, width):
59 '''
60 Populates the widgets
61
62 @param width: the length of the radio buttons
63 '''
64 title_label = gtk.Label()
65 title_label.set_alignment(xalign = 0, yalign = 0)
66 title_label.set_markup("<big><b>%s</b></big>" % self.title)
67 self.pack_start(title_label, True)
68 align = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1)
69 align.set_padding(0, 0, 10, 0)
70 self.pack_start(align, True)
71 vbox = gtk.VBox()
72 align.add(vbox)
73 self.all_tags_radio = gtk.RadioButton(group = None, \
74 label = self.anybox_text)
75 vbox.pack_start(self.all_tags_radio, True)
76 self.some_tags_radio = gtk.RadioButton(group = self.all_tags_radio,
77 label = self.somebox_text)
78 self.some_tags_radio.set_size_request(width = width, height = -1)
79 hbox = gtk.HBox()
80 vbox.pack_start(hbox, True)
81 hbox.pack_start(self.some_tags_radio, False)
82 self.tags_entry = gtk.Entry()
83 hbox.pack_start(self.tags_entry, True)
84
85 def on_changed(self, radio, data = None):
86 ''' Signal callback, executed when the user modifies something.
87 Disables the backend. The user will re-enable it to confirm the changes
88 (s)he made.
89
90 @param sender: not used, only here for signal compatibility
91 @param data: not used, only here for signal compatibility
92 '''
93 #every change in the config disables the backend
94 self.req.set_backend_enabled(self.backend.get_id(), False)
95 self._refresh_textbox_state()
96
97 def commit_changes(self):
98 '''Saves the changes to the backend parameter'''
99 if self.all_tags_radio.get_active():
100 tags = [GenericBackend.ALLTASKS_TAG]
101 else:
102 tags = self.tags_entry.get_text().split(",")
103 #stripping spaces
104 tags = map(lambda t: t.strip(), tags)
105 #removing empty tags
106 tags = filter(lambda t: t, tags)
107
108 self.backend.set_parameter(self.parameter_name, tags)
109
110 def _refresh_textbox_state(self):
111 '''Refreshes the content of the textbox'''
112 self.tags_entry.set_sensitive(self.some_tags_radio.get_active())
113
114 def _refresh_tags(self):
115 '''
116 Refreshes the list of tags to display in the textbox, and selects
117 the correct radio button
118 '''
119 tags_list = self.backend.get_parameters()[self.parameter_name]
120 has_all_tasks = GenericBackend.ALLTASKS_TAG in tags_list
121 self.all_tags_radio.set_active(has_all_tasks)
122 self.some_tags_radio.set_active(not has_all_tasks)
123 self._refresh_textbox_state()
124 if not has_all_tasks:
125 tags_text = ""
126 if tags_list:
127 tags_text = reduce(lambda a, b: a + ", " + b, tags_list)
128 self.tags_entry.set_text(tags_text)
129
130 def _connect_signals(self):
131 '''Connects the gtk signals'''
132 self.some_tags_radio.connect("toggled", self.on_changed)
133 self.all_tags_radio.connect("toggled", self.on_changed)
134 self.tags_entry.connect("changed", self.on_changed)
135
0136
=== added file 'GTG/gtk/backends_dialog/parameters_ui/passwordui.py'
--- GTG/gtk/backends_dialog/parameters_ui/passwordui.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/passwordui.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,84 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22from GTG import _
23
24
25
26class PasswordUI(gtk.HBox):
27 '''Widget displaying a gtk.Label and a textbox to input a password'''
28
29
30 def __init__(self, req, backend, width):
31 '''Creates the gtk widgets and loads the current password in the text
32 field
33
34 @param req: a Requester
35 @param backend: a backend object
36 @param width: the width of the gtk.Label object
37 '''
38 super(PasswordUI, self).__init__()
39 self.backend = backend
40 self.req = req
41 self._populate_gtk(width)
42 self._load_password()
43 self._connect_signals()
44
45 def _populate_gtk(self, width):
46 '''Creates the text box and the related label
47
48 @param width: the width of the gtk.Label object
49 '''
50 password_label = gtk.Label(_("Password:"))
51 password_label.set_alignment(xalign = 0, yalign = 0.5)
52 password_label.set_size_request(width = width, height = -1)
53 self.pack_start(password_label, False)
54 align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1)
55 align.set_padding(0, 0, 10, 0)
56 self.pack_start(align, True)
57 self.password_textbox = gtk.Entry()
58 align.add(self.password_textbox)
59
60 def _load_password(self):
61 '''Loads the password from the backend'''
62 password = self.backend.get_parameters()['password']
63 self.password_textbox.set_invisible_char('*')
64 self.password_textbox.set_visibility(False)
65 self.password_textbox.set_text(password)
66
67 def _connect_signals(self):
68 '''Connects the gtk signals'''
69 self.password_textbox.connect('changed', self.on_password_modified)
70
71 def commit_changes(self):
72 '''Saves the changes to the backend parameter ('password')'''
73 self.backend.set_parameter('password', self.password_textbox.get_text())
74
75 def on_password_modified(self, sender):
76 ''' Signal callback, executed when the user edits the password.
77 Disables the backend. The user will re-enable it to confirm the changes
78 (s)he made.
79
80 @param sender: not used, only here for signal compatibility
81 '''
82 if self.backend.is_enabled() and not self.backend.is_default():
83 self.req.set_backend_enabled(self.backend.get_id(), False)
84
085
=== added file 'GTG/gtk/backends_dialog/parameters_ui/pathui.py'
--- GTG/gtk/backends_dialog/parameters_ui/pathui.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/pathui.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,112 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21import os.path
22
23from GTG import _
24
25
26
27
28class PathUI(gtk.HBox):
29 '''Gtk widgets to show a path in a textbox, and a button to bring up a
30 filesystem explorer to modify that path (also, a label to describe those)
31 '''
32
33
34 def __init__(self, req, backend, width):
35 '''
36 Creates the textbox, the button and loads the current path.
37
38 @param req: a Requester
39 @param backend: a backend object
40 @param width: the width of the gtk.Label object
41 '''
42 super(PathUI, self).__init__()
43 self.backend = backend
44 self.req = req
45 self._populate_gtk(width)
46
47 def _populate_gtk(self, width):
48 '''Creates the gtk.Label, the textbox and the button
49
50 @param width: the width of the gtk.Label object
51 '''
52 label = gtk.Label(_("Filename:"))
53 label.set_line_wrap(True)
54 label.set_alignment(xalign = 0, yalign = 0.5)
55 label.set_size_request(width = width, height = -1)
56 self.pack_start(label, False)
57 align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1)
58 align.set_padding(0, 0, 10, 0)
59 self.pack_start(align, True)
60 self.textbox = gtk.Entry()
61 self.textbox.set_text(self.backend.get_parameters()['path'])
62 self.textbox.connect('changed', self.on_path_modified)
63 align.add(self.textbox)
64 self.button = gtk.Button(stock = gtk.STOCK_EDIT)
65 self.button.connect('clicked', self.on_button_clicked)
66 self.pack_start(self.button, False)
67
68 def commit_changes(self):
69 '''Saves the changes to the backend parameter'''
70 self.backend.set_parameter('path', self.textbox.get_text())
71
72 def on_path_modified(self, sender):
73 ''' Signal callback, executed when the user edits the path.
74 Disables the backend. The user will re-enable it to confirm the changes
75 (s)he made.
76
77 @param sender: not used, only here for signal compatibility
78 '''
79 if self.backend.is_enabled() and not self.backend.is_default():
80 self.req.set_backend_enabled(self.backend.get_id(), False)
81
82 def on_button_clicked(self, sender):
83 '''Shows the filesystem explorer to choose a new file
84
85 @param sender: not used, only here for signal compatibility
86 '''
87 self.chooser = gtk.FileChooserDialog( \
88 title=None,
89 action=gtk.FILE_CHOOSER_ACTION_SAVE,
90 buttons=(gtk.STOCK_CANCEL,
91 gtk.RESPONSE_CANCEL, \
92 gtk.STOCK_OK, \
93 gtk.RESPONSE_OK))
94 self.chooser.set_default_response(gtk.RESPONSE_OK)
95 #set default file as the current self.path
96 self.chooser.set_current_name(os.path.basename(self.textbox.get_text()))
97 self.chooser.set_current_folder(os.path.dirname(self.textbox.get_text()))
98
99 #filter files
100 afilter = gtk.FileFilter()
101 afilter.set_name("All files")
102 afilter.add_pattern("*")
103 self.chooser.add_filter(afilter)
104 afilter = gtk.FileFilter()
105 afilter.set_name("XML files")
106 afilter.add_mime_type("text/plain")
107 afilter.add_pattern("*.xml")
108 self.chooser.add_filter(afilter)
109 response = self.chooser.run()
110 if response == gtk.RESPONSE_OK:
111 self.textbox.set_text(self.chooser.get_filename())
112 self.chooser.destroy()
0113
=== added file 'GTG/gtk/backends_dialog/parameters_ui/periodui.py'
--- GTG/gtk/backends_dialog/parameters_ui/periodui.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/periodui.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,97 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22from GTG import _, ngettext
23
24
25
26class PeriodUI(gtk.HBox):
27 '''A widget to change the frequency of a backend synchronization
28 '''
29
30
31 def __init__(self, req, backend, width):
32 '''
33 Creates the gtk.Adjustment and the related label. Loads the current
34 period.
35
36 @param req: a Requester
37 @param backend: a backend object
38 @param width: the width of the gtk.Label object
39 '''
40 super(PeriodUI, self).__init__()
41 self.backend = backend
42 self.req = req
43 self._populate_gtk(width)
44 self._connect_signals()
45
46 def _populate_gtk(self, width):
47 '''Creates the gtk widgets
48
49 @param width: the width of the gtk.Label object
50 '''
51 period_label = gtk.Label(_("Check for new tasks every"))
52 period_label.set_alignment(xalign = 0, yalign = 0.5)
53 period_label.set_line_wrap(True)
54 period_label.set_size_request(width = width, height = -1)
55 self.pack_start(period_label, False)
56 align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1)
57 align.set_padding(0, 0, 10, 0)
58 self.pack_start(align, False)
59 period = self.backend.get_parameters()['period']
60 self.adjustment = gtk.Adjustment(value = period,
61 lower = 1,
62 upper = 120,
63 step_incr = 1,
64 page_incr = 0,
65 page_size = 0)
66 self.period_spin = gtk.SpinButton(adjustment = self.adjustment,
67 climb_rate = 0.3,
68 digits = 0)
69 self.minutes_label = gtk.Label()
70 self.update_minutes_label()
71 self.minutes_label.set_alignment(xalign = 0, yalign = 0.5)
72 self.pack_start(self.minutes_label, False)
73 align.add(self.period_spin)
74 self.show_all()
75
76 def _connect_signals(self):
77 '''Connects the gtk signals'''
78 self.period_spin.connect('changed', self.on_spin_changed)
79
80 def commit_changes(self):
81 '''Saves the changes to the backend parameter'''
82 self.backend.set_parameter('period', int(self.adjustment.get_value()))
83
84 def on_spin_changed(self, sender):
85 ''' Signal callback, executed when the user changes the period.
86 Disables the backend. The user will re-enable it to confirm the changes
87 (s)he made.
88
89 @param sender: not used, only here for signal compatibility
90 '''
91 self.update_minutes_label()
92 if self.backend.is_enabled() and not self.backend.is_default():
93 self.req.set_backend_enabled(self.backend.get_id(), False)
94
95 def update_minutes_label(self):
96 self.minutes_label.set_markup(ngettext(" minute", " minutes",
97 int(self.adjustment.get_value())))
098
=== added file 'GTG/gtk/backends_dialog/parameters_ui/textui.py'
--- GTG/gtk/backends_dialog/parameters_ui/textui.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/textui.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,78 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21
22
23
24class TextUI(gtk.HBox):
25 '''A widget to display a simple textbox and a label to describe its content
26 '''
27
28
29 def __init__(self, req, backend, width, description, parameter_name):
30 '''
31 Creates the textbox and the related label. Loads the current
32 content.
33
34 @param req: a Requester
35 @param backend: a backend object
36 @param width: the width of the gtk.Label object
37 '''
38 super(TextUI, self).__init__()
39 self.backend = backend
40 self.req = req
41 self.parameter_name = parameter_name
42 self.description = description
43 self._populate_gtk(width)
44
45 def _populate_gtk(self, width):
46 '''Creates the gtk widgets
47
48 @param width: the width of the gtk.Label object
49 '''
50 label = gtk.Label("%s:" % self.description)
51 label.set_line_wrap(True)
52 label.set_alignment(xalign = 0, yalign = 0.5)
53 label.set_size_request(width = width, height = -1)
54 self.pack_start(label, False)
55 align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1)
56 align.set_padding(0, 0, 10, 0)
57 self.pack_start(align, True)
58 self.textbox = gtk.Entry()
59 self.textbox.set_text(\
60 self.backend.get_parameters()[self.parameter_name])
61 self.textbox.connect('changed', self.on_text_modified)
62 align.add(self.textbox)
63
64 def commit_changes(self):
65 '''Saves the changes to the backend parameter'''
66 self.backend.set_parameter(self.parameter_name,\
67 self.textbox.get_text())
68
69 def on_text_modified(self, sender):
70 ''' Signal callback, executed when the user changes the text.
71 Disables the backend. The user will re-enable it to confirm the changes
72 (s)he made.
73
74 @param sender: not used, only here for signal compatibility
75 '''
76 if self.backend.is_enabled() and not self.backend.is_default():
77 self.req.set_backend_enabled(self.backend.get_id(), False)
78
079
=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py 2010-08-23 01:33:43 +0000
+++ GTG/gtk/browser/browser.py 2010-09-04 17:54:43 +0000
@@ -34,7 +34,9 @@
3434
35#our own imports35#our own imports
36import GTG36import GTG
37from GTG.core import CoreConfig37from GTG.backends.backendsignals import BackendSignals
38from GTG.gtk.browser.custominfobar import CustomInfoBar
39from GTG.core import CoreConfig
38from GTG import _, info, ngettext40from GTG import _, info, ngettext
39from GTG.core.task import Task41from GTG.core.task import Task
40from GTG.gtk.browser import GnomeConfig, tasktree, tagtree42from GTG.gtk.browser import GnomeConfig, tasktree, tagtree
@@ -207,6 +209,7 @@
207 self.sidebar_notebook = self.builder.get_object("sidebar_notebook")209 self.sidebar_notebook = self.builder.get_object("sidebar_notebook")
208 self.main_notebook = self.builder.get_object("main_notebook")210 self.main_notebook = self.builder.get_object("main_notebook")
209 self.accessory_notebook = self.builder.get_object("accessory_notebook")211 self.accessory_notebook = self.builder.get_object("accessory_notebook")
212 self.vbox_toolbars = self.builder.get_object("vbox_toolbars")
210 213
211 self.closed_pane = None214 self.closed_pane = None
212215
@@ -314,6 +317,8 @@
314 self.on_nonworkviewtag_toggled,317 self.on_nonworkviewtag_toggled,
315 "on_preferences_activate":318 "on_preferences_activate":
316 self.open_preferences,319 self.open_preferences,
320 "on_edit_backends_activate":
321 self.open_edit_backends,
317 }322 }
318 self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)323 self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
319324
@@ -347,6 +352,16 @@
347 # Connect requester signals to TreeModels352 # Connect requester signals to TreeModels
348 self.req.connect("task-added", self.on_task_added) 353 self.req.connect("task-added", self.on_task_added)
349 self.req.connect("task-deleted", self.on_task_deleted)354 self.req.connect("task-deleted", self.on_task_deleted)
355 #this causes changed be shouwn only on save
356 #tree = self.task_tree_model.get_tree()
357 #tree.connect("task-added-inview", self.on_task_added)
358 #tree.connect("task-deleted-inview", self.on_task_deleted)
359 b_signals = BackendSignals()
360 b_signals.connect(b_signals.BACKEND_FAILED, self.on_backend_failed)
361 b_signals.connect(b_signals.BACKEND_STATE_TOGGLED, \
362 self.remove_backend_infobar)
363 b_signals.connect(b_signals.INTERACTION_REQUESTED, \
364 self.on_backend_needing_interaction)
350 365
351 # Connect signals from models366 # Connect signals from models
352 self.task_modelsort.connect("row-has-child-toggled",\367 self.task_modelsort.connect("row-has-child-toggled",\
@@ -426,9 +441,12 @@
426441
427### HELPER FUNCTIONS ########################################################442### HELPER FUNCTIONS ########################################################
428443
429 def open_preferences(self,widget):444 def open_preferences(self, widget):
430 self.vmanager.open_preferences(self.priv)445 self.vmanager.open_preferences(self.priv)
431 446
447 def open_edit_backends(self, widget):
448 self.vmanager.open_edit_backends()
449
432 def quit(self,widget=None):450 def quit(self,widget=None):
433 self.vmanager.close_browser()451 self.vmanager.close_browser()
434 452
@@ -523,7 +541,7 @@
523 col_id,\541 col_id,\
524 self.priv["tasklist"]["sort_order"])542 self.priv["tasklist"]["sort_order"])
525 except:543 except:
526 print "Invalid configuration for sorting columns"544 Log.error("Invalid configuration for sorting columns")
527545
528 if "view" in self.config["browser"]:546 if "view" in self.config["browser"]:
529 view = self.config["browser"]["view"]547 view = self.config["browser"]["view"]
@@ -1513,3 +1531,82 @@
1513 """ Returns true if window is the currently active window """1531 """ Returns true if window is the currently active window """
1514 return self.window.get_property("is-active")1532 return self.window.get_property("is-active")
15151533
1534## BACKENDS RELATED METHODS ##################################################
1535
1536 def on_backend_failed(self, sender, backend_id, error_code):
1537 '''
1538 Signal callback.
1539 When a backend fails to work, loads a gtk.Infobar to alert the user
1540
1541 @param sender: not used, only here for signal compatibility
1542 @param backend_id: the id of the failing backend
1543 @param error_code: a backend error code, as specified in BackendsSignals
1544 '''
1545 infobar = self._new_infobar(backend_id)
1546 infobar.set_error_code(error_code)
1547
1548 def on_backend_needing_interaction(self, sender, backend_id, description, \
1549 interaction_type, callback):
1550 '''
1551 Signal callback.
1552 When a backend needs some kind of feedback from the user,
1553 loads a gtk.Infobar to alert the user.
1554 This is used, for example, to request confirmation after authenticating
1555 via OAuth.
1556
1557 @param sender: not used, only here for signal compatibility
1558 @param backend_id: the id of the failing backend
1559 @param description: a string describing the interaction needed
1560 @param interaction_type: a string describing the type of interaction
1561 (yes/no, only confirm, ok/cancel...)
1562 @param callback: the function to call when the user provides the
1563 feedback
1564 '''
1565 infobar = self._new_infobar(backend_id)
1566 infobar.set_interaction_request(description, interaction_type, callback)
1567
1568
1569 def __remove_backend_infobar(self, child, backend_id):
1570 '''
1571 Helper function to remove an gtk.Infobar related to a backend
1572
1573 @param child: a gtk.Infobar
1574 @param backend_id: the id of the backend which gtk.Infobar should be
1575 removed.
1576 '''
1577 if isinstance(child, CustomInfoBar) and\
1578 child.get_backend_id() == backend_id:
1579 if self.vbox_toolbars:
1580 self.vbox_toolbars.remove(child)
1581
1582 def remove_backend_infobar(self, sender, backend_id):
1583 '''
1584 Signal callback.
1585 Deletes the gtk.Infobars related to a backend
1586
1587 @param sender: not used, only here for signal compatibility
1588 @param backend_id: the id of the backend which gtk.Infobar should be
1589 removed.
1590 '''
1591 backend = self.req.get_backend(backend_id)
1592 if not backend or (backend and backend.is_enabled()):
1593 #remove old infobar related to backend_id, if any
1594 if self.vbox_toolbars:
1595 self.vbox_toolbars.foreach(self.__remove_backend_infobar, \
1596 backend_id)
1597
1598 def _new_infobar(self, backend_id):
1599 '''
1600 Helper function to create a new infobar for a backend
1601
1602 @param backend_id: the backend for which we're creating the infobar
1603 @returns gtk.Infobar: the created infobar
1604 '''
1605 #remove old infobar related to backend_id, if any
1606 if not self.vbox_toolbars:
1607 return
1608 self.vbox_toolbars.foreach(self.__remove_backend_infobar, backend_id)
1609 #add a new one
1610 infobar = CustomInfoBar(self.req, self, self.vmanager, backend_id)
1611 self.vbox_toolbars.pack_start(infobar, True)
1612 return infobar
15161613
=== added file 'GTG/gtk/browser/custominfobar.py'
--- GTG/gtk/browser/custominfobar.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/browser/custominfobar.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,210 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Getting Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20import gtk
21import threading
22
23from GTG import _
24from GTG.backends.backendsignals import BackendSignals
25from GTG.tools.networkmanager import is_connection_up
26
27
28
29class CustomInfoBar(gtk.InfoBar):
30 '''
31 A gtk.InfoBar specialized for displaying errors and requests for
32 interaction coming from the backends
33 '''
34
35
36 AUTHENTICATION_MESSAGE = _("The <b>%s</b> backend cannot login with the "
37 "supplied authentication data and has been"
38 " disabled. To retry the login, re-enable the backend.")
39
40 NETWORK_MESSAGE = _("Due to a network problem, I cannot contact "
41 "the <b>%s</b> backend.")
42
43 DBUS_MESSAGE = _("Cannot connect to DBUS, I've disabled "
44 "the <b>%s</b> backend.")
45
46 def __init__(self, req, browser, vmanager, backend_id):
47 '''
48 Constructor, Prepares the infobar.
49
50 @param req: a Requester object
51 @param browser: a TaskBrowser object
52 @param vmanager: a ViewManager object
53 @param backend_id: the id of the backend linked to the infobar
54 '''
55 super(CustomInfoBar, self).__init__()
56 self.req = req
57 self.browser = browser
58 self.vmanager = vmanager
59 self.backend_id = backend_id
60 self.backend = self.req.get_backend(backend_id)
61
62 def get_backend_id(self):
63 '''
64 Getter function to return the id of the backend for which this
65 gtk.InfoBar was created
66 '''
67 return self.backend_id
68
69 def _populate(self):
70 '''Setting up gtk widgets'''
71 content_hbox = self.get_content_area()
72 content_hbox.set_homogeneous(False)
73 self.label = gtk.Label()
74 self.label.set_line_wrap(True)
75 self.label.set_alignment(0.5, 0.5)
76 self.label.set_justify(gtk.JUSTIFY_FILL)
77 content_hbox.pack_start(self.label, True, True)
78
79 def _on_error_response(self, widget, event):
80 '''
81 Signal callback executed when the user acknowledges the error displayed
82 in the infobar
83
84 @param widget: not used, here for compatibility with signals callbacks
85 @param event: the code of the gtk response
86 '''
87 self.hide()
88 if event == gtk.RESPONSE_ACCEPT:
89 self.vmanager.configure_backend(backend_id = self.backend_id)
90
91 def set_error_code(self, error_code):
92 '''
93 Sets this infobar to show an error to the user
94
95 @param error_code: the code of the error to show. Error codes are listed
96 in BackendSignals
97 '''
98 self._populate()
99 self.connect("response", self._on_error_response)
100 backend_name = self.backend.get_human_name()
101
102 if error_code == BackendSignals.ERRNO_AUTHENTICATION:
103 self.set_message_type(gtk.MESSAGE_ERROR)
104 self.label.set_markup(self.AUTHENTICATION_MESSAGE % backend_name)
105 self.add_button(_('Configure backend'), gtk.RESPONSE_ACCEPT)
106 self.add_button(_('Ignore'), gtk.RESPONSE_CLOSE)
107
108 elif error_code == BackendSignals.ERRNO_NETWORK:
109 if not is_connection_up():
110 return
111 self.set_message_type(gtk.MESSAGE_WARNING)
112 self.label.set_markup(self.NETWORK_MESSAGE % backend_name)
113 #FIXME: use gtk stock button instead
114 self.add_button(_('Ok'), gtk.RESPONSE_CLOSE)
115
116 elif error_code == BackendSignals.ERRNO_DBUS:
117 self.set_message_type(gtk.MESSAGE_WARNING)
118 self.label.set_markup(self.DBUS_MESSAGE % backend_name)
119 self.add_button(_('Ok'), gtk.RESPONSE_CLOSE)
120
121 self.show_all()
122
123 def set_interaction_request(self, description, interaction_type, callback):
124 '''
125 Sets this infobar to request an interaction from the user
126
127 @param description: a string describing the interaction needed
128 @param interaction_type: a string describing the type of interaction
129 (yes/no, only confirm, ok/cancel...)
130 @param callback: the function to call when the user provides the
131 feedback
132 '''
133 self._populate()
134 self.callback = callback
135 self.set_message_type(gtk.MESSAGE_INFO)
136 self.label.set_markup(description)
137 self.connect("response", self._on_interaction_response)
138 self.interaction_type = interaction_type
139 if interaction_type == BackendSignals().INTERACTION_CONFIRM:
140 self.add_button(_('Confirm'), gtk.RESPONSE_ACCEPT)
141 elif interaction_type == BackendSignals().INTERACTION_TEXT:
142 self.add_button(_('Continue'), gtk.RESPONSE_ACCEPT)
143 self.show_all()
144
145 def _on_interaction_response(self, widget, event):
146 '''
147 Signal callback executed when the user gives the feedback for a
148 requested interaction
149
150 @param widget: not used, here for compatibility with signals callbacks
151 @param event: the code of the gtk response
152 '''
153 if event == gtk.RESPONSE_ACCEPT:
154 if self.interaction_type == BackendSignals().INTERACTION_TEXT:
155 self._prepare_textual_interaction()
156 print "done"
157 elif self.interaction_type == BackendSignals().INTERACTION_CONFIRM:
158 self.hide()
159 threading.Thread(target = getattr(self.backend,
160 self.callback)).start()
161
162 def _prepare_textual_interaction(self):
163 '''
164 Helper function. gtk calls to populate the infobar in the case of
165 interaction request
166 '''
167 title, description\
168 = getattr(self.backend, self.callback)("get_title")
169 self.dialog = gtk.Window()#type = gtk.WINDOW_POPUP)
170 self.dialog.set_title(title)
171 self.dialog.set_transient_for(self.browser.window)
172 self.dialog.set_destroy_with_parent(True)
173 self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
174 self.dialog.set_modal(True)
175 # self.dialog.set_size_request(300,170)
176 vbox = gtk.VBox()
177 self.dialog.add(vbox)
178 description_label = gtk.Label()
179 description_label.set_justify(gtk.JUSTIFY_FILL)
180 description_label.set_line_wrap(True)
181 description_label.set_markup(description)
182 align = gtk.Alignment(0.5, 0.5, 1, 1)
183 align.set_padding(10, 0, 20, 20)
184 align.add(description_label)
185 vbox.pack_start(align)
186 self.text_box = gtk.Entry()
187 self.text_box.set_size_request(-1, 40)
188 align = gtk.Alignment(0.5, 0.5, 1, 1)
189 align.set_padding(20, 20, 20, 20)
190 align.add(self.text_box)
191 vbox.pack_start(align)
192 button = gtk.Button(stock = gtk.STOCK_OK)
193 button.connect("clicked", self._on_text_confirmed)
194 button.set_size_request(-1, 40)
195 vbox.pack_start(button, False)
196 self.dialog.show_all()
197 self.hide()
198
199 def _on_text_confirmed(self, widget):
200 '''
201 Signal callback, used when the interaction needs a textual input to be
202 completed (e.g, the twitter OAuth, requesting a pin)
203
204 @param widget: not used, here for signal callback compatibility
205 '''
206 text = self.text_box.get_text()
207 self.dialog.destroy()
208 threading.Thread(target = getattr(self.backend, self.callback),
209 args = ("set_text", text)).start()
210
0211
=== modified file 'GTG/gtk/browser/taskbrowser.glade'
--- GTG/gtk/browser/taskbrowser.glade 2010-05-22 22:41:44 +0000
+++ GTG/gtk/browser/taskbrowser.glade 2010-09-04 17:54:43 +0000
@@ -11,7 +11,6 @@
11 <child>11 <child>
12 <object class="GtkVBox" id="master_vbox">12 <object class="GtkVBox" id="master_vbox">
13 <property name="visible">True</property>13 <property name="visible">True</property>
14 <property name="orientation">vertical</property>
15 <child>14 <child>
16 <object class="GtkMenuBar" id="browser_menu">15 <object class="GtkMenuBar" id="browser_menu">
17 <property name="visible">True</property>16 <property name="visible">True</property>
@@ -154,6 +153,13 @@
154 <signal name="activate" handler="on_preferences_activate"/>153 <signal name="activate" handler="on_preferences_activate"/>
155 </object>154 </object>
156 </child>155 </child>
156 <child>
157 <object class="GtkMenuItem" id="backends_mi">
158 <property name="label">Backends</property>
159 <property name="visible">True</property>
160 <signal name="activate" handler="on_edit_backends_activate"/>
161 </object>
162 </child>
157 </object>163 </object>
158 </child>164 </child>
159 </object>165 </object>
@@ -253,6 +259,7 @@
253 <property name="label">gtk-help</property>259 <property name="label">gtk-help</property>
254 <property name="visible">True</property>260 <property name="visible">True</property>
255 <property name="tooltip_text" translatable="yes">Open GTG documentation in your web browser</property>261 <property name="tooltip_text" translatable="yes">Open GTG documentation in your web browser</property>
262 <property name="use_underline">True</property>
256 <property name="use_stock">True</property>263 <property name="use_stock">True</property>
257 <property name="accel_group">accelgroup1</property>264 <property name="accel_group">accelgroup1</property>
258 <signal name="activate" handler="on_documentation_clicked"/>265 <signal name="activate" handler="on_documentation_clicked"/>
@@ -279,146 +286,156 @@
279 </packing>286 </packing>
280 </child>287 </child>
281 <child>288 <child>
282 <object class="GtkToolbar" id="task_toolbar">289 <object class="GtkVBox" id="vbox_toolbars">
283 <property name="visible">True</property>290 <property name="visible">True</property>
284 <child>291 <child>
285 <object class="GtkToolButton" id="new_task_b">292 <object class="GtkToolbar" id="task_toolbar">
286 <property name="visible">True</property>293 <property name="visible">True</property>
287 <property name="is_important">True</property>294 <child>
288 <property name="label" translatable="yes">New Task</property>295 <object class="GtkToolButton" id="new_task_b">
289 <property name="icon_name">gtg-task-new</property>296 <property name="visible">True</property>
290 <signal name="clicked" handler="on_add_task"/>297 <property name="is_important">True</property>
291 </object>298 <property name="label" translatable="yes">New Task</property>
292 <packing>299 <property name="icon_name">gtg-task-new</property>
293 <property name="expand">False</property>300 <signal name="clicked" handler="on_add_task"/>
294 <property name="homogeneous">True</property>301 </object>
295 </packing>302 <packing>
296 </child>303 <property name="expand">False</property>
297 <child>304 <property name="homogeneous">True</property>
298 <object class="GtkToolButton" id="new_subtask_b">305 </packing>
299 <property name="label" translatable="yes">New Subtask</property>306 </child>
300 <property name="icon_name">gtg-task-new</property>307 <child>
301 <signal name="clicked" handler="on_add_subtask"/>308 <object class="GtkToolButton" id="new_subtask_b">
302 </object>309 <property name="label" translatable="yes">New Subtask</property>
303 <packing>310 <property name="icon_name">gtg-task-new</property>
304 <property name="expand">False</property>311 <signal name="clicked" handler="on_add_subtask"/>
305 <property name="homogeneous">True</property>312 </object>
306 </packing>313 <packing>
307 </child>314 <property name="expand">False</property>
308 <child>315 <property name="homogeneous">True</property>
309 <object class="GtkToolButton" id="edit_b">316 </packing>
310 <property name="visible">True</property>317 </child>
311 <property name="visible_horizontal">False</property>318 <child>
312 <property name="visible_vertical">False</property>319 <object class="GtkToolButton" id="edit_b">
313 <property name="label" translatable="yes">Edit</property>320 <property name="visible">True</property>
314 <property name="stock_id">gtk-edit</property>321 <property name="visible_horizontal">False</property>
315 <signal name="clicked" handler="on_edit_active_task"/>322 <property name="visible_vertical">False</property>
316 </object>323 <property name="label" translatable="yes">Edit</property>
317 <packing>324 <property name="stock_id">gtk-edit</property>
318 <property name="expand">False</property>325 <signal name="clicked" handler="on_edit_active_task"/>
319 <property name="homogeneous">True</property>326 </object>
320 </packing>327 <packing>
321 </child>328 <property name="expand">False</property>
322 <child>329 <property name="homogeneous">True</property>
323 <object class="GtkSeparatorToolItem" id="&lt;separateur&gt;"/>330 </packing>
324 <packing>331 </child>
325 <property name="expand">False</property>332 <child>
326 <property name="homogeneous">True</property>333 <object class="GtkSeparatorToolItem" id="&lt;separateur&gt;"/>
327 </packing>334 <packing>
328 </child>335 <property name="expand">False</property>
329 <child>336 <property name="homogeneous">True</property>
330 <object class="GtkToolButton" id="Undo">337 </packing>
331 <property name="label" translatable="yes">Undo</property>338 </child>
332 <property name="stock_id">gtk-undo</property>339 <child>
333 </object>340 <object class="GtkToolButton" id="Undo">
334 <packing>341 <property name="label" translatable="yes">Undo</property>
335 <property name="expand">False</property>342 <property name="stock_id">gtk-undo</property>
336 <property name="homogeneous">True</property>343 </object>
337 </packing>344 <packing>
338 </child>345 <property name="expand">False</property>
339 <child>346 <property name="homogeneous">True</property>
340 <object class="GtkToolButton" id="Redo">347 </packing>
341 <property name="label" translatable="yes">Redo</property>348 </child>
342 <property name="stock_id">gtk-redo</property>349 <child>
343 </object>350 <object class="GtkToolButton" id="Redo">
344 <packing>351 <property name="label" translatable="yes">Redo</property>
345 <property name="expand">False</property>352 <property name="stock_id">gtk-redo</property>
346 <property name="homogeneous">True</property>353 </object>
347 </packing>354 <packing>
348 </child>355 <property name="expand">False</property>
349 <child>356 <property name="homogeneous">True</property>
350 <object class="GtkSeparatorToolItem" id="separator_6">357 </packing>
351 <property name="visible">True</property>358 </child>
352 </object>359 <child>
353 <packing>360 <object class="GtkSeparatorToolItem" id="separator_6">
354 <property name="expand">False</property>361 <property name="visible">True</property>
355 <property name="homogeneous">True</property>362 </object>
356 </packing>363 <packing>
357 </child>364 <property name="expand">False</property>
358 <child>365 <property name="homogeneous">True</property>
359 <object class="GtkToolButton" id="done_b">366 </packing>
360 <property name="visible">True</property>367 </child>
361 <property name="sensitive">False</property>368 <child>
362 <property name="is_important">True</property>369 <object class="GtkToolButton" id="done_b">
363 <property name="label" translatable="yes">Mark as Done</property>370 <property name="visible">True</property>
364 <property name="icon_name">gtg-task-done</property>371 <property name="sensitive">False</property>
365 <signal name="clicked" handler="on_mark_as_done"/>372 <property name="is_important">True</property>
366 </object>373 <property name="label" translatable="yes">Mark as Done</property>
367 <packing>374 <property name="icon_name">gtg-task-done</property>
368 <property name="expand">False</property>375 <signal name="clicked" handler="on_mark_as_done"/>
369 <property name="homogeneous">True</property>376 </object>
370 </packing>377 <packing>
371 </child>378 <property name="expand">False</property>
372 <child>379 <property name="homogeneous">True</property>
373 <object class="GtkToolButton" id="dismiss_b">380 </packing>
374 <property name="visible">True</property>381 </child>
375 <property name="sensitive">False</property>382 <child>
376 <property name="label" translatable="yes">Dismiss</property>383 <object class="GtkToolButton" id="dismiss_b">
377 <property name="icon_name">gtg-task-dismiss</property>384 <property name="visible">True</property>
378 <signal name="clicked" handler="on_dismiss_task"/>385 <property name="sensitive">False</property>
379 </object>386 <property name="label" translatable="yes">Dismiss</property>
380 <packing>387 <property name="icon_name">gtg-task-dismiss</property>
381 <property name="expand">False</property>388 <signal name="clicked" handler="on_dismiss_task"/>
382 <property name="homogeneous">True</property>389 </object>
383 </packing>390 <packing>
384 </child>391 <property name="expand">False</property>
385 <child>392 <property name="homogeneous">True</property>
386 <object class="GtkToolButton" id="delete_b">393 </packing>
387 <property name="sensitive">False</property>394 </child>
388 <property name="label" translatable="yes">Delete</property>395 <child>
389 <property name="icon_name">edit-delete</property>396 <object class="GtkToolButton" id="delete_b">
390 <signal name="clicked" handler="on_delete_task"/>397 <property name="sensitive">False</property>
391 </object>398 <property name="label" translatable="yes">Delete</property>
392 <packing>399 <property name="icon_name">edit-delete</property>
393 <property name="expand">False</property>400 <signal name="clicked" handler="on_delete_task"/>
394 <property name="homogeneous">True</property>401 </object>
395 </packing>402 <packing>
396 </child>403 <property name="expand">False</property>
397 <child>404 <property name="homogeneous">True</property>
398 <object class="GtkSeparatorToolItem" id="separator_7">405 </packing>
399 <property name="visible">True</property>406 </child>
400 </object>407 <child>
401 <packing>408 <object class="GtkSeparatorToolItem" id="separator_7">
402 <property name="expand">False</property>409 <property name="visible">True</property>
403 <property name="homogeneous">True</property>410 </object>
404 </packing>411 <packing>
405 </child>412 <property name="expand">False</property>
406 <child>413 <property name="homogeneous">True</property>
407 <object class="GtkToggleToolButton" id="workview_toggle">414 </packing>
408 <property name="visible">True</property>415 </child>
409 <property name="is_important">True</property>416 <child>
410 <property name="label" translatable="yes">Work View</property>417 <object class="GtkToggleToolButton" id="workview_toggle">
411 <property name="stock_id">gtk-index</property>418 <property name="visible">True</property>
412 <signal name="toggled" handler="on_workview_toggled"/>419 <property name="is_important">True</property>
413 </object>420 <property name="label" translatable="yes">Work View</property>
414 <packing>421 <property name="stock_id">gtk-index</property>
415 <property name="expand">False</property>422 <signal name="toggled" handler="on_workview_toggled"/>
416 <property name="homogeneous">True</property>423 </object>
424 <packing>
425 <property name="expand">False</property>
426 <property name="homogeneous">True</property>
427 </packing>
428 </child>
429 </object>
430 <packing>
431 <property name="expand">False</property>
432 <property name="position">0</property>
417 </packing>433 </packing>
418 </child>434 </child>
419 </object>435 </object>
420 <packing>436 <packing>
421 <property name="expand">False</property>437 <property name="expand">False</property>
438 <property name="fill">False</property>
422 <property name="position">1</property>439 <property name="position">1</property>
423 </packing>440 </packing>
424 </child>441 </child>
@@ -429,7 +446,6 @@
429 <child>446 <child>
430 <object class="GtkVBox" id="sidebar_vbox">447 <object class="GtkVBox" id="sidebar_vbox">
431 <property name="width_request">75</property>448 <property name="width_request">75</property>
432 <property name="orientation">vertical</property>
433 <child>449 <child>
434 <object class="GtkHBox" id="hbox4">450 <object class="GtkHBox" id="hbox4">
435 <property name="visible">True</property>451 <property name="visible">True</property>
@@ -484,9 +500,9 @@
484 <object class="GtkNotebook" id="sidebar_notebook">500 <object class="GtkNotebook" id="sidebar_notebook">
485 <property name="visible">True</property>501 <property name="visible">True</property>
486 <property name="can_focus">True</property>502 <property name="can_focus">True</property>
503 <property name="tab_pos">bottom</property>
504 <property name="show_tabs">False</property>
487 <property name="group_id">1</property>505 <property name="group_id">1</property>
488 <property name="show_tabs">False</property>
489 <property name="tab_pos">bottom</property>
490 <child>506 <child>
491 <object class="GtkScrolledWindow" id="sidebar-scroll">507 <object class="GtkScrolledWindow" id="sidebar-scroll">
492 <property name="visible">True</property>508 <property name="visible">True</property>
@@ -507,7 +523,6 @@
507 <packing>523 <packing>
508 <property name="tab_fill">False</property>524 <property name="tab_fill">False</property>
509 </packing>525 </packing>
510
511 </child>526 </child>
512 </object>527 </object>
513 <packing>528 <packing>
@@ -523,7 +538,6 @@
523 <child>538 <child>
524 <object class="GtkVBox" id="main_vbox">539 <object class="GtkVBox" id="main_vbox">
525 <property name="visible">True</property>540 <property name="visible">True</property>
526 <property name="orientation">vertical</property>
527 <child>541 <child>
528 <object class="GtkHBox" id="quickadd_pane">542 <object class="GtkHBox" id="quickadd_pane">
529 <property name="visible">True</property>543 <property name="visible">True</property>
@@ -567,14 +581,13 @@
567 <object class="GtkVPaned" id="vpaned1">581 <object class="GtkVPaned" id="vpaned1">
568 <property name="visible">True</property>582 <property name="visible">True</property>
569 <property name="can_focus">True</property>583 <property name="can_focus">True</property>
570 <property name="orientation">vertical</property>
571 <child>584 <child>
572 <object class="GtkNotebook" id="main_notebook">585 <object class="GtkNotebook" id="main_notebook">
573 <property name="visible">True</property>586 <property name="visible">True</property>
574 <property name="can_focus">True</property>587 <property name="can_focus">True</property>
588 <property name="tab_pos">bottom</property>
589 <property name="show_tabs">False</property>
575 <property name="group_id">2</property>590 <property name="group_id">2</property>
576 <property name="show_tabs">False</property>
577 <property name="tab_pos">bottom</property>
578 <child>591 <child>
579 <object class="GtkScrolledWindow" id="main_pane">592 <object class="GtkScrolledWindow" id="main_pane">
580 <property name="visible">True</property>593 <property name="visible">True</property>
@@ -603,11 +616,10 @@
603 </child>616 </child>
604 <child>617 <child>
605 <object class="GtkNotebook" id="accessory_notebook">618 <object class="GtkNotebook" id="accessory_notebook">
606 <property name="visible">False</property>
607 <property name="can_focus">True</property>619 <property name="can_focus">True</property>
620 <property name="tab_pos">bottom</property>
621 <property name="show_tabs">False</property>
608 <property name="group_id">2</property>622 <property name="group_id">2</property>
609 <property name="show_tabs">False</property>
610 <property name="tab_pos">bottom</property>
611 <child>623 <child>
612 <placeholder/>624 <placeholder/>
613 </child>625 </child>
@@ -695,7 +707,6 @@
695 <child internal-child="vbox">707 <child internal-child="vbox">
696 <object class="GtkVBox" id="about_dialog_vbox">708 <object class="GtkVBox" id="about_dialog_vbox">
697 <property name="visible">True</property>709 <property name="visible">True</property>
698 <property name="orientation">vertical</property>
699 <property name="spacing">2</property>710 <property name="spacing">2</property>
700 <child internal-child="action_area">711 <child internal-child="action_area">
701 <object class="GtkHButtonBox" id="dialog-action_area2">712 <object class="GtkHButtonBox" id="dialog-action_area2">
@@ -959,7 +970,6 @@
959 <child internal-child="vbox">970 <child internal-child="vbox">
960 <object class="GtkVBox" id="addtag_dialog_vbox">971 <object class="GtkVBox" id="addtag_dialog_vbox">
961 <property name="visible">True</property>972 <property name="visible">True</property>
962 <property name="orientation">vertical</property>
963 <property name="spacing">2</property>973 <property name="spacing">2</property>
964 <child>974 <child>
965 <object class="GtkLabel" id="addtag_label">975 <object class="GtkLabel" id="addtag_label">
@@ -1062,4 +1072,8 @@
1062 <property name="pixel_size">16</property>1072 <property name="pixel_size">16</property>
1063 <property name="icon_name">gtg-tag</property>1073 <property name="icon_name">gtg-tag</property>
1064 </object>1074 </object>
1075 <object class="GtkImage" id="image4">
1076 <property name="visible">True</property>
1077 <property name="stock">gtk-home</property>
1078 </object>
1065</interface>1079</interface>
10661080
=== modified file 'GTG/gtk/colors.py'
--- GTG/gtk/colors.py 2010-06-07 21:14:45 +0000
+++ GTG/gtk/colors.py 2010-09-04 17:54:43 +0000
@@ -20,7 +20,7 @@
2020
21#Take list of Tags and give the background color that should be applied21#Take list of Tags and give the background color that should be applied
22#The returned color might be None (in which case, the default is used)22#The returned color might be None (in which case, the default is used)
23def background_color(tags, bgcolor=None):23def background_color(tags, bgcolor = None):
24 if not bgcolor:24 if not bgcolor:
25 bgcolor = gtk.gdk.color_parse("#FFFFFF")25 bgcolor = gtk.gdk.color_parse("#FFFFFF")
26 # Compute color26 # Compute color
@@ -52,3 +52,29 @@
52 my_color = gtk.gdk.Color(red, green, blue).to_string()52 my_color = gtk.gdk.Color(red, green, blue).to_string()
53 return my_color53 return my_color
5454
55def get_colored_tag_markup(req, tag_name):
56 '''
57 Given a tag name, returns a string containing the markup to color the
58 tag name
59 '''
60 tag = req.get_tag(tag_name)
61 if tag is None:
62 #no task loaded with that tag, color cannot be taken
63 return tag_name
64 else:
65 tag_color = tag.get_attribute("color")
66 if tag_color:
67 return '<span color="%s">%s</span>' % (tag_color, tag_name)
68 else:
69 return tag_name
70
71def get_colored_tags_markup(req, tag_names):
72 '''
73 Calls get_colored_tag_markup for each tag_name in tag_names
74 '''
75 tag_markups = map(lambda t: get_colored_tag_markup(req, t), tag_names)
76 tags_txt = ""
77 if tag_markups:
78 #reduce crashes if applied to an empty list
79 tags_txt = reduce(lambda a, b: a + ", " + b, tag_markups)
80 return tags_txt
5581
=== modified file 'GTG/gtk/manager.py'
--- GTG/gtk/manager.py 2010-08-03 17:07:31 +0000
+++ GTG/gtk/manager.py 2010-09-04 17:54:43 +0000
@@ -39,10 +39,11 @@
39from GTG.core.plugins.engine import PluginEngine39from GTG.core.plugins.engine import PluginEngine
40from GTG.core.plugins.api import PluginAPI40from GTG.core.plugins.api import PluginAPI
41from GTG.tools.logger import Log41from GTG.tools.logger import Log
4242from GTG.gtk.backends_dialog import BackendsDialog
4343
4444
45class Manager:45
46class Manager(object):
46 47
4748
48 ############## init #####################################################49 ############## init #####################################################
@@ -80,6 +81,7 @@
80 #Preferences and Backends windows81 #Preferences and Backends windows
81 # Initialize dialogs82 # Initialize dialogs
82 self.preferences_dialog = None83 self.preferences_dialog = None
84 self.edit_backends_dialog = None
83 85
84 #DBus86 #DBus
85 DBusTaskWrapper(self.req, self)87 DBusTaskWrapper(self.req, self)
@@ -196,6 +198,16 @@
196 198
197################ Others dialog ############################################199################ Others dialog ############################################
198200
201 def open_edit_backends(self, sender = None, backend_id = None):
202 if not self.edit_backends_dialog:
203 self.edit_backends_dialog = BackendsDialog(self.req)
204 self.edit_backends_dialog.activate()
205 if backend_id != None:
206 self.edit_backends_dialog.show_config_for_backend(backend_id)
207
208 def configure_backend(self, backend_id):
209 self.open_edit_backends(None, backend_id)
210
199 def open_preferences(self, config_priv, sender=None):211 def open_preferences(self, config_priv, sender=None):
200 if not hasattr(self, "preferences"):212 if not hasattr(self, "preferences"):
201 self.preferences = PreferencesDialog(self.pengine, self.p_apis, \213 self.preferences = PreferencesDialog(self.pengine, self.p_apis, \
@@ -211,7 +223,8 @@
211 self.close_task(t)223 self.close_task(t)
212 224
213### MAIN ###################################################################225### MAIN ###################################################################
214 def main(self, once_thru=False):226
227 def main(self, once_thru = False):
215 gobject.threads_init()228 gobject.threads_init()
216 if once_thru:229 if once_thru:
217 gtk.main_iteration()230 gtk.main_iteration()
@@ -219,7 +232,6 @@
219 gtk.main()232 gtk.main()
220 return 0233 return 0
221 234
222
223 def quit(self,sender=None):235 def quit(self,sender=None):
224 gtk.main_quit()236 gtk.main_quit()
225 #save opened tasks and their positions.237 #save opened tasks and their positions.
226238
=== added file 'GTG/tests/test_interruptible.py'
--- GTG/tests/test_interruptible.py 1970-01-01 00:00:00 +0000
+++ GTG/tests/test_interruptible.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,69 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Gettings Things Gnome! - a personal organizer for the GNOME desktop
4# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5#
6# This program is free software: you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------
19
20'''
21Tests for interrupting cooperative threads
22'''
23
24import unittest
25import time
26from threading import Thread, Event
27
28from GTG.tools.interruptible import interruptible, _cancellation_point
29
30
31class TestInterruptible(unittest.TestCase):
32 '''
33 Tests for interrupting cooperative threads
34 '''
35
36 def test_interruptible_decorator(self):
37 self.quit_condition = False
38 cancellation_point = lambda: _cancellation_point(\
39 lambda: self.quit_condition)
40 self.thread_started = Event()
41 @interruptible
42 def never_ending(cancellation_point):
43 self.thread_started.set()
44 while True:
45 time.sleep(0.1)
46 cancellation_point()
47 thread = Thread(target = never_ending, args = (cancellation_point, ))
48 thread.start()
49 self.thread_started.wait()
50 self.quit_condition = True
51 countdown = 10
52 while thread.is_alive() and countdown > 0:
53 time.sleep(0.1)
54 countdown -= 1
55 self.assertFalse(thread.is_alive())
56
57
58
59
60
61
62
63
64
65
66
67def test_suite():
68 return unittest.TestLoader().loadTestsFromTestCase(TestInterruptible)
69
070
=== added file 'GTG/tools/networkmanager.py'
--- GTG/tools/networkmanager.py 1970-01-01 00:00:00 +0000
+++ GTG/tools/networkmanager.py 2010-09-04 17:54:43 +0000
@@ -0,0 +1,57 @@
1#!/bin/env python
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16#
17# Copyright (C) 2010 Red Hat, Inc.
18#
19
20import dbus
21
22
23def is_connection_up():
24 '''
25 Returns True if network-manager reports that at least one connection is up
26
27 @returns bool
28 '''
29 state = False
30 bus = dbus.SystemBus()
31
32 proxy = bus.get_object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
33 manager = dbus.Interface(proxy, "org.freedesktop.NetworkManager")
34
35 manager_prop_iface = dbus.Interface(proxy, "org.freedesktop.DBus.Properties")
36 active = manager_prop_iface.Get("org.freedesktop.NetworkManager", "ActiveConnections")
37 for a in active:
38 ac_proxy = bus.get_object("org.freedesktop.NetworkManager", a)
39 prop_iface = dbus.Interface(ac_proxy, "org.freedesktop.DBus.Properties")
40 state = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "State")
41
42 # Connections in NM are a collection of settings that describe everything
43 # needed to connect to a specific network. Lets get those details so we
44 # can find the user-readable name of the connection.
45 con_path = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "Connection")
46 con_service = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "ServiceName")
47
48 # ask the provider of the connection for its details
49 service_proxy = bus.get_object(con_service, con_path)
50 con_iface = dbus.Interface(service_proxy, "org.freedesktop.NetworkManagerSettings.Connection")
51 con_details = con_iface.GetSettings()
52 con_name = con_details['connection']['id']
53
54 if state == 2: # activated
55 state = True
56 return state
57
058
=== added file 'data/icons/hicolor/scalable/apps/backend_localfile.png'
1Binary 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 differ59Binary 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: