Merge lp:~tomkadwill/gtg/untouched-plugin into lp:~gtg/gtg/old-trunk
- untouched-plugin
- Merge into old-trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 1256 | ||||
Proposed branch: | lp:~tomkadwill/gtg/untouched-plugin | ||||
Merge into: | lp:~gtg/gtg/old-trunk | ||||
Diff against target: |
434 lines (+395/-0) 6 files modified
AUTHORS (+1/-0) CHANGELOG (+1/-0) GTG/plugins/untouched-tasks.gtg-plugin (+9/-0) GTG/plugins/untouched_tasks/__init__.py (+22/-0) GTG/plugins/untouched_tasks/untouchedTasks.py (+203/-0) GTG/plugins/untouched_tasks/untouchedTasks.ui (+159/-0) |
||||
To merge this branch: | bzr merge lp:~tomkadwill/gtg/untouched-plugin | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Izidor Matušov | Approve | ||
Review via email: mp+132986@code.launchpad.net |
Commit message
Description of the change
Added untouched-tasks plugin in response to bug #320589
Lionel Dricot (ploum-deactivatedaccount) wrote : | # |
Izidor Matušov (izidor) wrote : | # |
Hi Tom,
your plugin looks nice. As a reviewer I have a couple things to nitpick about. I am not sure about the modifiction time but it is matter of bug #1049607. The structure of your code is taken from delete old tasks so it should be okay.
Some remarks to UI in configuration window:
* come up with a name for the action, e.g. Find untouched tasks
* allow the user to enter its own tag
* get rid of "Add the "Plugins > Add @untouched plugin" menu item and assume it is always turned on
The configuration dialog could look like this:
-------
| Add [ @untouched ] tag to a task after it has been left untouched for at least [ 30 ] days. |
| |
| [ ] Find untouched tasks automatically |
| |
| [ Cancel ] [ OK ] |
-------
(I hope ASCII art would work)
Or you can redesign that dialog even more. I would change the menu item's title to "Find untouched tasks" and add it every time.
Please, change those details.
- 1244. By Tom Kadwill
-
Made changed as instructed by Izidor
- 1245. By Tom Kadwill
-
merged with trunk
Tom Kadwill (tomkadwill) wrote : | # |
I believe I have addressed all of your comments in my previous merge request.
- Came up with a name for the action
- The user can now enter their own tag
- Got rid of the "Add the "Plugins > Add @untouched plugin" menu item and assumed that it is always there.
Izidor Matušov (izidor) wrote : | # |
Thank you for your contribution :)
Preview Diff
1 | === modified file 'AUTHORS' |
2 | --- AUTHORS 2012-11-29 06:44:31 +0000 |
3 | +++ AUTHORS 2012-12-12 23:23:30 +0000 |
4 | @@ -114,3 +114,4 @@ |
5 | ---------- |
6 | * Antonio Roquentin <https://launchpad.net/~antonio-roquentin> (no email provided) |
7 | * Codee <kmhpfoss@gmail.com> |
8 | +* Tom Kadwill <tomkadwill@gmail.com> |
9 | |
10 | === modified file 'CHANGELOG' |
11 | --- CHANGELOG 2012-11-29 06:44:31 +0000 |
12 | +++ CHANGELOG 2012-12-12 23:23:30 +0000 |
13 | @@ -63,6 +63,7 @@ |
14 | * Remove use of liblarch's "transparent" concept (since it's been removed from liblarch), fixes bugs #1001962, #1001962, #1069257, #1069963: intermediary tags, counter initialization, and regressions caused by initial versions of the patch |
15 | * Fix for bug #1038662: Undefined due dates in subtasks should always stay undefined and displayed as such in the editor |
16 | * Fix for bug #1036695: Date constraints after drag and drop not applied |
17 | + * Fix for bug #320589: Added untouched_tasks plugin |
18 | |
19 | 2012-02-13 Getting Things GNOME! 0.2.9 |
20 | * Big refractorization of code, now using liblarch |
21 | |
22 | === added file 'GTG/plugins/untouched-tasks.gtg-plugin' |
23 | --- GTG/plugins/untouched-tasks.gtg-plugin 1970-01-01 00:00:00 +0000 |
24 | +++ GTG/plugins/untouched-tasks.gtg-plugin 2012-12-12 23:23:30 +0000 |
25 | @@ -0,0 +1,9 @@ |
26 | +[GTG Plugin] |
27 | +Module=untouched_tasks |
28 | +Name=Untouched tasks |
29 | +Short-description="Keep track of tasks you haven't touched for a while." |
30 | +Description="""Assigns tasks that you haven't touched for a while with the @untouched tag.""" |
31 | +Authors=Tom Kadwill <tomkadwill@gmail.com> |
32 | +Version=0.0.1 |
33 | +Enabled=False |
34 | +Dependencies=gio, urllib |
35 | |
36 | === added directory 'GTG/plugins/untouched_tasks' |
37 | === added file 'GTG/plugins/untouched_tasks/__init__.py' |
38 | --- GTG/plugins/untouched_tasks/__init__.py 1970-01-01 00:00:00 +0000 |
39 | +++ GTG/plugins/untouched_tasks/__init__.py 2012-12-12 23:23:30 +0000 |
40 | @@ -0,0 +1,22 @@ |
41 | +# -*- coding: utf-8 -*- |
42 | +# Copyright (c) 2012 - Tom Kadwill <tomkadwill@gmail.com> |
43 | +# |
44 | +# This program is free software: you can redistribute it and/or modify it under |
45 | +# the terms of the GNU General Public License as published by the Free Software |
46 | +# Foundation, either version 3 of the License, or (at your option) any later |
47 | +# version. |
48 | +# |
49 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
50 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
51 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
52 | +# details. |
53 | +# |
54 | +# You should have received a copy of the GNU General Public License along with |
55 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
56 | + |
57 | +from GTG.plugins.untouched_tasks.untouchedTasks import pluginUntouchedTasks |
58 | + |
59 | + |
60 | +#suppress pyflakes warning (given by make lint) |
61 | +if False == True: |
62 | + pluginUntouchedTasks() |
63 | |
64 | === added file 'GTG/plugins/untouched_tasks/untouchedTasks.py' |
65 | --- GTG/plugins/untouched_tasks/untouchedTasks.py 1970-01-01 00:00:00 +0000 |
66 | +++ GTG/plugins/untouched_tasks/untouchedTasks.py 2012-12-12 23:23:30 +0000 |
67 | @@ -0,0 +1,203 @@ |
68 | +# -*- coding: utf-8 -*- |
69 | +# Copyright (c) 2012 - Tom Kadwill <tomkadwill@gmail.com> |
70 | +# |
71 | +# This program is free software: you can redistribute it and/or modify it under |
72 | +# the terms of the GNU General Public License as published by the Free Software |
73 | +# Foundation, either version 3 of the License, or (at your option) any later |
74 | +# version. |
75 | +# |
76 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
77 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
78 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
79 | +# details. |
80 | +# |
81 | +# You should have received a copy of the GNU General Public License along with |
82 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
83 | + |
84 | +import sys |
85 | +import os |
86 | +import gio |
87 | +import gtk |
88 | +import urllib |
89 | +import datetime |
90 | + |
91 | +from GTG import _ |
92 | +from GTG.tools.logger import Log |
93 | +from GTG.tools.dates import Date |
94 | +from threading import Timer |
95 | +from datetime import date, timedelta |
96 | + |
97 | +try: |
98 | + import pygtk |
99 | + pygtk.require("2.0") |
100 | +except: # pylint: disable-msg=W0702 |
101 | + sys.exit(1) |
102 | + |
103 | +try: |
104 | + import gtk |
105 | +except: # pylint: disable-msg=W0702 |
106 | + sys.exit(1) |
107 | + |
108 | +################################### |
109 | + |
110 | +class pluginUntouchedTasks: |
111 | + |
112 | + DEFAULT_PREFERENCES = {'max_days': 30, |
113 | + 'is_automatic': False, |
114 | + 'default_tag': '@untouched'} |
115 | + |
116 | + PLUGIN_NAME = "untouched-tasks" |
117 | + |
118 | + # In case of automatic removing tasks, the time |
119 | + # between two runs of the cleaner function |
120 | + TIME_BETWEEN_PURGES = 60 * 60 |
121 | + |
122 | + def __init__(self): |
123 | + self.path = os.path.dirname(os.path.abspath(__file__)) |
124 | + #GUI initialization |
125 | + self.builder = gtk.Builder() |
126 | + self.builder.add_from_file(os.path.join( |
127 | + os.path.dirname(os.path.abspath(__file__)) + \ |
128 | + "/untouchedTasks.ui")) |
129 | + self.preferences_dialog = self.builder.get_object("preferences_dialog") |
130 | + self.pref_chbox_is_automatic = \ |
131 | + self.builder.get_object("pref_chbox_is_automatic") |
132 | + self.pref_spinbtn_max_days = \ |
133 | + self.builder.get_object("pref_spinbtn_max_days") |
134 | + self.pref_tag_name = \ |
135 | + self.builder.get_object("pref_tag_name") |
136 | + SIGNAL_CONNECTIONS_DIC = { |
137 | + "on_preferences_dialog_delete_event": |
138 | + self.on_preferences_cancel, |
139 | + "on_btn_preferences_cancel_clicked": |
140 | + self.on_preferences_cancel, |
141 | + "on_btn_preferences_ok_clicked": |
142 | + self.on_preferences_ok, |
143 | + } |
144 | + self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC) |
145 | + self.menu_item = gtk.MenuItem("Add @untouched tag") |
146 | + self.menu_item.connect('activate', self.add_untouched_tag) |
147 | + |
148 | + def activate(self, plugin_api): |
149 | + self.plugin_api = plugin_api |
150 | + #preferences initialization |
151 | + self.is_automatic = False |
152 | + self.timer = None |
153 | + self.preferences_load() |
154 | + self.preferences_apply() |
155 | + requester = self.plugin_api.get_requester() |
156 | + #add menu item |
157 | + self.plugin_api.add_menu_item(self.menu_item) |
158 | + |
159 | + def deactivate(self, plugin_api): |
160 | + """ |
161 | + Deactivates the plugin. |
162 | + """ |
163 | + #everything should be removed, in case a task is currently opened |
164 | + try: |
165 | + self.plugin_api.remove_task_toolbar_item(self.tb_Taskbutton) |
166 | + except: |
167 | + pass |
168 | + |
169 | +## HELPER FUNCTIONS ########################################################### |
170 | + def __log(self, message): |
171 | + Log.debug(message) |
172 | + |
173 | +## CORE FUNCTIONS ############################################################# |
174 | + def schedule_autopurge(self): |
175 | + self.timer = Timer(self.TIME_BETWEEN_PURGES, |
176 | + self.add_untouched_tag) |
177 | + self.timer.setDaemon(True) |
178 | + self.timer.start() |
179 | + self.__log("Automatic untouched tasks check scheduled") |
180 | + |
181 | + def cancel_autopurge(self): |
182 | + if self.timer: |
183 | + self.__log("Automatic untouched tasks check cancelled") |
184 | + self.timer.cancel() |
185 | + |
186 | + def add_untouched_tag(self, widget = None): |
187 | + #If no tag is picked up from preferences |
188 | + tag_name = self.pref_tag_name.get_text() |
189 | + if not tag_name: |
190 | + tag_name = self.preferences['default_tag'] |
191 | + |
192 | + #Add @ if user has not entered it |
193 | + if tag_name.find('@') != 0: |
194 | + tag_name = '@' + tag_name |
195 | + |
196 | + self.__log("Starting process for adding " + tag_name) |
197 | + today = datetime.datetime.now() |
198 | + max_days = self.preferences["max_days"] |
199 | + requester = self.plugin_api.get_requester() |
200 | + closed_tree = requester.get_tasks_tree(name = 'inactive') |
201 | + closed_tasks = [requester.get_task(tid) for tid in \ |
202 | + closed_tree.get_all_nodes()] |
203 | + |
204 | + #Add untouched tag to all tasks where new_date < time now |
205 | + for task in closed_tasks: |
206 | + modified_time = task.get_modified() |
207 | + new_time = modified_time + datetime.timedelta(days=max_days) |
208 | + if new_time < today: |
209 | + self.__log('Adding ' + tag_name + ' tag to: "' + task.title + |
210 | + '" as last time it was modified was ' + str(modified_time)) |
211 | + task.add_tag(tag_name) |
212 | + |
213 | + #If automatic purging is on, schedule another run |
214 | + if self.is_automatic: |
215 | + self.schedule_autopurge() |
216 | + |
217 | +## Preferences methods ######################################################## |
218 | + def is_configurable(self): |
219 | + """A configurable plugin should have this method and return True""" |
220 | + return True |
221 | + |
222 | + def configure_dialog(self, manager_dialog): |
223 | + self.preferences_load() |
224 | + self.preferences_dialog.set_transient_for(manager_dialog) |
225 | + self.pref_chbox_is_automatic.set_active( |
226 | + self.preferences["is_automatic"]) |
227 | + self.pref_spinbtn_max_days.set_value( |
228 | + self.preferences["max_days"]) |
229 | + self.pref_tag_name.set_text( |
230 | + self.preferences["default_tag"]) |
231 | + self.preferences_dialog.show_all() |
232 | + |
233 | + def on_preferences_cancel(self, widget = None, data = None): |
234 | + self.preferences_dialog.hide() |
235 | + return True |
236 | + |
237 | + def on_preferences_ok(self, widget = None, data = None): |
238 | + self.preferences["is_automatic"] = \ |
239 | + self.pref_chbox_is_automatic.get_active() |
240 | + self.preferences["max_days"] = \ |
241 | + self.pref_spinbtn_max_days.get_value() |
242 | + self.preferences['default_tag'] = \ |
243 | + self.pref_tag_name.get_text() |
244 | + self.preferences_apply() |
245 | + self.preferences_store() |
246 | + self.preferences_dialog.hide() |
247 | + |
248 | + def preferences_load(self): |
249 | + data = self.plugin_api.load_configuration_object(self.PLUGIN_NAME, |
250 | + "preferences") |
251 | + if data == None or type(data) != type(dict()): |
252 | + self.preferences = self.DEFAULT_PREFERENCES |
253 | + else: |
254 | + self.preferences = data |
255 | + |
256 | + def preferences_store(self): |
257 | + self.plugin_api.save_configuration_object(self.PLUGIN_NAME, |
258 | + "preferences", self.preferences) |
259 | + |
260 | + def preferences_apply(self): |
261 | + #Auto-purge |
262 | + if self.preferences['is_automatic'] == True and \ |
263 | + self.is_automatic == False: |
264 | + self.is_automatic = True |
265 | + # Run the first iteration immediately and schedule next iteration |
266 | + self.add_untouched_tag() |
267 | + elif self.preferences['is_automatic'] == False and \ |
268 | + self.is_automatic == True: |
269 | + self.cancel_autopurge() |
270 | + self.is_automatic = False |
271 | |
272 | === added file 'GTG/plugins/untouched_tasks/untouchedTasks.ui' |
273 | --- GTG/plugins/untouched_tasks/untouchedTasks.ui 1970-01-01 00:00:00 +0000 |
274 | +++ GTG/plugins/untouched_tasks/untouchedTasks.ui 2012-12-12 23:23:30 +0000 |
275 | @@ -0,0 +1,159 @@ |
276 | +<?xml version="1.0"?> |
277 | +<interface> |
278 | + <requires lib="gtk+" version="2.16"/> |
279 | + <!-- interface-naming-policy toplevel-contextual --> |
280 | + <object class="GtkAccelGroup" id="accelgroup1"/> |
281 | + <object class="GtkWindow" id="preferences_dialog"> |
282 | + <property name="border_width">10</property> |
283 | + <property name="window_position">center-on-parent</property> |
284 | + <property name="type_hint">dialog</property> |
285 | + <signal name="delete_event" handler="on_preferences_dialog_delete_event"/> |
286 | + <child> |
287 | + <object class="GtkVBox" id="vbox1"> |
288 | + <property name="visible">True</property> |
289 | + <property name="orientation">vertical</property> |
290 | + <child> |
291 | + <object class="GtkVBox" id="vbox2"> |
292 | + <property name="visible">True</property> |
293 | + <property name="orientation">vertical</property> |
294 | + <property name="spacing">12</property> |
295 | + <child> |
296 | + <object class="GtkHBox" id="hbox2"> |
297 | + <property name="visible">True</property> |
298 | + <child> |
299 | + <object class="GtkLabel" id="label2"> |
300 | + <property name="visible">True</property> |
301 | + <property name="label" translatable="yes">Add </property> |
302 | + </object> |
303 | + <packing> |
304 | + <property name="position">0</property> |
305 | + </packing> |
306 | + </child> |
307 | + <child> |
308 | + <object class="GtkEntry" id="pref_tag_name"> |
309 | + <property name="visible">True</property> |
310 | + <property name="can_focus">True</property> |
311 | + <property name="invisible_char">●</property> |
312 | + <property name="width_chars">15</property> |
313 | + <!--<property name="text" translatable="yes">@untouched</property>--> |
314 | + </object> |
315 | + <packing> |
316 | + <property name="position">1</property> |
317 | + </packing> |
318 | + </child> |
319 | + <child> |
320 | + <object class="GtkLabel" id="label4"> |
321 | + <property name="visible">True</property> |
322 | + <property name="label" translatable="yes"> tag to task after it has been left untouched for at least </property> |
323 | + </object> |
324 | + <packing> |
325 | + <property name="position">2</property> |
326 | + </packing> |
327 | + </child> |
328 | + <child> |
329 | + <object class="GtkSpinButton" id="pref_spinbtn_max_days"> |
330 | + <property name="visible">True</property> |
331 | + <property name="can_focus">True</property> |
332 | + <property name="max_length">3</property> |
333 | + <property name="invisible_char">•</property> |
334 | + <property name="adjustment">adjustment1</property> |
335 | + <property name="climb_rate">0.5</property> |
336 | + <property name="snap_to_ticks">True</property> |
337 | + <property name="numeric">True</property> |
338 | + </object> |
339 | + <packing> |
340 | + <property name="position">3</property> |
341 | + </packing> |
342 | + </child> |
343 | + <child> |
344 | + <object class="GtkLabel" id="label3"> |
345 | + <property name="visible">True</property> |
346 | + <property name="label" translatable="yes"> days</property> |
347 | + </object> |
348 | + <packing> |
349 | + <property name="position">4</property> |
350 | + </packing> |
351 | + </child> |
352 | + </object> |
353 | + <packing> |
354 | + <property name="position">0</property> |
355 | + </packing> |
356 | + </child> |
357 | + <child> |
358 | + <object class="GtkCheckButton" id="pref_chbox_is_automatic"> |
359 | + <property name="label" translatable="yes">Check for untouched tasks automatically</property> |
360 | + <property name="visible">True</property> |
361 | + <property name="can_focus">True</property> |
362 | + <property name="receives_default">False</property> |
363 | + <property name="image_position">top</property> |
364 | + <property name="draw_indicator">True</property> |
365 | + </object> |
366 | + <packing> |
367 | + <property name="position">1</property> |
368 | + </packing> |
369 | + </child> |
370 | + <!--<child> |
371 | + <object class="GtkCheckButton" id="pref_chbox_show_menu_item"> |
372 | + <property name="label" translatable="yes">Add the "Plugins > Add @untouched plugin" menu item</property> |
373 | + <property name="visible">True</property> |
374 | + <property name="can_focus">True</property> |
375 | + <property name="receives_default">False</property> |
376 | + <property name="image_position">top</property> |
377 | + <property name="draw_indicator">True</property> |
378 | + </object> |
379 | + <packing> |
380 | + <property name="position">2</property> |
381 | + </packing> |
382 | + </child>--> |
383 | + </object> |
384 | + <packing> |
385 | + <property name="padding">21</property> |
386 | + <property name="position">0</property> |
387 | + </packing> |
388 | + </child> |
389 | + <child> |
390 | + <object class="GtkHBox" id="hbox1"> |
391 | + <property name="height_request">30</property> |
392 | + <property name="visible">True</property> |
393 | + <property name="spacing">50</property> |
394 | + <child> |
395 | + <object class="GtkButton" id="btn_preferences_cancel"> |
396 | + <property name="label">gtk-cancel</property> |
397 | + <property name="visible">True</property> |
398 | + <property name="can_focus">True</property> |
399 | + <property name="receives_default">True</property> |
400 | + <property name="use_stock">True</property> |
401 | + <signal name="clicked" handler="on_btn_preferences_cancel_clicked"/> |
402 | + </object> |
403 | + <packing> |
404 | + <property name="position">0</property> |
405 | + </packing> |
406 | + </child> |
407 | + <child> |
408 | + <object class="GtkButton" id="btn_preferences_ok"> |
409 | + <property name="label">gtk-ok</property> |
410 | + <property name="visible">True</property> |
411 | + <property name="can_focus">True</property> |
412 | + <property name="receives_default">True</property> |
413 | + <property name="use_stock">True</property> |
414 | + <signal name="clicked" handler="on_btn_preferences_ok_clicked"/> |
415 | + </object> |
416 | + <packing> |
417 | + <property name="position">1</property> |
418 | + </packing> |
419 | + </child> |
420 | + </object> |
421 | + <packing> |
422 | + <property name="position">1</property> |
423 | + </packing> |
424 | + </child> |
425 | + </object> |
426 | + </child> |
427 | + </object> |
428 | + <object class="GtkAdjustment" id="adjustment1"> |
429 | + <property name="lower">1</property> |
430 | + <property name="upper">100</property> |
431 | + <property name="step_increment">1</property> |
432 | + <property name="page_increment">10</property> |
433 | + </object> |
434 | +</interface> |
How could we test this? It looks like a nice feature.
Maybe the "@untouched" string should be translatable.