Merge lp:~cjohnston/gtg/bugfix-lp-531053 into lp:gtg/0.2
- bugfix-lp-531053
- Merge into release-0.2
Proposed by
Chris Johnston
Status: | Merged |
---|---|
Merge reported by: | Luca Invernizzi |
Merged at revision: | not available |
Proposed branch: | lp:~cjohnston/gtg/bugfix-lp-531053 |
Merge into: | lp:gtg/0.2 |
Diff against target: |
9187 lines (+3997/-2883) (has conflicts) 58 files modified
GTG/__init__.py (+4/-0) GTG/backends/__init__.py (+5/-4) GTG/backends/localfile.py (+36/-22) GTG/core/__init__.py (+19/-24) GTG/core/datastore.py (+110/-294) GTG/core/dbuswrapper.py (+0/-168) GTG/core/filteredtree.py (+594/-0) GTG/core/filters_bank.py (+158/-0) GTG/core/firstrun_tasks.py (+1/-1) GTG/core/plugins/api.py (+74/-93) GTG/core/requester.py (+93/-227) GTG/core/tagstore.py (+121/-93) GTG/core/task.py (+82/-106) GTG/core/tree.py (+264/-126) GTG/gtg.py (+13/-18) GTG/info.py (+1/-1) GTG/plugins/__init__.py (+1/-0) GTG/plugins/export/export.ui (+1/-0) GTG/plugins/hamster/prefs.ui (+1/-1) GTG/plugins/notification_area/notification_area.ui (+1/-0) GTG/plugins/rtm_sync/__init__.py (+0/-1) GTG/plugins/rtm_sync/genericTask.py (+2/-2) GTG/plugins/rtm_sync/gtgProxy.py (+0/-1) GTG/plugins/rtm_sync/gtgTask.py (+3/-5) GTG/plugins/rtm_sync/rtmProxy.py (+6/-7) GTG/plugins/rtm_sync/rtmTask.py (+16/-18) GTG/plugins/rtm_sync/syncEngine.py (+13/-9) GTG/plugins/send_email/sendEmail.py (+0/-7) GTG/plugins/task_reaper/reaper.py (+2/-3) GTG/plugins/task_reaper/reaper.ui (+1/-0) GTG/taskbrowser/__init__.py (+6/-2) GTG/taskbrowser/browser.py (+178/-638) GTG/taskbrowser/preferences.py (+0/-364) GTG/taskbrowser/tagtree.py (+72/-62) GTG/taskbrowser/taskbrowser.glade (+24/-208) GTG/taskbrowser/tasktree.py (+171/-217) GTG/taskeditor/__init__.py (+4/-1) GTG/taskeditor/editor.py (+29/-59) GTG/taskeditor/taskeditor.glade (+0/-12) GTG/taskeditor/taskview.py (+10/-5) GTG/tests/__init__.py (+2/-0) GTG/tests/test_backends.py (+69/-69) GTG/tests/test_tree.py (+122/-0) GTG/tools/__init__.py (+3/-2) GTG/tools/logger.py (+64/-0) GTG/tools/taskxml.py (+2/-4) GTG/viewmanager/__init__.py (+33/-0) GTG/viewmanager/dbuswrapper.py (+161/-0) GTG/viewmanager/delete_dialog.py (+90/-0) GTG/viewmanager/deletion.glade (+164/-0) GTG/viewmanager/manager.py (+205/-0) GTG/viewmanager/preferences.glade (+554/-0) GTG/viewmanager/preferences.py (+374/-0) HACKING (+28/-2) Makefile (+5/-3) README (+1/-1) gtg (+2/-2) scripts/linecount.sh (+2/-1) Text conflict in GTG/taskbrowser/taskbrowser.glade |
To merge this branch: | bzr merge lp:~cjohnston/gtg/bugfix-lp-531053 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gtg developers | Pending | ||
Review via email: mp+20510@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Chris Johnston (cjohnston) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'GTG/__init__.py' |
2 | --- GTG/__init__.py 2009-12-28 14:08:19 +0000 |
3 | +++ GTG/__init__.py 2010-03-03 02:01:18 +0000 |
4 | @@ -16,6 +16,10 @@ |
5 | # You should have received a copy of the GNU General Public License along with |
6 | # this program. If not, see <http://www.gnu.org/licenses/>. |
7 | # ----------------------------------------------------------------------------- |
8 | +""" |
9 | +Getting Things Gnome! A personal organizer for the GNOME desktop |
10 | +""" |
11 | + |
12 | import os |
13 | import locale |
14 | #Fallback to LANG C if unsupported locale |
15 | |
16 | === modified file 'GTG/backends/__init__.py' |
17 | --- GTG/backends/__init__.py 2009-09-24 13:15:17 +0000 |
18 | +++ GTG/backends/__init__.py 2010-03-03 02:01:18 +0000 |
19 | @@ -18,10 +18,11 @@ |
20 | # ----------------------------------------------------------------------------- |
21 | |
22 | |
23 | -#This is the backends package. |
24 | -#Backends are a way to store permanently a project on a medium |
25 | -# (like on the hard disk or on the internet) |
26 | -#and to read projects from this medium |
27 | +""" |
28 | +Backends are a way to store permanently a project on a medium |
29 | +(like on the hard disk or on the internet) |
30 | +and to read projects from this medium |
31 | +""" |
32 | # |
33 | #Current backends are : |
34 | # |
35 | |
36 | === modified file 'GTG/backends/localfile.py' |
37 | --- GTG/backends/localfile.py 2009-12-03 09:47:09 +0000 |
38 | +++ GTG/backends/localfile.py 2010-03-03 02:01:18 +0000 |
39 | @@ -35,7 +35,7 @@ |
40 | def get_description(): |
41 | return "Your tasks are saved in an XML file located in your HOME folder" |
42 | |
43 | -#Return a dictionnary of parameters. Keys should be strings and |
44 | +#Return a dictionary of parameters. Keys should be strings and |
45 | #are the name of the parameter. |
46 | #Value are string with value : string, password, int, bool |
47 | #and are an information about the type of the parameter |
48 | @@ -48,17 +48,19 @@ |
49 | def get_features(): |
50 | return {} |
51 | |
52 | -#Types is one of : readwrite, readonly,import,export |
53 | +#Types is one of : readwrite, readonly, import, export |
54 | def get_type(): |
55 | return "readwrite" |
56 | |
57 | -#The parameters dictionnary should match the dictionnary returned in |
58 | +#The parameters dictionary should match the dictionary returned in |
59 | #get_parameters. Anyway, the backend should care if one expected value is |
60 | -#None or do not exist in the dictionnary. |
61 | +#None or do not exist in the dictionary. |
62 | #firstrun is only needed for the default backend. You can ignore it. |
63 | class Backend: |
64 | |
65 | def __init__(self, parameters, firstrunxml=None): |
66 | + self.tids = [] |
67 | + self.pid = 1 |
68 | if "filename" in parameters: |
69 | zefile = parameters["filename"] |
70 | #If zefile is None, we create a new file |
71 | @@ -73,36 +75,39 @@ |
72 | else: |
73 | self.zefile = zefile |
74 | self.filename = zefile |
75 | - #Create the defaut tasks for the first run. |
76 | + #Create the default tasks for the first run. |
77 | #We write the XML object in a file |
78 | if firstrunxml and not os.path.exists(zefile): |
79 | #shutil.copy(firstrunfile,self.zefile) |
80 | cleanxml.savexml(self.zefile, firstrunxml) |
81 | self.doc, self.xmlproj = cleanxml.openxmlfile(self.zefile, "project") |
82 | |
83 | - #Return the list of the task ID available in this backend |
84 | - def get_tasks_list(self): |
85 | - #time.sleep(2) |
86 | + #Once this function is launched, the backend can start pushing tasks to gtg |
87 | + #parameters : |
88 | + #1) push_task_func, a function that takes a Task as parameter and push it |
89 | + # into GTG. |
90 | + #2) task_factory_func, a function that takes a tid as parameter and returns |
91 | + # a Task object with the given pid. |
92 | + # |
93 | + # starst_get_tasks() might not return or finish |
94 | + def start_get_tasks(self,push_task_func,task_factory_func): |
95 | tid_list = [] |
96 | for node in self.xmlproj.childNodes: |
97 | - tid_list.append(node.getAttribute("id")) |
98 | - return tid_list |
99 | - |
100 | - |
101 | - #Fill the task "task_to_fill" with the information of the task TID |
102 | - #Return True if successful, False otherwhise |
103 | - def get_task(self, task_to_fill, tid): |
104 | -# import time |
105 | -# time.sleep(1) |
106 | - for node in self.xmlproj.childNodes: |
107 | - if node.getAttribute("id") == tid: |
108 | - return taskxml.task_from_xml(task_to_fill, node) |
109 | - return task_to_fill |
110 | + #time.sleep(2) |
111 | + tid = node.getAttribute("id") |
112 | + if tid not in self.tids: |
113 | + self.tids.append(tid) |
114 | + task = task_factory_func(tid) |
115 | + task = taskxml.task_from_xml(task,node) |
116 | + push_task_func(task) |
117 | + #print "#### finishing pushing tasks" |
118 | |
119 | #Save the task in the backend |
120 | def set_task(self, task): |
121 | #time.sleep(4) |
122 | tid = task.get_id() |
123 | + if tid not in self.tids: |
124 | + self.tids.append(tid) |
125 | existing = None |
126 | #First, we find the existing task from the treenode |
127 | for node in self.xmlproj.childNodes: |
128 | @@ -132,6 +137,8 @@ |
129 | for node in self.xmlproj.childNodes: |
130 | if node.getAttribute("id") == tid: |
131 | self.xmlproj.removeChild(node) |
132 | + if tid in self.tids: |
133 | + self.tids.remove(tid) |
134 | cleanxml.savexml(self.zefile, self.doc) |
135 | |
136 | #Return an available ID for a new task so that a task with this ID |
137 | @@ -139,7 +146,14 @@ |
138 | #If None, then GTG will create a new ID by itself |
139 | #The ID cannot contain the character "@" |
140 | def new_task_id(self): |
141 | - return None |
142 | + k = 0 |
143 | + pid = self.pid |
144 | + newid = "%s@%s" %(k, pid) |
145 | + while str(newid) in self.tids: |
146 | + k += 1 |
147 | + newid = "%s@%s" %(k, pid) |
148 | + self.tids.append(newid) |
149 | + return newid |
150 | |
151 | #Called when GTG quit or disconnect the backend |
152 | #You might pass here. |
153 | |
154 | === modified file 'GTG/core/__init__.py' |
155 | --- GTG/core/__init__.py 2009-12-28 15:30:05 +0000 |
156 | +++ GTG/core/__init__.py 2010-03-03 02:01:18 +0000 |
157 | @@ -18,30 +18,25 @@ |
158 | # ----------------------------------------------------------------------------- |
159 | |
160 | |
161 | -#This is the core package. It contains the core of GTG. |
162 | - |
163 | -#Current files are : |
164 | - |
165 | -#datastore.py |
166 | -#------------ |
167 | -#datastore is the heart of GTG. It contains a list of "TagSource". |
168 | -#Each TagSource is a proxy between a backend and the datastore itself |
169 | -# |
170 | -#tagstore.py |
171 | -#----------- |
172 | -#Tagstore is to tag as datastore is to task. Of course, the tagstore is easier |
173 | -#The Tag object is also provided in this file. |
174 | -# |
175 | -#task.py |
176 | -#------- |
177 | -#task.py contains the Task. A task represent, guess what,a task. |
178 | -# |
179 | -#requester.py |
180 | -#--------- |
181 | -#In order to not interact directly with the datastore, we provide "requesters" |
182 | -#The requester is only an interface and there can be as many requester as |
183 | -#you want as long as they are all from the same datastore. |
184 | -#Requester also provides an interface for the tagstore |
185 | +""" |
186 | +The core functionality GTG. |
187 | + |
188 | +In order to not interact directly with the datastore, we provide |
189 | +"requesters". The requester is only an interface and there can be as |
190 | +many requester as you want as long as they are all from the same |
191 | +datastore. Requester also provides an interface for the tagstore |
192 | + |
193 | +If you want to display only a subset of tasks, you can either: |
194 | + |
195 | + - have access to the main FilteredTree (the one displayed in the main |
196 | + window) and apply filters on it. (You can create your own) |
197 | + |
198 | + - get your own personal FilteredTree and apply on it the filters you |
199 | + want without interfering with the main view. (This is how the closed |
200 | + tasks pane is built currently) |
201 | + |
202 | +""" |
203 | + |
204 | |
205 | #=== IMPORT ==================================================================== |
206 | import os |
207 | |
208 | === modified file 'GTG/core/datastore.py' |
209 | --- GTG/core/datastore.py 2010-02-22 08:32:54 +0000 |
210 | +++ GTG/core/datastore.py 2010-03-03 02:01:18 +0000 |
211 | @@ -17,13 +17,17 @@ |
212 | # this program. If not, see <http://www.gnu.org/licenses/>. |
213 | # ----------------------------------------------------------------------------- |
214 | |
215 | +""" |
216 | +datastore contains a list of "TagSource", which are proxies between a backend and the datastore itself |
217 | +""" |
218 | + |
219 | import threading |
220 | import gobject |
221 | import time |
222 | -import sys |
223 | |
224 | from GTG.core import tagstore, requester |
225 | from GTG.core.task import Task |
226 | +from GTG.core.tree import Tree |
227 | |
228 | |
229 | #Only the datastore should access to the backend |
230 | @@ -37,105 +41,89 @@ |
231 | |
232 | def __init__(self): |
233 | self.backends = {} |
234 | - self.tasks = {} |
235 | + self.open_tasks = Tree() |
236 | + self.closed_tasks = Tree() |
237 | self.requester = requester.Requester(self) |
238 | self.tagstore = tagstore.TagStore(self.requester) |
239 | |
240 | def all_tasks(self): |
241 | - all_tasks = [] |
242 | - #We also add tasks that are still not in a backend (because of threads) |
243 | - tlist = self.tasks.keys() |
244 | - for t in tlist: |
245 | - #NOTE: Sometimes tids are not found in the datastore, therefore |
246 | - # the command "task = self.tasks[t]" fails. This is a temporary |
247 | - # fix until the problem is understood (Luca Invernizzi) |
248 | - if not self.tasks.has_key(t): |
249 | - sys.stderr.write('Tid does not exist in backend, skipping') |
250 | - continue |
251 | - task = self.tasks[t] |
252 | - if task.is_loaded(): |
253 | - all_tasks.append(t) |
254 | - else: |
255 | - if task.get_status() == "Active": |
256 | -# print "task %s is not loaded" %task.get_id() |
257 | -# print task.get_title() |
258 | - self.get_task(task.get_id()) |
259 | - #print "%s tasks but we return %s" %(len(tlist),len(all_tasks)) |
260 | - return all_tasks |
261 | + return self.open_tasks.get_all_keys() |
262 | |
263 | def has_task(self, tid): |
264 | - return tid in self.tasks |
265 | + return self.open_tasks.has_node(tid) or self.closed_tasks.has_node(tid) |
266 | |
267 | def get_task(self, tid): |
268 | -# if tid == "46@1": |
269 | -# print "getting 46@1" |
270 | - if tid in self.tasks: |
271 | - empty_task = self.tasks[tid] |
272 | - else: |
273 | - empty_task = self.new_task(tid, newtask=False) |
274 | - if tid and not empty_task.is_loaded(): |
275 | - uid, pid = tid.split('@') #pylint: disable-msg=W0612 |
276 | - back = self.backends[pid] |
277 | - task = back.get_task(empty_task, tid) |
278 | - else: |
279 | - task = empty_task |
280 | - #If the task doesn't exist, we create it with a forced pid |
281 | -# if not self.tasks[tid].is_loaded(): |
282 | -# print "tid %s - %s" %(tid,self.tasks[tid].get_title()) |
283 | -# for t in self.tasks: |
284 | -# print self.tasks[t] |
285 | -# print "----------------" |
286 | -# print "###########################" |
287 | + uid, pid = tid.split('@') |
288 | + if self.has_task(tid): |
289 | + task = self.__internal_get_task(tid) |
290 | + else: |
291 | + #print "no task %s" %tid |
292 | + task = None |
293 | return task |
294 | + |
295 | + def __internal_get_task(self, tid): |
296 | + toreturn = self.open_tasks.get_node(tid) |
297 | + if toreturn == None: |
298 | + self.closed_tasks.get_node(tid) |
299 | + #else: |
300 | + #print "error : this task doesn't exist in either tree" |
301 | + #pass |
302 | + #we return None if the task doesn't exist |
303 | + return toreturn |
304 | |
305 | def delete_task(self, tid): |
306 | - if tid and tid in self.tasks: |
307 | - self.tasks[tid].delete() |
308 | + if tid and self.has_task(tid): |
309 | + self.__internal_get_task(tid).delete() |
310 | uid, pid = tid.split('@') #pylint: disable-msg=W0612 |
311 | back = self.backends[pid] |
312 | #Check that the task still exist. It might have been deleted |
313 | #by its parent a few line earlier : |
314 | - if tid in self.tasks: |
315 | - self.tasks.pop(tid) |
316 | + if self.has_task(tid): |
317 | + self.open_tasks.remove_node(tid) |
318 | + self.closed_tasks.remove_node(tid) |
319 | back.remove_task(tid) |
320 | - |
321 | - #Create a new task and return it. |
322 | - #newtask should be True if you create a task |
323 | - #it should be False if you are importing an existing Task |
324 | - def new_task(self, tid=None, pid=None, newtask=False): |
325 | - #If we don't have anything, we use the default PID |
326 | + return True |
327 | + |
328 | + |
329 | + def new_task(self,pid=None): |
330 | if not pid: |
331 | pid = DEFAULT_BACKEND |
332 | - #If tid, we force that tid and create a real new task |
333 | - if tid and tid not in self.tasks: |
334 | - task = Task(tid, self.requester, newtask=newtask) |
335 | - bundle = tid.split('@') |
336 | - if not bundle: |
337 | - sys.stderr.write('Tid does not exist in backend') |
338 | - return None |
339 | - uid, pid = bundle |
340 | - self.tasks[tid] = task |
341 | - toreturn = task |
342 | - #Else we create a new task in the given pid |
343 | - elif not tid and pid and pid in self.backends: |
344 | + newtid = self.backends[pid].new_task_id() |
345 | + while self.has_task(newtid): |
346 | + print "error : tid already exists" |
347 | newtid = self.backends[pid].new_task_id() |
348 | - task = Task(newtid, self.requester, newtask=newtask) |
349 | - self.tasks[newtid] = task |
350 | - task = self.backends[pid].get_task(task, newtid) |
351 | - toreturn = task |
352 | - tid = newtid |
353 | - elif tid: |
354 | - toreturn = self.tasks[tid] |
355 | - else: |
356 | - print "not possible to build the task = bug" |
357 | - toreturn = None |
358 | - return toreturn |
359 | + task = Task(newtid, self.requester,newtask=True) |
360 | + self.open_tasks.add_node(task) |
361 | + task.set_sync_func(self.backends[pid].set_task,callsync=False) |
362 | + return task |
363 | |
364 | def get_tagstore(self): |
365 | return self.tagstore |
366 | |
367 | def get_requester(self): |
368 | return self.requester |
369 | + |
370 | + def get_tasks_tree(self): |
371 | + return self.open_tasks |
372 | + |
373 | + def push_task(self,task): |
374 | + tid = task.get_id() |
375 | + if self.has_task(tid): |
376 | + print "pushing an existing task. We should care about modifications" |
377 | + else: |
378 | + uid, pid = tid.split('@') |
379 | + self.open_tasks.add_node(task) |
380 | + task.set_loaded() |
381 | + task.set_sync_func(self.backends[pid].set_task,callsync=False) |
382 | + |
383 | + def task_factory(self,tid): |
384 | + task = None |
385 | + if self.has_task(tid): |
386 | + print "error : tid already exists" |
387 | + else: |
388 | + task = Task(tid, self.requester, newtask=False) |
389 | + return task |
390 | + |
391 | |
392 | def register_backend(self, dic): |
393 | if "backend" in dic: |
394 | @@ -146,7 +134,7 @@ |
395 | #Filling the backend |
396 | #Doing this at start is more efficient than |
397 | #after the GUI is launched |
398 | - source.get_tasks_list(func=self.refresh_tasklist) |
399 | + source.start_get_tasks(self.push_task,self.task_factory) |
400 | else: |
401 | print "Register a dic without backend key: BUG" |
402 | |
403 | @@ -159,12 +147,6 @@ |
404 | l.append(self.backends[key]) |
405 | return l |
406 | |
407 | - def refresh_tasklist(self, task_list): |
408 | - for tid in task_list: |
409 | - #Just calling new_task then get_task is enough |
410 | - self.new_task(tid=tid) |
411 | - self.get_task(tid) |
412 | - |
413 | #Task source is an transparent interface between the real backend and datastore |
414 | #Task source has also more functionnalities |
415 | |
416 | @@ -173,223 +155,57 @@ |
417 | def __init__(self, backend, parameters): |
418 | self.backend = backend |
419 | self.dic = parameters |
420 | - self.tasks = {} |
421 | - self.time = time.time() |
422 | - self.locks = lockslibrary() |
423 | - self.tosleep = 0 |
424 | - self.backend_lock = threading.Lock() |
425 | - self.removed = [] |
426 | - self.to_write = [] |
427 | - self.writing_lock = threading.Lock() |
428 | - self.to_get = [] |
429 | - self.getting_lock = threading.Lock() |
430 | - |
431 | -##### The Backend interface ############### |
432 | -########################################## |
433 | -# All functions here are proxied from the backend itself |
434 | - |
435 | - #Then test by putting some articial sleeps in the localfile.py |
436 | - def get_tasks_list(self, func): |
437 | - |
438 | - def getall(): |
439 | - #print "acquiring lock to getall" |
440 | - self.backend_lock.acquire() |
441 | - try: |
442 | - #print "acquired lock to getall" |
443 | - tlist = self.backend.get_tasks_list() |
444 | - for t in tlist: |
445 | - self.locks.create_lock(t) |
446 | - finally: |
447 | - self.backend_lock.release() |
448 | - #print "releasing lock to getall" |
449 | - func(tlist) |
450 | - if THREADING: |
451 | - t = threading.Thread(target=getall) |
452 | - t.start() |
453 | -# gobject.idle_add(getall) |
454 | - else: |
455 | - getall() |
456 | - return None |
457 | - |
458 | - def get_task(self, empty_task, tid): |
459 | - #Our thread |
460 | - def getting(): |
461 | - #self.locks.acquire(tid) |
462 | - try: |
463 | - while len(self.to_get) > 0: |
464 | - tid, empty_task = self.to_get.pop() |
465 | - #if self.locks.ifnotblocked(tid): |
466 | - self.backend.get_task(empty_task, tid) |
467 | - #calling sync in a thread might cause a segfault |
468 | - #thus callsync to false |
469 | - empty_task.set_sync_func(self.set_task, callsync=False) |
470 | - #set_loaded is a function that emits a signal. |
471 | - #Emiting a signal in a thread is likely to segfault |
472 | - #by wrapping it in idle_add, we ensure that gobject |
473 | - #mainloop handles the signal and not the tread itself. |
474 | - #it's not a problem to not know when it is executed |
475 | - #since it's the last instruction of the tread |
476 | - gobject.idle_add(empty_task.set_loaded) |
477 | - finally: |
478 | - #self.locks.release(tid) |
479 | - self.getting_lock.release() |
480 | - ########## |
481 | - task = None |
482 | - if tid in self.tasks: |
483 | - task = self.tasks[tid] |
484 | - if task: |
485 | -# print "already existing" |
486 | - empty_task = task |
487 | - #this might not be needed |
488 | - #gobject.idle_add(empty_task.set_loaded) |
489 | - #We will not try to get a removed task |
490 | - elif tid not in self.removed: |
491 | - #By putting the task in the dic, we say: |
492 | - #"This task is already fetched (or at least in fetching process) |
493 | - self.tasks[tid] = False |
494 | - self.to_get.append([tid, empty_task]) |
495 | - if self.getting_lock.acquire(False): |
496 | -# Disabling this to circumvent Bug #411420 |
497 | -# if THREADING: |
498 | -# self.locks.create_lock(tid) |
499 | -# gobject.idle_add(getting,empty_task,tid) |
500 | -# t = threading.Thread(target=getting) |
501 | -# t.start() |
502 | -# else: |
503 | -# self.locks.create_lock(tid) |
504 | -# #getting(empty_task,tid) |
505 | -# getting() |
506 | - getting() |
507 | - self.tasks[tid] = empty_task |
508 | - return empty_task |
509 | - |
510 | - #only one thread is used to write the task |
511 | - #if this thread is not started, we start it. |
512 | + self.to_set = [] |
513 | + self.to_remove = [] |
514 | + self.lock = threading.Lock() |
515 | + self.count_set = 0 |
516 | + |
517 | + ### TaskSource/bakcend mapping |
518 | + def start_get_tasks(self,push_task,task_factory): |
519 | + func = self.backend.start_get_tasks |
520 | + t = threading.Thread(target=func,args=(push_task,task_factory)) |
521 | + t.start() |
522 | + |
523 | def set_task(self, task): |
524 | - self.to_write.append(task) |
525 | - if self.writing_lock.acquire(False): |
526 | - if THREADING: |
527 | -# gobject.idle_add(self.__write,task) |
528 | - t = threading.Thread(target=self.__write) |
529 | - t.start() |
530 | - else: |
531 | - self.__write() |
532 | - return None |
533 | - |
534 | - #This function, called in a thread, write to the backend. |
535 | - #It acquires a lock to avoid multiple thread writing at the same time |
536 | - #the lock is writing_lock |
537 | - def __write(self): |
538 | + tid = task.get_id() |
539 | + if task not in self.to_set and tid not in self.to_remove: |
540 | + self.to_set.append(task) |
541 | + if self.lock.acquire(False): |
542 | + func = self.setting_thread |
543 | + t = threading.Thread(target=func) |
544 | + t.start() |
545 | +# else: |
546 | +# print "cannot acquire lock : not a problem, just for debug purpose" |
547 | + |
548 | + def setting_thread(self): |
549 | try: |
550 | - while len(self.to_write) > 0: |
551 | - task = self.to_write.pop() |
552 | - tid = task.get_id() |
553 | - if tid not in self.removed: |
554 | -# self.locks.acquire(tid) |
555 | -# try: |
556 | -# print self.locks.acquire(tid) |
557 | - self.backend.set_task(task) |
558 | -# finally: |
559 | -# self.locks.release(tid) |
560 | + while len(self.to_set) > 0: |
561 | + t = self.to_set.pop(0) |
562 | + tid = t.get_id() |
563 | + if tid not in self.to_remove: |
564 | + self.count_set += 1 |
565 | + #print "saving task %s (%s saves)" %(tid,self.count_set) |
566 | + self.backend.set_task(t) |
567 | + while len(self.to_remove) > 0: |
568 | + tid = self.to_remove.pop(0) |
569 | + self.backend.remove_task(tid) |
570 | finally: |
571 | - self.writing_lock.release() |
572 | - |
573 | - #TODO : This has to be threaded too |
574 | + self.lock.release() |
575 | + |
576 | def remove_task(self, tid): |
577 | - self.backend_lock.acquire() |
578 | - try: |
579 | - if tid not in self.removed: |
580 | - self.removed.append(tid) |
581 | - if self.locks.acquire(tid): |
582 | - toreturn = self.backend.remove_task(tid) |
583 | - self.tasks.pop(tid) |
584 | - self.locks.remove_lock(tid) |
585 | - else: |
586 | - toreturn = False |
587 | - finally: |
588 | - self.backend_lock.release() |
589 | - return toreturn |
590 | - |
591 | - #TODO: This has to be threaded too |
592 | + if tid not in self.to_remove: |
593 | + self.to_remove.append(tid) |
594 | + if self.lock.acquire(False): |
595 | + func = self.setting_thread |
596 | + t = threading.Thread(target=func) |
597 | + t.start() |
598 | + |
599 | def new_task_id(self): |
600 | - newid = self.backend.new_task_id() |
601 | - if not newid: |
602 | - k = 0 |
603 | - pid = self.dic["pid"] |
604 | - newid = "%s@%s" %(k, pid) |
605 | - while str(newid) in self.tasks: |
606 | - k += 1 |
607 | - newid = "%s@%s" %(k, pid) |
608 | - if newid in self.removed: |
609 | - self.removed.remove(newid) |
610 | - self.locks.create_lock(newid) |
611 | - return newid |
612 | - |
613 | - #TODO : This has to be threaded too |
614 | + return self.backend.new_task_id() |
615 | + |
616 | def quit(self): |
617 | - return self.backend.quit() |
618 | - |
619 | -########## End of Backend interface ########### |
620 | -############################################### |
621 | - |
622 | -#Those functions are only for TaskSource |
623 | + self.backend.quit() |
624 | + |
625 | + #Those functions are only for TaskSource |
626 | def get_parameters(self): |
627 | return self.dic |
628 | - |
629 | -#This is the lock library. Each task has a lock to avoir concurrency |
630 | -#on the same task when writing/reading on/from the backend |
631 | - |
632 | -class lockslibrary: |
633 | - |
634 | - def __init__(self): |
635 | - self.locks = {} |
636 | - #The lock library itself is protected by a lock to avoid deadlock |
637 | - self.glob = threading.Lock() |
638 | - |
639 | - def create_lock(self, tid): |
640 | - self.glob.acquire() |
641 | - try: |
642 | - if tid not in self.locks: |
643 | - self.locks[tid] = threading.Lock() |
644 | - finally: |
645 | - self.glob.release() |
646 | - |
647 | - #To be removed, a lock should be acquired before ! |
648 | - #So acquire the lock before calling this function ! |
649 | - def remove_lock(self, tid): |
650 | - if self.glob.acquire(False): |
651 | - if tid in self.locks: |
652 | - zelock = self.locks[tid] |
653 | - self.locks.pop(tid) |
654 | - zelock.release() |
655 | - self.glob.release() |
656 | - #else: |
657 | - #print "This is a very rare bug : we were unable to remove the lock" |
658 | - #But this not really a problem because the lock alone does do anything |
659 | - def ifnotblocked(self, tid): |
660 | - self.glob.acquire() |
661 | - try: |
662 | - if tid in self.locks: |
663 | - return self.locks[tid].acquire(False) |
664 | - else: |
665 | - print "ifnotblock on non-existing lock %s = BUG" %tid |
666 | - finally: |
667 | - self.glob.release() |
668 | - |
669 | - def acquire(self, tid): |
670 | - self.glob.acquire() |
671 | - try: |
672 | - if tid in self.locks: |
673 | - self.locks[tid].acquire() |
674 | - toreturn = True |
675 | - else: |
676 | - toreturn = False |
677 | - finally: |
678 | - self.glob.release() |
679 | - return toreturn |
680 | - |
681 | - def release(self, tid): |
682 | - if tid in self.locks: |
683 | - self.locks[tid].release() |
684 | -# else: |
685 | -# print "removing non-existing lock = BUG" |
686 | |
687 | === removed file 'GTG/core/dbuswrapper.py' |
688 | --- GTG/core/dbuswrapper.py 2010-02-27 03:45:52 +0000 |
689 | +++ GTG/core/dbuswrapper.py 1970-01-01 00:00:00 +0000 |
690 | @@ -1,168 +0,0 @@ |
691 | -import dbus |
692 | -import dbus.glib |
693 | -import dbus.service |
694 | -import unicodedata |
695 | - |
696 | -from GTG.core import CoreConfig |
697 | -from GTG.tools import dates |
698 | - |
699 | -BUSNAME = CoreConfig.BUSNAME |
700 | -BUSFACE = CoreConfig.BUSINTERFACE |
701 | - |
702 | - |
703 | -def dsanitize(data): |
704 | - # Clean up a dict so that it can be transmitted through D-Bus |
705 | - for k, v in data.items(): |
706 | - # Manually specify an arbitrary content type for empty Python arrays |
707 | - # because D-Bus can't handle the type conversion for empty arrays |
708 | - if not v and isinstance(v, list): |
709 | - data[k] = dbus.Array([], "s") |
710 | - # D-Bus has no concept of a null or empty value so we have to convert |
711 | - # None types to something else. I use an empty string because it has |
712 | - # the same behavior as None in a Python conditional expression |
713 | - elif v == None: |
714 | - data[k] = "" |
715 | - |
716 | - return data |
717 | - |
718 | - |
719 | -def task_to_dict(task): |
720 | - # Translate a task object into a D-Bus dictionary |
721 | - return dbus.Dictionary(dsanitize({ |
722 | - "id": task.get_id(), |
723 | - "status": task.get_status(), |
724 | - "title": task.get_title(), |
725 | - "duedate": str(task.get_due_date()), |
726 | - "startdate": str(task.get_start_date()), |
727 | - "donedate": str(task.get_closed_date()), |
728 | - "tags": task.get_tags_name(), |
729 | - "text": task.get_text(), |
730 | - "subtask": task.get_subtask_tids(), |
731 | - }), signature="sv") |
732 | - |
733 | - |
734 | -class DBusTaskWrapper(dbus.service.Object): |
735 | - |
736 | - # D-Bus service object that exposes GTG's task store to third-party apps |
737 | - def __init__(self, req, ui): |
738 | - # Attach the object to D-Bus |
739 | - self.bus = dbus.SessionBus() |
740 | - bus_name = dbus.service.BusName(BUSNAME, bus=self.bus) |
741 | - dbus.service.Object.__init__(self, bus_name, BUSFACE) |
742 | - self.req = req |
743 | - self.ui = ui |
744 | - |
745 | - |
746 | - @dbus.service.method(BUSNAME,in_signature="s") |
747 | - def get_task_ids(self, status_string): |
748 | - # Retrieve a list of task ID values |
749 | - status = [s.strip() for s in status_string.split(',')] |
750 | - #need to convert the statuses to ascii (these are given in unicode) |
751 | - status = [unicodedata.normalize('NFKD', s).encode('ascii','ignore') \ |
752 | - for s in status] |
753 | - return self.req.get_tasks_list(status = status) |
754 | - |
755 | - @dbus.service.method(BUSNAME) |
756 | - def get_task(self, tid): |
757 | - # Retrieve a specific task by ID and return the data |
758 | - toret = task_to_dict(self.req.get_task(tid)) |
759 | - return toret |
760 | - |
761 | - @dbus.service.method(BUSNAME) |
762 | - def get_tasks(self): |
763 | - # Retrieve a list of task data dicts |
764 | - return [self.get_task(id) for id in self.get_task_ids(u"Active")] |
765 | - |
766 | - @dbus.service.method(BUSNAME, in_signature="asasbb") |
767 | - def get_task_ids_filtered(self, tags, status, started_only, is_root): |
768 | - # Retrieve a list of task IDs filtered by specified parameters |
769 | - tags_obj = [] |
770 | - for t in tags: |
771 | - zetag = self.req.get_tag(t) |
772 | - if zetag: |
773 | - tags_obj.append(zetag) |
774 | - ids = self.req.get_tasks_list( |
775 | - tags_obj, status, False, started_only, is_root) |
776 | - # If there are no matching tasks, return an empty D-Bus array |
777 | - return ids if ids else dbus.Array([], "s") |
778 | - |
779 | - @dbus.service.method(BUSNAME, in_signature="asasbb") |
780 | - def get_tasks_filtered(self, tags, status, started_only, is_root): |
781 | - # Retrieve a list of task data dicts filtered by specificed parameters |
782 | - tasks = self.get_task_ids_filtered( |
783 | - tags, status, started_only, is_root) |
784 | - # If no tasks match the filter, return an empty D-Bus array |
785 | - if tasks: |
786 | - return [self.get_task(id) for id in tasks] |
787 | - else: |
788 | - return dbus.Array([], "s") |
789 | - |
790 | - @dbus.service.method(BUSNAME) |
791 | - def has_task(self, tid): |
792 | - return self.req.has_task(tid) |
793 | - |
794 | - @dbus.service.method(BUSNAME) |
795 | - def delete_task(self, tid): |
796 | - self.req.delete_task(tid) |
797 | - |
798 | - @dbus.service.method(BUSNAME, in_signature="sssssassas") |
799 | - def new_task(self, status, title, duedate, startdate, donedate, tags, |
800 | - text, subtasks): |
801 | - # Generate a new task object and return the task data as a dict |
802 | - tags_objects = [] |
803 | - for t in tags: |
804 | - #we have to create the tag objects if we don't have them |
805 | - tag_object = self.req.get_tag(t) |
806 | - if tag_object == None: |
807 | - tag_object = self.req.new_tag(t) |
808 | - tags_objects.append(tag_object) |
809 | - nt = self.req.new_task(tags=tags_objects) |
810 | - for sub in subtasks: |
811 | - nt.add_subtask(sub) |
812 | - nt.set_status(status, donedate=dates.strtodate(donedate)) |
813 | - nt.set_title(title) |
814 | - nt.set_due_date(dates.strtodate(duedate)) |
815 | - nt.set_start_date(dates.strtodate(startdate)) |
816 | - nt.set_text(text) |
817 | - return task_to_dict(nt) |
818 | - |
819 | - @dbus.service.method(BUSNAME) |
820 | - def modify_task(self, tid, task_data): |
821 | - # Apply supplied task data to the task object with the specified ID |
822 | - task = self.req.get_task(tid) |
823 | - task.set_status(task_data["status"], donedate=task_data["donedate"]) |
824 | - task.set_title(task_data["title"]) |
825 | - task.set_due_date(dates.strtodate(task_data["duedate"])) |
826 | - task.set_start_date(dates.strtodate(task_data["startdate"])) |
827 | - task.set_text(task_data["text"]) |
828 | - |
829 | - for tag in task_data["tags"]: |
830 | - task.add_tag(tag) |
831 | - for sub in task_data["subtask"]: |
832 | - task.add_subtask(sub) |
833 | - return task_to_dict(task) |
834 | - |
835 | - @dbus.service.method(BUSNAME) |
836 | - def open_task_editor(self, tid): |
837 | - self.ui.open_task(tid) |
838 | - |
839 | - @dbus.service.method(BUSNAME, in_signature="ss") |
840 | - def open_new_task(self, title, description): |
841 | - nt = self.req.new_task(newtask=True) |
842 | - nt.set_title(title) |
843 | - if description != "": |
844 | - nt.set_text(description) |
845 | - uid = nt.get_id() |
846 | - self.ui.open_task(uid,thisisnew=True) |
847 | - |
848 | - @dbus.service.method(BUSNAME) |
849 | - def hide_task_browser(self): |
850 | - self.ui.window.hide() |
851 | - |
852 | - @dbus.service.method(BUSNAME) |
853 | - def show_task_browser(self): |
854 | - self.ui.window.present() |
855 | - self.ui.window.move( |
856 | - self.ui.priv["window_xpos"], self.ui.priv["window_ypos"]) |
857 | - gdk_window = self.ui.window |
858 | - gdk_window.show() |
859 | |
860 | === added file 'GTG/core/filteredtree.py' |
861 | --- GTG/core/filteredtree.py 1970-01-01 00:00:00 +0000 |
862 | +++ GTG/core/filteredtree.py 2010-03-03 02:01:18 +0000 |
863 | @@ -0,0 +1,594 @@ |
864 | +# -*- coding: utf-8 -*- |
865 | +# ----------------------------------------------------------------------------- |
866 | +# Gettings Things Gnome! - a personal organizer for the GNOME desktop |
867 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
868 | +# |
869 | +# This program is free software: you can redistribute it and/or modify it under |
870 | +# the terms of the GNU General Public License as published by the Free Software |
871 | +# Foundation, either version 3 of the License, or (at your option) any later |
872 | +# version. |
873 | +# |
874 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
875 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
876 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
877 | +# details. |
878 | +# |
879 | +# You should have received a copy of the GNU General Public License along with |
880 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
881 | +# ----------------------------------------------------------------------------- |
882 | +# |
883 | +""" |
884 | +FilteredTree provides a filtered view (subset) of tasks |
885 | + |
886 | +FilteredTree |
887 | +============ |
888 | +The problem we have is that, sometimes, we don't want to display all tasks. |
889 | +We want tasks to be filtered (workview, tags, âĤ) |
890 | + |
891 | +The expected approach would be to put a gtk.TreeModelFilter above our |
892 | +TaskTree. Unfortunatly, this doesn't work because TreeModelFilter hides |
893 | +all children of hidden nodes (not what we want!) |
894 | + |
895 | +The solution we have found is to insert a fake tree between Tree and |
896 | +TaskTree. This fake tree is called FilteredTree and will map path and |
897 | +node methods to a result corresponding to the filtered tree. |
898 | + |
899 | +Note that the nodes are not aware that they are in a filtered tree. |
900 | +Use the FilteredTree methods, not the node methods directly. |
901 | +If you believe a function would be useful in a filtered tree, don't |
902 | +hesitate to make a proposal. |
903 | + |
904 | +To be more efficient, a quick way to optimize the FilteredTree is to cache |
905 | +all answers in a dictionary so we don't have to compute the answer |
906 | +all the time. This is not done yet. |
907 | + |
908 | +B{Warning}: this is very fragile. Calls to any GTK registered view should be |
909 | +perfecly in sync with changes in the underlying model. |
910 | +We definitely should develop some unit tests for this class. |
911 | + |
912 | +Structure of the source: |
913 | + |
914 | + 1. Standard tree functions mapping (get_node, get_all_nodes, get_all_keys) |
915 | + 2. Receiving signal functions ( task-added,task-modified,task-deleted) |
916 | + 3. Treemodel helper functions. To make it easy to build a treemodel on top. |
917 | + 4. Filtering : is_displayed() and refilter() |
918 | + 5. Changing the filters (not for the main FilteredTree) |
919 | + 6. Private helpers. |
920 | + |
921 | +There's one main FilteredTree that you can get through the requester. This |
922 | +main FilteredTree does use the filters applied throught the requester. This |
923 | +allow plugin writers to easily get the current displayed tree (main View). |
924 | + |
925 | +For custom views, the plugin writers are able to get their own |
926 | +FilteredTree and apply on it the filters they want. (this is not finished |
927 | +yet but in good shape). |
928 | + |
929 | +""" |
930 | + |
931 | +import gobject |
932 | + |
933 | +class FilteredTree(gobject.GObject): |
934 | + |
935 | + #Those are the three signals you want to catch if displaying |
936 | + #a filteredtree. The argument of all signals is the tid of the task |
937 | + __gsignals__ = {'task-added-inview': (gobject.SIGNAL_RUN_FIRST, \ |
938 | + gobject.TYPE_NONE, (str, )), |
939 | + 'task-deleted-inview': (gobject.SIGNAL_RUN_FIRST, \ |
940 | + gobject.TYPE_NONE, (str, )), |
941 | + 'task-modified-inview': (gobject.SIGNAL_RUN_FIRST, \ |
942 | + gobject.TYPE_NONE, (str, )),} |
943 | + |
944 | + def __init__(self,req,tree,maintree=False): |
945 | + """ |
946 | + Construct a FilteredTree object on top of an existing task tree. |
947 | + @param req: The requestor object |
948 | + @param tree: The tree to filter from |
949 | + @param maintree: Whether this tree is the main tree. The requester |
950 | + must be used to change filters against the main tree. |
951 | + """ |
952 | + gobject.GObject.__init__(self) |
953 | + self.is_main = maintree |
954 | + self.applied_filters = [] |
955 | + self.req = req |
956 | + self.tree = tree |
957 | + self.update_count = 0 |
958 | + self.add_count = 0 |
959 | + self.remove_count = 0 |
960 | + #virtual root is the list of root nodes |
961 | + #initially, they are the root nodes of the original tree |
962 | + self.virtual_root = [] |
963 | + self.displayed_nodes = [] |
964 | + #useful for temp storage : |
965 | + self.node_to_add = [] |
966 | + #it looks like an initial refilter is not needed. |
967 | + #self.refilter() |
968 | + self.__reset_cache() |
969 | + #connecting |
970 | + self.req.connect("task-added", self.__task_added) |
971 | + self.req.connect("task-modified", self.__task_modified) |
972 | + self.req.connect("task-deleted", self.__task_deleted) |
973 | + |
974 | + def __reset_cache(self): |
975 | + self.path_for_node_cache = {} |
976 | + |
977 | + #### Standard tree functions |
978 | + def get_node(self,id): |
979 | + """ |
980 | + Retrieves the given node |
981 | + @param id: The tid of the task node |
982 | + @return: Node from the underlying tree |
983 | + """ |
984 | + return self.tree.get_node(id) |
985 | + |
986 | + def get_root(self): |
987 | + """ |
988 | + returns the root node |
989 | + """ |
990 | + return self.tree.get_root() |
991 | + |
992 | + def get_all_keys(self): |
993 | + """ |
994 | + returns list of all displayed node keys |
995 | + """ |
996 | + return list(self.displayed_nodes) |
997 | + |
998 | + def get_all_nodes(self): |
999 | + """ |
1000 | + returns list of all nodes |
1001 | + """ |
1002 | + k = [] |
1003 | + for n in self.get_all_nodes(): |
1004 | + k.append(self.get_node(n)) |
1005 | + return k |
1006 | + |
1007 | + def get_n_nodes(self): |
1008 | + """ |
1009 | + returns quantity of displayed nodes in this tree |
1010 | + """ |
1011 | + return len(self.displayed_nodes) |
1012 | + |
1013 | + ### signals functions |
1014 | + def __task_added(self,sender,tid): |
1015 | + todis = self.__is_displayed(tid) |
1016 | + curdis = self.is_displayed(tid) |
1017 | + if todis and not curdis: |
1018 | + self.__add_node(tid) |
1019 | + |
1020 | + def __task_modified(self,sender,tid): |
1021 | + todis = self.__is_displayed(tid) |
1022 | + curdis = self.is_displayed(tid) |
1023 | + if todis: |
1024 | + #if the task was not displayed previously but now should |
1025 | + #we add it. |
1026 | + if not curdis: |
1027 | + self.__add_node(tid) |
1028 | + #There doesn't seem to be a need for calling the update_node |
1029 | +# else: |
1030 | +# task = self.get_node(tid) |
1031 | +# inroot = self.__is_root(task) |
1032 | +# self.__update_node(tid,inroot) |
1033 | + else: |
1034 | + #if the task was displayed previously but shouldn't be anymore |
1035 | + #we remove it |
1036 | + if curdis: |
1037 | + self.__remove_node(tid) |
1038 | + |
1039 | + def __task_deleted(self,sender,tid): |
1040 | + self.__remove_node(tid) |
1041 | + |
1042 | + ####TreeModel functions ############################## |
1043 | + |
1044 | + #The path received is only for tasks that are displayed |
1045 | + #We have to find the good node. |
1046 | + def get_node_for_path(self, path): |
1047 | + """ |
1048 | + Returns node for the given path. |
1049 | + """ |
1050 | + #We should convert the path to the base.path |
1051 | + if str(path) == '()': |
1052 | + print "WEÂ SHOULDÂ RETURNÂ ROOTÂ NODE" |
1053 | + p0 = path[0] |
1054 | + if len(self.virtual_root) > p0: |
1055 | + n1id = self.virtual_root[p0] |
1056 | + n1 = self.get_node(n1id) |
1057 | + pa = path[1:] |
1058 | + toreturn = self.__node_for_path(n1,pa) |
1059 | + else: |
1060 | + toreturn = None |
1061 | + return toreturn |
1062 | + |
1063 | + def __node_for_path(self,basenode,path): |
1064 | + if len(path) == 0: |
1065 | + return basenode |
1066 | + elif path[0] < self.node_n_children(basenode): |
1067 | + if len(path) == 1: |
1068 | + return self.node_nth_child(basenode,path[0]) |
1069 | + else: |
1070 | + node = self.node_nth_child(basenode,path[0]) |
1071 | + path = path[1:] |
1072 | + return self.__node_for_path(node, path) |
1073 | + else: |
1074 | + return None |
1075 | + |
1076 | + def get_path_for_node(self, node): |
1077 | + """ |
1078 | + Return a path for a given node |
1079 | + """ |
1080 | + #For that node, we should convert the base_path to path |
1081 | + if not node or not self.is_displayed(node.get_id()): |
1082 | + return None |
1083 | + #This is the cache so we don't compute it all the time |
1084 | + elif self.path_for_node_cache.has_key(node): |
1085 | + return self.path_for_node_cache[node] |
1086 | + elif node == self.get_root(): |
1087 | + toreturn = () |
1088 | + elif node.get_id() in self.virtual_root: |
1089 | + ind = self.virtual_root.index(node.get_id()) |
1090 | + toreturn = (ind,) |
1091 | + #The node is not a virtual root |
1092 | + else: |
1093 | + pos = 0 |
1094 | + par = self.node_parent(node) |
1095 | + max = self.node_n_children(par) |
1096 | + child = self.node_children(par) |
1097 | + while pos < max and node != child: |
1098 | + pos += 1 |
1099 | + child = self.node_nth_child(par,pos) |
1100 | + par_path = self.get_path_for_node(par) |
1101 | + if par_path: |
1102 | + toreturn = par_path + (pos,) |
1103 | + else: |
1104 | + print "*** Node %s not in vr and no path for parent" %(node.get_id()) |
1105 | + print "*** please report a bug against FilteredTree" |
1106 | + toreturn = None |
1107 | + #print "get_path_for_node %s is %s" %(node.get_id(),str(toreturn)) |
1108 | +# self.path_for_node_cache[node] = toreturn |
1109 | + return toreturn |
1110 | + |
1111 | + #Done |
1112 | + def next_node(self, node): |
1113 | + """ |
1114 | + Returns the next sibling node, or None if there are no other siblings |
1115 | + """ |
1116 | + #print "on_iter_next for node %s" %node |
1117 | + #We should take the next good node, not the next base node |
1118 | + if node: |
1119 | + tid = node.get_id() |
1120 | + if tid in self.virtual_root: |
1121 | + i = self.virtual_root.index(tid) + 1 |
1122 | + if len(self.virtual_root) > i: |
1123 | + nextnode_id = self.virtual_root[i] |
1124 | + nextnode = self.get_node(nextnode_id) |
1125 | + else: |
1126 | + nextnode = None |
1127 | + else: |
1128 | + parent_node = self.node_parent(node) |
1129 | + if parent_node: |
1130 | + next_idx = parent_node.get_child_index(node.get_id()) + 1 |
1131 | + total = parent_node.get_n_children()-1 |
1132 | + if total < next_idx: |
1133 | + nextnode = None |
1134 | + else: |
1135 | + nextnode = parent_node.get_nth_child(next_idx) |
1136 | + while next_idx < total and not self.is_displayed(nextnode.get_id()): |
1137 | + next_idx += 1 |
1138 | + nextnode = parent_node.get_nth_child(next_idx) |
1139 | + else: |
1140 | + nextnode = None |
1141 | + else: |
1142 | + nextnode = None |
1143 | + return nextnode |
1144 | + |
1145 | + #Done |
1146 | + def node_children(self, parent): |
1147 | + """ |
1148 | + Returns the first child node of the given parent, or None |
1149 | + if the parent has no children. |
1150 | + @param parent: The parent node or None to retrieve the children |
1151 | + of the virtual root. |
1152 | + """ |
1153 | + #print "on_iter_children for parent %s" %parent.get_id() |
1154 | + #here, we should return only good childrens |
1155 | + if parent: |
1156 | + if self.node_has_child(parent): |
1157 | + child = self.node_nth_child(parent,0) |
1158 | + else: |
1159 | + #The spec says that the child can be None |
1160 | + child = None |
1161 | + else: |
1162 | + child = self.virtual_root[0] |
1163 | + return child |
1164 | + |
1165 | + #Done |
1166 | + def node_has_child(self, node): |
1167 | + """ |
1168 | + Returns true if the given node has any children |
1169 | + """ |
1170 | + #print "on_iter_has_child for node %s" %node |
1171 | + #we should say "has_good_child" |
1172 | + if node and self.node_n_children(node)>0: |
1173 | + return True |
1174 | + else: |
1175 | + if not node: |
1176 | + print "NODE IS NULL, we should maybe return True" |
1177 | + return False |
1178 | + |
1179 | + #Done |
1180 | + def node_n_children(self, node): |
1181 | + """ |
1182 | + Returns number of children for the given node |
1183 | + """ |
1184 | + #we should return the number of "good" children |
1185 | + if not node: |
1186 | + toreturn = len(self.virtual_root) |
1187 | + id = 'root' |
1188 | + else: |
1189 | + n = 0 |
1190 | + for cid in node.get_children(): |
1191 | + if self.is_displayed(cid): |
1192 | + n+= 1 |
1193 | + toreturn = n |
1194 | + return toreturn |
1195 | + |
1196 | + #Done |
1197 | + def node_nth_child(self, node, n): |
1198 | + """ |
1199 | + Retrieves the nth child of the node. |
1200 | + @param node: The parent node, or None to look at children of the |
1201 | + virtual_root. |
1202 | + """ |
1203 | + #we return the nth good children ! |
1204 | + if not node: |
1205 | + if len(self.virtual_root) > n: |
1206 | + to_id = self.virtual_root[n] |
1207 | + toreturn = self.get_node(to_id) |
1208 | + else: |
1209 | + toreturn = None |
1210 | + else: |
1211 | + total = node.get_n_children() |
1212 | + cur = 0 |
1213 | + good = 0 |
1214 | + toreturn = None |
1215 | + while good <= n and cur < total: |
1216 | + curn = node.get_nth_child(cur) |
1217 | + if curn and self.is_displayed(curn.get_id()): |
1218 | + if good == n: |
1219 | + toreturn = curn |
1220 | + good += 1 |
1221 | + cur += 1 |
1222 | + return toreturn |
1223 | + |
1224 | + #Done |
1225 | + def node_parent(self, node): |
1226 | + """ |
1227 | + Returns parent of the given node, or None if there is no |
1228 | + parent (such as if the node is a child of the virtual root), |
1229 | + or if the parent is not displayable. |
1230 | + """ |
1231 | + #return None if we are at a Virtual root |
1232 | + tid = node.get_id() |
1233 | + if node and tid in self.virtual_root: |
1234 | + return None |
1235 | + elif node and node.has_parent(): |
1236 | + parent_id = node.get_parent() |
1237 | + parent = self.tree.get_node(parent_id) |
1238 | + if parent == self.tree.get_root(): |
1239 | + return None |
1240 | + elif self.is_displayed(parent_id): |
1241 | + return parent |
1242 | + else: |
1243 | + return None |
1244 | + else: |
1245 | + return None |
1246 | + |
1247 | + |
1248 | + ####Â Filtering methods ######### |
1249 | + |
1250 | + def is_displayed(self,tid): |
1251 | + """ |
1252 | + This is a public method that return True if the task is |
1253 | + currently displayed in the tree |
1254 | + """ |
1255 | + if tid: |
1256 | + return tid in self.displayed_nodes |
1257 | + else: |
1258 | + toreturn = False |
1259 | + return toreturn |
1260 | + |
1261 | + def __is_displayed(self, tid): |
1262 | + """ |
1263 | + This is a private method that return True if the task *should* |
1264 | + be displayed in the tree, regardless of its current status |
1265 | + """ |
1266 | + if tid: |
1267 | + result = True |
1268 | + for f in self.applied_filters: |
1269 | + filt = self.req.get_filter(f) |
1270 | + if filt: |
1271 | + result = result and filt.is_displayed(tid) |
1272 | + return result |
1273 | + else: |
1274 | + return False |
1275 | + |
1276 | + def refilter(self): |
1277 | + """ |
1278 | + rebuilds the tree from scratch. It should be called only when |
1279 | + the filter is changed (i.e. only filters_bank should call it). |
1280 | + """ |
1281 | + self.update_count = 0 |
1282 | + self.add_count = 0 |
1283 | + self.remove_count = 0 |
1284 | + virtual_root2 = [] |
1285 | + to_add = [] |
1286 | + #First things, we list the nodes that will be |
1287 | + #ultimately displayed |
1288 | + for n in self.tree.get_all_nodes(): |
1289 | + tid = n.get_id() |
1290 | + is_root = False |
1291 | + if self.__is_displayed(tid): |
1292 | + to_add.append(tid) |
1293 | + is_root = self.__is_root(n) |
1294 | + #and we care about those who will be virtual roots |
1295 | + #(their parents are not displayed) |
1296 | + if is_root and tid not in virtual_root2: |
1297 | + virtual_root2.append(tid) |
1298 | + |
1299 | + #Second step, we empty the current tree as we will rebuild it |
1300 | + #from scratch |
1301 | + for rid in list(self.virtual_root): |
1302 | + n = self.get_node(rid) |
1303 | + self.__clean_from_node(n) |
1304 | + self.__reset_cache() |
1305 | + |
1306 | + #Here, we reconstruct our filtered trees. It cannot be random |
1307 | + # Parents should be added before their children |
1308 | + #First, we start we the nodes in the virtual root |
1309 | + for nid in list(to_add): |
1310 | + isroot = nid in virtual_root2 |
1311 | + self.__add_node(nid,isroot) |
1312 | + #end of refiltering |
1313 | + |
1314 | + ####### Change filters ################# |
1315 | + def apply_filter(self,filter_name,parameters=None,imtherequester=False): |
1316 | + """ |
1317 | + Applies a new filter to the tree. Can't be called on the main tree. |
1318 | + @param filter_name: The name of an already registered filter to apply |
1319 | + @param parameters: Optional parameters to pass to the filter |
1320 | + @param imtherequester: If true enables adding filters to the main tree |
1321 | + """ |
1322 | + if self.is_main and not imtherequester: |
1323 | + print "Error : use the requester to apply a filter to the main tree" |
1324 | + print "We don't do that automatically on purpose" |
1325 | + else: |
1326 | + if parameters: |
1327 | + filt = self.req.get_filter(filter_name) |
1328 | + if filt: |
1329 | + filt.set_parameters(parameters) |
1330 | + if filter_name not in self.applied_filters: |
1331 | + self.applied_filters.append(filter_name) |
1332 | + self.refilter() |
1333 | + return True |
1334 | + return False |
1335 | + |
1336 | + def unapply_filter(self,filter_name,imtherequester=False): |
1337 | + """ |
1338 | + Removes a filter from the tree. Can't be called on the main tree. |
1339 | + @param filter_name: The name of an already added filter to remove |
1340 | + @param imtherequester: If true enables removing filters from the main tree |
1341 | + """ |
1342 | + if self.is_main and not imtherequester: |
1343 | + print "Error : use the requester to remove a filter to the main tree" |
1344 | + print "We don't do that automatically on purpose" |
1345 | + elif filter_name in self.applied_filters: |
1346 | + self.applied_filters.remove(filter_name) |
1347 | + self.refilter() |
1348 | + return True |
1349 | + return False |
1350 | + |
1351 | + def reset_filters(self,imtherequester=False): |
1352 | + """ |
1353 | + Clears all filters currently set on the tree. Can't be called on |
1354 | + the main tree. |
1355 | + @param imtherequester: If true enables clearing filters from the main tree |
1356 | + """ |
1357 | + if self.is_main and not imtherequester: |
1358 | + print "Error : use the requester to remove a filter to the main tree" |
1359 | + print "We don't do that automatically on purpose" |
1360 | + else: |
1361 | + self.applied_filters = [] |
1362 | + self.refilter() |
1363 | + |
1364 | + def reset_tag_filters(self,refilter=True,imtherequester=False): |
1365 | + """ |
1366 | + Clears all filters currently set on the tree. Can't be called on |
1367 | + the main tree. |
1368 | + @param imtherequester: If true enables clearing filters from the main tree |
1369 | + """ |
1370 | + if self.is_main and not imtherequester: |
1371 | + print "Error : use the requester to remove a filter to the main tree" |
1372 | + print "We don't do that automatically on purpose" |
1373 | + else: |
1374 | + if "notag" in self.applied_filters: |
1375 | + self.applied_filters.remove('notag') |
1376 | + for f in self.applied_filters: |
1377 | + if f.startswith('@'): |
1378 | + self.applied_filters.remove(f) |
1379 | + if refilter: |
1380 | + self.refilter() |
1381 | + |
1382 | + ####### Private methods ################# |
1383 | + |
1384 | + # Return True if the node should be a virtual root node |
1385 | + # regardless of the current state |
1386 | + def __is_root(self,n): |
1387 | + is_root = True |
1388 | + if n.has_parent(): |
1389 | + for par in n.get_parents(): |
1390 | + if self.__is_displayed(par): |
1391 | + is_root = False |
1392 | + return is_root |
1393 | + |
1394 | + # Put or remove a node from the virtual root |
1395 | + def __root_update(self,tid,inroot): |
1396 | + if inroot: |
1397 | + if tid not in self.virtual_root: |
1398 | + self.virtual_root.append(tid) |
1399 | + else: |
1400 | + if tid in self.virtual_root: |
1401 | + self.virtual_root.remove(tid) |
1402 | + |
1403 | + def __update_node(self,tid,inroot): |
1404 | + self.update_count += 1 |
1405 | + self.__root_update(tid,inroot) |
1406 | + self.emit("task-modified-inview", tid) |
1407 | + |
1408 | + def __add_node(self,tid,inroot=None): |
1409 | + self.add_count += 1 |
1410 | + if not self.is_displayed(tid): |
1411 | + node = self.get_node(tid) |
1412 | + if inroot == None: |
1413 | + inroot = self.__is_root(node) |
1414 | + #If the parent's node is not already displayed, we wait |
1415 | + if not inroot and not self.node_parent(node): |
1416 | + self.node_to_add.append(tid) |
1417 | + else: |
1418 | + self.__root_update(tid,inroot) |
1419 | + self.displayed_nodes.append(tid) |
1420 | + self.emit("task-added-inview", tid) |
1421 | + #We added a new node so we can check with those waiting |
1422 | + if len(self.node_to_add) > 0: |
1423 | + n = self.node_to_add.pop(0) |
1424 | + #node still to add cannot be root |
1425 | + self.__add_node(n,False) |
1426 | + |
1427 | + def __remove_node(self,tid): |
1428 | + self.remove_count += 1 |
1429 | + self.emit('task-deleted-inview',tid) |
1430 | + self.__root_update(tid,False) |
1431 | + if tid in self.displayed_nodes: |
1432 | + self.displayed_nodes.remove(tid) |
1433 | + self.__reset_cache() |
1434 | + #Test if this is necessary |
1435 | + parent = self.node_parent(self.get_node(tid)) |
1436 | + if parent: |
1437 | + inroot = self.__is_root(parent) |
1438 | + self.__update_node(parent.get_id(),inroot) |
1439 | + |
1440 | + #This function print the actual tree. Useful for debugging |
1441 | + def __print_from_node(self, node, prefix=""): |
1442 | + print prefix + node.get_id() |
1443 | + prefix = prefix + "->" |
1444 | + if self.node_has_child(node): |
1445 | + child = self.node_children(node) |
1446 | + while child: |
1447 | + self._print_from_node(child,prefix) |
1448 | + child = self.next_node(child) |
1449 | + |
1450 | + #This function removes all the nodes, leaves first. |
1451 | + def __clean_from_node(self, node): |
1452 | + if self.node_has_child(node): |
1453 | + child = self.node_children(node) |
1454 | + while child: |
1455 | + self.__clean_from_node(child) |
1456 | + child = self.next_node(child) |
1457 | + self.__remove_node(node.get_id()) |
1458 | |
1459 | === added file 'GTG/core/filters_bank.py' |
1460 | --- GTG/core/filters_bank.py 1970-01-01 00:00:00 +0000 |
1461 | +++ GTG/core/filters_bank.py 2010-03-03 02:01:18 +0000 |
1462 | @@ -0,0 +1,158 @@ |
1463 | +# -*- coding: utf-8 -*- |
1464 | +# ----------------------------------------------------------------------------- |
1465 | +# Gettings Things Gnome! - a personal organizer for the GNOME desktop |
1466 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
1467 | +# |
1468 | +# This program is free software: you can redistribute it and/or modify it under |
1469 | +# the terms of the GNU General Public License as published by the Free Software |
1470 | +# Foundation, either version 3 of the License, or (at your option) any later |
1471 | +# version. |
1472 | +# |
1473 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1474 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
1475 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
1476 | +# details. |
1477 | +# |
1478 | +# You should have received a copy of the GNU General Public License along with |
1479 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
1480 | +# |
1481 | + |
1482 | +""" |
1483 | +filters_bank stores all of GTG's filters in centralized place |
1484 | +""" |
1485 | + |
1486 | +class Filter: |
1487 | + def __init__(self,func,req): |
1488 | + self.func = func |
1489 | + self.dic = None |
1490 | + self.req = req |
1491 | + |
1492 | + def set_parameters(self,dic): |
1493 | + self.dic = dic |
1494 | + |
1495 | + def is_displayed(self,tid): |
1496 | + task = self.req.get_task(tid) |
1497 | + if not task: |
1498 | + return False |
1499 | + elif self.dic: |
1500 | + return self.func(task,parameters=self.dic) |
1501 | + else: |
1502 | + return self.func(task) |
1503 | + |
1504 | +class SimpleTagFilter: |
1505 | + def __init__(self,tagname,req): |
1506 | + self.req = req |
1507 | + self.tname = tagname |
1508 | + |
1509 | + def is_displayed(self,tid): |
1510 | + task = self.req.get_task(tid) |
1511 | + if not task: |
1512 | + return False |
1513 | + else: |
1514 | + return task.has_tags([self.tname]) |
1515 | + |
1516 | + |
1517 | +class FiltersBank: |
1518 | + """ |
1519 | + Stores filter objects in a centralized place. |
1520 | + """ |
1521 | + |
1522 | + #FIXME : put those 3 constants and those in Task.py in one place |
1523 | + STA_ACTIVE = "Active" |
1524 | + STA_DISMISSED = "Dismiss" |
1525 | + STA_DONE = "Done" |
1526 | + |
1527 | + def __init__(self,req,tree=None): |
1528 | + self.tree = tree |
1529 | + self.req = req |
1530 | + self.available_filters = {} |
1531 | + self.custom_filters = {} |
1532 | + #Workview |
1533 | + filt_obj = Filter(self.workview,self.req) |
1534 | + self.available_filters['workview'] = filt_obj |
1535 | + #Active |
1536 | + filt_obj = Filter(self.active,self.req) |
1537 | + self.available_filters['active'] = filt_obj |
1538 | + #closed |
1539 | + filt_obj = Filter(self.closed,self.req) |
1540 | + self.available_filters['closed'] = filt_obj |
1541 | + #notag |
1542 | + filt_obj = Filter(self.notag,self.req) |
1543 | + self.available_filters['notag'] = filt_obj |
1544 | + |
1545 | + ######### hardcoded filters ############# |
1546 | + def notag(self,task): |
1547 | + """ Filter of tasks without tags """ |
1548 | + return task.has_tags(notag_only=True) |
1549 | + |
1550 | + def is_leaf(self,task): |
1551 | + """ Filter of tasks which have no children """ |
1552 | + return not task.has_child() |
1553 | + |
1554 | + def is_workable(self,task): |
1555 | + """ Filter of tasks that can be worked """ |
1556 | + return task.is_workable() |
1557 | + |
1558 | + def workview(self,task): |
1559 | + wv = self.active(task) and\ |
1560 | + task.is_started() and\ |
1561 | + self.is_workable(task) |
1562 | + return wv |
1563 | + |
1564 | + def active(self,task): |
1565 | + """ Filter of tasks which are active """ |
1566 | + #FIXME: we should also handle unactive tags |
1567 | + return task.get_status() == self.STA_ACTIVE |
1568 | + |
1569 | + def closed(self,task): |
1570 | + """ Filter of tasks which are closed """ |
1571 | + return task.get_status() in [self.STA_DISMISSED,self.STA_DONE] |
1572 | + |
1573 | + ########################################## |
1574 | + |
1575 | + def get_filter(self,filter_name): |
1576 | + """ Get the filter object for a given name """ |
1577 | + if self.available_filters.has_key(filter_name): |
1578 | + return self.available_filters[filter_name] |
1579 | + elif self.custom_filters.has_key(filter_name): |
1580 | + return self.custom_filters[filter_name] |
1581 | + else: |
1582 | + return None |
1583 | + |
1584 | + def list_filters(self): |
1585 | + """ List, by name, all available filters """ |
1586 | + liste = self.available_filters.keys() |
1587 | + liste += self.custom_filters.keys() |
1588 | + return liste |
1589 | + |
1590 | + def add_filter(self,filter_name,filter_func): |
1591 | + """ |
1592 | + Adds a filter to the filter bank |
1593 | + Return True if the filter was added |
1594 | + Return False if the filter_name was already in the bank |
1595 | + """ |
1596 | + if filter_name not in self.list_filters(): |
1597 | + if filter_name.startswith('@'): |
1598 | + filter_obj = SimpleTagFilter(filter_name,self.req) |
1599 | + else: |
1600 | + filter_obj = Filter(filter_func,self.req) |
1601 | + self.custom_filters[filter_name] = filter_obj |
1602 | + return True |
1603 | + else: |
1604 | + return False |
1605 | + |
1606 | + def remove_filter(self,filter_name): |
1607 | + """ |
1608 | + Remove a filter from the bank. |
1609 | + Only custom filters that were added here can be removed |
1610 | + Return False if the filter was not removed |
1611 | + """ |
1612 | + if not self.available_filters.has_key(filter_name): |
1613 | + if self.custom_filters.has_key(filter_name): |
1614 | + self.unapply_filter(filter_name) |
1615 | + self.custom_filters.pop(filter_name) |
1616 | + return True |
1617 | + else: |
1618 | + return False |
1619 | + else: |
1620 | + return False |
1621 | |
1622 | === modified file 'GTG/core/firstrun_tasks.py' |
1623 | --- GTG/core/firstrun_tasks.py 2010-02-09 22:38:05 +0000 |
1624 | +++ GTG/core/firstrun_tasks.py 2010-03-03 02:01:18 +0000 |
1625 | @@ -89,7 +89,7 @@ |
1626 | text5 += "\n\n" |
1627 | text5 += _("Some examples of the current plugins are Syncing with Remember the Milk and Evolution, Tomboy/Gnote integration and Geolocalized Tasks.") |
1628 | text5 += "\n" |
1629 | - text5 += _("You can find the Plugin Manager by selecting Plugins > Plugin Preferences on the menu.") |
1630 | + text5 += _("You can find the Plugin Manager by selecting Edit in the Menu Bar, then clicking Preferences. You will then see a tab labeled Plugins.") |
1631 | |
1632 | t5 = addtask(doc, "4@1", title5, text5, []) |
1633 | root.appendChild(t5) |
1634 | |
1635 | === modified file 'GTG/core/plugins/api.py' |
1636 | --- GTG/core/plugins/api.py 2010-02-16 01:39:41 +0000 |
1637 | +++ GTG/core/plugins/api.py 2010-03-03 02:01:18 +0000 |
1638 | @@ -16,13 +16,13 @@ |
1639 | # You should have received a copy of the GNU General Public License along with |
1640 | # this program. If not, see <http://www.gnu.org/licenses/>. |
1641 | # ----------------------------------------------------------------------------- |
1642 | - |
1643 | from __future__ import with_statement |
1644 | |
1645 | import os |
1646 | import pickle |
1647 | from xdg.BaseDirectory import xdg_config_home |
1648 | |
1649 | + |
1650 | class PluginAPI: |
1651 | """The plugin engine's API. |
1652 | |
1653 | @@ -40,18 +40,12 @@ |
1654 | data_dir, |
1655 | builder, |
1656 | requester, |
1657 | - taskview, |
1658 | - ctask_modelsort, |
1659 | - ctaskview, |
1660 | - task_modelsort, |
1661 | - filter_cbs, |
1662 | tagpopup, |
1663 | tagview, |
1664 | browser, |
1665 | task=None, |
1666 | texteditor=None, |
1667 | - quick_add_cbs=[], |
1668 | - logger = None): |
1669 | + quick_add_cbs=[]): |
1670 | """Construct a L{PluginAPI} object. |
1671 | |
1672 | @param window: The window where the plugin API object is being |
1673 | @@ -60,15 +54,11 @@ |
1674 | @param data_dir: The data dir path. |
1675 | @param builder: The window's gtkBuilder object. |
1676 | @param requester: The requester. |
1677 | - @param taskview: The task view object. |
1678 | - @param filter_cbs: The filter callback list. |
1679 | @param tagpopup: The tag popoup menu of the tag view. |
1680 | @param tagview: The tag view object. |
1681 | @param task: The current task (Only works with the task editor). |
1682 | @param textview: The task editor's text view (Only works with the task editor). |
1683 | - @param ctextview: The task editor's closed tasks text view (Only works with the task editor). |
1684 | - @param task_modelsort: The browser's active task model. |
1685 | - @param ctask_modelsort: The browser's closed task model. |
1686 | + @param ctextview: The task editor's closed tasks text view (Only works with the task editor) |
1687 | """ |
1688 | self.__window = window |
1689 | self.config = config |
1690 | @@ -76,16 +66,8 @@ |
1691 | self.__builder = builder |
1692 | self.__requester = requester |
1693 | |
1694 | - self.taskview = taskview |
1695 | - self.task_modelsort = task_modelsort |
1696 | - |
1697 | - self.ctaskview = taskview |
1698 | - self.ctask_modelsort = ctask_modelsort |
1699 | - |
1700 | self.__tagpopup = tagpopup |
1701 | self.tagview = tagview |
1702 | - |
1703 | - self.__filter_cbs = filter_cbs |
1704 | self.__quick_add_cbs = quick_add_cbs |
1705 | |
1706 | #those are added widgets dictionaries |
1707 | @@ -93,7 +75,6 @@ |
1708 | self.tasktoolbar_widg = {} |
1709 | self.taskwidget_id = 0 |
1710 | self.taskwidget_widg = {} |
1711 | - self.logger = logger |
1712 | self.browser = browser |
1713 | |
1714 | if task: |
1715 | @@ -115,10 +96,7 @@ |
1716 | return False |
1717 | |
1718 | def is_browser(self): |
1719 | - if self.taskview: |
1720 | - return True |
1721 | - else: |
1722 | - return False |
1723 | + print "is_browser method in plugin/api should be updated" |
1724 | |
1725 | def get_browser(self): |
1726 | return self.browser |
1727 | @@ -130,7 +108,7 @@ |
1728 | |
1729 | @param item: The gtk.MenuItem that is going to be added. |
1730 | """ |
1731 | - widget = self.__builder.get_object('menu_plugin') |
1732 | + widget = self.__builder.get_object('plugin_mi') |
1733 | if widget: |
1734 | widget.show_all() |
1735 | widget.get_submenu().append(item) |
1736 | @@ -145,7 +123,7 @@ |
1737 | fails. |
1738 | """ |
1739 | try: |
1740 | - wi = self.__builder.get_object('menu_plugin') |
1741 | + wi = self.__builder.get_object('plugin_mi') |
1742 | if wi: |
1743 | menu = wi.get_submenu() |
1744 | menu.remove(item) |
1745 | @@ -290,14 +268,15 @@ |
1746 | """ |
1747 | self.__requester.connect(action, func) |
1748 | |
1749 | - def change_task_tree_store(self, treestore): |
1750 | - """Changes the TreeStore in the task browser's task view. |
1751 | - |
1752 | - @param treestore: The new gtk.TreeStore model. |
1753 | - """ |
1754 | - task_tview = self.__builder.get_object("task_tview") |
1755 | - if task_tview: |
1756 | - task_tview.set_model(treestore) |
1757 | +# #This is definitely not allowed anymore !! |
1758 | +# def change_task_tree_store(self, treestore): |
1759 | +# """Changes the TreeStore in the task browser's task view. |
1760 | +# |
1761 | +# @param treestore: The new gtk.TreeStore model. |
1762 | +# """ |
1763 | +# task_tview = self.__builder.get_object("task_tview") |
1764 | +# if task_tview: |
1765 | +# task_tview.set_model(treestore) |
1766 | |
1767 | def set_parent_window(self, child): |
1768 | """Sets the plugin dialog as a child of it's parent window, |
1769 | @@ -308,49 +287,54 @@ |
1770 | """ |
1771 | child.set_transient_for(self.__window) |
1772 | |
1773 | - def get_taskview(self): |
1774 | - """Returns the task view object. |
1775 | - |
1776 | - @return: The gtk.TreeView task view object. |
1777 | - """ |
1778 | - return self.taskview |
1779 | - |
1780 | - def get_task_modelsort(self): |
1781 | - """Returns the current Active task browser view. |
1782 | - |
1783 | - @return: The gtk.TreeModelSort task object for visible active tasks. |
1784 | - """ |
1785 | - return self.task_modelsort |
1786 | - |
1787 | - def get_closed_taskview(self): |
1788 | - """Returns the closed task view object. |
1789 | - |
1790 | - @return: The gtk.TreeView task view object. |
1791 | - """ |
1792 | - return self.ctaskview |
1793 | - |
1794 | - def get_ctask_modelsort(self): |
1795 | - """Returns the current Done task browser view. |
1796 | - |
1797 | - @return: The gtk.TreeModelSort task object for visible closed tasks. |
1798 | - """ |
1799 | - return self.ctask_modelsort |
1800 | - |
1801 | +# # Not allowed anymore |
1802 | +# def get_taskview(self): |
1803 | +# """Returns the task view object. |
1804 | +# |
1805 | +# @return: The gtk.TreeView task view object. |
1806 | +# """ |
1807 | +# return self.taskview |
1808 | + |
1809 | +## # Not allowed anymore |
1810 | +# def get_task_modelsort(self): |
1811 | +# """Returns the current Active task browser view. |
1812 | +# |
1813 | +# @return: The gtk.TreeModelSort task object for visible active tasks. |
1814 | +# """ |
1815 | +# return self.task_modelsort |
1816 | + |
1817 | +## # Not allowed anymore |
1818 | +# def get_closed_taskview(self): |
1819 | +# """Returns the closed task view object. |
1820 | +# |
1821 | +# @return: The gtk.TreeView task view object. |
1822 | +# """ |
1823 | +# return self.ctaskview |
1824 | + |
1825 | +## # Not allowed anymore |
1826 | +# def get_ctask_modelsort(self): |
1827 | +# """Returns the current Done task browser view. |
1828 | +# |
1829 | +# @return: The gtk.TreeModelSort task object for visible closed tasks. |
1830 | +# """ |
1831 | +# return self.ctask_modelsort |
1832 | + |
1833 | def get_selected_task(self): |
1834 | """Returns the selected task in the task view. |
1835 | |
1836 | @return: A task. |
1837 | """ |
1838 | + print "get_selected_task in plugins/api should be updated" |
1839 | if self.is_editor(): |
1840 | return self.task |
1841 | - elif self.is_browser(): |
1842 | - selection = self.taskview.get_selection() |
1843 | - model, paths = selection.get_selected_rows() |
1844 | - iters = [model.get_iter(path) for path in paths] |
1845 | - if len(iters) > 0 and iters[0]: |
1846 | - return self.__requester.get_task(model.get_value(iters[0], 0)) |
1847 | - else: |
1848 | - return None |
1849 | +# elif self.is_browser(): |
1850 | +# selection = self.taskview.get_selection() |
1851 | +# model, paths = selection.get_selected_rows() |
1852 | +# iters = [model.get_iter(path) for path in paths] |
1853 | +# if len(iters) > 0 and iters[0]: |
1854 | +# return self.__requester.get_task(model.get_value(iters[0], 0)) |
1855 | +# else: |
1856 | +# return None |
1857 | else: |
1858 | return None |
1859 | |
1860 | @@ -366,7 +350,7 @@ |
1861 | |
1862 | @return: The about dialog. |
1863 | """ |
1864 | - wi = self.__builder.get_object("aboutdialog1") |
1865 | + wi = self.__builder.get_object("about_dialog") |
1866 | if wi: |
1867 | return wi |
1868 | else: |
1869 | @@ -391,10 +375,6 @@ |
1870 | """Returns the window for which the plug-in has been created""" |
1871 | return self.__window |
1872 | |
1873 | - def get_logger(self): |
1874 | - """Returns the logger, used for debug output""" |
1875 | - return self.logger |
1876 | - |
1877 | #=== General Methods ========================================================== |
1878 | |
1879 | |
1880 | @@ -570,22 +550,23 @@ |
1881 | """ |
1882 | self.__requester.remove_tag_from_filter(tag) |
1883 | |
1884 | - def register_filter_cb(self, func): |
1885 | - """Registers a callback filter function with the callback filter. |
1886 | - |
1887 | - @param func: The function that is going to be registered. |
1888 | - |
1889 | - """ |
1890 | - if func not in self.__filter_cbs: |
1891 | - self.__filter_cbs.append(func) |
1892 | - |
1893 | - def unregister_filter_cb(self, func): |
1894 | - """Unregisters a previously registered callback filter function. |
1895 | - |
1896 | - @param func: The function that is going to be unregistered. |
1897 | - """ |
1898 | - if func in self.__filter_cbs: |
1899 | - self.__filter_cbs.remove(func) |
1900 | +# #Those functions are not relevant anymore with the new requester |
1901 | +# def register_filter_cb(self, func): |
1902 | +# """Registers a callback filter function with the callback filter. |
1903 | +# |
1904 | +# @param func: The function that is going to be registered. |
1905 | +# |
1906 | +# """ |
1907 | +# if func not in self.__filter_cbs: |
1908 | +# self.__filter_cbs.append(func) |
1909 | +# |
1910 | +# def unregister_filter_cb(self, func): |
1911 | +# """Unregisters a previously registered callback filter function. |
1912 | +# |
1913 | +# @param func: The function that is going to be unregistered. |
1914 | +# """ |
1915 | +# if func in self.__filter_cbs: |
1916 | +# self.__filter_cbs.remove(func) |
1917 | #=== Filtering methods ======================================================== |
1918 | |
1919 | def register_quick_add_cb(self, func): |
1920 | |
1921 | === modified file 'GTG/core/requester.py' |
1922 | --- GTG/core/requester.py 2010-02-08 08:32:44 +0000 |
1923 | +++ GTG/core/requester.py 2010-03-03 02:01:18 +0000 |
1924 | @@ -17,8 +17,14 @@ |
1925 | # this program. If not, see <http://www.gnu.org/licenses/>. |
1926 | # ----------------------------------------------------------------------------- |
1927 | |
1928 | +""" |
1929 | +A nice general purpose interface for the datastore and tagstore |
1930 | +""" |
1931 | + |
1932 | import gobject |
1933 | |
1934 | +from GTG.core.filteredtree import FilteredTree |
1935 | +from GTG.core.filters_bank import FiltersBank |
1936 | |
1937 | class Requester(gobject.GObject): |
1938 | """A view on a GTG datastore. |
1939 | @@ -34,18 +40,23 @@ |
1940 | 'task-deleted': (gobject.SIGNAL_RUN_FIRST, \ |
1941 | gobject.TYPE_NONE, (str, )), |
1942 | 'task-modified': (gobject.SIGNAL_RUN_FIRST, \ |
1943 | + gobject.TYPE_NONE, (str, )), |
1944 | + 'tag-added': (gobject.SIGNAL_RUN_FIRST, \ |
1945 | + gobject.TYPE_NONE, (str, )), |
1946 | + 'tag-deleted': (gobject.SIGNAL_RUN_FIRST, \ |
1947 | + gobject.TYPE_NONE, (str, )), |
1948 | + 'tag-modified': (gobject.SIGNAL_RUN_FIRST, \ |
1949 | gobject.TYPE_NONE, (str, ))} |
1950 | |
1951 | def __init__(self, datastore): |
1952 | """Construct a L{Requester}.""" |
1953 | + gobject.GObject.__init__(self) |
1954 | self.ds = datastore |
1955 | - |
1956 | - #filter |
1957 | - self.filter = {} |
1958 | - self.filter["tasks"] = [] |
1959 | - self.filter["tags"] = [] |
1960 | - |
1961 | - gobject.GObject.__init__(self) |
1962 | + self.basetree = self.ds.get_tasks_tree() |
1963 | + self.main_tree = FilteredTree(self,self.basetree,maintree=True) |
1964 | + |
1965 | + self.filters = FiltersBank(self,tree=self.main_tree) |
1966 | + self.counter_call = 0 |
1967 | |
1968 | ############# Signals ######################### |
1969 | #Used by the tasks to emit the task added/modified signal |
1970 | @@ -54,7 +65,74 @@ |
1971 | gobject.idle_add(self.emit, "task-added", tid) |
1972 | |
1973 | def _task_modified(self, tid): |
1974 | + self.counter_call += 1 |
1975 | + #print "signal task_modified %s (%s modifications)" %(tid,self.counter_call) |
1976 | gobject.idle_add(self.emit, "task-modified", tid) |
1977 | + |
1978 | + def _tag_added(self,tagname): |
1979 | + gobject.idle_add(self.emit, "tag-added", tagname) |
1980 | + |
1981 | + ############ Tasks Tree ###################### |
1982 | + # This is the main FilteredTree. You cannot apply filters |
1983 | + # directly to it, you have to pass them through the requester. |
1984 | + # This is the tree as it is displayed in the main window |
1985 | + def get_main_tasks_tree(self): |
1986 | + return self.main_tree |
1987 | + |
1988 | + # This is a FilteredTree that you have to handle yourself. |
1989 | + # You can apply/unapply filters on it as you wich. |
1990 | + def get_custom_tasks_tree(self): |
1991 | + return FilteredTree(self,self.basetree,maintree=False) |
1992 | + |
1993 | + def get_main_tasks_list(self): |
1994 | + return self.main_tree.get_all_keys() |
1995 | + |
1996 | + def get_main_n_tasks(self): |
1997 | + return self.main_tree.get_n_nodes() |
1998 | + |
1999 | + def get_all_tasks_list(self): |
2000 | + return self.basetree.get_all_keys() |
2001 | + |
2002 | + # Apply a given filter to the main FilteredTree |
2003 | + def apply_filter(self,filter_name,parameters=None): |
2004 | + r = self.main_tree.apply_filter(filter_name,parameters=parameters,imtherequester=True) |
2005 | + return r |
2006 | + |
2007 | + # Unapply a filter from the main FilteredTree. |
2008 | + # Does nothing if the filter was not previously applied. |
2009 | + def unapply_filter(self,filter_name): |
2010 | + r = self.main_tree.unapply_filter(filter_name,imtherequester=True) |
2011 | + return r |
2012 | + |
2013 | + def reset_filters(self): |
2014 | + self.main_tree.reset_filters(imtherequester=True) |
2015 | + |
2016 | + def reset_tag_filters(self,refilter=True): |
2017 | + self.main_tree.reset_tag_filters(refilter=refilter,imtherequester=True) |
2018 | + |
2019 | + def is_displayed(self,task): |
2020 | + return self.main_tree.is_displayed(task) |
2021 | + |
2022 | + ######### Filters bank ####################### |
2023 | + # Get the filter object for a given name |
2024 | + def get_filter(self,filter_name): |
2025 | + return self.filters.get_filter(filter_name) |
2026 | + |
2027 | + # List, by name, all available filters |
2028 | + def list_filters(self): |
2029 | + return self.filters.list_filters() |
2030 | + |
2031 | + # Add a filter to the filter bank |
2032 | + # Return True if the filter was added |
2033 | + # Return False if the filter_name was already in the bank |
2034 | + def add_filter(self,filter_name,filter_func): |
2035 | + return self.filters.add_filter(filter_name,filter_func) |
2036 | + |
2037 | + # Remove a filter from the bank. |
2038 | + # Only custom filters that were added here can be removed |
2039 | + # Return False if the filter was not removed |
2040 | + def remove_filter(self,filter_name): |
2041 | + return self.filters.remove_filter(filter_name) |
2042 | |
2043 | ############## Tasks ########################## |
2044 | ############################################### |
2045 | @@ -85,7 +163,7 @@ |
2046 | existed, C{False} if importing an existing task from a backend. |
2047 | @return: A task. |
2048 | """ |
2049 | - task = self.ds.new_task(pid=pid, newtask=newtask) |
2050 | + task = self.ds.new_task(pid=pid) |
2051 | if tags: |
2052 | for t in tags: |
2053 | task.tag_added(t.get_name()) |
2054 | @@ -98,225 +176,14 @@ |
2055 | |
2056 | @param tid: The id of the task to be deleted. |
2057 | """ |
2058 | - self.ds.delete_task(tid) |
2059 | - gobject.idle_add(self.emit, "task-deleted", tid) |
2060 | - |
2061 | - def get_tasks_list(self, tags=None, status=["Active"], notag_only=False, |
2062 | - started_only=True, is_root=False): |
2063 | - """Return a list of tids of tasks. |
2064 | - |
2065 | - By default, returns a list of all the tids of all active tasks. |
2066 | - |
2067 | - @param tags: A list of tags. If provided, restricts the list of |
2068 | - returned tasks to those that have one or more of these tags. |
2069 | - @param status: A list of statuses. If provided, restricts the list of |
2070 | - returned tasks to those that are in one of these states. |
2071 | - @param notag_only: If True, only include tasks without tags. Defaults |
2072 | - to C{False}. |
2073 | - @param started_only: If True, only include tasks that have been |
2074 | - started. That is, tasks that have an already-passed start date or |
2075 | - tasks with no startdate. Defaults to C{True}. |
2076 | - @param is_root: If True, only include tasks that have no parent in the |
2077 | - current selection. Defaults to False. |
2078 | - |
2079 | - @return: A list of task ids (tids). |
2080 | - """ |
2081 | - l_tasks = [] |
2082 | - temp_list = [] |
2083 | - if tags: |
2084 | - for t in tags: |
2085 | - for tid in t.get_tasks(): |
2086 | - if tid not in temp_list: |
2087 | - temp_list.append(tid) |
2088 | - else: |
2089 | - temp_list = self.ds.all_tasks() |
2090 | - for tid in temp_list: |
2091 | - task = self.get_task(tid) |
2092 | - if task and not task.is_loaded(): |
2093 | - task = None |
2094 | - # This is status filtering. |
2095 | - if task and not task.get_status() in status: |
2096 | - task = None |
2097 | - # This is tag filtering. |
2098 | - # If we still have a task and we need to filter tags |
2099 | - # (if tags is None, this test is skipped) |
2100 | -# if task and tags: |
2101 | -# if not task.has_tags(tags): |
2102 | -# task = None |
2103 | -# # Checking here the is_root because it has sense only with |
2104 | -# # tags. |
2105 | -# elif is_root and task.has_parents(tag=tags): |
2106 | -# task = None |
2107 | - if task and tags and is_root and task.has_parents(tag=tags): |
2108 | - task = None |
2109 | - #If tags = [], we still check the is_root. |
2110 | - elif task and is_root: |
2111 | - if task.has_parents(): |
2112 | - # We accept children of a note. |
2113 | - for p in task.get_parents(): |
2114 | - pp = self.get_task(p) |
2115 | - if pp.get_status() != "Note": |
2116 | - task = None |
2117 | - # Now checking if it has no tag. |
2118 | - if task and notag_only: |
2119 | - if not task.has_tags(notag_only=notag_only): |
2120 | - task = None |
2121 | - # This is started filtering. |
2122 | - if task and started_only: |
2123 | - if not task.is_started(): |
2124 | - task = None |
2125 | - |
2126 | - # If we still have a task, we return it. |
2127 | - if task: |
2128 | - l_tasks.append(tid) |
2129 | - return l_tasks |
2130 | - |
2131 | - ############# Filters ######################### |
2132 | - def set_filter(self, filter): |
2133 | - """Set a filter for the tasks. |
2134 | - |
2135 | - @param filter: A dictionary with two keys, 'tags' and 'tasks'. |
2136 | - The 'tags' key corresponds to a list of tag names and the 'tasks' |
2137 | - corresponds to a list of tids. |
2138 | - """ |
2139 | - self.filter = filter |
2140 | - |
2141 | - def get_filter(self): |
2142 | - """Return the current task filter. |
2143 | - |
2144 | - @return: The filter object. |
2145 | - """ |
2146 | - return self.filter |
2147 | - |
2148 | - def add_task_to_filter(self, tid): |
2149 | - """Adds (appends) a task to the filter (task list). |
2150 | - |
2151 | - @param tid: A task id. |
2152 | - """ |
2153 | - if tid not in self.filter["tasks"]: |
2154 | - self.filter["tasks"].append(tid) |
2155 | - |
2156 | - def remove_task_from_filter(self, tid): |
2157 | - """Removes a task from the filter (task list). |
2158 | - |
2159 | - @param tid: A task id. |
2160 | - """ |
2161 | - if tid in self.filter["tasks"]: |
2162 | - self.filter["tasks"].remove(tid) |
2163 | - |
2164 | - def add_tag_to_filter(self, tag): |
2165 | - """Adds (appends) a tag to the filter (tag list). |
2166 | - |
2167 | - @param tag: A tag name. |
2168 | - """ |
2169 | - if tag not in self.filter["tags"]: |
2170 | - self.filter["tags"].append(tag) |
2171 | - |
2172 | - def remove_tag_from_filter(self, tag): |
2173 | - """Removes a tag from the filter (tag list). |
2174 | - |
2175 | - @param tag: A tag name. |
2176 | - """ |
2177 | - if tag in self.filter["tags"]: |
2178 | - self.filter["tags"].remove(tag) |
2179 | - |
2180 | - ############# Filters ######################### |
2181 | - def get_active_tasks_list(self, tags=None, notag_only=False, |
2182 | - started_only=True, is_root=False, |
2183 | - workable=False): |
2184 | - """Return a list of task ids for all active tasks. |
2185 | - |
2186 | - See L{get_tasks_list} for more information about the parameters. |
2187 | - |
2188 | - @param workable: If C{True}, then only include tasks with no pending |
2189 | - subtasks and that can be done directly and exclude any tasks that |
2190 | - have a C{nonworkview} tag which is not explicitly provided in the |
2191 | - C{tags} parameter. Defaults to C{False}. |
2192 | - """ |
2193 | - l_tasks = [] |
2194 | - if workable: |
2195 | - nonwork_tag = self.ds.get_tagstore().get_all_tags( |
2196 | - attname="nonworkview", attvalue="True") |
2197 | - # We build the list of tags we will skip. |
2198 | - #for nwtag in nonwork_tag: |
2199 | - # If the tag is explicitly selected, it doesn't go in the |
2200 | - # nonwork_tag. |
2201 | - #if tags and nwtag in tags: |
2202 | - # nonwork_tag.remove(nwtag) |
2203 | - # We build the task list. |
2204 | - temp_tasks = self.get_active_tasks_list( |
2205 | - tags=tags, notag_only=notag_only, started_only=True, |
2206 | - is_root=False, workable=False) |
2207 | - |
2208 | - #remove from temp_tasks the filtered out tasks |
2209 | - #for tid in temp_tasks: |
2210 | - # if tid in self.filter["tasks"]: |
2211 | - # temp_tasks.remove(tid) |
2212 | - # else: |
2213 | - # for filter_tag in self.get_task(tid).get_tags(): |
2214 | - # if filter_tag.get_attribute("name") in \ |
2215 | - # self.filter["tags"]: |
2216 | - # print self.get_task(tid).get_title() |
2217 | - # temp_tasks.remove(tid) |
2218 | - # break |
2219 | - |
2220 | - # Now we verify that the tasks are workable and don't have a |
2221 | - # nonwork_tag. |
2222 | - for tid in temp_tasks: |
2223 | - filtered_tag = False |
2224 | - t = self.get_task(tid) |
2225 | - if t and t.is_workable() and (tid not in self.filter["tasks"]): |
2226 | - for filter_tag in t.get_tags(): |
2227 | - if filter_tag.get_attribute("name") in \ |
2228 | - self.filter["tags"]: |
2229 | - #print t.get_title() |
2230 | - temp_tasks.remove(tid) |
2231 | - filtered_tag = True |
2232 | - |
2233 | - if not filtered_tag: |
2234 | - if len(nonwork_tag) == 0: |
2235 | - #print t.get_title() |
2236 | - l_tasks.append(tid) |
2237 | - elif not t.has_tags(nonwork_tag): |
2238 | - #print t.get_title() |
2239 | - l_tasks.append(tid) |
2240 | - return l_tasks |
2241 | - else: |
2242 | - active = ["Active"] |
2243 | - temp_tasks = self.get_tasks_list( |
2244 | - tags=tags, status=active, notag_only=notag_only, |
2245 | - started_only=started_only, is_root=is_root) |
2246 | - for t in temp_tasks: |
2247 | - l_tasks.append(t) |
2248 | - return l_tasks |
2249 | - |
2250 | - def get_closed_tasks_list(self, tags=None, notag_only=False, |
2251 | - started_only=False, is_root=False): |
2252 | - """Return a list of task ids for closed tasks. |
2253 | - |
2254 | - "Closed" means either "done", "dismissed" or "deleted". |
2255 | - |
2256 | - See L{get_tasks_list} for more information about the parameters. |
2257 | - """ |
2258 | - closed = ["Done", "Dismiss", "Deleted"] |
2259 | - return self.get_tasks_list( |
2260 | - tags=tags, status=closed, notag_only=notag_only, |
2261 | - started_only=started_only, is_root=is_root) |
2262 | - |
2263 | - def get_notes_list(self, tags=None, notag_only=False): |
2264 | - """Return a list of task ids for notes. |
2265 | - |
2266 | - See `get_tasks_list` for more information about the parameters. |
2267 | - """ |
2268 | - note = ["Note"] |
2269 | - return self.get_tasks_list( |
2270 | - tags=tags, status=note, notag_only=notag_only, started_only=False, |
2271 | - is_root=False) |
2272 | + #send the signal before actually deleting the task ! |
2273 | + self.emit('task-deleted', tid) |
2274 | + return self.ds.delete_task(tid) |
2275 | |
2276 | ############### Tags ########################## |
2277 | ############################################### |
2278 | def get_tag_tree(self): |
2279 | - return self.ds.get_tagstore().get_tree() |
2280 | + return self.ds.get_tagstore() |
2281 | |
2282 | def new_tag(self, tagname): |
2283 | """Create a new tag called 'tagname'. |
2284 | @@ -357,12 +224,11 @@ |
2285 | def get_used_tags(self): |
2286 | """Return tags currently used by a task. |
2287 | |
2288 | - @return: A list of tags used by a task. |
2289 | + @return: A list of tag names used by a task. |
2290 | """ |
2291 | l = [] |
2292 | for t in self.ds.get_tagstore().get_all_tags(): |
2293 | if t.is_actively_used() and t not in l: |
2294 | - l.append(t) |
2295 | - l.sort(cmp=lambda x, y: cmp(x.get_name().lower(),\ |
2296 | - y.get_name().lower())) |
2297 | + l.append(t.get_name()) |
2298 | + l.sort(cmp=lambda x, y: cmp(x.lower(),y.lower())) |
2299 | return l |
2300 | |
2301 | === modified file 'GTG/core/tagstore.py' |
2302 | --- GTG/core/tagstore.py 2010-01-07 16:27:06 +0000 |
2303 | +++ GTG/core/tagstore.py 2010-03-03 02:01:18 +0000 |
2304 | @@ -17,12 +17,17 @@ |
2305 | # this program. If not, see <http://www.gnu.org/licenses/>. |
2306 | # ----------------------------------------------------------------------------- |
2307 | |
2308 | -#The tagstore is where the tag objects are handled. See the end of the file for |
2309 | -#the tag object implementation |
2310 | +""" |
2311 | +tagstore is where the tag objects are handled. Also defines the Tag object. |
2312 | + |
2313 | +Tagstore is to tag as datastore is to task. Of course, the tagstore is |
2314 | +easier. See the end of this file for the Tag object implementation. |
2315 | +""" |
2316 | |
2317 | import os |
2318 | import xml.sax.saxutils as saxutils |
2319 | |
2320 | +from GTG import _ |
2321 | from GTG.core import CoreConfig |
2322 | from GTG.core.tree import Tree, TreeNode |
2323 | from GTG.tools import cleanxml |
2324 | @@ -33,13 +38,32 @@ |
2325 | |
2326 | # There's only one Tag store by user. It will store all the tag used |
2327 | # and their attribute. |
2328 | -class TagStore: |
2329 | +class TagStore(Tree): |
2330 | |
2331 | def __init__(self,requester): |
2332 | + Tree.__init__(self) |
2333 | self.req = requester |
2334 | - self.tree = Tree() |
2335 | - self.tags={} |
2336 | - self.root = self.tree.get_root() |
2337 | + |
2338 | + ### building the initial tags |
2339 | + # Build the "all tags tag" |
2340 | + self.alltag_tag = self.new_tag("gtg-tags-all") |
2341 | + self.alltag_tag.set_attribute("special","all") |
2342 | + self.alltag_tag.set_attribute("label","<span weight='bold'>%s</span>"\ |
2343 | + % _("All tasks")) |
2344 | + self.alltag_tag.set_attribute("icon","gtg-tags-all") |
2345 | + self.alltag_tag.set_attribute("order",0) |
2346 | + # Build the "without tag tag" |
2347 | + self.notag_tag = self.new_tag("gtg-tags-none") |
2348 | + self.notag_tag.set_attribute("special","notag") |
2349 | + self.notag_tag.set_attribute("label","<span weight='bold'>%s</span>"\ |
2350 | + % _("Tasks with no tags")) |
2351 | + self.notag_tag.set_attribute("icon","gtg-tags-none") |
2352 | + self.notag_tag.set_attribute("order",1) |
2353 | + # Build the separator |
2354 | + self.sep_tag = self.new_tag("gtg-tags-sep") |
2355 | + self.sep_tag.set_attribute("special","sep") |
2356 | + self.sep_tag.set_attribute("order",2) |
2357 | + |
2358 | self.filename = os.path.join(CoreConfig.DATA_DIR, XMLFILE) |
2359 | doc, self.xmlstore = cleanxml.openxmlfile(self.filename, |
2360 | XMLROOT) #pylint: disable-msg=W0612 |
2361 | @@ -55,70 +79,80 @@ |
2362 | at_val = t.getAttribute(at_name) |
2363 | tag.set_attribute(at_name, at_val) |
2364 | i += 1 |
2365 | - parent = tag.get_attribute('parent') |
2366 | - if parent: |
2367 | - pnode=self.new_tag(parent) |
2368 | - tag.reparent(pnode, update_attr=False) |
2369 | - |
2370 | - #Now we build special tags. Special tags are not |
2371 | - #in the traditional tag list |
2372 | - #Their name doesn't begin with "@" |
2373 | -# #Build the "all tags tag" |
2374 | -# self.alltag_tag = Tag("alltags_tag",save_cllbk=self.save) |
2375 | -# self.alltag_tag.set_attribute("special","all") |
2376 | -# self.alltag_tag.set_attribute("icon","gtg-tags-all") |
2377 | -# #Build the "without tag tag" |
2378 | -# self.notag_tag = Tag("notag_tag",save_cllbk=self.save) |
2379 | -# self.notag_tag.set_attribute("special","notag") |
2380 | -# self.notag_tag.set_attribute("icon","gtg-tags-none") |
2381 | - def get_tree(self): |
2382 | - return self.tree |
2383 | +# parent = tag.get_attribute('parent') |
2384 | +# if parent: |
2385 | +# pnode=self.new_tag(parent) |
2386 | +# tag.reparent(pnode, update_attr=False) |
2387 | |
2388 | def new_tag(self, tagname): |
2389 | """Create a new tag and return it or return the existing one |
2390 | with corresponding name""" |
2391 | #we create a new tag from a name |
2392 | tname = tagname.encode("UTF-8") |
2393 | - if tname not in self.tags: |
2394 | + #if tname not in self.tags: |
2395 | + if not self.has_node(tname): |
2396 | tag = Tag(tname, save_cllbk=self.save, req=self.req) |
2397 | - tag.reparent(self.root) |
2398 | - self.tags[tname] = tag |
2399 | - return tag |
2400 | - else: |
2401 | - return self.tags[tname] |
2402 | + self.add_node(tag) |
2403 | + self.req._tag_added(tname) |
2404 | + self.req.add_filter(tname,None) |
2405 | + #self.tags[tname] = tag |
2406 | +# print "********* tag added *******" |
2407 | +# self.print_tree() |
2408 | + return self.get_node(tname) |
2409 | |
2410 | +# Is this thing used ? What does it do ? |
2411 | def add_tag(self, tag): |
2412 | - name = tag.get_name() |
2413 | - #If tag does not exist in the store, we add it |
2414 | - if name not in self.tags: |
2415 | - tag.reparent(self.root) |
2416 | - self.tags[name] = tag |
2417 | - #else, we just take the attributes of the new tag |
2418 | - #This allow us to keep attributes of the old tag |
2419 | - #that might be not set in the new one |
2420 | - else: |
2421 | - atts = tag.get_all_attributes() |
2422 | - for att_name in atts: |
2423 | - val = tag.get_attribute(att_name) |
2424 | - if att_name != 'name' and val: |
2425 | - self.tags[name].set_attribute(att_name, val) |
2426 | + print "add_tag %s ***************** it is used !!!" %tag |
2427 | +# name = tag.get_name() |
2428 | +# #If tag does not exist in the store, we add it |
2429 | +# if not self.has_node(name): |
2430 | +# self.add_node(name,tag) |
2431 | +# #self.tags[name] = tag |
2432 | +# #else, we just take the attributes of the new tag |
2433 | +# #This allow us to keep attributes of the old tag |
2434 | +# #that might be not set in the new one |
2435 | +# else: |
2436 | +# atts = tag.get_all_attributes() |
2437 | +# oldtag = self.get_node(name) |
2438 | +# for att_name in atts: |
2439 | +# val = tag.get_attribute(att_name) |
2440 | +# if att_name != 'name' and val: |
2441 | +# #self.tags[name].set_attribute(att_name, val) |
2442 | +# oldtag.set_attribute(att_name, val) |
2443 | |
2444 | def get_tag(self, tagname): |
2445 | if tagname[0] != "@": |
2446 | tagname = "@" + tagname |
2447 | - return self.tags.get(tagname, None) |
2448 | + return self.get_node(tagname) |
2449 | |
2450 | + #FIXME : also add a new filter |
2451 | def rename_tag(self, oldname, newname): |
2452 | if len(newname) > 0 and \ |
2453 | oldname not in ['gtg-tags-none','gtg-tags-all']: |
2454 | if newname[0] != "@": |
2455 | newname = "@" + newname |
2456 | if newname != oldname and newname != None \ |
2457 | - and newname not in self.tags: |
2458 | - self.tags[newname] = self.tags[oldname] |
2459 | - self.tags[newname].rename(newname) |
2460 | - del self.tags[oldname] |
2461 | - |
2462 | + and not self.has_node(newname): |
2463 | + |
2464 | + ntag = self.new_tag(newname) |
2465 | + otag = self.get_node(oldname) |
2466 | + #copy attributes |
2467 | + for att in otag.get_all_attributes(butname=True): |
2468 | + ntag.set_attribute(att,otag.get_attribute(att)) |
2469 | + #restore position in tree |
2470 | + if otag.has_parent(): |
2471 | + opar = otag.get_parent() |
2472 | + ntag.set_parent(opar) |
2473 | + for ch in otag.get_children(): |
2474 | + tagchild = self.get_tag(ch) |
2475 | + tagchild.set_parent(ntag) |
2476 | + #copy tasks |
2477 | + for tid in otag.get_tasks(): |
2478 | + tas = self.req.get_task(tid) |
2479 | + tas.rename_tag(oldname,newname) |
2480 | + #remove the old one |
2481 | + self.remove_node(oldname,otag) |
2482 | + |
2483 | def get_all_tags_name(self, attname=None, attvalue=None): |
2484 | """Return the name of all tags |
2485 | Optionally, if you pass the attname and attvalue argument, it will |
2486 | @@ -126,7 +160,7 @@ |
2487 | excluding tags that don't have this attribute |
2488 | (except if attvalue is None)""" |
2489 | l = [] |
2490 | - for t in self.tags.values(): |
2491 | + for t in self.get_all_nodes(): |
2492 | if not attname: |
2493 | l.append(t.get_name()) |
2494 | elif t.get_attribute(attname) == attvalue: |
2495 | @@ -135,7 +169,7 @@ |
2496 | |
2497 | def get_all_tags(self, attname=None, attvalue=None): |
2498 | l = [] |
2499 | - for t in self.tags.values(): |
2500 | + for t in self.get_all_nodes(): |
2501 | if not attname: |
2502 | l.append(t) |
2503 | elif t.get_attribute(attname) == attvalue: |
2504 | @@ -197,18 +231,8 @@ |
2505 | def get_name(self): |
2506 | """Return the name of the tag.""" |
2507 | return self.get_attribute("name") |
2508 | - |
2509 | - def rename(self,newname): |
2510 | - old = self.get_name() |
2511 | - newname = saxutils.unescape(newname) |
2512 | - self.set_attribute("name",newname,internalrename=True) |
2513 | - for t in self.get_tasks(): |
2514 | - ta = self.req.get_task(t) |
2515 | - ta.rename_tag(old,newname) |
2516 | - ta.sync() |
2517 | - |
2518 | |
2519 | - def set_attribute(self, att_name, att_value, internalrename=False): |
2520 | + def set_attribute(self, att_name, att_value): |
2521 | """Set an arbitrary attribute. |
2522 | |
2523 | This will call the C{save_cllbk} callback passed to the constructor. |
2524 | @@ -217,33 +241,50 @@ |
2525 | @param att_value: The value of the attribute. Will be converted to a |
2526 | string. |
2527 | """ |
2528 | - if att_name == "name" and not internalrename: |
2529 | + if att_name == "name": |
2530 | # Warning : only the constructor can set the "name". |
2531 | #or the internalrename |
2532 | - # |
2533 | - # XXX: This should actually raise an exception, or warn, or |
2534 | - # something. The Zen of Python says "Errors should never pass |
2535 | - # silently." -- jml, 2009-07-17 |
2536 | - return |
2537 | - # Attributes should all be strings. |
2538 | - val = unicode(str(att_value), "UTF-8") |
2539 | - self._attributes[att_name] = val |
2540 | - if self._save: |
2541 | - self._save() |
2542 | + #This should raise an exception : FIXME |
2543 | + #print "Error : The name of a tag cannot be manually set" |
2544 | + pass |
2545 | + elif att_name == "parent": |
2546 | + tree = self.get_tree() |
2547 | + par = tree.get_node(att_value) |
2548 | + if par: |
2549 | + self.add_parent(par.get_id()) |
2550 | + self._attributes['parent'] = "We don't care about that value" |
2551 | + else: |
2552 | + # Attributes should all be strings. |
2553 | + val = unicode(str(att_value), "UTF-8") |
2554 | + self._attributes[att_name] = val |
2555 | + if self._save: |
2556 | + self._save() |
2557 | |
2558 | def get_attribute(self, att_name): |
2559 | """Get the attribute C{att_name}. |
2560 | |
2561 | Returns C{None} if there is no attribute matching C{att_name}. |
2562 | """ |
2563 | - return self._attributes.get(att_name, None) |
2564 | + to_return = None |
2565 | + if att_name == 'parent': |
2566 | + if self.has_parent(): |
2567 | + parlist = self.get_parents() |
2568 | + to_return = parlist.pop() |
2569 | + while len(parlist) > 0: |
2570 | + to_return += ",%s" % parlist.pop() |
2571 | + else: |
2572 | + to_return = self._attributes.get(att_name, None) |
2573 | + return to_return |
2574 | |
2575 | def del_attribute(self, att_name): |
2576 | """Deletes the attribute C{att_name}. |
2577 | """ |
2578 | if not att_name in self._attributes: |
2579 | return |
2580 | - del self._attributes[att_name] |
2581 | + elif att_name in ['name','parent']: |
2582 | + return |
2583 | + else: |
2584 | + del self._attributes[att_name] |
2585 | if self._save: |
2586 | self._save() |
2587 | |
2588 | @@ -258,20 +299,6 @@ |
2589 | attributes.remove('name') |
2590 | return attributes |
2591 | |
2592 | - def reparent(self, parent, update_attr=True): |
2593 | - if update_attr: |
2594 | - if isinstance(parent, Tag): |
2595 | - self.set_attribute('parent', parent.get_name()) |
2596 | - elif 'parent' in self._attributes: |
2597 | - del self._attributes['parent'] |
2598 | - TreeNode.reparent(self, parent) |
2599 | - |
2600 | - def all_children(self): |
2601 | - l = [self] |
2602 | - for i in self.get_children_objs(): |
2603 | - l += i.all_children() |
2604 | - return l |
2605 | - |
2606 | ### TASK relation #### |
2607 | def add_task(self, tid): |
2608 | if tid not in self.tasks: |
2609 | @@ -283,8 +310,9 @@ |
2610 | #return a copy of the list |
2611 | toreturn = self.tasks[:] |
2612 | tmplist = [] |
2613 | - for c in self.get_children_objs(): |
2614 | - tmplist.extend(c.get_tasks()) |
2615 | + for c in self.get_children(): |
2616 | + node = self.tree.get_node(c) |
2617 | + tmplist.extend(node.get_tasks()) |
2618 | for ti in tmplist: |
2619 | if ti not in toreturn: |
2620 | toreturn.append(ti) |
2621 | @@ -308,7 +336,7 @@ |
2622 | else: |
2623 | for t in tasks: |
2624 | ta = self.req.get_task(t) |
2625 | - if ta.get_status() == "Active" and t not in temp_list: |
2626 | + if ta and ta.get_status() == "Active" and t not in temp_list: |
2627 | temp_list.append(t) |
2628 | toreturn = len(temp_list) |
2629 | return toreturn |
2630 | |
2631 | === modified file 'GTG/core/task.py' |
2632 | --- GTG/core/task.py 2010-02-12 14:19:29 +0000 |
2633 | +++ GTG/core/task.py 2010-03-03 02:01:18 +0000 |
2634 | @@ -17,6 +17,10 @@ |
2635 | # this program. If not, see <http://www.gnu.org/licenses/>. |
2636 | # ----------------------------------------------------------------------------- |
2637 | |
2638 | +""" |
2639 | +task.py contains the Task class which represents (guess what) a task |
2640 | +""" |
2641 | + |
2642 | import xml.dom.minidom |
2643 | import uuid |
2644 | import xml.sax.saxutils as saxutils |
2645 | @@ -24,9 +28,10 @@ |
2646 | from GTG import _ |
2647 | from GTG.tools.dates import date_today, no_date, Date |
2648 | from datetime import datetime |
2649 | - |
2650 | - |
2651 | -class Task: |
2652 | +from GTG.core.tree import TreeNode |
2653 | + |
2654 | + |
2655 | +class Task(TreeNode): |
2656 | """ This class represent a task in GTG. |
2657 | You should never create a Task directly. Use the datastore.new_task() |
2658 | function.""" |
2659 | @@ -36,6 +41,7 @@ |
2660 | STA_DONE = "Done" |
2661 | |
2662 | def __init__(self, ze_id, requester, newtask=False): |
2663 | + TreeNode.__init__(self, ze_id) |
2664 | #the id of this task in the project should be set |
2665 | #tid is a string ! (we have to choose a type and stick to it) |
2666 | self.tid = str(ze_id) |
2667 | @@ -50,9 +56,6 @@ |
2668 | self.closed_date = no_date |
2669 | self.due_date = no_date |
2670 | self.start_date = no_date |
2671 | - self.parents = [] |
2672 | - #The list of children tid |
2673 | - self.children = [] |
2674 | self.can_be_deleted = newtask |
2675 | # tags |
2676 | self.tags = [] |
2677 | @@ -73,7 +76,8 @@ |
2678 | self.loaded = True |
2679 | if signal: |
2680 | self.req._task_loaded(self.tid) |
2681 | - self.call_modified() |
2682 | + #not sure the following is necessary |
2683 | + #self.req._task_modified(self.tid) |
2684 | |
2685 | def set_to_keep(self): |
2686 | self.can_be_deleted = False |
2687 | @@ -159,8 +163,9 @@ |
2688 | def is_workable(self): |
2689 | workable = True |
2690 | for c in self.get_subtasks(): |
2691 | - if c.get_status() == self.STA_ACTIVE: |
2692 | + if c and c.get_status() == self.STA_ACTIVE: |
2693 | workable = False |
2694 | +# print "task %s workable :Â %s" %(self.get_id(),workable) |
2695 | return workable |
2696 | |
2697 | #A task is in the workview if it is workable, started, active and |
2698 | @@ -321,65 +326,60 @@ |
2699 | """ |
2700 | uid, pid = self.get_id().split('@') #pylint: disable-msg=W0612 |
2701 | subt = self.req.new_task(pid=pid, newtask=True) |
2702 | - self.add_subtask(subt.get_id()) |
2703 | + #we use the inherited childrens |
2704 | + self.add_child(subt.get_id()) |
2705 | return subt |
2706 | - |
2707 | - def add_subtask(self, tid): |
2708 | + |
2709 | + def add_child(self, tid): |
2710 | """Add a subtask to this task |
2711 | |
2712 | - @param tid: the ID of the added task |
2713 | + @param child: the added task |
2714 | """ |
2715 | self.can_be_deleted = False |
2716 | - #The if prevent an infinite loop |
2717 | - if tid not in self.children and tid not in self.parents and\ |
2718 | - tid != self.get_id(): |
2719 | - self.children.append(tid) |
2720 | - task = self.req.get_task(tid) |
2721 | - task.add_parent(self.get_id()) |
2722 | + #the core of the method is in the TreeNode object |
2723 | + if TreeNode.add_child(self,tid): |
2724 | #now we set inherited attributes only if it's a new task |
2725 | - if task.can_be_deleted: |
2726 | - task.set_start_date(self.get_start_date()) |
2727 | + child = self.req.get_task(tid) |
2728 | + if child.can_be_deleted: |
2729 | + child.set_start_date(self.get_start_date()) |
2730 | for t in self.get_tags(): |
2731 | - task.tag_added(t.get_name()) |
2732 | - |
2733 | - def remove_subtask(self, tid): |
2734 | + child.tag_added(t.get_name()) |
2735 | + return True |
2736 | + else: |
2737 | + return False |
2738 | + |
2739 | + def remove_child(self,tid): |
2740 | """Removed a subtask from the task. |
2741 | |
2742 | @param tid: the ID of the task to remove |
2743 | """ |
2744 | - if tid in self.children: |
2745 | - self.children.remove(tid) |
2746 | + if TreeNode.remove_child(self,tid): |
2747 | task = self.req.get_task(tid) |
2748 | if task.can_be_deleted: |
2749 | self.req.delete_task(tid) |
2750 | - else: |
2751 | - task.remove_parent(self.get_id()) |
2752 | self.sync() |
2753 | - |
2754 | - def has_subtasks(self): |
2755 | - """Returns True if task has subtasks. |
2756 | - """ |
2757 | - return len(self.children) != 0 |
2758 | - |
2759 | - def get_n_subtasks(self): |
2760 | - """Return the number of subtasks of a task. |
2761 | - """ |
2762 | - return len(self.children) |
2763 | - |
2764 | + return True |
2765 | + else: |
2766 | + return False |
2767 | + |
2768 | + #FIXME : remove this method |
2769 | def get_subtasks(self): |
2770 | - """Return the list of subtasks. |
2771 | - """ |
2772 | +# print "Deprecation Warning : use get_children instead of get_subtasks" |
2773 | #XXX: is this useful? |
2774 | zelist = [] |
2775 | - for i in self.children: |
2776 | - zelist.append(self.req.get_task(i)) |
2777 | + for i in self.get_children(): |
2778 | + t = self.req.get_task(i) |
2779 | + if t: |
2780 | + zelist.append(t) |
2781 | return zelist |
2782 | |
2783 | def get_self_and_all_subtasks(self, active_only=False, tasks=[]): |
2784 | tasks.append(self) |
2785 | - for i in self.get_subtasks(): |
2786 | - if not active_only or i.status == self.STA_ACTIVE: |
2787 | - i.get_self_and_all_subtasks(active_only, tasks) |
2788 | + for tid in self.get_children(): |
2789 | + i = self.req.get_task(tid) |
2790 | + if i: |
2791 | + if not active_only or i.status == self.STA_ACTIVE: |
2792 | + i.get_self_and_all_subtasks(active_only, tasks) |
2793 | return tasks |
2794 | |
2795 | def get_subtask(self, tid): |
2796 | @@ -389,69 +389,41 @@ |
2797 | """ |
2798 | return self.req.get_task(tid) |
2799 | |
2800 | - def get_subtask_tids(self): |
2801 | - """Return the list of subtasks. Return a list of IDs. |
2802 | - """ |
2803 | - return list(self.children) |
2804 | - |
2805 | - def get_nth_subtask(self, index): |
2806 | - """Return the task ID stored at a given index. |
2807 | - |
2808 | - @param index: the index of the task to return. |
2809 | - """ |
2810 | - try: |
2811 | - return self.children[index] |
2812 | - except(IndexError): |
2813 | - raise ValueError("Index is not in task list") |
2814 | - |
2815 | - def get_subtask_index(self, tid): |
2816 | - """Return the index of a given subtask. |
2817 | - |
2818 | - @param tid: the tid of the task whose index must be returned. |
2819 | - """ |
2820 | - return self.children.index(tid) |
2821 | - |
2822 | - #add and remove parents are private |
2823 | - #Only the task itself can play with it's parent |
2824 | - |
2825 | ### PARENTS ############################################################## |
2826 | |
2827 | #Take a tid object as parameter |
2828 | - def add_parent(self, tid): |
2829 | - #The if prevent a loop |
2830 | - if tid and tid not in self.children and tid not in self.parents: |
2831 | - self.parents.append(tid) |
2832 | + def add_parent(self, parent_tid): |
2833 | + added = TreeNode.add_parent(self, parent_tid) |
2834 | + if added: |
2835 | + #print "add_parent %s to %s" %(parent.get_id(),self.get_id()) |
2836 | self.sync() |
2837 | - task = self.req.get_task(tid) |
2838 | - task.add_subtask(self.get_id()) |
2839 | - task.sync() |
2840 | + parent.sync() |
2841 | + return True |
2842 | + else: |
2843 | + return False |
2844 | |
2845 | #Take a tid as parameter |
2846 | def remove_parent(self, tid): |
2847 | - if tid and tid in self.parents: |
2848 | - self.parents.remove(tid) |
2849 | - self.sync() |
2850 | - parent = self.req.get_task(tid) |
2851 | - if parent: |
2852 | - parent.remove_subtask(self.get_id()) |
2853 | - parent.sync() |
2854 | - |
2855 | - def get_parents(self): |
2856 | - return list(self.parents) |
2857 | + TreeNode.remove_parent(self,tid) |
2858 | + self.sync() |
2859 | + parent = self.req.get_task(tid) |
2860 | + if parent: |
2861 | + parent.sync() |
2862 | |
2863 | #Return true is the task has parent |
2864 | #If tag is provided, return True only |
2865 | #if the parent has this particular tag |
2866 | def has_parents(self, tag=None): |
2867 | + has_par = TreeNode.has_parent(self) |
2868 | #The "all tag" argument |
2869 | - if tag and len(self.parents)!=0: |
2870 | + if tag and has_par: |
2871 | a = 0 |
2872 | - for tid in self.parents: |
2873 | + for tid in self.get_parents(): |
2874 | p = self.req.get_task(tid) |
2875 | a += p.has_tags(tag) |
2876 | to_return = a |
2877 | else: |
2878 | - to_return = len(self.parents)!=0 |
2879 | + to_return = has_par |
2880 | return to_return |
2881 | |
2882 | def set_attribute(self, att_name, att_value, namespace=""): |
2883 | @@ -482,8 +454,9 @@ |
2884 | self.req.delete_task(task.get_id()) |
2885 | for i in self.get_parents(): |
2886 | task = self.req.get_task(i) |
2887 | - task.remove_subtask(self.get_id()) |
2888 | - for tag in self.tags: |
2889 | + task.remove_child(self.get_id()) |
2890 | + for tagname in self.tags: |
2891 | + tag = self.req.get_tag(tagname) |
2892 | tag.remove_task(self.get_id()) |
2893 | #then we remove effectively the task |
2894 | #self.req.delete_task(self.get_id()) |
2895 | @@ -506,7 +479,7 @@ |
2896 | def call_modified(self): |
2897 | self.req._task_modified(self.tid) |
2898 | #we also modify parents and children |
2899 | - for s in self.get_subtask_tids(): |
2900 | + for s in self.get_children(): |
2901 | self.req._task_modified(s) |
2902 | for p in self.get_parents(): |
2903 | self.req._task_modified(p) |
2904 | @@ -520,15 +493,14 @@ |
2905 | # |
2906 | def get_tags_name(self): |
2907 | #Return a copy of the list of tags. Not the original object. |
2908 | + return list(self.tags) |
2909 | + |
2910 | + #return a copy of the list of tag objects |
2911 | + def get_tags(self): |
2912 | l = [] |
2913 | - for t in self.tags: |
2914 | - name = t.get_name().encode("UTF-8") |
2915 | - l.append(name) |
2916 | + for tname in self.tags: |
2917 | + l.append(self.req.get_tag(tname)) |
2918 | return l |
2919 | - |
2920 | - #return a copy of the list of tag objects |
2921 | - def get_tags(self): |
2922 | - return list(self.tags) |
2923 | |
2924 | def rename_tag(self, old, new): |
2925 | eold = saxutils.escape(saxutils.unescape(old)) |
2926 | @@ -538,15 +510,19 @@ |
2927 | self.tag_added(new) |
2928 | |
2929 | def tag_added(self, tagname): |
2930 | + #print "tag %s added to task %s" %(tagname,self.get_id()) |
2931 | "Add a tag. Does not add '@tag' to the contents. See insert_tag" |
2932 | - t = self.req.new_tag(tagname.encode("UTF-8")) |
2933 | - t.add_task(self.get_id()) |
2934 | + t = tagname.encode("UTF-8") |
2935 | + tag = self.req.get_tag(t) |
2936 | + if not tag: |
2937 | + tag = self.req.new_tag(t) |
2938 | + tag.add_task(self.get_id()) |
2939 | #Do not add the same tag twice |
2940 | if not t in self.tags: |
2941 | self.tags.append(t) |
2942 | for child in self.get_subtasks(): |
2943 | if child.can_be_deleted: |
2944 | - child.add_tag(tagname) |
2945 | + child.add_tag(t) |
2946 | return True |
2947 | |
2948 | def add_tag(self, tagname): |
2949 | @@ -577,8 +553,8 @@ |
2950 | def remove_tag(self, tagname): |
2951 | t = self.req.get_tag(tagname) |
2952 | t.remove_task(self.get_id()) |
2953 | - if t in self.tags: |
2954 | - self.tags.remove(t) |
2955 | + if tagname in self.tags: |
2956 | + self.tags.remove(tagname) |
2957 | for child in self.get_subtasks(): |
2958 | if child.can_be_deleted: |
2959 | child.remove_tag(tagname) |
2960 | @@ -596,7 +572,7 @@ |
2961 | ) |
2962 | |
2963 | |
2964 | - #tag_list is a list of tags object |
2965 | + #tag_list is a list of tags names |
2966 | #return true if at least one of the list is in the task |
2967 | def has_tags(self, tag_list=None, notag_only=False): |
2968 | #We want to see if the task has no tags |
2969 | |
2970 | === modified file 'GTG/core/tree.py' |
2971 | --- GTG/core/tree.py 2009-12-26 20:59:06 +0000 |
2972 | +++ GTG/core/tree.py 2010-03-03 02:01:18 +0000 |
2973 | @@ -1,26 +1,39 @@ |
2974 | +# -*- coding: utf-8 -*- |
2975 | +# ----------------------------------------------------------------------------- |
2976 | +# Gettings Things Gnome! - a personal organizer for the GNOME desktop |
2977 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
2978 | +# |
2979 | +# This program is free software: you can redistribute it and/or modify it under |
2980 | +# the terms of the GNU General Public License as published by the Free Software |
2981 | +# Foundation, either version 3 of the License, or (at your option) any later |
2982 | +# version. |
2983 | +# |
2984 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2985 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
2986 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
2987 | +# details. |
2988 | +# |
2989 | +# You should have received a copy of the GNU General Public License along with |
2990 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
2991 | +# ----------------------------------------------------------------------------- |
2992 | + |
2993 | class Tree(): |
2994 | |
2995 | def __init__(self, root=None): |
2996 | + self.root_id = 'root' |
2997 | self.nodes = {} |
2998 | + self.pending_relationships = [] |
2999 | if root: |
3000 | self.root = root |
3001 | else: |
3002 | - self.root = TreeNode(id="root") |
3003 | + self.root = TreeNode(id=self.root_id) |
3004 | + self.root.set_tree(self) |
3005 | |
3006 | def __str__(self): |
3007 | return "<Tree: root = '%s'>" % (str(self.root)) |
3008 | |
3009 | - def get_rowref_for_path(self, path): |
3010 | - return self._rowref_for_path(self.root, path) |
3011 | - |
3012 | - def get_path_for_rowref(self, rowref): |
3013 | - return self._path_for_rowref(self.root, rowref) |
3014 | - |
3015 | - def get_node_for_rowref(self, rowref): |
3016 | - return self._node_for_rowref(self.root, rowref) |
3017 | - |
3018 | - def get_rowref_for_node(self, node): |
3019 | - return self._rowref_for_node(node) |
3020 | + def get_node_for_path(self, path): |
3021 | + return self._node_for_path(self.root,path) |
3022 | |
3023 | def get_path_for_node(self, node): |
3024 | return self._path_for_node(node) |
3025 | @@ -30,38 +43,122 @@ |
3026 | |
3027 | def set_root(self, root): |
3028 | self.root = root |
3029 | - |
3030 | - def add_node(self, id, node, parent): |
3031 | - if parent: |
3032 | - node.set_parent(parent) |
3033 | - parent.add_child(id, node) |
3034 | - else: |
3035 | - node.set_parent(self.root) |
3036 | - self.root.add_child(id, node) |
3037 | - node_list = self.nodes.get(id) |
3038 | - if node_list: |
3039 | - node_list.append(node) |
3040 | - else: |
3041 | - self.nodes[id] = [node] |
3042 | - |
3043 | - def remove_node(self, id, node): |
3044 | - if node.has_child(): |
3045 | - for c_id in node.get_children(): |
3046 | - self.remove_node(c_id, node.get_child(c_id)) |
3047 | - if node.has_parent(): |
3048 | - par = node.get_parent() |
3049 | - par.remove_child(node.get_id()) |
3050 | - node_list = self.nodes.get(id) |
3051 | - node_list.remove(node) |
3052 | - |
3053 | - def get_nodes(self, id): |
3054 | - if id in self.nodes: |
3055 | - return list(self.nodes[id]) |
3056 | - else: |
3057 | - return [] |
3058 | + self.root.set_tree(self) |
3059 | + |
3060 | + #We add a node. By default, it's a child of the root |
3061 | + def add_node(self, node, parent=None): |
3062 | + #print "*************adding node %s %s" %(node, parent) |
3063 | + id = node.get_id() |
3064 | + if self.nodes.get(id): |
3065 | + print "Error : A node with this id %s already exists" %id |
3066 | + return False |
3067 | + else: |
3068 | + #We add the node |
3069 | + node.set_tree(self) |
3070 | + if parent:# |
3071 | + node.set_parent(parent) |
3072 | + parent.add_child(id) |
3073 | + else: |
3074 | + self.root.add_child(id) |
3075 | + self.nodes[id] = node |
3076 | + #build the relationships that were waiting for that node |
3077 | + for rel in list(self.pending_relationships): |
3078 | + if id in rel: |
3079 | + self.new_relationship(rel[0],rel[1]) |
3080 | + return True |
3081 | + |
3082 | + #this will remove a node and all his children |
3083 | + #does nothing if the node doesn't exist |
3084 | + def remove_node(self, id): |
3085 | + node = self.get_node(id) |
3086 | + if not node : |
3087 | + return |
3088 | + else: |
3089 | + if node.has_child(): |
3090 | + for c_id in node.get_children(): |
3091 | + self.remove_node(c_id) |
3092 | + if node.has_parent(): |
3093 | + for p_id in node.get_parents(): |
3094 | + par = self.get_node(p_id) |
3095 | + par.remove_child(id) |
3096 | + else: |
3097 | + self.root.remove_child(id) |
3098 | + self.nodes.pop(id) |
3099 | + |
3100 | + #create a new relationship between nodes if it doesn't already exist |
3101 | + #return False if nothing was done |
3102 | + def new_relationship(self,parent_id,child_id): |
3103 | + #print "new relationship between %s and %s" %(parent_id,child_id) |
3104 | + if [parent_id,child_id] in self.pending_relationships: |
3105 | + self.pending_relationships.remove([parent_id,child_id]) |
3106 | + toreturn = False |
3107 | + #no relationship allowed with yourself |
3108 | + if parent_id != child_id: |
3109 | + p = self.get_node(parent_id) |
3110 | + c = self.get_node(child_id) |
3111 | + if p and c : |
3112 | + #no circular relationship allowed |
3113 | + if not p.has_parent(child_id) and not c.has_child(parent_id): |
3114 | + if not p.has_child(child_id): |
3115 | + p.add_child(child_id) |
3116 | + toreturn = True |
3117 | + if not c.has_parent(parent_id): |
3118 | + #print "creating the %s - %s relation" %(parent_id,child_id) |
3119 | + c.add_parent(parent_id) |
3120 | + toreturn = True |
3121 | + #removing the root from the list of parent |
3122 | + if self.root.has_child(child_id): |
3123 | + self.root.remove_child(child_id) |
3124 | + else: |
3125 | + #a circular relationship was found |
3126 | + #undo everything |
3127 | + self.break_relationship(parent_id,child_id) |
3128 | + toreturn = False |
3129 | + else: |
3130 | + #at least one of the node is not loaded. Save the relation for later |
3131 | + #undo everything |
3132 | + self.break_relationship(parent_id,child_id) |
3133 | + #save it for later |
3134 | + if [parent_id,child_id] not in self.pending_relationships: |
3135 | + self.pending_relationships.append([parent_id,child_id]) |
3136 | + toreturn = True |
3137 | + return toreturn |
3138 | + |
3139 | + #break an existing relationship. The child is added to the root |
3140 | + #return False if the relationship didn't exist |
3141 | + def break_relationship(self,parent_id,child_id): |
3142 | + toreturn = False |
3143 | + p = self.get_node(parent_id) |
3144 | + c = self.get_node(child_id) |
3145 | + if p and c : |
3146 | + if p.has_child(child_id): |
3147 | + p.remove_child(child_id) |
3148 | + toreturn = True |
3149 | + if c.has_parent(parent_id): |
3150 | + c.remove_parent(parent_id) |
3151 | + toreturn = True |
3152 | + #if no more parent left, adding to the root |
3153 | + if not c.has_parent(): |
3154 | + self.root.add_child(child_id) |
3155 | + return toreturn |
3156 | + |
3157 | + #Trying to make a function that bypass the weirdiness of lists |
3158 | + def get_node(self,id): |
3159 | + return self.nodes.get(id) |
3160 | + |
3161 | + def get_all_keys(self): |
3162 | + return list(self.nodes.keys()) |
3163 | + |
3164 | + def get_all_nodes(self): |
3165 | + li = [] |
3166 | + for k in self.nodes.keys(): |
3167 | + no = self.get_node(k) |
3168 | + if no: |
3169 | + li.append(no) |
3170 | + return li |
3171 | |
3172 | def has_node(self, id): |
3173 | - return id in self.nodes.keys() |
3174 | + return (self.nodes.get(id) != None) |
3175 | |
3176 | def print_tree(self): |
3177 | self._print_from_node(self.root) |
3178 | @@ -74,57 +171,33 @@ |
3179 | |
3180 | ### HELPER FUNCTION FOR TREE ################################################# |
3181 | # |
3182 | - def _rowref_for_path(self, node, path): |
3183 | + def _node_for_path(self,node,path): |
3184 | if path[0] < node.get_n_children(): |
3185 | if len(path) == 1: |
3186 | - return "/" + str(node.get_nth_child(path[0]).get_id()) |
3187 | + return node.get_nth_child(path[0]) |
3188 | else: |
3189 | node = node.get_nth_child(path[0]) |
3190 | path = path[1:] |
3191 | - c_path = self._rowref_for_path(node, path) |
3192 | - if c_path: |
3193 | - return "/" + str(node.get_id()) + str(c_path) |
3194 | - else: |
3195 | - return None |
3196 | + return self._node_for_path(node, path) |
3197 | else: |
3198 | return None |
3199 | |
3200 | - def _path_for_rowref(self, node, rowref): |
3201 | - if rowref.rfind('/') == 0: |
3202 | - return (node.get_child_index(rowref[1:]), ) |
3203 | - else: |
3204 | - cur_id = rowref[1:rowref.find('/', 1)] |
3205 | - cur_node = node.get_child(cur_id) |
3206 | - cur_path = (node.get_child_index(cur_id), ) |
3207 | - rowref = rowref[rowref.find(cur_id)+len(cur_id):] |
3208 | - return cur_path + self._path_for_rowref(cur_node, rowref) |
3209 | - |
3210 | - def _node_for_rowref(self, node, rowref): |
3211 | - #print "_node_for_rowref: %s" % rowref |
3212 | - if rowref == '': |
3213 | - return self.root |
3214 | - elif rowref.rfind('/') == 0: |
3215 | - return node.get_child(rowref[1:]) |
3216 | - else: |
3217 | - cur_id = rowref[1:rowref.find('/', 1)] |
3218 | - cur_node = node.get_child(cur_id) |
3219 | - rowref = rowref[rowref.find(cur_id)+len(cur_id):] |
3220 | - return self._node_for_rowref(cur_node, rowref) |
3221 | - |
3222 | - def _rowref_for_node(self, node): |
3223 | - if not node.has_parent(): |
3224 | - return "" |
3225 | - else: |
3226 | - parent = node.get_parent() |
3227 | - return self._rowref_for_node(parent) + "/" + str(node.get_id()) |
3228 | - |
3229 | def _path_for_node(self, node): |
3230 | - if not node.has_parent(): |
3231 | - return () |
3232 | + if node: |
3233 | + if node == self.root: |
3234 | + toreturn = () |
3235 | + elif not node.has_parent(): |
3236 | + index = self.root.get_child_index(node.get_id()) |
3237 | + toreturn = self._path_for_node(self.root) + (index, ) |
3238 | + else: |
3239 | + #FIXMEÂ : no multiparent support here |
3240 | + parent_id = node.get_parent() |
3241 | + parent = self.get_node(parent_id) |
3242 | + index = parent.get_child_index(node.get_id()) |
3243 | + toreturn = self._path_for_node(parent) + (index, ) |
3244 | else: |
3245 | - parent = node.get_parent() |
3246 | - index = parent.get_child_index(node.get_id()) |
3247 | - return self._path_for_node(parent) + (index, ) |
3248 | + toreturn = None |
3249 | + return toreturn |
3250 | |
3251 | def _print_from_node(self, node, prefix=""): |
3252 | print prefix + node.id |
3253 | @@ -147,71 +220,136 @@ |
3254 | |
3255 | class TreeNode(): |
3256 | |
3257 | - def __init__(self, id, obj=None, parent=None): |
3258 | - self.parent = parent |
3259 | + def __init__(self, id, tree=None, parent=None): |
3260 | + self.parents = [] |
3261 | self.id = id |
3262 | - self.ids = [] |
3263 | - self.children = [] |
3264 | - self.obj = obj |
3265 | + self.children = [] |
3266 | + self.tree = tree |
3267 | + self.pending_relationship = [] |
3268 | + if parent: |
3269 | + self.add_parent(parent) |
3270 | |
3271 | def __str__(self): |
3272 | return "<TreeNode: '%s'>" % (self.id) |
3273 | - |
3274 | - def get_obj(self): |
3275 | - return self.obj |
3276 | - |
3277 | - def set_obj(self, obj): |
3278 | - self.obj = obj |
3279 | + |
3280 | + def set_tree(self,tree): |
3281 | + self.tree = tree |
3282 | + for rel in list(self.pending_relationship): |
3283 | + self.tree.new_relationship(rel[0],rel[1]) |
3284 | + self.pending_relationship.remove(rel) |
3285 | + |
3286 | + def get_tree(self): |
3287 | + return self.tree |
3288 | |
3289 | def get_id(self): |
3290 | return self.id |
3291 | - |
3292 | - def has_parent(self): |
3293 | - return self.parent is not None |
3294 | - |
3295 | + |
3296 | + |
3297 | + def new_relationship(self,par,chi): |
3298 | + if self.tree: |
3299 | + self.tree.new_relationship(par,chi) |
3300 | + else: |
3301 | + self.pending_relationship.append([par,chi]) |
3302 | + |
3303 | + |
3304 | +##### Parents |
3305 | + |
3306 | + def has_parent(self,id=None): |
3307 | + if id: |
3308 | + return id in self.parents |
3309 | + else: |
3310 | + return len(self.parents) > 0 |
3311 | + |
3312 | + #this one return only one parent. |
3313 | + #useful for tree where we know that there is only one |
3314 | def get_parent(self): |
3315 | - return self.parent |
3316 | - |
3317 | - def set_parent(self, par): |
3318 | - self.parent = par |
3319 | - |
3320 | - def has_child(self): |
3321 | - return len(self.ids) != 0 |
3322 | + #we should throw an error if there are multiples parents |
3323 | + if len(self.parents) > 1: |
3324 | + print "Warning : get_parent will return one random parent because \ |
3325 | + there are multiple parents." |
3326 | + if self.has_parent(): |
3327 | + return self.parents[0] |
3328 | + else: |
3329 | + return None |
3330 | + def get_parents(self): |
3331 | + return list(self.parents) |
3332 | + |
3333 | + def add_parent(self, parent_id): |
3334 | +# root = self.tree.get_root() |
3335 | +# print "removing root node has parent" |
3336 | +# self.tree.break_relationship(root.get_id(),self.get_id()) |
3337 | + if parent_id not in self.parents: |
3338 | + self.parents.append(parent_id) |
3339 | + return self.new_relationship(parent_id, self.get_id()) |
3340 | + else: |
3341 | + return False |
3342 | + |
3343 | + #set_parent means that we remove all other parents |
3344 | + def set_parent(self,par): |
3345 | + if par: |
3346 | + for i in self.parents: |
3347 | + if i != par: |
3348 | + self.remove_parent(i) |
3349 | + self.add_parent(par) |
3350 | + |
3351 | + def remove_parent(self,id): |
3352 | + if id in self.parents: |
3353 | + self.parents.remove(id) |
3354 | + self.tree.break_relationship(id,self.get_id()) |
3355 | + |
3356 | +###### Children |
3357 | + |
3358 | + def has_child(self,id=None): |
3359 | + if id : |
3360 | + return id in self.children |
3361 | + else: |
3362 | + return len(self.children) != 0 |
3363 | |
3364 | def get_children(self): |
3365 | - return list(self.ids) |
3366 | - |
3367 | - def get_children_objs(self): |
3368 | return list(self.children) |
3369 | |
3370 | def get_n_children(self): |
3371 | - return len(self.ids) |
3372 | + return len(self.children) |
3373 | |
3374 | def get_nth_child(self, index): |
3375 | - return self.children[index] |
3376 | + try: |
3377 | + id = self.children[index] |
3378 | + return self.tree.get_node(id) |
3379 | + except(IndexError): |
3380 | + raise ValueError("Index is not in the children list") |
3381 | |
3382 | def get_child(self, id): |
3383 | - if id in self.ids: |
3384 | - idx = self.ids.index(id) |
3385 | - return self.children[idx] |
3386 | + if id in self.children: |
3387 | + return self.tree.get_node(id) |
3388 | else: |
3389 | return None |
3390 | |
3391 | def get_child_index(self, id): |
3392 | - return self.ids.index(id) |
3393 | + return self.children.index(id) |
3394 | |
3395 | - def add_child(self, id, child): |
3396 | - self.ids.append(id) |
3397 | - self.children.append(child) |
3398 | + #return True if the child was added correctly. False otherwise |
3399 | + #takes the id of the child as parameter. |
3400 | + #if the child is not already in the tree, the relation is anyway "saved" |
3401 | + def add_child(self, id): |
3402 | + self.children.append(id) |
3403 | + return self.new_relationship(self.get_id(),id) |
3404 | |
3405 | def remove_child(self, id): |
3406 | - idx = self.ids.index(id) |
3407 | - child = self.children[idx] |
3408 | - self.ids.remove(id) |
3409 | - self.children.remove(child) |
3410 | + if id in self.children: |
3411 | + self.children.remove(id) |
3412 | + self.tree.break_relationship(self.get_id(),id) |
3413 | + return True |
3414 | + else: |
3415 | + return False |
3416 | + |
3417 | |
3418 | - def reparent(self, parent): |
3419 | - if self.has_parent(): |
3420 | - self.get_parent().remove_child(self.id) |
3421 | - self.set_parent(parent) |
3422 | - parent.add_child(self.id, self) |
3423 | + def change_id(self,newid): |
3424 | + oldid = self.id |
3425 | + self.id = newid |
3426 | + for p in self.parents: |
3427 | + par = self.tree.get(p) |
3428 | + par.remove_child(oldid) |
3429 | + par.add_child(self.id) |
3430 | + for c in self.get_children(): |
3431 | + c.add_parent(newid) |
3432 | + c.remove_parent(oldid) |
3433 | |
3434 | === modified file 'GTG/gtg.py' |
3435 | --- GTG/gtg.py 2009-12-03 09:47:09 +0000 |
3436 | +++ GTG/gtg.py 2010-03-03 02:01:18 +0000 |
3437 | @@ -42,6 +42,8 @@ |
3438 | # |
3439 | #============================================================================== |
3440 | |
3441 | +"""This is the top-level exec script for running GTG""" |
3442 | + |
3443 | #=== IMPORT =================================================================== |
3444 | from __future__ import with_statement |
3445 | |
3446 | @@ -51,11 +53,11 @@ |
3447 | import logging |
3448 | |
3449 | #our own imports |
3450 | -from GTG import _ |
3451 | -from GTG.taskbrowser.browser import TaskBrowser |
3452 | -from GTG.core.datastore import DataStore |
3453 | -from GTG.core.dbuswrapper import DBusTaskWrapper |
3454 | -from GTG.core import CoreConfig |
3455 | +from GTG import _ |
3456 | +from GTG.viewmanager.manager import Manager |
3457 | +from GTG.core.datastore import DataStore |
3458 | +from GTG.core import CoreConfig |
3459 | +from GTG.tools.logger import Log |
3460 | |
3461 | #=== OBJECTS ================================================================== |
3462 | |
3463 | @@ -90,17 +92,10 @@ |
3464 | #=== MAIN CLASS =============================================================== |
3465 | |
3466 | def main(options=None, args=None): |
3467 | - |
3468 | - # init logging system |
3469 | - logger = logging.getLogger("gtg_logger") |
3470 | - ch = logging.StreamHandler() |
3471 | - ch.setLevel(logging.DEBUG) |
3472 | - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(module)s:%(funcName)s:%(lineno)d - %(message)s") |
3473 | - ch.setFormatter(formatter) |
3474 | - logger.addHandler(ch) |
3475 | + # Debugging subsystem initialization |
3476 | if options.debug: |
3477 | - logger.setLevel(logging.DEBUG) |
3478 | - logger.debug("Debug output enabled.") |
3479 | + Log.setLevel(logging.DEBUG) |
3480 | + Log.debug("Debug output enabled.") |
3481 | |
3482 | config = CoreConfig() |
3483 | check_instance(config.DATA_DIR) |
3484 | @@ -117,9 +112,9 @@ |
3485 | |
3486 | # Launch task browser |
3487 | req = ds.get_requester() |
3488 | - tb = TaskBrowser(req, config, logger=logger) |
3489 | - DBusTaskWrapper(req, tb) |
3490 | - tb.main() |
3491 | + manager = Manager(req, config) |
3492 | + #main loop |
3493 | + manager.main() |
3494 | |
3495 | # Ideally we should load window geometry configuration from a config. |
3496 | # backend like gconf at some point, and restore the appearance of the |
3497 | |
3498 | === modified file 'GTG/info.py' |
3499 | --- GTG/info.py 2010-02-28 23:44:14 +0000 |
3500 | +++ GTG/info.py 2010-03-03 02:01:18 +0000 |
3501 | @@ -1,5 +1,5 @@ |
3502 | # -*- coding: utf-8 -*- |
3503 | -#various information about GTG. Should be updated for every release. |
3504 | +"""Various information about GTG. Should be updated for every release.""" |
3505 | |
3506 | URL = "http://gtg.fritalk.com" |
3507 | EMAIL = "gtg-contributors@lists.launchpad.net" |
3508 | |
3509 | === modified file 'GTG/plugins/__init__.py' |
3510 | --- GTG/plugins/__init__.py 2009-12-28 08:28:41 +0000 |
3511 | +++ GTG/plugins/__init__.py 2010-03-03 02:01:18 +0000 |
3512 | @@ -0,0 +1,1 @@ |
3513 | +"""Optional plug-ins for GTG""" |
3514 | |
3515 | === modified file 'GTG/plugins/export/export.ui' |
3516 | --- GTG/plugins/export/export.ui 2010-01-07 09:08:19 +0000 |
3517 | +++ GTG/plugins/export/export.ui 2010-03-03 02:01:18 +0000 |
3518 | @@ -172,6 +172,7 @@ |
3519 | <object class="GtkWindow" id="preferences_dialog"> |
3520 | <property name="border_width">10</property> |
3521 | <property name="window_position">center-on-parent</property> |
3522 | + <property name="type_hint">dialog</property> |
3523 | <signal name="delete_event" handler="on_preferences_dialog_delete_event"/> |
3524 | <child> |
3525 | <object class="GtkVBox" id="vbox1"> |
3526 | |
3527 | === modified file 'GTG/plugins/hamster/prefs.ui' |
3528 | --- GTG/plugins/hamster/prefs.ui 2010-02-11 20:23:43 +0000 |
3529 | +++ GTG/plugins/hamster/prefs.ui 2010-03-03 02:01:18 +0000 |
3530 | @@ -5,7 +5,7 @@ |
3531 | <object class="GtkDialog" id="dialog1"> |
3532 | <property name="border_width">5</property> |
3533 | <property name="title" translatable="yes">Hamster Preferences</property> |
3534 | - <property name="type_hint">normal</property> |
3535 | + <property name="type_hint">dialog</property> |
3536 | <property name="has_separator">False</property> |
3537 | <signal name="delete_event" handler="prefs_close"/> |
3538 | <child internal-child="vbox"> |
3539 | |
3540 | === modified file 'GTG/plugins/notification_area/notification_area.ui' |
3541 | --- GTG/plugins/notification_area/notification_area.ui 2010-02-15 21:37:57 +0000 |
3542 | +++ GTG/plugins/notification_area/notification_area.ui 2010-03-03 02:01:18 +0000 |
3543 | @@ -6,6 +6,7 @@ |
3544 | <object class="GtkWindow" id="preferences_dialog"> |
3545 | <property name="border_width">10</property> |
3546 | <property name="window_position">center-on-parent</property> |
3547 | + <property name="type_hint">dialog</property> |
3548 | <signal name="delete_event" handler="on_preferences_dialog_delete_event"/> |
3549 | <child> |
3550 | <object class="GtkVBox" id="vbox1"> |
3551 | |
3552 | === modified file 'GTG/plugins/rtm_sync/__init__.py' |
3553 | --- GTG/plugins/rtm_sync/__init__.py 2009-12-28 14:08:19 +0000 |
3554 | +++ GTG/plugins/rtm_sync/__init__.py 2010-03-03 02:01:18 +0000 |
3555 | @@ -23,4 +23,3 @@ |
3556 | |
3557 | #suppress pyflakes warning (given by make lint) |
3558 | if False == True: RtmSync() |
3559 | - |
3560 | |
3561 | === modified file 'GTG/plugins/rtm_sync/genericTask.py' |
3562 | --- GTG/plugins/rtm_sync/genericTask.py 2010-02-03 05:43:48 +0000 |
3563 | +++ GTG/plugins/rtm_sync/genericTask.py 2010-03-03 02:01:18 +0000 |
3564 | @@ -36,8 +36,8 @@ |
3565 | # (where GET is fast, but SET is slow) |
3566 | if self.title != task.title: |
3567 | self.title = task.title |
3568 | - # if self.text != task.text: |
3569 | - #self.text = task.text |
3570 | + if self.text != task.text: |
3571 | + self.text = task.text |
3572 | if self.status != task.status: |
3573 | self.status = task.status |
3574 | if self.due_date != task.due_date: |
3575 | |
3576 | === modified file 'GTG/plugins/rtm_sync/gtgProxy.py' |
3577 | --- GTG/plugins/rtm_sync/gtgProxy.py 2010-02-13 23:14:31 +0000 |
3578 | +++ GTG/plugins/rtm_sync/gtgProxy.py 2010-03-03 02:01:18 +0000 |
3579 | @@ -53,4 +53,3 @@ |
3580 | #NOTE: delete_task wants the internal gtg id, not the uuid |
3581 | id = task.get_gtg_task().get_id() |
3582 | self.requester.delete_task(id) |
3583 | - |
3584 | |
3585 | === modified file 'GTG/plugins/rtm_sync/gtgTask.py' |
3586 | --- GTG/plugins/rtm_sync/gtgTask.py 2010-02-10 05:51:04 +0000 |
3587 | +++ GTG/plugins/rtm_sync/gtgTask.py 2010-03-03 02:01:18 +0000 |
3588 | @@ -58,7 +58,7 @@ |
3589 | self._gtg_task.remove_tag(tag) |
3590 | #tags to add |
3591 | for tag in other_tags_set.difference(gtg_tags_set): |
3592 | - gtg_all_tags = [ t.get_name() for t in \ |
3593 | + gtg_all_tags = [t.get_name() for t in \ |
3594 | self.plugin_api.get_requester().get_all_tags()] |
3595 | matching_tags = filter(lambda t: t.lower() == tag, gtg_all_tags) |
3596 | if len(matching_tags) != 0: |
3597 | @@ -66,12 +66,10 @@ |
3598 | self._gtg_task.add_tag(tag) |
3599 | |
3600 | def _get_text(self): |
3601 | - return self._gtg_task.get_excerpt() |
3602 | - |
3603 | + return self._gtg_task.get_excerpt(strip_tags = True, \ |
3604 | + strip_subtasks = True) |
3605 | def _set_text(self, text): |
3606 | #fill in subtasks |
3607 | - |
3608 | - |
3609 | self._gtg_task.set_text(text) |
3610 | |
3611 | def _set_status(self, status): |
3612 | |
3613 | === modified file 'GTG/plugins/rtm_sync/rtmProxy.py' |
3614 | --- GTG/plugins/rtm_sync/rtmProxy.py 2010-03-01 23:15:01 +0000 |
3615 | +++ GTG/plugins/rtm_sync/rtmProxy.py 2010-03-03 02:01:18 +0000 |
3616 | @@ -23,6 +23,7 @@ |
3617 | #import gobject |
3618 | from xdg.BaseDirectory import xdg_config_home |
3619 | from GTG.core.task import Task |
3620 | +from GTG.tools.logger import Log |
3621 | from GTG import _ |
3622 | import pickle |
3623 | #import xml.utils.iso8601 |
3624 | @@ -44,10 +45,9 @@ |
3625 | __RTM_STATUSES = [True, |
3626 | False] |
3627 | |
3628 | - def __init__(self, logger): |
3629 | + def __init__(self): |
3630 | super(RtmProxy, self).__init__() |
3631 | self.token = None |
3632 | - self.logger = logger |
3633 | self._gtg_to_rtm_status = dict(zip(self.__GTG_STATUSES, |
3634 | self.__RTM_STATUSES)) |
3635 | self._gtg_to_rtm_status[Task.STA_DISMISSED] = "1" |
3636 | @@ -129,16 +129,15 @@ |
3637 | for task, list_id, taskseries_id in data: |
3638 | self._task_list.append(RtmTask(task, list_id, taskseries_id, \ |
3639 | self.rtm, self.timeline, \ |
3640 | - self.logger, self)) |
3641 | - if self.logger: |
3642 | - map(lambda task: self.logger.debug("RTM task: |" + task.title), |
3643 | - self._task_list) |
3644 | + self)) |
3645 | + map(lambda task: Log.debug("RTM task: |" + task.title), |
3646 | + self._task_list) |
3647 | |
3648 | def create_new_task(self, title): |
3649 | result = self.rtm.tasks.add(timeline=self.timeline, name=title) |
3650 | new_task= RtmTask(result.list.taskseries.task, result.list.id,\ |
3651 | result.list.taskseries.id, self.rtm, self.timeline,\ |
3652 | - self.logger, self) |
3653 | + self) |
3654 | self._task_list.append(new_task) |
3655 | return new_task |
3656 | |
3657 | |
3658 | === modified file 'GTG/plugins/rtm_sync/rtmTask.py' |
3659 | --- GTG/plugins/rtm_sync/rtmTask.py 2010-02-12 02:46:45 +0000 |
3660 | +++ GTG/plugins/rtm_sync/rtmTask.py 2010-03-03 02:01:18 +0000 |
3661 | @@ -21,19 +21,18 @@ |
3662 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
3663 | from genericTask import GenericTask |
3664 | |
3665 | +from GTG.tools.logger import Log |
3666 | |
3667 | |
3668 | class RtmTask(GenericTask): |
3669 | |
3670 | - def __init__(self, task, list_id, taskseries_id, rtm, timeline, logger, |
3671 | - proxy): |
3672 | + def __init__(self, task, list_id, taskseries_id, rtm, timeline, proxy): |
3673 | super(RtmTask, self).__init__(proxy) |
3674 | self.rtm = rtm |
3675 | self.timeline = timeline |
3676 | self.task = task |
3677 | self.list_id = list_id |
3678 | self.taskseries_id = taskseries_id |
3679 | - self.logger = logger |
3680 | #Checking if a task is recurring is done inside __get_rtm_task_attribute |
3681 | # so we call that to set self.recurring correctly |
3682 | self.recurring = False |
3683 | @@ -135,16 +134,22 @@ |
3684 | hasattr(self.task.notes, 'note'): |
3685 | #Rtm saves the notes text inside the member "$t". Don't ask me why. |
3686 | if type(self.task.notes.note) == list: |
3687 | - return "".join(map(lambda note: getattr(note, '$t') + "\n", \ |
3688 | + text = "".join(map(lambda note: getattr(note, '$t') + "\n", \ |
3689 | self.task.notes.note)) |
3690 | else: |
3691 | - return getattr(self.task.notes.note, '$t') |
3692 | - else: |
3693 | - return "" |
3694 | + text = getattr(self.task.notes.note, '$t') |
3695 | + else: |
3696 | + text = "" |
3697 | + #adding back the tags (subtasks are added automatically) |
3698 | + tags = self.tags |
3699 | + if len(tags) > 0: |
3700 | + tagstxt = "@" + reduce(lambda x,y: x + ", " + "@" + y, self.tags) + "\n" |
3701 | + else: |
3702 | + tagstxt = "" |
3703 | + return tagstxt + text.strip() |
3704 | |
3705 | def _set_text(self, text): |
3706 | #delete old notes |
3707 | - #FIXME: the first check *should* not be necessary (but it is?). |
3708 | if hasattr(self.task, 'notes') and \ |
3709 | hasattr(self.task.notes, 'note'): |
3710 | if type(self.task.notes.note) == list: |
3711 | @@ -155,8 +160,7 @@ |
3712 | note_id=id), note_ids) |
3713 | #add a new one |
3714 | #TODO: investigate what is "Note title", since there doesn't seem to |
3715 | - #be a note |
3716 | - # title in the web access. |
3717 | + #be a note title in the web access. |
3718 | #FIXME: minidom this way is ok, or do we suppose to get multiple |
3719 | # nodes in "content"? |
3720 | if text == "": |
3721 | @@ -168,9 +172,6 @@ |
3722 | note_title = "",\ |
3723 | note_text = text) |
3724 | |
3725 | - |
3726 | - |
3727 | - |
3728 | def _get_due_date(self): |
3729 | if hasattr(self.task,'task'): |
3730 | if type(self.task.task) != list: |
3731 | @@ -179,7 +180,6 @@ |
3732 | task = self.task.task[len(self.task.task) - 1] |
3733 | if hasattr(task, 'due') and task.due != "": |
3734 | to_return = self.__time_rtm_to_datetime(task.due) |
3735 | - # - datetime.timedelta(seconds = time.timezone) |
3736 | return to_return.date() |
3737 | return None |
3738 | |
3739 | @@ -223,13 +223,11 @@ |
3740 | |
3741 | |
3742 | def __time_rtm_to_datetime(self, string): |
3743 | - #FIXME: need to handle time with TIMEZONES! |
3744 | string = string.split('.')[0].split('Z')[0] |
3745 | return datetime.datetime.strptime(string.split(".")[0], \ |
3746 | "%Y-%m-%dT%H:%M:%S") |
3747 | |
3748 | def __time_rtm_to_date(self, string): |
3749 | - #FIXME: need to handle time with TIMEZONES! |
3750 | string = string.split('.')[0].split('Z')[0] |
3751 | return datetime.datetime.strptime(string.split(".")[0], "%Y-%m-%d") |
3752 | |
3753 | @@ -244,5 +242,5 @@ |
3754 | return timeobject.strftime("%Y-%m-%d") |
3755 | |
3756 | def __log(self, message): |
3757 | - if self.logger: |
3758 | - self.logger.debug (message) |
3759 | + Log.debug(message) |
3760 | + |
3761 | |
3762 | === modified file 'GTG/plugins/rtm_sync/syncEngine.py' |
3763 | --- GTG/plugins/rtm_sync/syncEngine.py 2010-02-13 10:28:15 +0000 |
3764 | +++ GTG/plugins/rtm_sync/syncEngine.py 2010-03-03 02:01:18 +0000 |
3765 | @@ -29,6 +29,7 @@ |
3766 | from time import sleep |
3767 | import gobject |
3768 | from GTG import _ |
3769 | +from GTG.tools.logger import Log |
3770 | |
3771 | |
3772 | |
3773 | @@ -67,9 +68,8 @@ |
3774 | super(SyncEngine, self).__init__() |
3775 | self.this_plugin = this_plugin |
3776 | self.plugin_api = this_plugin.plugin_api |
3777 | - self.logger = self.this_plugin.plugin_api.get_logger() |
3778 | self.local_proxy = GtgProxy(self.this_plugin.plugin_api) |
3779 | - self.remote_proxy = RtmProxy(self.logger) |
3780 | + self.remote_proxy = RtmProxy() |
3781 | self.rtm_has_logon = False |
3782 | |
3783 | def rtmLogin(self): |
3784 | @@ -127,18 +127,20 @@ |
3785 | local_tasks, \ |
3786 | self.remote_proxy) |
3787 | self._append_to_taskpairs(new_local_tasks, new_remote_tasks) |
3788 | + self.update_substatus("") |
3789 | |
3790 | #Add tasks to the local proxy |
3791 | - self.update_status(_("Adding tasks to gtg..")) |
3792 | + self.update_status(_("Adding tasks to gtg..")) |
3793 | self.update_progressbar(0.4) |
3794 | [new_remote_tasks, new_local_tasks] = self._process_new_tasks(\ |
3795 | new_remote_ids,\ |
3796 | remote_tasks,\ |
3797 | self.local_proxy) |
3798 | self._append_to_taskpairs(new_local_tasks, new_remote_tasks) |
3799 | + self.update_substatus("") |
3800 | |
3801 | #Delete tasks from the remote proxy |
3802 | - self.update_status(_("Deleting tasks from rtm..")) |
3803 | + self.update_status(_("Deleting tasks from rtm..")) |
3804 | self.update_progressbar(0.5) |
3805 | taskpairs_deleted = filter(lambda tp: tp.local_id in deleted_local_ids,\ |
3806 | self.taskpairs) |
3807 | @@ -146,18 +148,20 @@ |
3808 | self._process_deleted_tasks(remote_ids_to_delete, remote_tasks,\ |
3809 | self.remote_proxy) |
3810 | map(lambda tp: self.taskpairs.remove(tp), taskpairs_deleted) |
3811 | + self.update_substatus("") |
3812 | |
3813 | #Delete tasks from the local proxy |
3814 | - self.update_status(_("Deleting tasks from gtg..")) |
3815 | + self.update_status(_("Deleting tasks from gtg..")) |
3816 | self.update_progressbar(0.6) |
3817 | taskpairs_deleted = filter(lambda tp: tp.remote_id in deleted_remote_ids,\ |
3818 | self.taskpairs) |
3819 | local_ids_to_delete = map( lambda tp: tp.local_id, taskpairs_deleted) |
3820 | self._process_deleted_tasks(local_ids_to_delete, local_tasks, self.local_proxy) |
3821 | map(lambda tp: self.taskpairs.remove(tp), taskpairs_deleted) |
3822 | + self.update_substatus("") |
3823 | |
3824 | #Update tasks |
3825 | - self.update_status(_("Updating changed tasks..")) |
3826 | + self.update_status(_("Updating changed tasks..")) |
3827 | self.update_progressbar(0.8) |
3828 | local_to_taskpair = self._list_to_dict(self.taskpairs, \ |
3829 | "local_id", \ |
3830 | @@ -169,7 +173,7 @@ |
3831 | "id", \ |
3832 | "self") |
3833 | for local_id in updatable_local_ids: |
3834 | - if not local_to_taskpair.has_key(local_id): |
3835 | + if not local_id in local_to_taskpair: |
3836 | #task has been removed, skipping |
3837 | continue |
3838 | taskpair = local_to_taskpair[local_id] |
3839 | @@ -198,6 +202,7 @@ |
3840 | |
3841 | taskpair.remote_synced_until = remote_task.modified |
3842 | taskpair.local_synced_until = local_task.modified |
3843 | + self.update_substatus("") |
3844 | |
3845 | #Lastly, save the list of known links |
3846 | self.update_status(_("Saving current state..")) |
3847 | @@ -262,8 +267,7 @@ |
3848 | remote_task = remote_title_to_task[title])) |
3849 | |
3850 | def __log(self, message): |
3851 | - if self.logger: |
3852 | - self.logger.debug(message) |
3853 | + Log.debug(message) |
3854 | |
3855 | ## GUI |
3856 | def close_gui(self, msg): |
3857 | |
3858 | === modified file 'GTG/plugins/send_email/sendEmail.py' |
3859 | --- GTG/plugins/send_email/sendEmail.py 2010-02-25 00:49:44 +0000 |
3860 | +++ GTG/plugins/send_email/sendEmail.py 2010-03-03 02:01:18 +0000 |
3861 | @@ -35,7 +35,6 @@ |
3862 | |
3863 | def activate(self, plugin_api): |
3864 | self.plugin_api = plugin_api |
3865 | - self.logger = self.plugin_api.get_logger() |
3866 | |
3867 | def onTaskClosed(self, plugin_api): |
3868 | pass |
3869 | @@ -62,12 +61,6 @@ |
3870 | plugin_api.remove_task_toolbar_item(self.tb_Taskbutton) |
3871 | print "ciao" |
3872 | |
3873 | -## HELPER FUNCTIONS ############################################################ |
3874 | - |
3875 | - def __log(self, message): |
3876 | - if self.logger: |
3877 | - self.logger.debug(message) |
3878 | - |
3879 | ## CORE FUNCTIONS ############################################################## |
3880 | |
3881 | def onTbTaskButton(self, widget, plugin_api): |
3882 | |
3883 | === modified file 'GTG/plugins/task_reaper/reaper.py' |
3884 | --- GTG/plugins/task_reaper/reaper.py 2010-02-15 10:27:58 +0000 |
3885 | +++ GTG/plugins/task_reaper/reaper.py 2010-03-03 02:01:18 +0000 |
3886 | @@ -28,6 +28,7 @@ |
3887 | from threading import Timer |
3888 | |
3889 | from GTG.core.task import Task |
3890 | +from GTG.tools.logger import Log |
3891 | |
3892 | |
3893 | class pluginReaper: |
3894 | @@ -74,7 +75,6 @@ |
3895 | self.menu_item_is_shown = False |
3896 | self.is_automatic = False |
3897 | self.timer = None |
3898 | - self.logger = self.plugin_api.get_logger() |
3899 | self.preferences_load() |
3900 | self.preferences_apply() |
3901 | |
3902 | @@ -91,8 +91,7 @@ |
3903 | ## HELPER FUNCTIONS ############################################################ |
3904 | |
3905 | def __log(self, message): |
3906 | - if self.logger: |
3907 | - self.logger.debug(message) |
3908 | + Log.debug(message) |
3909 | |
3910 | ## CORE FUNCTIONS ############################################################## |
3911 | |
3912 | |
3913 | === modified file 'GTG/plugins/task_reaper/reaper.ui' |
3914 | --- GTG/plugins/task_reaper/reaper.ui 2010-02-16 01:45:07 +0000 |
3915 | +++ GTG/plugins/task_reaper/reaper.ui 2010-03-03 02:01:18 +0000 |
3916 | @@ -6,6 +6,7 @@ |
3917 | <object class="GtkWindow" id="preferences_dialog"> |
3918 | <property name="border_width">10</property> |
3919 | <property name="window_position">center-on-parent</property> |
3920 | + <property name="type_hint">dialog</property> |
3921 | <signal name="delete_event" handler="on_preferences_dialog_delete_event"/> |
3922 | <child> |
3923 | <object class="GtkVBox" id="vbox1"> |
3924 | |
3925 | === modified file 'GTG/taskbrowser/__init__.py' |
3926 | --- GTG/taskbrowser/__init__.py 2009-12-28 15:10:47 +0000 |
3927 | +++ GTG/taskbrowser/__init__.py 2010-03-03 02:01:18 +0000 |
3928 | @@ -18,8 +18,12 @@ |
3929 | # ----------------------------------------------------------------------------- |
3930 | |
3931 | |
3932 | -#This is the gnome_frontend package. It's a GTK interface that want to be |
3933 | -#simple, HIG compliant and well integrated with Gnome. |
3934 | +""" |
3935 | +The GTK frontend for browsing collections of tasks. |
3936 | + |
3937 | +This is the gnome_frontend package. It's a GTK interface that wants to be |
3938 | +simple, HIG compliant and well integrated with Gnome. |
3939 | +""" |
3940 | import os |
3941 | |
3942 | from GTG import _ |
3943 | |
3944 | === modified file 'GTG/taskbrowser/browser.py' |
3945 | --- GTG/taskbrowser/browser.py 2010-02-25 00:54:40 +0000 |
3946 | +++ GTG/taskbrowser/browser.py 2010-03-03 02:01:18 +0000 |
3947 | @@ -18,6 +18,7 @@ |
3948 | # this program. If not, see <http://www.gnu.org/licenses/>. |
3949 | # ----------------------------------------------------------------------------- |
3950 | |
3951 | +""" The main window for GTG, listing tags, and open and closed tasks """ |
3952 | |
3953 | #=== IMPORT =================================================================== |
3954 | #system imports |
3955 | @@ -36,12 +37,12 @@ |
3956 | import GTG |
3957 | from GTG import info |
3958 | from GTG import _ |
3959 | +from GTG.tools.logger import Log |
3960 | from GTG.core.task import Task |
3961 | from GTG.core.tagstore import Tag |
3962 | -from GTG.taskeditor.editor import TaskEditor |
3963 | from GTG.taskbrowser import GnomeConfig |
3964 | from GTG.taskbrowser import tasktree |
3965 | -from GTG.taskbrowser.preferences import PreferencesDialog |
3966 | +#from GTG.taskbrowser.preferences import PreferencesDialog |
3967 | from GTG.taskbrowser.tasktree import TaskTreeModel,\ |
3968 | ActiveTaskTreeView,\ |
3969 | ClosedTaskTreeView |
3970 | @@ -53,10 +54,6 @@ |
3971 | no_date,\ |
3972 | FuzzyDate |
3973 | from GTG.tools import clipboard |
3974 | -from GTG.core.plugins.engine import PluginEngine |
3975 | -from GTG.core.plugins.api import PluginAPI |
3976 | - |
3977 | -#=== OBJECTS ================================================================== |
3978 | |
3979 | #=== MAIN CLASS =============================================================== |
3980 | |
3981 | @@ -69,7 +66,6 @@ |
3982 | QUICKADD_PANE = True |
3983 | TOOLBAR = True |
3984 | BG_COLOR = True |
3985 | -#EXPERIMENTAL_NOTES = False |
3986 | TIME = 0 |
3987 | |
3988 | class Timer: |
3989 | @@ -80,25 +76,23 @@ |
3990 | print "%s : %s" %(self.st,time.time() - self.start) |
3991 | |
3992 | class TaskBrowser: |
3993 | - |
3994 | - def __init__(self, requester, config, logger=None): |
3995 | - |
3996 | - self.logger=logger |
3997 | - |
3998 | + """ The UI for browsing open and closed tasks, and listing tags in a tree """ |
3999 | + |
4000 | + def __init__(self, requester, vmanager, config): |
4001 | # Object prime variables |
4002 | self.priv = {} |
4003 | self.req = requester |
4004 | - self.config = config.conf_dict |
4005 | - self.task_config = config.task_conf_dict |
4006 | + self.vmanager = vmanager |
4007 | + self.config = config |
4008 | + self.tag_active = False |
4009 | + |
4010 | + #treeviews handlers |
4011 | + self.tags_tv = None |
4012 | + self.tasks_tv = None |
4013 | + self.ctask_tv = ClosedTaskTreeView() |
4014 | |
4015 | ### YOU CAN DEFINE YOUR INTERNAL MECHANICS VARIABLES BELOW |
4016 | - # Task deletion |
4017 | - self.tids_todelete = None # The tid that will be deleted |
4018 | - # Editors |
4019 | - self.opened_task = {} # This is the list of tasks that are already |
4020 | - # opened in an editor of course it's empty |
4021 | - # right now |
4022 | - |
4023 | + |
4024 | # Setup default values for view |
4025 | self._init_browser_config() |
4026 | |
4027 | @@ -124,9 +118,6 @@ |
4028 | # Initialize "About" dialog |
4029 | self._init_about_dialog() |
4030 | |
4031 | - # Initialize "Preferences" dialog |
4032 | - self.preferences = PreferencesDialog(self) |
4033 | - |
4034 | #Create our dictionary and connect it |
4035 | self._init_signal_connections() |
4036 | |
4037 | @@ -138,24 +129,12 @@ |
4038 | # Define accelerator keys |
4039 | self._init_accelerators() |
4040 | |
4041 | - # Initialize the plugin-engine |
4042 | - self.p_apis = [] #the list of each plugin apis. |
4043 | - self._init_plugin_engine() |
4044 | - |
4045 | - self.refresh_lock = threading.Lock() |
4046 | - |
4047 | - # NOTES |
4048 | - #self._init_note_support() |
4049 | - |
4050 | - #Shared clipboard |
4051 | - self.clipboard = clipboard.TaskClipboard(self.req) |
4052 | - |
4053 | - |
4054 | - self.tag_active = False |
4055 | - |
4056 | #Autocompletion for Tags |
4057 | self._init_tag_list() |
4058 | self._init_tag_completion() |
4059 | + |
4060 | + self.restore_state_from_conf() |
4061 | + self.window.show() |
4062 | |
4063 | ### INIT HELPER FUNCTIONS ##################################################### |
4064 | # |
4065 | @@ -170,7 +149,6 @@ |
4066 | self.priv["ctasklist"]["sort_order"] = gtk.SORT_ASCENDING |
4067 | self.priv['selected_rows'] = None |
4068 | self.priv['workview'] = False |
4069 | - #self.priv['noteview'] = False |
4070 | self.priv['filter_cbs'] = [] |
4071 | self.priv['quick_add_cbs'] = [] |
4072 | |
4073 | @@ -180,25 +158,18 @@ |
4074 | gtk.icon_theme_get_default().prepend_search_path(i) |
4075 | gtk.window_set_default_icon_name("gtg") |
4076 | |
4077 | + #FIXME: we should group the initialization by widgets, not by type of methods |
4078 | + # it should be "init_active_tasks_pane", "init_sidebar", etc. |
4079 | def _init_models(self): |
4080 | - |
4081 | - # Base models |
4082 | - self.task_tree_model = TaskTreeModel(requester=self.req) |
4083 | - |
4084 | # Active Tasks |
4085 | - self.task_modelfilter = self.task_tree_model.filter_new() |
4086 | - self.task_modelfilter.set_visible_func(self.active_task_visible_func) |
4087 | - self.task_modelsort = gtk.TreeModelSort(self.task_modelfilter) |
4088 | + self.req.apply_filter('active') |
4089 | + self.task_tree_model = TaskTreeModel(self.req) |
4090 | + self.task_modelsort = gtk.TreeModelSort(self.task_tree_model) |
4091 | self.task_modelsort.set_sort_func(\ |
4092 | tasktree.COL_DDATE, self.dleft_sort_func) |
4093 | self.task_modelsort.set_sort_func(\ |
4094 | tasktree.COL_DLEFT, self.dleft_sort_func) |
4095 | - |
4096 | - # Closed Tasks: dismissed and done |
4097 | - self.ctask_modelfilter = self.task_tree_model.filter_new() |
4098 | - self.ctask_modelfilter.set_visible_func(self.closed_task_visible_func) |
4099 | - self.ctask_modelsort = gtk.TreeModelSort(self.ctask_modelfilter) |
4100 | - |
4101 | + |
4102 | # Tags |
4103 | self.tag_model = TagTreeModel(requester=self.req) |
4104 | self.tag_modelfilter = self.tag_model.filter_new() |
4105 | @@ -207,45 +178,25 @@ |
4106 | self.tag_modelsort.set_sort_func(\ |
4107 | tagtree.COL_ID, self.tag_sort_func) |
4108 | |
4109 | - # Build the "all tags tag" |
4110 | - self.alltag_tag = Tag("gtg-tags-all") |
4111 | - self.alltag_tag.set_attribute("special","all") |
4112 | - self.alltag_tag.set_attribute("label","<span weight='bold'>%s</span>"\ |
4113 | - % _("All tasks")) |
4114 | - self.alltag_tag.set_attribute("icon","gtg-tags-all") |
4115 | - self.alltag_tag.set_attribute("order",0) |
4116 | - # Build the "without tag tag" |
4117 | - self.notag_tag = Tag("gtg-tags-none") |
4118 | - self.notag_tag.set_attribute("special","notag") |
4119 | - self.notag_tag.set_attribute("label","<span weight='bold'>%s</span>"\ |
4120 | - % _("Tasks with no tags")) |
4121 | - self.notag_tag.set_attribute("icon","gtg-tags-none") |
4122 | - self.notag_tag.set_attribute("order",1) |
4123 | - # Build the separator |
4124 | - self.sep_tag = Tag("gtg-tags-sep") |
4125 | - self.sep_tag.set_attribute("special","sep") |
4126 | - self.sep_tag.set_attribute("order",2) |
4127 | - # Add them to the model |
4128 | - self.tag_model.add_tag(self.alltag_tag.get_name(), self.alltag_tag) |
4129 | - self.tag_model.add_tag(self.notag_tag.get_name(), self.notag_tag) |
4130 | - self.tag_model.add_tag(self.sep_tag.get_name(), self.sep_tag) |
4131 | - |
4132 | def _init_widget_aliases(self): |
4133 | self.window = self.builder.get_object("MainWindow") |
4134 | self.tagpopup = self.builder.get_object("TagContextMenu") |
4135 | - self.nonworkviewtag_checkbox = self.builder.get_object("nonworkviewtag") |
4136 | + self.nonworkviewtag_cb = self.builder.get_object("nonworkviewtag") |
4137 | self.taskpopup = self.builder.get_object("TaskContextMenu") |
4138 | self.defertopopup = self.builder.get_object("DeferToContextMenu") |
4139 | - self.ctaskpopup = \ |
4140 | - self.builder.get_object("ClosedTaskContextMenu") |
4141 | + self.ctaskpopup = self.builder.get_object("ClosedTaskContextMenu") |
4142 | self.editbutton = self.builder.get_object("edit_b") |
4143 | - self.donebutton = self.builder.get_object("mark_as_done_b") |
4144 | + self.edit_mi = self.builder.get_object("edit_mi") |
4145 | + self.donebutton = self.builder.get_object("done_b") |
4146 | + self.done_mi = self.builder.get_object("done_mi") |
4147 | self.deletebutton = self.builder.get_object("delete_b") |
4148 | + self.delete_mi = self.builder.get_object("delete_mi") |
4149 | self.newtask = self.builder.get_object("new_task_b") |
4150 | self.newsubtask = self.builder.get_object("new_subtask_b") |
4151 | - self.dismissbutton = self.builder.get_object("dismiss") |
4152 | - self.about = self.builder.get_object("aboutdialog1") |
4153 | - self.edit_mi = self.builder.get_object("edit_mi") |
4154 | + self.new_subtask_mi = self.builder.get_object("new_subtask_mi") |
4155 | + self.dismissbutton = self.builder.get_object("dismiss_b") |
4156 | + self.dismiss_mi = self.builder.get_object("dismiss_mi") |
4157 | + self.about = self.builder.get_object("about_dialog") |
4158 | self.main_pane = self.builder.get_object("main_pane") |
4159 | self.menu_view_workview = self.builder.get_object("view_workview") |
4160 | self.toggle_workview = self.builder.get_object("workview_toggle") |
4161 | @@ -255,11 +206,8 @@ |
4162 | self.quickadd_pane = self.builder.get_object("quickadd_pane") |
4163 | self.sidebar = self.builder.get_object("sidebar") |
4164 | self.sidebar_container = self.builder.get_object("sidebar-scroll") |
4165 | - # Tree views |
4166 | - #self.tags_tv = self.builder.get_object("tag_tview") |
4167 | - # NOTES |
4168 | - #self.new_note_button = self.builder.get_object("new_note_button") |
4169 | - #self.note_toggle = self.builder.get_object("note_toggle") |
4170 | + |
4171 | + self.closed_pane.add(self.ctask_tv) |
4172 | |
4173 | def _init_ui_widget(self): |
4174 | # The Active tasks treeview |
4175 | @@ -267,11 +215,6 @@ |
4176 | self.task_tv.set_model(self.task_modelsort) |
4177 | self.main_pane.add(self.task_tv) |
4178 | |
4179 | - # The done/dismissed taks treeview |
4180 | - self.ctask_tv = ClosedTaskTreeView() |
4181 | - self.ctask_tv.set_model(self.ctask_modelsort) |
4182 | - self.closed_pane.add(self.ctask_tv) |
4183 | - |
4184 | # The tags treeview |
4185 | self.tags_tv = TagTreeView() |
4186 | self.tags_tv.set_model(self.tag_modelsort) |
4187 | @@ -296,22 +239,15 @@ |
4188 | self.about.set_translator_credits(info.TRANSLATORS) |
4189 | |
4190 | def _init_signal_connections(self): |
4191 | - |
4192 | SIGNAL_CONNECTIONS_DIC = { |
4193 | -# "on_force_refresh": |
4194 | -# self.on_force_refresh, |
4195 | "on_add_task": |
4196 | self.on_add_task, |
4197 | - "on_add_note": |
4198 | - (self.on_add_task, 'Note'), |
4199 | "on_edit_active_task": |
4200 | self.on_edit_active_task, |
4201 | "on_edit_done_task": |
4202 | self.on_edit_done_task, |
4203 | -# "on_edit_note": |
4204 | -# self.on_edit_note, |
4205 | "on_delete_task": |
4206 | - self.on_delete_task, |
4207 | + self.on_delete_tasks, |
4208 | "on_add_new_tag": |
4209 | self.on_add_new_tag, |
4210 | "on_mark_as_done": |
4211 | @@ -334,10 +270,6 @@ |
4212 | self.on_size_allocate, |
4213 | "gtk_main_quit": |
4214 | self.on_close, |
4215 | - "on_delete_confirm": |
4216 | - self.on_delete_confirm, |
4217 | - "on_delete_cancel": |
4218 | - lambda x: x.hide, |
4219 | "on_addtag_confirm": |
4220 | self.on_addtag_confirm, |
4221 | "on_addtag_cancel": |
4222 | @@ -354,8 +286,6 @@ |
4223 | self.on_tagcontext_deactivate, |
4224 | "on_workview_toggled": |
4225 | self.on_workview_toggled, |
4226 | - "on_note_toggled": |
4227 | - self.on_note_toggled, |
4228 | "on_view_workview_toggled": |
4229 | self.on_workview_toggled, |
4230 | "on_view_closed_toggled": |
4231 | @@ -380,14 +310,13 @@ |
4232 | self.on_about_close, |
4233 | "on_nonworkviewtag_toggled": |
4234 | self.on_nonworkviewtag_toggled, |
4235 | + "on_preferences_activate": |
4236 | + self.open_preferences, |
4237 | } |
4238 | - |
4239 | - SIGNAL_CONNECTIONS_DIC.update(self.preferences.get_signals_dict()) |
4240 | - |
4241 | self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC) |
4242 | |
4243 | if (self.window): |
4244 | - self.window.connect("destroy", gtk.main_quit) |
4245 | + self.window.connect("destroy", self.quit) |
4246 | #The following is needed to let the Notification Area plugin to |
4247 | # minimize the window instead of closing the program |
4248 | self.delete_event_handle = \ |
4249 | @@ -404,7 +333,7 @@ |
4250 | self.on_task_treeview_row_expanded) |
4251 | self.task_tv.connect('row-collapsed',\ |
4252 | self.on_task_treeview_row_collapsed) |
4253 | - |
4254 | + |
4255 | # Closed tasks TreeView |
4256 | self.ctask_tv.connect('row-activated',\ |
4257 | self.on_edit_done_task) |
4258 | @@ -428,7 +357,6 @@ |
4259 | # Connect requester signals to TreeModels |
4260 | self.req.connect("task-added", self.on_task_added) |
4261 | self.req.connect("task-deleted", self.on_task_deleted) |
4262 | - self.req.connect("task-modified", self.on_task_modified) |
4263 | |
4264 | # Connect signals from models |
4265 | self.task_modelsort.connect("row-has-child-toggled",\ |
4266 | @@ -452,118 +380,38 @@ |
4267 | # Set sorting order |
4268 | self.task_modelsort.set_sort_column_id(\ |
4269 | tasktree.COL_DLEFT, gtk.SORT_ASCENDING) |
4270 | - self.ctask_modelsort.set_sort_column_id(\ |
4271 | - tasktree.COL_CDATE, gtk.SORT_DESCENDING) |
4272 | self.tag_modelsort.set_sort_column_id(\ |
4273 | tagtree.COL_ID, gtk.SORT_ASCENDING) |
4274 | |
4275 | + def _add_accelerator_for_widget(self, agr, name, accel): |
4276 | + widget = self.builder.get_object(name) |
4277 | + key, mod = gtk.accelerator_parse(accel) |
4278 | + widget.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) |
4279 | + |
4280 | def _init_accelerators(self): |
4281 | agr = gtk.AccelGroup() |
4282 | self.builder.get_object("MainWindow").add_accel_group(agr) |
4283 | |
4284 | - view_sidebar = self.builder.get_object("view_sidebar") |
4285 | - key, mod = gtk.accelerator_parse("F9") |
4286 | - view_sidebar.add_accelerator("activate", agr, key, mod,\ |
4287 | - gtk.ACCEL_VISIBLE) |
4288 | - |
4289 | - file_quit = self.builder.get_object("file_quit") |
4290 | - key, mod = gtk.accelerator_parse("<Control>q") |
4291 | - file_quit.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) |
4292 | - |
4293 | - edit_undo = self.builder.get_object("edit_undo") |
4294 | - key, mod = gtk.accelerator_parse("<Control>z") |
4295 | - edit_undo.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) |
4296 | - |
4297 | - edit_redo = self.builder.get_object("edit_redo") |
4298 | - key, mod = gtk.accelerator_parse("<Control>y") |
4299 | - edit_redo.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) |
4300 | - |
4301 | - new_task_mi = self.builder.get_object("new_task_mi") |
4302 | - key, mod = gtk.accelerator_parse("<Control>n") |
4303 | - new_task_mi.add_accelerator("activate", agr, key, mod,\ |
4304 | - gtk.ACCEL_VISIBLE) |
4305 | - |
4306 | - self.new_subtask_mi = self.builder.get_object("new_subtask_mi") |
4307 | - key, mod = gtk.accelerator_parse("<Control><Shift>n") |
4308 | - self.new_subtask_mi.add_accelerator("activate", agr, key, mod,\ |
4309 | - gtk.ACCEL_VISIBLE) |
4310 | + self._add_accelerator_for_widget(agr, "view_sidebar", "F9") |
4311 | + self._add_accelerator_for_widget(agr, "file_quit", "<Control>q") |
4312 | + self._add_accelerator_for_widget(agr, "edit_undo", "<Control>z") |
4313 | + self._add_accelerator_for_widget(agr, "edit_redo", "<Control>y") |
4314 | + self._add_accelerator_for_widget(agr, "new_task_mi", "<Control>n") |
4315 | + self._add_accelerator_for_widget(agr, "new_subtask_mi", "<Control><Shift>n") |
4316 | + self._add_accelerator_for_widget(agr, "done_mi", "<Control>d") |
4317 | + self._add_accelerator_for_widget(agr, "dismiss_mi", "<Control>i") |
4318 | + self._add_accelerator_for_widget(agr, "delete_mi", "Cancel") |
4319 | + self._add_accelerator_for_widget(agr, "tcm_addtag", "<Control>t") |
4320 | + self._add_accelerator_for_widget(agr, "view_closed", "<Control>F9") |
4321 | |
4322 | edit_button = self.builder.get_object("edit_b") |
4323 | key, mod = gtk.accelerator_parse("<Control>e") |
4324 | - edit_button.add_accelerator("clicked", agr, key, mod,\ |
4325 | - gtk.ACCEL_VISIBLE) |
4326 | - |
4327 | - quickadd_field = self.builder.get_object('quickadd_field') |
4328 | - key, mod = gtk.accelerator_parse('<Control>l') |
4329 | - quickadd_field.add_accelerator( |
4330 | - 'grab-focus', agr, key, mod, gtk.ACCEL_VISIBLE) |
4331 | - |
4332 | - self.mark_done_mi = self.builder.get_object('mark_done_mi') |
4333 | - key, mod = gtk.accelerator_parse('<Control>d') |
4334 | - self.mark_done_mi.add_accelerator( |
4335 | - 'activate', agr, key, mod, gtk.ACCEL_VISIBLE) |
4336 | - |
4337 | - self.dismiss_mi = self.builder.get_object('task_dismiss') |
4338 | - key, mod = gtk.accelerator_parse('<Control>i') |
4339 | - self.dismiss_mi.add_accelerator( |
4340 | - 'activate', agr, key, mod, gtk.ACCEL_VISIBLE) |
4341 | - |
4342 | - self.delete_mi = self.builder.get_object('delete_mi') |
4343 | - key, mod = gtk.accelerator_parse('Cancel') |
4344 | - self.delete_mi.add_accelerator( |
4345 | - 'activate', agr, key, mod, gtk.ACCEL_VISIBLE) |
4346 | - |
4347 | - addtag_button = self.builder.get_object('tcm_addtag') |
4348 | - key, mod = gtk.accelerator_parse('<Control>t') |
4349 | - addtag_button.add_accelerator('activate', agr, key, mod, \ |
4350 | - gtk.ACCEL_VISIBLE) |
4351 | - |
4352 | - addtag_button = self.builder.get_object('view_closed') |
4353 | - key, mod = gtk.accelerator_parse('<Control>F9') |
4354 | - addtag_button.add_accelerator('activate', agr, key, mod, \ |
4355 | - gtk.ACCEL_VISIBLE) |
4356 | - |
4357 | - def _init_plugin_engine(self): |
4358 | - # plugins - Init |
4359 | - self.pengine = PluginEngine(GTG.PLUGIN_DIR) |
4360 | - # loads the plugins in the plugin dir |
4361 | - self.pengine.load_plugins() |
4362 | - # initializes the plugin api class |
4363 | - self.plugin_api = PluginAPI(window = self.window, |
4364 | - config = self.config, |
4365 | - data_dir = GTG.DATA_DIR, |
4366 | - builder = self.builder, |
4367 | - requester = self.req, |
4368 | - taskview = self.task_tv, |
4369 | - task_modelsort = self.task_modelsort, |
4370 | - ctaskview = self.ctask_tv, |
4371 | - ctask_modelsort= self.ctask_modelsort, |
4372 | - filter_cbs = self.priv['filter_cbs'], |
4373 | - tagpopup = self.tagpopup, |
4374 | - tagview = self.tags_tv, |
4375 | - task = None, |
4376 | - texteditor = None, |
4377 | - quick_add_cbs = self.priv['quick_add_cbs'], |
4378 | - browser = self, |
4379 | - logger = self.logger) |
4380 | - self.p_apis.append(self.plugin_api) |
4381 | - # enable some plugins |
4382 | - if len(self.pengine.plugins) > 0: |
4383 | - # checks the conf for user settings |
4384 | - if "plugins" in self.config: |
4385 | - if "enabled" in self.config["plugins"]: |
4386 | - plugins_enabled = self.config["plugins"]["enabled"] |
4387 | - if "disabled" in self.config["plugins"]: |
4388 | - plugins_disabled = self.config["plugins"]["disabled"] |
4389 | - for name, plugin in self.pengine.plugins.iteritems(): |
4390 | - if name in plugins_enabled and name not in plugins_disabled: |
4391 | - plugin.enabled = True |
4392 | - else: |
4393 | - # plugins not explicitly enabled are disabled |
4394 | - plugin.enabled = False |
4395 | - # initializes and activates each plugin (that is enabled) |
4396 | - self.pengine.activate_plugins(self.p_apis) |
4397 | - |
4398 | + edit_button.add_accelerator("clicked", agr, key, mod, gtk.ACCEL_VISIBLE) |
4399 | + |
4400 | + quickadd_field = self.builder.get_object("quickadd_field") |
4401 | + key, mod = gtk.accelerator_parse("<Control>l") |
4402 | + quickadd_field.add_accelerator("grab-focus", agr, key, mod, gtk.ACCEL_VISIBLE) |
4403 | + |
4404 | def _init_tag_list(self): |
4405 | self.tag_list_model = gtk.ListStore(gobject.TYPE_STRING) |
4406 | self.tag_list = self.req.get_all_tags() |
4407 | @@ -580,22 +428,14 @@ |
4408 | self.tag_completion.set_inline_selection(True) |
4409 | self.tag_completion.set_popup_single_match(False) |
4410 | |
4411 | -# def _init_note_support(self): |
4412 | -# self.notes = EXPERIMENTAL_NOTES |
4413 | -# # Hide notes if disabled |
4414 | -# if not self.notes: |
4415 | -# self.note_toggle.hide() |
4416 | -# self.new_note_button.hide() |
4417 | -# #Set the tooltip for the toolbar button |
4418 | -# self.new_note_button.set_tooltip_text("Create a new note") |
4419 | -# self.note_tview = self.builder.get_object("note_tview") |
4420 | -# self.note_tview = gtk.TreeView() |
4421 | -# self.note_tview.connect("row-activated", self.on_edit_note) |
4422 | -# self.note_tview.show() |
4423 | -# self.note_ts = gtk.TreeStore(gobject.TYPE_PYOBJECT, str, str) |
4424 | +### HELPER FUNCTIONS ######################################################## |
4425 | |
4426 | -### HELPER FUNCTIONS ########################################################## |
4427 | -# |
4428 | + def open_preferences(self,widget): |
4429 | + self.vmanager.show_preferences() |
4430 | + |
4431 | + def quit(self,widget): |
4432 | + self.vmanager.close_browser() |
4433 | + |
4434 | def restore_state_from_conf(self): |
4435 | |
4436 | # Extract state from configuration dictionary |
4437 | @@ -635,11 +475,9 @@ |
4438 | closed_task_pane = eval( |
4439 | self.config["browser"]["closed_task_pane"]) |
4440 | if not closed_task_pane: |
4441 | - self.closed_pane.hide() |
4442 | - self.builder.get_object("view_closed").set_active(False) |
4443 | + self.hide_closed_pane() |
4444 | else: |
4445 | - self.closed_pane.show() |
4446 | - self.builder.get_object("view_closed").set_active(True) |
4447 | + self.show_closed_pane() |
4448 | |
4449 | if "ctask_pane_height" in self.config["browser"]: |
4450 | ctask_pane_height = eval( |
4451 | @@ -699,36 +537,7 @@ |
4452 | if odic == "None" or (len(odic)> 0 and odic[0] == "None"): |
4453 | return |
4454 | for t in odic: |
4455 | - ted = self.open_task(t) |
4456 | - #restoring position doesn't work, IÂ don't know why |
4457 | - #ted.move(odic[t][0],odic[t][1]) |
4458 | - |
4459 | -# if "experimental_notes" in self.config["browser"]: |
4460 | -# self.notes = eval(self.config["browser"]["experimental_notes"]) |
4461 | -# if self.notes: |
4462 | -# self.note_toggle.show() |
4463 | -# self.new_note_button.show() |
4464 | -# else: |
4465 | -# self.note_toggle.hide() |
4466 | -# self.new_note_button.hide() |
4467 | - |
4468 | - def count_tasks_rec(self, my_task, active_tasks): |
4469 | - count = 0 |
4470 | - for t in my_task.get_subtasks(): |
4471 | - if t.get_id() in active_tasks: |
4472 | - if len(t.get_subtasks()) != 0: |
4473 | - count = count + 1 + self.count_tasks_rec(t, active_tasks) |
4474 | - else: |
4475 | - count = count + 1 |
4476 | - return count |
4477 | - |
4478 | - def _count_subtask(self, model, iter): |
4479 | - count = 0 |
4480 | - c = model.iter_children(iter) |
4481 | - while c: |
4482 | - count = count + 1 + self._count_subtask(model, c) |
4483 | - c = model.iter_next(c) |
4484 | - return count |
4485 | + ted = self.vmanager.open_task(t) |
4486 | |
4487 | |
4488 | def _start_gtg_maximized(self): |
4489 | @@ -740,21 +549,21 @@ |
4490 | #We have to be careful here to avoid a loop of signals |
4491 | #menu_state = self.menu_view_workview.get_active() |
4492 | #button_state = self.toggle_workview.get_active() |
4493 | - #We cannot have note and workview at the same time |
4494 | -# if not self.priv['workview'] and self.note_toggle.get_active(): |
4495 | -# self.note_toggle.set_active(False) |
4496 | #We do something only if both widget are in different state |
4497 | tobeset = not self.priv['workview'] |
4498 | self.menu_view_workview.set_active(tobeset) |
4499 | self.toggle_workview.set_active(tobeset) |
4500 | self.priv['workview'] = tobeset |
4501 | self.tag_model.set_workview(self.priv['workview']) |
4502 | - self.task_modelfilter.refilter() |
4503 | + if tobeset: |
4504 | + self.req.apply_filter('workview') |
4505 | + else: |
4506 | + self.req.unapply_filter('workview') |
4507 | self.tag_modelfilter.refilter() |
4508 | self._update_window_title() |
4509 | |
4510 | def _update_window_title(self): |
4511 | - count = self.get_n_active_tasks() |
4512 | + count = self.req.get_main_n_tasks() |
4513 | #Set the title of the window: |
4514 | parenthesis = "" |
4515 | if count == 0: |
4516 | @@ -825,125 +634,10 @@ |
4517 | return no_date |
4518 | return strtodate(date) |
4519 | |
4520 | - def open_task(self, uid,thisisnew=False): |
4521 | - """Open the task identified by 'uid'. |
4522 | - |
4523 | - If a Task editor is already opened for a given task, we present it. |
4524 | - Else, we create a new one. |
4525 | - """ |
4526 | - t = self.req.get_task(uid) |
4527 | - tv = None |
4528 | - if uid in self.opened_task: |
4529 | - tv = self.opened_task[uid] |
4530 | - tv.present() |
4531 | - elif t: |
4532 | - tv = TaskEditor( |
4533 | - self.req, t, self.pengine.plugins.values(), \ |
4534 | - self.on_delete_task, self.close_task, self.open_task, \ |
4535 | - self.get_tasktitle,taskconfig=self.task_config, \ |
4536 | - plugin_apis=self.p_apis,thisisnew=thisisnew,\ |
4537 | - clipboard = self.clipboard) |
4538 | - #registering as opened |
4539 | - self.opened_task[uid] = tv |
4540 | - return tv |
4541 | - |
4542 | def get_tasktitle(self, tid): |
4543 | task = self.req.get_task(tid) |
4544 | return task.get_title() |
4545 | |
4546 | - def close_task(self, tid): |
4547 | - # When an editor is closed, it should deregister itself. |
4548 | - if tid in self.opened_task: |
4549 | - del self.opened_task[tid] |
4550 | - |
4551 | - def is_task_visible(self, task): |
4552 | - """Returns True if the task meets the criterion to be displayed |
4553 | - @param task: the task to assess |
4554 | - """ |
4555 | - |
4556 | - # Checks to see if we're working with a tag through a right click |
4557 | - # menu item. If we are, we'll treat the tag that was selected |
4558 | - # previous to the right click as the currently selected tag, |
4559 | - # even if the cursor is somewhere else. |
4560 | - if self.tag_active: |
4561 | - tag_list, notag_only = self.previous_tag |
4562 | - else: |
4563 | - tag_list, notag_only = self.get_selected_tags() |
4564 | - |
4565 | - if len(tag_list)==1: #include child tags |
4566 | - tag_list = tag_list[0].all_children() |
4567 | - |
4568 | - if not task.has_tags(tag_list=tag_list, notag_only=notag_only): |
4569 | - return False |
4570 | - |
4571 | - #if workview is enabled |
4572 | - if self.priv['workview']: |
4573 | - res = True |
4574 | - |
4575 | - # filter tasks view callbacks |
4576 | - for cb in self.priv['filter_cbs']: |
4577 | - res = cb(task.get_id()) |
4578 | - if res == False: |
4579 | - return False |
4580 | - |
4581 | - #we verify that the task is started |
4582 | - if not task.is_started() : |
4583 | - return False |
4584 | - |
4585 | - #we verify that there is no non-workview tag for this task |
4586 | - for t in task.get_tags(): |
4587 | - if t.get_attribute("nonworkview") and t not in tag_list: |
4588 | - res = res and (not eval(t.get_attribute("nonworkview"))) |
4589 | - return res and task.is_workable() |
4590 | - else: |
4591 | - return True |
4592 | - |
4593 | - def is_lineage_visible(self, task): |
4594 | - """Returns True if at least one set of tasks that compose a lineage of |
4595 | - the given task can be found where all the tasks meets the criterion |
4596 | - to be displayed. (i.e.: there exists a chain of tasks from root to task |
4597 | - that can all be displayed) |
4598 | - @param task: the task whose lineage will be assessed |
4599 | - """ |
4600 | - res = False |
4601 | - parents = task.get_parents() |
4602 | - for par_tid in parents: |
4603 | - par_task = self.req.get_task(par_tid) |
4604 | - if par_task.has_parents(): |
4605 | - res = res or (self.is_task_visible(par_task) and self.is_lineage_visible(par_task)) |
4606 | - else: |
4607 | - res = res or self.is_task_visible(par_task) |
4608 | - return res |
4609 | - |
4610 | - def active_task_visible_func(self, model, iter, user_data=None): |
4611 | - """Return True if the row must be displayed in the treeview. |
4612 | - @param model: the model of the filtered treeview |
4613 | - @param iter: the iter whose visiblity must be evaluated |
4614 | - @param user_data: |
4615 | - """ |
4616 | - task = model.get_value(iter, tasktree.COL_OBJ) |
4617 | - if not task or task.get_status() != Task.STA_ACTIVE: |
4618 | - return False |
4619 | - if not model.iter_parent(iter): |
4620 | - return self.is_task_visible(task) and not self.is_lineage_visible(task) |
4621 | - return self.is_task_visible(task) |
4622 | - |
4623 | - def closed_task_visible_func(self, model, iter, user_data=None): |
4624 | - """Return True if the row must be displayed in the treeview. |
4625 | - @param model: the model of the filtered treeview |
4626 | - @param iter: the iter whose visiblity must be evaluated |
4627 | - @param user_data: |
4628 | - """ |
4629 | - tag_list, notag_only = self.get_selected_tags() |
4630 | - task = model.get_value(iter, tasktree.COL_OBJ) |
4631 | - if len(tag_list)==1: #include child tags |
4632 | - tag_list = tag_list[0].all_children() |
4633 | - if not task.has_tags(tag_list=tag_list, notag_only=notag_only): |
4634 | - return False |
4635 | - return task.get_status() != Task.STA_ACTIVE and\ |
4636 | - not model.iter_parent(iter) |
4637 | - |
4638 | - |
4639 | def tag_visible_func(self, model, iter, user_data=None): |
4640 | """Return True if the row must be displayed in the treeview. |
4641 | @param model: the model of the filtered treeview |
4642 | @@ -969,13 +663,17 @@ |
4643 | return True |
4644 | |
4645 | def dleft_sort_func(self, model, iter1, iter2, user_data=None): |
4646 | - order = self.task_tv.get_model().get_sort_column_id()[1] |
4647 | + order = self.task_modelsort.get_sort_column_id()[1] |
4648 | task1 = model.get_value(iter1, tasktree.COL_OBJ) |
4649 | task2 = model.get_value(iter2, tasktree.COL_OBJ) |
4650 | - t1_dleft = task1.get_due_date() |
4651 | - t2_dleft = task2.get_due_date() |
4652 | + if task1 and task2: |
4653 | + t1_dleft = task1.get_due_date() |
4654 | + t2_dleft = task2.get_due_date() |
4655 | + sort = cmp(t2_dleft, t1_dleft) |
4656 | + else: |
4657 | + sort = -1 |
4658 | |
4659 | - sort = 0 |
4660 | + #sort = 0 |
4661 | |
4662 | def reverse_if_descending(s): |
4663 | """Make a cmp() result relative to the top instead of following |
4664 | @@ -985,7 +683,7 @@ |
4665 | else: |
4666 | return -1 * s |
4667 | |
4668 | - sort = cmp(t2_dleft, t1_dleft) |
4669 | + |
4670 | |
4671 | if sort == 0: |
4672 | # Put fuzzy dates below real dates |
4673 | @@ -1105,15 +803,6 @@ |
4674 | view = "workview" |
4675 | else: |
4676 | view = "default" |
4677 | - |
4678 | - # plugins are deactivated |
4679 | - self.pengine.deactivate_plugins(self.p_apis) |
4680 | - |
4681 | - #save opened tasks and their positions. |
4682 | - open_task = [] |
4683 | - for otid in self.opened_task.keys(): |
4684 | - open_task.append(otid) |
4685 | - self.opened_task[otid].close() |
4686 | |
4687 | # Populate configuration dictionary |
4688 | self.config["browser"] = { |
4689 | @@ -1145,28 +834,12 @@ |
4690 | quickadd_pane, |
4691 | 'view': |
4692 | view, |
4693 | - 'opened_tasks': |
4694 | - open_task, |
4695 | } |
4696 | if sort_column is not None and sort_order == gtk.SORT_ASCENDING: |
4697 | self.config["browser"]["tasklist_sort"] = [sort_column, 0] |
4698 | elif sort_column is not None and sort_order == gtk.SORT_DESCENDING: |
4699 | self.config["browser"]["tasklist_sort"] = [sort_column, 1] |
4700 | self.config["browser"]["view"] = view |
4701 | -# if self.notes: |
4702 | -# self.config["browser"]["experimental_notes"] = True |
4703 | - |
4704 | - # adds the plugin settings to the conf |
4705 | - if len(self.pengine.plugins) > 0: |
4706 | - self.config["plugins"] = {} |
4707 | - self.config["plugins"]["disabled"] = \ |
4708 | - self.pengine.disabled_plugins().keys() |
4709 | - self.config["plugins"]["enabled"] = \ |
4710 | - self.pengine.enabled_plugins().keys() |
4711 | - |
4712 | - def on_force_refresh(self, widget): |
4713 | - if self.refresh_lock.acquire(False): |
4714 | - gobject.idle_add(self.general_refresh) |
4715 | |
4716 | def on_about_clicked(self, widget): |
4717 | self.about.show() |
4718 | @@ -1189,8 +862,8 @@ |
4719 | #fact we should have a TagPropertiesEditor (like for project) Also, |
4720 | #color change should be immediate. There's no reason for a Ok/Cancel |
4721 | self.set_target_cursor() |
4722 | - dialog = gtk.ColorSelectionDialog('Choose color') |
4723 | - colorsel = dialog.colorsel |
4724 | + color_dialog = gtk.ColorSelectionDialog('Choose color') |
4725 | + colorsel = color_dialog.colorsel |
4726 | colorsel.connect("color_changed", self.on_color_changed) |
4727 | |
4728 | # Get previous color |
4729 | @@ -1203,7 +876,7 @@ |
4730 | colorsel.set_previous_color(colorspec) |
4731 | colorsel.set_current_color(colorspec) |
4732 | init_color = colorsel.get_current_color() |
4733 | - response = dialog.run() |
4734 | + response = color_dialog.run() |
4735 | # Check response and set color if required |
4736 | if response != gtk.RESPONSE_OK and init_color: |
4737 | strcolor = gtk.color_selection_palette_to_string([init_color]) |
4738 | @@ -1212,7 +885,7 @@ |
4739 | t.set_attribute("color", strcolor) |
4740 | self.reset_cursor() |
4741 | self.task_tv.refresh() |
4742 | - dialog.destroy() |
4743 | + color_dialog.destroy() |
4744 | |
4745 | def on_resetcolor_activate(self, widget): |
4746 | self.set_target_cursor() |
4747 | @@ -1238,18 +911,29 @@ |
4748 | view_sidebar.set_active(True) |
4749 | self.sidebar.show() |
4750 | |
4751 | - def on_note_toggled(self, widget): |
4752 | - self.priv['noteview'] = not self.priv['noteview'] |
4753 | - workview_state = self.toggle_workview.get_active() |
4754 | - if workview_state: |
4755 | - self.toggle_workview.set_active(False) |
4756 | - #self.do_refresh() |
4757 | - |
4758 | def on_closed_toggled(self, widget): |
4759 | if widget.get_active(): |
4760 | - self.closed_pane.show() |
4761 | + self.show_closed_pane() |
4762 | else: |
4763 | - self.closed_pane.hide() |
4764 | + self.hide_closed_pane() |
4765 | + |
4766 | + def show_closed_pane(self): |
4767 | + # The done/dismissed taks treeview |
4768 | + self.ctask_tree = self.req.get_custom_tasks_tree() |
4769 | + self.ctask_tree.apply_filter('closed') |
4770 | + ctask_tree_model = TaskTreeModel(self.req,self.ctask_tree) |
4771 | + ctask_modelsort = gtk.TreeModelSort(ctask_tree_model) |
4772 | + self.ctask_tv.set_model(ctask_modelsort) |
4773 | + ctask_modelsort.set_sort_column_id(\ |
4774 | + tasktree.COL_CDATE, gtk.SORT_DESCENDING) |
4775 | + self.closed_pane.show() |
4776 | + self.builder.get_object("view_closed").set_active(True) |
4777 | + |
4778 | + def hide_closed_pane(self): |
4779 | + self.closed_pane.hide() |
4780 | + self.ctask_tv.set_model(None) |
4781 | + self.ctask_tree = None |
4782 | + self.builder.get_object("view_closed").set_active(False) |
4783 | |
4784 | def on_bg_color_toggled(self, widget): |
4785 | if widget.get_active(): |
4786 | @@ -1260,8 +944,6 @@ |
4787 | self.priv["bg_color_enable"] = False |
4788 | self.task_tv.set_bg_color(False) |
4789 | self.ctask_tv.set_bg_color(False) |
4790 | - self.task_tv.refresh() |
4791 | - self.ctask_tv.refresh() |
4792 | |
4793 | def on_toolbar_toggled(self, widget): |
4794 | if widget.get_active(): |
4795 | @@ -1281,7 +963,7 @@ |
4796 | self.task_tv.expand_row(path, False) |
4797 | else: |
4798 | self.task_tv.collapse_row(path) |
4799 | - |
4800 | + |
4801 | def on_task_treeview_row_expanded(self, treeview, iter, path): |
4802 | tid = treeview.get_model().get_value(iter, tasktree.COL_TID) |
4803 | if tid in self.priv["collapsed_tids"]: |
4804 | @@ -1291,8 +973,7 @@ |
4805 | tid = treeview.get_model().get_value(iter, tasktree.COL_TID) |
4806 | if tid not in self.priv["collapsed_tids"]: |
4807 | self.priv["collapsed_tids"].append(tid) |
4808 | - |
4809 | - |
4810 | + |
4811 | def on_tag_child_toggled(self, model, path, iter): |
4812 | tag = model.get_value(iter, tagtree.COL_ID) |
4813 | if tag not in self.priv.get("collapsed_tags", []): |
4814 | @@ -1309,8 +990,6 @@ |
4815 | tag = treeview.get_model().get_value(iter, tagtree.COL_ID) |
4816 | if tag not in self.priv["collapsed_tags"]: |
4817 | self.priv["collapsed_tags"].append(tag) |
4818 | - |
4819 | - |
4820 | |
4821 | def on_quickadd_activate(self, widget): |
4822 | text = self.quickadd_entry.get_text() |
4823 | @@ -1363,7 +1042,6 @@ |
4824 | task.set_due_date(due_date) |
4825 | task.set_start_date(defer_date) |
4826 | id_toselect = task.get_id() |
4827 | - ############# |
4828 | self.quickadd_entry.set_text('') |
4829 | # Refresh the treeview |
4830 | #self.do_refresh(toselect=id_toselect) |
4831 | @@ -1424,14 +1102,12 @@ |
4832 | def on_nonworkviewtag_toggled(self, widget): |
4833 | self.set_target_cursor() |
4834 | tags = self.get_selected_tags()[0] |
4835 | - nonworkview_item = self.nonworkviewtag_checkbox |
4836 | #We must inverse because the tagstore has True |
4837 | #for tasks that are not in workview (and also convert to string) |
4838 | - toset = str(not nonworkview_item.get_active()) |
4839 | + toset = str(not self.nonworkview_cb.get_active()) |
4840 | if len(tags) > 0: |
4841 | tags[0].set_attribute("nonworkview", toset) |
4842 | if self.priv['workview']: |
4843 | - self.task_modelfilter.refilter() |
4844 | self.tag_modelfilter.refilter() |
4845 | if not self.dont_reset: |
4846 | self.reset_cursor() |
4847 | @@ -1452,7 +1128,7 @@ |
4848 | |
4849 | def on_task_treeview_key_press_event(self, treeview, event): |
4850 | if gtk.gdk.keyval_name(event.keyval) == "Delete": |
4851 | - self.on_delete_task() |
4852 | + self.on_delete_tasks() |
4853 | |
4854 | def on_closed_task_treeview_button_press_event(self, treeview, event): |
4855 | if event.button == 3: |
4856 | @@ -1469,7 +1145,7 @@ |
4857 | |
4858 | def on_closed_task_treeview_key_press_event(self, treeview, event): |
4859 | if gtk.gdk.keyval_name(event.keyval) == "Delete": |
4860 | - self.on_delete_task() |
4861 | + self.on_delete_tasks() |
4862 | |
4863 | def on_add_task(self, widget, status=None): |
4864 | tags, notagonly = self.get_selected_tags() |
4865 | @@ -1477,7 +1153,7 @@ |
4866 | uid = task.get_id() |
4867 | if status: |
4868 | task.set_status(status) |
4869 | - self.open_task(uid,thisisnew=True) |
4870 | + self.vmanager.open_task(uid,thisisnew=True) |
4871 | |
4872 | def on_add_subtask(self, widget): |
4873 | uid = self.get_selected_task() |
4874 | @@ -1486,87 +1162,29 @@ |
4875 | tags = zetask.get_tags() |
4876 | task = self.req.new_task(tags=tags, newtask=True) |
4877 | task.add_parent(uid) |
4878 | - zetask.add_subtask(task.get_id()) |
4879 | - self.open_task(task.get_id(),thisisnew=True) |
4880 | + zetask.add_child(task.get_id()) |
4881 | + self.vmanager.open_task(task.get_id(),thisisnew=True) |
4882 | #self.do_refresh() |
4883 | |
4884 | def on_edit_active_task(self, widget, row=None, col=None): |
4885 | tid = self.get_selected_task() |
4886 | if tid: |
4887 | - self.open_task(tid) |
4888 | + self.vmanager.open_task(tid) |
4889 | |
4890 | def on_edit_done_task(self, widget, row=None, col=None): |
4891 | tid = self.get_selected_task(self.ctask_tv) |
4892 | if tid: |
4893 | - self.open_task(tid) |
4894 | - |
4895 | -# def on_edit_note(self, widget, row=None, col=None): |
4896 | -# tid = self.get_selected_task(self.note_tview) |
4897 | -# if tid: |
4898 | -# self.open_task(tid) |
4899 | - |
4900 | - def on_delete_confirm(self, widget): |
4901 | - """if we pass a tid as a parameter, we delete directly |
4902 | - otherwise, we will look which tid is selected""" |
4903 | - for tid in self.tids_todelete: |
4904 | - self.req.delete_task(tid) |
4905 | - if tid in self.opened_task: |
4906 | - self.opened_task[tid].close() |
4907 | - self.tids_todelete = None |
4908 | - if self.refresh_lock.acquire(False): |
4909 | - gobject.idle_add(self.general_refresh) |
4910 | - |
4911 | - def on_delete_task(self, widget=None, tid=None): |
4912 | + self.vmanager.open_task(tid) |
4913 | + |
4914 | + def on_delete_tasks(self, widget=None, tid=None): |
4915 | #If we don't have a parameter, then take the selection in the treeview |
4916 | if not tid: |
4917 | #tid_to_delete is a [project,task] tuple |
4918 | - self.tids_todelete = self.get_selected_tasks() |
4919 | - else: |
4920 | - self.tids_todelete = [tid] |
4921 | - #We must at least have something to delete ! |
4922 | - if len(self.tids_todelete) > 0: |
4923 | - # We fill the text and the buttons' labels according to the number |
4924 | - # of tasks to delete |
4925 | - label = self.builder.get_object("label1") |
4926 | - label_text = label.get_text() |
4927 | - cdlabel2 = self.builder.get_object("cd-label2") |
4928 | - cdlabel3 = self.builder.get_object("cd-label3") |
4929 | - cdlabel4 = self.builder.get_object("cd-label4") |
4930 | - if len(self.tids_todelete) == 1: |
4931 | - label_text = _("Deleting a task cannot be undone, and will delete the following task: ") |
4932 | - cdlabel2.set_label(_("Are you sure you want to delete this task?")) |
4933 | - cdlabel3.set_label(_("Keep selected task")) |
4934 | - cdlabel4.set_label(_("Permanently remove task")) |
4935 | - else: |
4936 | - label_text = _("Deleting a task cannot be undone, and will delete the following tasks: ") |
4937 | - cdlabel2.set_label(_("Are you sure you want to delete these tasks?")) |
4938 | - cdlabel3.set_label(_("Keep selected tasks")) |
4939 | - cdlabel4.set_label(_("Permanently remove tasks")) |
4940 | - label_text = label_text[0:label_text.find(":") + 1] |
4941 | - |
4942 | - # I find the tasks that are going to be deleted |
4943 | - tasks = [] |
4944 | - for tid in self.tids_todelete: |
4945 | - def recursive_list_tasks(task_list, root): |
4946 | - """Populate a list of all the subtasks and |
4947 | - their children, recursively""" |
4948 | - if root not in task_list: |
4949 | - task_list.append(root) |
4950 | - for i in root.get_subtasks(): |
4951 | - recursive_list_tasks(task_list, i) |
4952 | - task = self.req.get_task(tid) |
4953 | - recursive_list_tasks(tasks, task) |
4954 | - titles_list = [task.get_title() for task in tasks] |
4955 | - titles = reduce (lambda x, y: x + "\n - " + y, titles_list) |
4956 | - label.set_text("%s %s" % (label_text, "\n - " + titles)) |
4957 | - delete_dialog = self.builder.get_object("confirm_delete") |
4958 | - delete_dialog.run() |
4959 | - delete_dialog.hide() |
4960 | - #has the task been deleted ? |
4961 | - return not self.tids_todelete |
4962 | - else: |
4963 | - return False |
4964 | - |
4965 | + tids_todelete = self.get_selected_tasks() |
4966 | + else: |
4967 | + tids_todelete = [tid] |
4968 | + self.vmanager.ask_delete_tasks(tids_todelete) |
4969 | + |
4970 | def update_start_date(self, widget, new_start_date): |
4971 | tasks_uid = filter(lambda uid: uid != None, self.get_selected_tasks()) |
4972 | if len(tasks_uid) == 0: |
4973 | @@ -1575,8 +1193,6 @@ |
4974 | tasks_status = [task.get_status() for task in tasks] |
4975 | for uid, task, status in zip(tasks_uid, tasks, tasks_status): |
4976 | task.set_start_date(self.get_canonical_date(new_start_date)) |
4977 | - if self.refresh_lock.acquire(False): |
4978 | - gobject.idle_add(self.general_refresh) |
4979 | #FIXME: If the task dialog is displayed, refresh its start_date widget |
4980 | |
4981 | def on_mark_as_started(self, widget): |
4982 | @@ -1610,7 +1226,7 @@ |
4983 | apply_to_subtasks.set_active(False) |
4984 | tag_entry.set_completion(self.tag_completion) |
4985 | tag_entry.grab_focus() |
4986 | - addtag_dialog = self.builder.get_object("TaskAddTag") |
4987 | + addtag_dialog = self.builder.get_object("addtag_dialog") |
4988 | addtag_dialog.run() |
4989 | addtag_dialog.hide() |
4990 | self.tids_to_addtag = None |
4991 | @@ -1619,7 +1235,7 @@ |
4992 | |
4993 | def on_addtag_confirm(self, widget): |
4994 | tag_entry = self.builder.get_object("tag_entry") |
4995 | - addtag_dialog = self.builder.get_object("TaskAddTag") |
4996 | + addtag_dialog = self.builder.get_object("addtag_dialog") |
4997 | apply_to_subtasks = self.builder.get_object("apply_to_subtasks") |
4998 | addtag_error = False |
4999 | entry_text = tag_entry.get_text() |
5000 | @@ -1672,7 +1288,6 @@ |
The diff has been truncated for viewing.
Needs to be fixed in 0.2.2 as well.