GTG

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

Proposed by Luca Invernizzi
Status: Superseded
Proposed branch: lp:~gtg-user/gtg/backends-window
Merge into: lp:~gtg/gtg/old-trunk
Diff against target: 3065 lines (+2530/-139)
24 files modified
CHANGELOG (+1/-0)
GTG/backends/backendsignals.py (+0/-127)
GTG/core/requester.py (+3/-0)
GTG/gtk/__init__.py (+3/-1)
GTG/gtk/backends_dialog.glade (+166/-0)
GTG/gtk/backends_dialog/__init__.py (+294/-0)
GTG/gtk/backends_dialog/addpanel.py (+214/-0)
GTG/gtk/backends_dialog/backendscombo.py (+92/-0)
GTG/gtk/backends_dialog/backendstree.py (+252/-0)
GTG/gtk/backends_dialog/configurepanel.py (+298/-0)
GTG/gtk/backends_dialog/parameters_ui/__init__.py (+149/-0)
GTG/gtk/backends_dialog/parameters_ui/checkboxui.py (+72/-0)
GTG/gtk/backends_dialog/parameters_ui/importtagsui.py (+131/-0)
GTG/gtk/backends_dialog/parameters_ui/passwordui.py (+84/-0)
GTG/gtk/backends_dialog/parameters_ui/pathui.py (+111/-0)
GTG/gtk/backends_dialog/parameters_ui/periodui.py (+88/-0)
GTG/gtk/backends_dialog/parameters_ui/textui.py (+77/-0)
GTG/gtk/browser/browser.py (+103/-4)
GTG/gtk/browser/custominfobar.py (+210/-0)
GTG/gtk/browser/taskbrowser.glade (+11/-0)
GTG/gtk/colors.py (+27/-1)
GTG/gtk/manager.py (+18/-6)
GTG/tests/test_interruptible.py (+69/-0)
GTG/tools/networkmanager.py (+57/-0)
To merge this branch: bzr merge lp:~gtg-user/gtg/backends-window
Reviewer Review Type Date Requested Status
Gtg developers Pending
Review via email: mp+32005@code.launchpad.net

This proposal has been superseded by a proposal from 2010-08-13.

Description of the change

This merge contains all the code relative to the window used to add, remove and edit backends.
All the functions should be documented.

To post a comment you must log in.
lp:~gtg-user/gtg/backends-window updated
868. By Luca Invernizzi

Small fix

869. By Luca Invernizzi

a

870. By Luca Invernizzi

Uri support will be given in a different merge request

871. By Luca Invernizzi

merge with trunk

872. By Luca Invernizzi

merge with trunk

873. By Luca Invernizzi

updated changelog

874. By Luca Invernizzi

merge with trunk

875. By Luca Invernizzi

small fix for marko kevec comment

876. By Luca Invernizzi

small fix in the backends tree

877. By Luca Invernizzi

cherrypicking from my development branch

878. By Luca Invernizzi

cherrypicking from my development branch

879. By Luca Invernizzi

merge w/ trunk

880. By Luca Invernizzi

Workaround for arch linux, as for bug #lp 624204

881. By Luca Invernizzi

Bugfix for bug lp #624298 by Andrew Starr-Bochicchio
New backends window should have close button not quit

882. By Luca Invernizzi

cherrypicking from my development branch

883. By Luca Invernizzi

cherrypicking from my development branch

884. By Luca Invernizzi

