GTG

Merge lp:~tomkadwill/gtg/untouched-plugin into lp:~gtg/gtg/old-trunk

Proposed by Tom Kadwill
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
Reviewer Review Type Date Requested Status
Izidor Matušov Approve
Review via email: mp+132986@code.launchpad.net

Description of the change

Added untouched-tasks plugin in response to bug #320589

To post a comment you must log in.
Revision history for this message
Lionel Dricot (ploum-deactivatedaccount) wrote :

How could we test this? It looks like a nice feature.

Maybe the "@untouched" string should be translatable.

Revision history for this message
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.

review: Needs Fixing (code, run)
lp:~tomkadwill/gtg/untouched-plugin updated
1244. By Tom Kadwill

Made changed as instructed by Izidor

1245. By Tom Kadwill

merged with trunk

Revision history for this message
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.

Revision history for this message
Izidor Matušov (izidor) wrote :

Thank you for your contribution :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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">&#x25CF;</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">&#x2022;</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 &gt; 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>

Subscribers

People subscribed via source and target branches

to status/vote changes: