GTG

Merge lp:~kevin-mehall/gtg/hamster-plugin into lp:~gtg/gtg/old-trunk

Proposed by Kevin Mehall
Status: Merged
Approved by: Paulo Cabido
Approved revision: 309
Merged at revision: not available
Proposed branch: lp:~kevin-mehall/gtg/hamster-plugin
Merge into: lp:~gtg/gtg/old-trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~kevin-mehall/gtg/hamster-plugin
Reviewer Review Type Date Requested Status
Paulo Cabido (community) Approve
Review via email: mp+9516@code.launchpad.net

Commit message

Merge with Kevin Mehall's hamster-plugin branch

To post a comment you must log in.
Revision history for this message
Kevin Mehall (kevin-mehall) wrote :

Added the ability to display related Hamster activities and time totals at the bottom of the task window. This required 2 changes to GTG core:
    - Add the ability to save arbitrary attributes with tasks
    - Add a function to the plugin API that adds a widget to the bottom of the tasks window (pcabido, please review this)

Also fixes bug #207291

lp:~kevin-mehall/gtg/hamster-plugin updated
308. By Kevin Mehall

Fix code that chooses Hamster activity

Revision history for this message
Kevin Mehall (kevin-mehall) wrote :
lp:~kevin-mehall/gtg/hamster-plugin updated
309. By Kevin Mehall

plugin api suggestions from pcabido