cherrypicking from my development branch

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CHANGELOG'
--- CHANGELOG 2010-08-04 00:30:22 +0000
+++ CHANGELOG 2010-08-13 23:43:07 +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-08-13 23:43:07 +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-08-13 23:43:07 +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-08-13 23:43:07 +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-08-13 23:43:07 +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="quit_button">
136 <property name="label">gtk-quit</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_quit_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-08-13 23:43:07 +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_quit_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-08-13 23:43:07 +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-08-13 23:43:07 +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-08-13 23:43:07 +0000
@@ -0,0 +1,252 @@
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 if backend_id in self.backendid_to_iter:
229 backend_iter = self.backendid_to_iter[backend_id]
230 selection = self.get_selection()
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 self.get_selection().select_path("0")
240 self.dialog.on_backend_selected(self.get_selected_backend_id())
241
242 def get_selected_backend_id(self):
243 '''
244 returns the selected backend id, or none
245
246 @return string: the selected backend id (or None)
247 '''
248 selected_path = self._get_selected_path()
249 if not selected_path:
250 return None
251 selected_iter = self.liststore.get_iter(selected_path)
252 return self.liststore.get_value(selected_iter, self.COLUMN_BACKEND_ID)
0253
=== 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-08-13 23:43:07 +0000
@@ -0,0 +1,298 @@
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 self.spinner = gtk.Spinner()
93 self.spinner.set_size_request(32, 32)
94 self.spinner.connect("show", self.on_spinner_show)
95 align_spin = gtk.Alignment(xalign = 1, yalign = 0)
96 align_spin.add(self.spinner)
97 hbox_top.pack_start(self.human_name_label, True)
98 hbox_top.pack_start(align_spin, False)
99 self.sync_desc_label = gtk.Label()
100 self.sync_desc_label.set_alignment(xalign = 0, yalign = 1)
101 self.sync_desc_label.set_line_wrap(True)
102 vbox.pack_start(hbox_top, True)
103 vbox.pack_start(self.sync_desc_label, True)
104 hbox.pack_start(self.image_icon, False)
105 align_vbox = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1)
106 align_vbox.set_padding(10, 0, 20, 0)
107 align_vbox.add(vbox)
108 hbox.pack_start(align_vbox, True)
109
110 def _fill_middle_hbox(self, hbox):
111 '''
112 Helper function to fill an hbox with a label and a button
113
114 @param hbox: the gtk.HBox to fill
115 '''
116 self.sync_status_label = gtk.Label()
117 self.sync_status_label.set_alignment(xalign = 0.8, yalign = 0.5)
118 self.sync_button = gtk.Button()
119 self.sync_button.connect("clicked", self.on_sync_button_clicked)
120 hbox.pack_start(self.sync_status_label, True)
121 hbox.pack_start(self.sync_button, True)
122
123 def set_backend(self, backend_id):
124 '''Changes the backend to configure, refreshing this view.
125
126 @param backend_id: the id of the backend to configure
127 '''
128 self.backend = self.dialog.get_requester().get_backend(backend_id)
129 self.refresh_title()
130 self.refresh_sync_status()
131 self.parameters_ui.refresh(self.backend)
132 self.image_icon.set_from_pixbuf(self.dialog.get_pixbuf_from_icon_name(\
133 self.backend.get_name(), 80, 80))
134
135 def refresh_title(self, sender = None, data = None):
136 '''
137 Callback for the signal that notifies backends name changes. It changes
138 the title of this view
139
140 @param sender: not used, here only for signal callback compatibility
141 @param data: not used, here only for signal callback compatibility
142 '''
143 markup = "<big><big><big><b>%s</b></big></big></big>" % \
144 self.backend.get_human_name()
145 self.human_name_label.set_markup(markup)
146
147 def refresh_number_of_tasks(self):
148 '''refreshes the number of synced tasks by this backend'''
149 #FIXME: disabled for now. I'm not sure that this is nice because the
150 # count is correct only after the backend has synced all the pending
151 # tasks, and this is quite misleading (invernizzi)
152 return
153 #This will have to be changed for import/export..
154 tags = self.backend.get_attached_tags()
155 tasks_number = self.backend.get_number_of_tasks()
156 if GenericBackend.ALLTASKS_TAG in tags:
157 if tasks_number == 0:
158 markup = _("Ready to start syncing")
159 else:
160 markup = ngettext("Syncing your only task", \
161 "Syncing all %d tasks" % tasks_number, tasks_number)
162 else:
163 tags_txt = get_colored_tags_markup(self.req, tags)
164 if tasks_number == 0:
165 markup = _("There's no task tagged %s") % tags_txt
166 else:
167 markup = ngettext("Syncing a task tagged %s" % tags_txt, \
168 "Syncing %d tasks tagged %s" % (tasks_number, tags_txt), \
169 tasks_number)
170 self.sync_desc_label.set_markup(markup)
171
172 def refresh_sync_button(self):
173 '''
174 Refreshes the state of the button that enables the backend
175 '''
176 self.sync_button.set_sensitive(not self.backend.is_default())
177 if self.backend.is_enabled():
178 label = _("Disable syncing")
179 else:
180 label = _("Enable syncing")
181 self.sync_button.set_label(label)
182
183 def refresh_sync_status_label(self):
184 '''
185 Refreshes the gtk.Label that shows the current state of this backend
186 '''
187 if self.backend.is_default():
188 label = _("This is the default backend")
189 else:
190 if self.backend.is_enabled():
191 label = _("Syncing is enabled")
192 else:
193 label = _('Syncing is <span color="red">disabled</span>')
194 self.sync_status_label.set_markup(label)
195
196 def refresh_sync_status(self, sender = False, data = False):
197 '''Signal callback function, called when a backend state
198 (enabled/disabled) changes. Refreshes this view.
199
200 @param sender: not used, here only for signal callback compatibility
201 @param data: not used, here only for signal callback compatibility
202 '''
203 self.refresh_number_of_tasks()
204 self.refresh_sync_button()
205 self.refresh_sync_status_label()
206
207 def set_hidden(self, is_hidden):
208 '''
209 Notifies this pane if it's hidden or not. We disconnect signals when
210 hidden, since there is no need to keep the UI updated.
211 Hopefully, this should make GTG faster :)
212
213 @param is_hidden: boolean, True if the window is not visible
214 '''
215 #These is only needed to refresh the number of synced tasks.
216 #since that is disabled for now, there is no need for this
217
218# if is_hidden:
219# if self.task_added_handle:
220# self.req.disconnect(self.task_added_handle)
221# self.task_added_handle = None
222# if self.task_deleted_handle:
223# self.req.disconnect(self.task_deleted_handle)
224# self.task_deleted_handle = None
225# else:
226# self.task_added_handle = self.req.connect("task-added", \
227# self.__on_task_changed)
228# self.task_added_handle = self.req.connect("task-modified", \
229# self.__on_task_changed)
230# self.task_deleted_handle = self.req.connect("task-deleted", \
231# self.__on_task_changed)
232#
233# def __on_task_changed(self, sender, task_id):
234# '''
235# If tasks are added, modified or removed, updates the number of
236# tasks of the current backend
237# '''
238# self.refresh_sync_status()
239
240 def on_sync_button_clicked(self, sender):
241 '''
242 Signal callback when a backend is enabled/disabled via the UI button
243
244 @param sender: not used, here only for signal callback compatibility
245 '''
246 self.parameters_ui.commit_changes()
247 self.req.set_backend_enabled(self.backend.get_id(), \
248 not self.backend.is_enabled())
249
250 def on_sync_started(self, sender, backend_id):
251 '''
252 If the backend has started syncing tasks, update the state of the
253 gtk.Spinner
254
255 @param sender: not used, here only for signal callback compatibility
256 @param backend_id: the id of the backend that emitted this signal
257 '''
258 if backend_id == self.backend.get_id():
259 self.spinner_set_active(True)
260
261 def on_sync_ended(self, sender, backend_id):
262 '''
263 If the backend has stopped syncing tasks, update the state of the
264 gtk.Spinner
265
266 @param sender: not used, here only for signal callback compatibility
267 @param backend_id: the id of the backend that emitted this signal
268 '''
269
270 if backend_id == self.backend.get_id():
271 self.spinner_set_active(False)
272
273 def on_spinner_show(self, sender):
274 '''This signal callback hides the spinner if it's not supposed to be
275 seen. It's a workaround to let us call show_all on the whole window
276 while keeping this hidden (it's the only widget that requires special
277 attention)
278
279 @param sender: not used, here only for signal callback compatibility
280 '''
281 if self.should_spinner_be_shown == False:
282 self.spinner.hide()
283
284 def spinner_set_active(self, active):
285 '''
286 Enables/disables the gtk.Spinner, while showing/hiding it at the same
287 time
288
289 @param active: True if the spinner should spin
290 '''
291 self.should_spinner_be_shown = active
292 if active:
293 self.spinner.start()
294 self.spinner.show()
295 else:
296 self.spinner.hide()
297 self.spinner.stop()
298
0299
=== 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-08-13 23:43:07 +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
27#FIXME: all the parameters have one function in common (2 lines total).
28# Evaluate if there is a clean way to avoid duplication of this code,
29# without becoming too difficult to understand.
30# (invernizzi)
31
32import gtk
33import functools
34
35from GTG import _
36from GTG.backends.genericbackend import GenericBackend
37from GTG.gtk.backends_dialog.parameters_ui.importtagsui import ImportTagsUI
38from GTG.gtk.backends_dialog.parameters_ui.textui import TextUI
39from GTG.gtk.backends_dialog.parameters_ui.passwordui import PasswordUI
40from GTG.gtk.backends_dialog.parameters_ui.periodui import PeriodUI
41from GTG.gtk.backends_dialog.parameters_ui.checkboxui import CheckBoxUI
42from GTG.gtk.backends_dialog.parameters_ui.pathui import PathUI
43
44
45
46class ParametersUI(gtk.VBox):
47 '''
48 Given a bakcend, this gtk.VBox populates itself with all the necessary
49 widgets to view and edit a backend configuration
50 '''
51
52
53 COMMON_WIDTH = 150
54
55 def __init__(self, requester):
56 '''Constructs the list of the possible widgets.
57
58 @param requester: a GTG.core.requester.Requester object
59 '''
60 super(ParametersUI, self).__init__(False)
61 self.req = requester
62 self.set_spacing(10)
63
64 #builds a list of widget generators. More precisely, it's a
65 # list of tuples: (backend_parameter_name, widget_generator)
66 self.parameter_widgets = ( \
67 ("import-tags", self.UI_generator(ImportTagsUI, \
68 {"title": _("Import tags"), \
69 "anybox_text": _("All tags"), \
70 "somebox_text": _("Just these tags"), \
71 "parameter_name": "import-tags"}) \
72 ),\
73 ("attached-tags", self.UI_generator(ImportTagsUI, \
74 {"title": _("Tags to sync"), \
75 "anybox_text": _("All tasks"), \
76 "somebox_text": _("Tasks with these tags"), \
77 "parameter_name": "attached-tags"}) \
78 ),\
79 ("path", self.UI_generator(PathUI)), \
80 ("username", self.UI_generator(TextUI, \
81 {"description": _("Username"),
82 "parameter_name": "username"})
83 ), \
84 ("password" , self.UI_generator(PasswordUI)), \
85 ("period" , self.UI_generator(PeriodUI)), \
86 ("import-from-replies", self.UI_generator(CheckBoxUI, \
87 {"text": _("Import tasks from @ replies " + \
88 "directed to you"), \
89 "parameter": "import-from-replies"}) \
90 ),\
91 ("import-from-direct-messages", self.UI_generator(CheckBoxUI, \
92 {"text": _("Import tasks from direct messages"), \
93 "parameter": "import-from-direct-messages"}) \
94 ),\
95 ("import-from-my-tweets", self.UI_generator(CheckBoxUI, \
96 {"text": _("Import tasks from your tweets"), \
97 "parameter": "import-from-my-tweets"}) \
98 ),\
99 ("import-bug-tags", self.UI_generator(CheckBoxUI, \
100 {"text": _("Tag your tasks with the bug tags"), \
101 "parameter": "import-bug-tags"}) \
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-08-13 23:43:07 +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-08-13 23:43:07 +0000
@@ -0,0 +1,131 @@
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 tags = filter(lambda t: t, tags)
104 self.backend.set_parameter(self.parameter_name, tags)
105
106 def _refresh_textbox_state(self):
107 '''Refreshes the content of the textbox'''
108 self.tags_entry.set_sensitive(self.some_tags_radio.get_active())
109
110 def _refresh_tags(self):
111 '''
112 Refreshes the list of tags to display in the textbox, and selects
113 the correct radio button
114 '''
115 tags_list = self.backend.get_parameters()[self.parameter_name]
116 has_all_tasks = GenericBackend.ALLTASKS_TAG in tags_list
117 self.all_tags_radio.set_active(has_all_tasks)
118 self.some_tags_radio.set_active(not has_all_tasks)
119 self._refresh_textbox_state()
120 if not has_all_tasks:
121 tags_text = ""
122 if tags_list:
123 tags_text = reduce(lambda a, b: a + ", " + b, tags_list)
124 self.tags_entry.set_text(tags_text)
125
126 def _connect_signals(self):
127 '''Connects the gtk signals'''
128 self.some_tags_radio.connect("toggled", self.on_changed)
129 self.all_tags_radio.connect("toggled", self.on_changed)
130 self.tags_entry.connect("changed", self.on_changed)
131
0132
=== 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-08-13 23:43:07 +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-08-13 23:43:07 +0000
@@ -0,0 +1,111 @@
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_alignment(xalign = 0, yalign = 0.5)
54 label.set_size_request(width = width, height = -1)
55 self.pack_start(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, True)
59 self.textbox = gtk.Entry()
60 self.textbox.set_text(self.backend.get_parameters()['path'])
61 self.textbox.connect('changed', self.on_path_modified)
62 align.add(self.textbox)
63 self.button = gtk.Button(stock = gtk.STOCK_EDIT)
64 self.button.connect('clicked', self.on_button_clicked)
65 self.pack_start(self.button, False)
66
67 def commit_changes(self):
68 '''Saves the changes to the backend parameter'''
69 self.backend.set_parameter('path', self.textbox.get_text())
70
71 def on_path_modified(self, sender):
72 ''' Signal callback, executed when the user edits the path.
73 Disables the backend. The user will re-enable it to confirm the changes
74 (s)he made.
75
76 @param sender: not used, only here for signal compatibility
77 '''
78 if self.backend.is_enabled() and not self.backend.is_default():
79 self.req.set_backend_enabled(self.backend.get_id(), False)
80
81 def on_button_clicked(self, sender):
82 '''Shows the filesystem explorer to choose a new file
83
84 @param sender: not used, only here for signal compatibility
85 '''
86 self.chooser = gtk.FileChooserDialog( \
87 title=None,
88 action=gtk.FILE_CHOOSER_ACTION_SAVE,
89 buttons=(gtk.STOCK_CANCEL,
90 gtk.RESPONSE_CANCEL, \
91 gtk.STOCK_OK, \
92 gtk.RESPONSE_OK))
93 self.chooser.set_default_response(gtk.RESPONSE_OK)
94 #set default file as the current self.path
95 self.chooser.set_current_name(os.path.basename(self.textbox.get_text()))
96 self.chooser.set_current_folder(os.path.dirname(self.textbox.get_text()))
97
98 #filter files
99 afilter = gtk.FileFilter()
100 afilter.set_name("All files")
101 afilter.add_pattern("*")
102 self.chooser.add_filter(afilter)
103 afilter = gtk.FileFilter()
104 afilter.set_name("XML files")
105 afilter.add_mime_type("text/plain")
106 afilter.add_pattern("*.xml")
107 self.chooser.add_filter(afilter)
108 response = self.chooser.run()
109 if response == gtk.RESPONSE_OK:
110 self.textbox.set_text(self.chooser.get_filename())
111 self.chooser.destroy()
0112
=== 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-08-13 23:43:07 +0000
@@ -0,0 +1,88 @@
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 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(_("Period:"))
52 period_label.set_alignment(xalign = 0, yalign = 0.5)
53 period_label.set_size_request(width = width, height = -1)
54 self.pack_start(period_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, False)
58 period = self.backend.get_parameters()['period']
59 self.adjustment = gtk.Adjustment(value = period,
60 lower = 1,
61 upper = 120,
62 step_incr = 1,
63 page_incr = 0,
64 page_size = 0)
65 self.period_spin = gtk.SpinButton(adjustment = self.adjustment,
66 climb_rate = 0.3,
67 digits = 0)
68 align.add(self.period_spin)
69 self.show_all()
70
71 def _connect_signals(self):
72 '''Connects the gtk signals'''
73 self.period_spin.connect('changed', self.on_spin_changed)
74
75 def commit_changes(self):
76 '''Saves the changes to the backend parameter'''
77 self.backend.set_parameter('period', int(self.adjustment.get_value()))
78
79 def on_spin_changed(self, sender):
80 ''' Signal callback, executed when the user changes the period.
81 Disables the backend. The user will re-enable it to confirm the changes
82 (s)he made.
83
84 @param sender: not used, only here for signal compatibility
85 '''
86 if self.backend.is_enabled() and not self.backend.is_default():
87 self.req.set_backend_enabled(self.backend.get_id(), False)
88
089
=== 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-08-13 23:43:07 +0000
@@ -0,0 +1,77 @@
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_alignment(xalign = 0, yalign = 0.5)
52 label.set_size_request(width = width, height = -1)
53 self.pack_start(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.textbox = gtk.Entry()
58 self.textbox.set_text(\
59 self.backend.get_parameters()[self.parameter_name])
60 self.textbox.connect('changed', self.on_text_modified)
61 align.add(self.textbox)
62
63 def commit_changes(self):
64 '''Saves the changes to the backend parameter'''
65 self.backend.set_parameter(self.parameter_name,\
66 self.textbox.get_text())
67
68 def on_text_modified(self, sender):
69 ''' Signal callback, executed when the user changes the text.
70 Disables the backend. The user will re-enable it to confirm the changes
71 (s)he made.
72
73 @param sender: not used, only here for signal compatibility
74 '''
75 if self.backend.is_enabled() and not self.backend.is_default():
76 self.req.set_backend_enabled(self.backend.get_id(), False)
77
078
=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py 2010-08-10 17:30:24 +0000
+++ GTG/gtk/browser/browser.py 2010-08-13 23:43:07 +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
@@ -206,6 +208,7 @@
206 self.sidebar_notebook = self.builder.get_object("sidebar_notebook")208 self.sidebar_notebook = self.builder.get_object("sidebar_notebook")
207 self.main_notebook = self.builder.get_object("main_notebook")209 self.main_notebook = self.builder.get_object("main_notebook")
208 self.accessory_notebook = self.builder.get_object("accessory_notebook")210 self.accessory_notebook = self.builder.get_object("accessory_notebook")
211 self.vbox_toolbars = self.builder.get_object("vbox_toolbars")
209 212
210 self.closed_pane = None213 self.closed_pane = None
211214
@@ -313,6 +316,8 @@
313 self.on_nonworkviewtag_toggled,316 self.on_nonworkviewtag_toggled,
314 "on_preferences_activate":317 "on_preferences_activate":
315 self.open_preferences,318 self.open_preferences,
319 "on_edit_backends_activate":
320 self.open_edit_backends,
316 }321 }
317 self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)322 self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
318323
@@ -346,6 +351,16 @@
346 # Connect requester signals to TreeModels351 # Connect requester signals to TreeModels
347 self.req.connect("task-added", self.on_task_added) 352 self.req.connect("task-added", self.on_task_added)
348 self.req.connect("task-deleted", self.on_task_deleted)353 self.req.connect("task-deleted", self.on_task_deleted)
354 #this causes changed be shouwn only on save
355 #tree = self.task_tree_model.get_tree()
356 #tree.connect("task-added-inview", self.on_task_added)
357 #tree.connect("task-deleted-inview", self.on_task_deleted)
358 b_signals = BackendSignals()
359 b_signals.connect(b_signals.BACKEND_FAILED, self.on_backend_failed)
360 b_signals.connect(b_signals.BACKEND_STATE_TOGGLED, \
361 self.remove_backend_infobar)
362 b_signals.connect(b_signals.INTERACTION_REQUESTED, \
363 self.on_backend_needing_interaction)
349 364
350 # Connect signals from models365 # Connect signals from models
351 self.task_modelsort.connect("row-has-child-toggled",\366 self.task_modelsort.connect("row-has-child-toggled",\
@@ -425,9 +440,12 @@
425440
426### HELPER FUNCTIONS ########################################################441### HELPER FUNCTIONS ########################################################
427442
428 def open_preferences(self,widget):443 def open_preferences(self, widget):
429 self.vmanager.open_preferences(self.priv)444 self.vmanager.open_preferences(self.priv)
430 445
446 def open_edit_backends(self, widget):
447 self.vmanager.open_edit_backends()
448
431 def quit(self,widget=None):449 def quit(self,widget=None):
432 self.vmanager.close_browser()450 self.vmanager.close_browser()
433 451
@@ -522,7 +540,7 @@
522 col_id,\540 col_id,\
523 self.priv["tasklist"]["sort_order"])541 self.priv["tasklist"]["sort_order"])
524 except:542 except:
525 print "Invalid configuration for sorting columns"543 Log.error("Invalid configuration for sorting columns")
526544
527 if "view" in self.config["browser"]:545 if "view" in self.config["browser"]:
528 view = self.config["browser"]["view"]546 view = self.config["browser"]["view"]
@@ -953,7 +971,9 @@
953 text = \971 text = \
954 text.replace("%s%s:%s" % (spaces, attribute, args), "")972 text.replace("%s%s:%s" % (spaces, attribute, args), "")
955 # Create the new task973 # Create the new task
956 task = self.req.new_task(tags=[t.get_name() for t in tags], newtask=True)974 task = self.req.new_task( newtask=True)
975 for tag in tags:
976 task.add_tag(tag.get_name())
957 if text != "":977 if text != "":
958 task.set_title(text.strip())978 task.set_title(text.strip())
959 task.set_to_keep()979 task.set_to_keep()
@@ -1516,3 +1536,82 @@
1516 """ Returns true if window is the currently active window """1536 """ Returns true if window is the currently active window """
1517 return self.window.get_property("is-active")1537 return self.window.get_property("is-active")
15181538
1539## BACKENDS RELATED METHODS ##################################################
1540
1541 def on_backend_failed(self, sender, backend_id, error_code):
1542 '''
1543 Signal callback.
1544 When a backend fails to work, loads a gtk.Infobar to alert the user
1545
1546 @param sender: not used, only here for signal compatibility
1547 @param backend_id: the id of the failing backend
1548 @param error_code: a backend error code, as specified in BackendsSignals
1549 '''
1550 infobar = self._new_infobar(backend_id)
1551 infobar.set_error_code(error_code)
1552
1553 def on_backend_needing_interaction(self, sender, backend_id, description, \
1554 interaction_type, callback):
1555 '''
1556 Signal callback.
1557 When a backend needs some kind of feedback from the user,
1558 loads a gtk.Infobar to alert the user.
1559 This is used, for example, to request confirmation after authenticating
1560 via OAuth.
1561
1562 @param sender: not used, only here for signal compatibility
1563 @param backend_id: the id of the failing backend
1564 @param description: a string describing the interaction needed
1565 @param interaction_type: a string describing the type of interaction
1566 (yes/no, only confirm, ok/cancel...)
1567 @param callback: the function to call when the user provides the
1568 feedback
1569 '''
1570 infobar = self._new_infobar(backend_id)
1571 infobar.set_interaction_request(description, interaction_type, callback)
1572
1573
1574 def __remove_backend_infobar(self, child, backend_id):
1575 '''
1576 Helper function to remove an gtk.Infobar related to a backend
1577
1578 @param child: a gtk.Infobar
1579 @param backend_id: the id of the backend which gtk.Infobar should be
1580 removed.
1581 '''
1582 if isinstance(child, CustomInfoBar) and\
1583 child.get_backend_id() == backend_id:
1584 if self.vbox_toolbars:
1585 self.vbox_toolbars.remove(child)
1586
1587 def remove_backend_infobar(self, sender, backend_id):
1588 '''
1589 Signal callback.
1590 Deletes the gtk.Infobars related to a backend
1591
1592 @param sender: not used, only here for signal compatibility
1593 @param backend_id: the id of the backend which gtk.Infobar should be
1594 removed.
1595 '''
1596 backend = self.req.get_backend(backend_id)
1597 if not backend or (backend and backend.is_enabled()):
1598 #remove old infobar related to backend_id, if any
1599 if self.vbox_toolbars:
1600 self.vbox_toolbars.foreach(self.__remove_backend_infobar, \
1601 backend_id)
1602
1603 def _new_infobar(self, backend_id):
1604 '''
1605 Helper function to create a new infobar for a backend
1606
1607 @param backend_id: the backend for which we're creating the infobar
1608 @returns gtk.Infobar: the created infobar
1609 '''
1610 #remove old infobar related to backend_id, if any
1611 if not self.vbox_toolbars:
1612 return
1613 self.vbox_toolbars.foreach(self.__remove_backend_infobar, backend_id)
1614 #add a new one
1615 infobar = CustomInfoBar(self.req, self, self.vmanager, backend_id)
1616 self.vbox_toolbars.pack_start(infobar, True)
1617 return infobar
15191618
=== 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-08-13 23:43:07 +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-08-13 23:43:07 +0000
@@ -154,6 +154,17 @@
154 <signal name="activate" handler="on_preferences_activate"/>154 <signal name="activate" handler="on_preferences_activate"/>
155 </object>155 </object>
156 </child>156 </child>
157 <child>
158 <object class="GtkImageMenuItem" id="backends_mi">
159 <property name="label">_Backends</property>
160 <property name="visible">True</property>
161 <property name="use_underline">True</property>
162 <property name="image">image4</property>
163 <property name="use_stock">False</property>
164 <property name="accel_group">accelgroup1</property>
165 <signal name="activate" handler="on_edit_backends_activate"/>
166 </object>
167 </child>
157 </object>168 </object>
158 </child>169 </child>
159 </object>170 </object>
160171
=== modified file 'GTG/gtk/colors.py'
--- GTG/gtk/colors.py 2010-06-07 21:14:45 +0000
+++ GTG/gtk/colors.py 2010-08-13 23:43:07 +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-08-13 23:43:07 +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-08-13 23:43:07 +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-08-13 23:43:07 +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-08-13 23:43:07 +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-08-13 23:43:07 +0000 differ

Subscribers

People subscribed via source and target branches

to status/vote changes: