GTG

Merge lp:~cjohnston/gtg/bugfix-lp-531053 into lp:gtg/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
Reviewer Review Type Date Requested Status
Gtg developers Pending
Review via email: mp+20510@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Chris Johnston (cjohnston) wrote :

Needs to be fixed in 0.2.2 as well.

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.

Subscribers

People subscribed via source and target branches