Revision history for this message
Paulo Cabido (pcabido) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'GTG/core/plugins/api.py'
--- GTG/core/plugins/api.py 2009-07-29 18:00:00 +0000
+++ GTG/core/plugins/api.py 2009-07-30 20:28:53 +0000
@@ -88,6 +88,13 @@
88 except Exception, e:88 except Exception, e:
89 print "Error adding a toolbar item in to the TaskEditor: %s" % e89 print "Error adding a toolbar item in to the TaskEditor: %s" % e
90 90
91 def add_task_window_region(self, widget):
92 "Adds a widget to the bottom of the task editor dialog"
93 v = self.__wTree.get_widget('vbox4')
94 v.pack_start(widget)
95 v.reorder_child(widget, -2)
96 widget.show_all()
97
91 # passes the requester to the plugin98 # passes the requester to the plugin
92 def get_requester(self):99 def get_requester(self):
93 return self.__requester100 return self.__requester
@@ -182,4 +189,4 @@
182 # add's a tid to the workview filter189 # add's a tid to the workview filter
183 def add_task_to_workview_filter(self, tid):190 def add_task_to_workview_filter(self, tid):
184 self.__workview_task_filter.append(tid)191 self.__workview_task_filter.append(tid)
185
186\ No newline at end of file192\ No newline at end of file
193
187194
=== modified file 'GTG/core/task.py'
--- GTG/core/task.py 2009-07-30 12:15:24 +0000
+++ GTG/core/task.py 2009-07-30 20:25:17 +0000
@@ -50,6 +50,7 @@
50 self.loaded = newtask50 self.loaded = newtask
51 if self.loaded :51 if self.loaded :
52 self.req._task_loaded(self.tid)52 self.req._task_loaded(self.tid)
53 self.attributes={}
53 54
54 def is_loaded(self) :55 def is_loaded(self) :
55 return self.loaded56 return self.loaded
@@ -366,6 +367,24 @@
366 else :367 else :
367 to_return = len(self.parents)!=0368 to_return = len(self.parents)!=0
368 return to_return369 return to_return
370
371 def set_attribute(self, att_name, att_value, namespace=""):
372 """Set an arbitrary attribute.
373
374 @param att_name: The name of the attribute.
375 @param att_value: The value of the attribute. Will be converted to a
376 string.
377 """
378 val = unicode(str(att_value), "UTF-8")
379 self.attributes[(namespace,att_name)] = val
380 self.sync()
381
382 def get_attribute(self, att_name, namespace=""):
383 """Get the attribute C{att_name}.
384
385 Returns C{None} if there is no attribute matching C{att_name}.
386 """
387 return self.attributes.get((namespace,att_name), None)
369 388
370 #Method called before the task is deleted389 #Method called before the task is deleted
371 #This method is called by the datastore and should not be called directly390 #This method is called by the datastore and should not be called directly
372391
=== modified file 'GTG/plugins/hamster/hamster.py'
--- GTG/plugins/hamster/hamster.py 2009-07-30 15:43:14 +0000
+++ GTG/plugins/hamster/hamster.py 2009-07-31 15:34:47 +0000
@@ -19,15 +19,20 @@
19import gtk, pygtk19import gtk, pygtk
20import os20import os
21import dbus21import dbus
22import time
23from calendar import timegm
2224
23class hamsterPlugin:25class hamsterPlugin:
24 PLUGIN_NAME = 'Hamster Time Tracker Integration'26 PLUGIN_NAME = 'Hamster Time Tracker Integration'
25 PLUGIN_AUTHORS = 'Kevin Mehall <km@kevinmehall.net>'27 PLUGIN_AUTHORS = 'Kevin Mehall <km@kevinmehall.net>'
26 PLUGIN_VERSION = '0.1'28 PLUGIN_VERSION = '0.2'
27 PLUGIN_DESCRIPTION = 'Adds the ability to send a task to the Hamster time tracking applet'29 PLUGIN_DESCRIPTION = 'Adds the ability to send a task to the Hamster time tracking applet'
28 PLUGIN_ENABLED = False30 PLUGIN_ENABLED = False
29 31 PLUGIN_NAMESPACE = 'hamster-plugin'
32
33 #### Interaction with Hamster
30 def sendTask(self, task):34 def sendTask(self, task):
35 """Send a gtg task to hamster-applet"""
31 if task is None: return36 if task is None: return
32 title=task.get_title()37 title=task.get_title()
33 tags=task.get_tags_name()38 tags=task.get_tags_name()
@@ -39,22 +44,48 @@
39 if len(activity_candidates)>=1:44 if len(activity_candidates)>=1:
40 activity=list(activity_candidates)[0]45 activity=list(activity_candidates)[0]
41 #TODO: if >1, how to choose best one?46 #TODO: if >1, how to choose best one?
42 else:47 elif len(activity_candidates)>0:
43 #TODO: is there anything more reasonable that can be done?48 #TODO: is there anything more reasonable that can be done?
44 activity=tags[0]49 activity=tags[0]
50 else:
51 activity = "Other"
45 52
46 self.hamster.AddFact('%s,%s'%(activity, title), 0, 0)53 hamster_id=self.hamster.AddFact('%s,%s'%(activity, title), 0, 0)
4754
48 def hamsterError(self):55 ids=self.get_hamster_ids(task)
49 d=gtk.MessageDialog(buttons=gtk.BUTTONS_CANCEL)56 ids.append(str(hamster_id))
50 d.set_markup("<big>Error loading plugin</big>")57 self.set_hamster_ids(task, ids)
51 d.format_secondary_markup("This plugin requires hamster-applet 2.27.3 or greater\n\58
52Please install hamster-applet and make sure the applet is added to the panel")59 def get_records(self, task):
53 d.run()60 """Get a list of hamster facts for a task"""
54 d.destroy()61 ids = self.get_hamster_ids(task)
5562 records=[]
56 # plugin engine methods 63 modified=False
64 valid_ids=[]
65 for i in ids:
66 d=self.hamster.GetFactById(i)
67 if d.get("id", None): # check if fact still exists
68 records.append(d)
69 valid_ids.append(i)
70 else:
71 modified=True
72 print "Removing invalid fact", i
73 if modified:
74 self.set_hamster_ids(task, valid_ids)
75 return records
76
77 #### Datastore
78 def get_hamster_ids(self, task):
79 a = task.get_attribute("id-list", namespace=self.PLUGIN_NAMESPACE)
80 if not a: return []
81 else: return a.split(',')
82
83 def set_hamster_ids(self, task, ids):
84 task.set_attribute("id-list", ",".join(ids), namespace=self.PLUGIN_NAMESPACE)
85
86 #### Plugin api methods
57 def activate(self, plugin_api):87 def activate(self, plugin_api):
88 # connect to hamster-applet
58 try:89 try:
59 self.hamster=dbus.SessionBus().get_object('org.gnome.Hamster', '/org/gnome/Hamster')90 self.hamster=dbus.SessionBus().get_object('org.gnome.Hamster', '/org/gnome/Hamster')
60 self.hamster.GetActivities()91 self.hamster.GetActivities()
@@ -62,25 +93,22 @@
62 self.hamsterError()93 self.hamsterError()
63 return False94 return False
64 95
96 # add menu item
65 self.menu_item = gtk.MenuItem("Start task in Hamster")97 self.menu_item = gtk.MenuItem("Start task in Hamster")
66 self.menu_item.connect('activate', self.browser_cb, plugin_api)98 self.menu_item.connect('activate', self.browser_cb, plugin_api)
99 plugin_api.add_menu_item(self.menu_item)
67 100
101 # and button
68 self.button=gtk.ToolButton()102 self.button=gtk.ToolButton()
69 self.button.set_label("Start")103 self.button.set_label("Start")
70 self.button.set_icon_name('hamster-applet')104 self.button.set_icon_name('hamster-applet')
71 self.button.set_tooltip_text("Start a new activity in Hamster Time Tracker based on the selected task")105 self.button.set_tooltip_text("Start a new activity in Hamster Time Tracker based on the selected task")
72 self.button.connect('clicked', self.browser_cb, plugin_api)106 self.button.connect('clicked', self.browser_cb, plugin_api)
73 107 self.separator = plugin_api.add_toolbar_item(gtk.SeparatorToolItem()) # saves the separator's index to later remove it
74 # add a menu item to the menu bar
75 plugin_api.add_menu_item(self.menu_item)
76
77 # saves the separator's index to later remove it
78 self.separator = plugin_api.add_toolbar_item(gtk.SeparatorToolItem())
79 # add a item (button) to the ToolBar
80 plugin_api.add_toolbar_item(self.button)108 plugin_api.add_toolbar_item(self.button)
81109
82 def onTaskOpened(self, plugin_api):110 def onTaskOpened(self, plugin_api):
83 # add a item (button) to the ToolBar111 # add button
84 self.taskbutton = gtk.ToolButton()112 self.taskbutton = gtk.ToolButton()
85 self.taskbutton.set_label("Start")113 self.taskbutton.set_label("Start")
86 self.taskbutton.set_icon_name('hamster-applet')114 self.taskbutton.set_icon_name('hamster-applet')
@@ -89,6 +117,54 @@
89 plugin_api.add_task_toolbar_item(gtk.SeparatorToolItem())117 plugin_api.add_task_toolbar_item(gtk.SeparatorToolItem())
90 plugin_api.add_task_toolbar_item(self.taskbutton)118 plugin_api.add_task_toolbar_item(self.taskbutton)
91 119
120 task = plugin_api.get_task()
121 records = self.get_records(task)
122
123 if len(records):
124 # add section to bottom of window
125 vbox = gtk.VBox()
126 inner_table = gtk.Table(rows=len(records), columns=2)
127 if len(records)>8:
128 s = gtk.ScrolledWindow()
129 s.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
130 v=gtk.Viewport()
131 v.add(inner_table)
132 s.add(v)
133 v.set_shadow_type(gtk.SHADOW_NONE)
134 s.set_size_request(-1, 150)
135 else:
136 s=inner_table
137
138 outer_table = gtk.Table(rows=1, columns=2)
139 vbox.pack_start(s)
140 vbox.pack_start(outer_table)
141 vbox.pack_end(gtk.HSeparator())
142
143 total = 0
144
145 def add(w, a, b, offset):
146 dateLabel=gtk.Label(a)
147 dateLabel.set_use_markup(True)
148 dateLabel.set_alignment(xalign=0.0, yalign=0.5)
149 dateLabel.set_size_request(200, -1)
150 w.attach(dateLabel, left_attach=0, right_attach=1, top_attach=offset,
151 bottom_attach=offset+1, xoptions=gtk.FILL, xpadding=20, yoptions=0)
152
153 durLabel=gtk.Label(b)
154 durLabel.set_use_markup(True)
155 durLabel.set_alignment(xalign=0.0, yalign=0.5)
156 w.attach(durLabel, left_attach=1, right_attach=2, top_attach=offset,
157 bottom_attach=offset+1, xoptions=gtk.FILL, yoptions=0)
158
159 for offset,i in enumerate(records):
160 t = calc_duration(i)
161 total += t
162 add(inner_table, format_date(i), format_duration(t), offset)
163
164 add(outer_table, "<big><b>Total</b></big>", "<big><b>%s</b></big>"%format_duration(total), 1)
165
166 plugin_api.add_task_window_region(vbox)
167
92 def deactivate(self, plugin_api):168 def deactivate(self, plugin_api):
93 plugin_api.remove_menu_item(self.menu_item)169 plugin_api.remove_menu_item(self.menu_item)
94 plugin_api.remove_toolbar_item(self.button)170 plugin_api.remove_toolbar_item(self.button)
@@ -99,5 +175,48 @@
99 175
100 def task_cb(self, widget, plugin_api):176 def task_cb(self, widget, plugin_api):
101 self.sendTask(plugin_api.get_task())177 self.sendTask(plugin_api.get_task())
102 178
103 179 def hamsterError(self):
180 """Display error dialog"""
181 d=gtk.MessageDialog(buttons=gtk.BUTTONS_CANCEL)
182 d.set_markup("<big>Error loading plugin</big>")
183 d.format_secondary_markup("This plugin requires hamster-applet 2.27.3 or greater\n\
184Please install hamster-applet and make sure the applet is added to the panel")
185 d.run()
186 d.destroy()
187
188#### Helper Functions
189def format_date(task):
190 return time.strftime("<b>%A, %b %e</b> %l:%M %p", time.gmtime(task['start_time']))
191
192def calc_duration(fact):
193 start=fact['start_time']
194 end=fact['end_time']
195 if not end: end=timegm(time.localtime())
196 return end-start
197
198def format_duration(seconds):
199 # Based on hamster-applet code - hamster/stuff.py
200 """formats duration in a human readable format."""
201
202 minutes = seconds / 60
203
204 if not minutes:
205 return "0min"
206
207 hours = minutes / 60
208 minutes = minutes % 60
209 formatted_duration = ""
210
211 if minutes % 60 == 0:
212 # duration in round hours
213 formatted_duration += "%dh" % (hours)
214 elif hours == 0:
215 # duration less than hour
216 formatted_duration += "%dmin" % (minutes % 60.0)
217 else:
218 # x hours, y minutes
219 formatted_duration += "%dh %dmin" % (hours, minutes % 60)
220
221 return formatted_duration
222
104223
=== modified file 'GTG/tools/taskxml.py'
--- GTG/tools/taskxml.py 2009-03-01 14:09:12 +0000
+++ GTG/tools/taskxml.py 2009-07-31 15:52:52 +0000
@@ -35,6 +35,15 @@
35 for s in sub_list :35 for s in sub_list :
36 sub_tid = s.childNodes[0].nodeValue36 sub_tid = s.childNodes[0].nodeValue
37 cur_task.add_subtask(sub_tid)37 cur_task.add_subtask(sub_tid)
38 attr_list = xmlnode.getElementsByTagName("attribute")
39 for a in attr_list:
40 if len(a.childNodes):
41 content = a.childNodes[0].nodeValue
42 else:
43 content = ""
44 key = a.getAttribute("key")
45 namespace = a.getAttribute("namespace")
46 cur_task.set_attribute(key, content, namespace=namespace)
38 tasktext = xmlnode.getElementsByTagName("content")47 tasktext = xmlnode.getElementsByTagName("content")
39 if len(tasktext) > 0 :48 if len(tasktext) > 0 :
40 if tasktext[0].firstChild :49 if tasktext[0].firstChild :
@@ -67,6 +76,14 @@
67 childs = task.get_subtasks_tid()76 childs = task.get_subtasks_tid()
68 for c in childs :77 for c in childs :
69 cleanxml.addTextNode(doc,t_xml,"subtask",c)78 cleanxml.addTextNode(doc,t_xml,"subtask",c)
79 for a in task.attributes:
80 namespace,key=a
81 content=task.attributes[a]
82 element = doc.createElement('attribute')
83 element.setAttribute("namespace", namespace)
84 element.setAttribute("key", key)
85 element.appendChild(doc.createTextNode(content))
86 t_xml.appendChild(element)
70 tex = task.get_text()87 tex = task.get_text()
71 if tex :88 if tex :
72 #We take the xml text and convert it to a string89 #We take the xml text and convert it to a string

Subscribers

People subscribed via source and target branches

to status/vote changes: