1
# -*- coding: utf-8 -*-
2
# -----------------------------------------------------------------------------
3
# Getting Things Gnome! - a personal organizer for the GNOME desktop
4
# Copyright (c) 2008-2011- Lionel Dricot & Bertrand Rousseau
6
# This program is free software: you can redistribute it and/or modify it under
7
# the terms of the GNU General Public License as published by the Free Software
8
# Foundation, either version 3 of the License, or (at your option) any later
11
# This program is distributed in the hope that it will be useful, but WITHOUT
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16
# You should have received a copy of the GNU General Public License along with
17
# this program. If not, see <http://www.gnu.org/licenses/>.
21
from GTG.tools.liblarch.tree import MainTree
22
from GTG.tools.liblarch.filteredtree import FilteredTree
23
from GTG.tools.liblarch.filters_bank import FiltersBank
26
""" A thin wrapper to MainTree that adds filtering capabilities.
27
It also provides a few methods to operate complex operation on the
28
MainTree (e.g, move_node) """
31
""" Creates MainTree which wraps and a main view without filters """
32
self.__tree = MainTree()
33
self.__fbank = FiltersBank(self.__tree)
35
self.views['main'] = ViewTree(self, self.__tree, self.__fbank, static=True)
37
##### HANDLE NODES ############################################################
39
def get_node(self, node_id):
40
""" Returns the object of node.
41
If the node does not exists, a ValueError is raised. """
42
return self.__tree.get_node(node_id)
44
def has_node(self, node_id):
45
""" Does the node exists in this tree? """
46
return self.__tree.has_node(node_id)
48
def add_node(self, node, parent_id=None, high_priority=False):
49
""" Add a node to tree. If parent_id is set, put the node as a child of
50
this node, otherwise put it as a child of the root node."""
51
self.__tree.add_node(node, parent_id, high_priority)
53
def del_node(self, node_id, recursive=False):
54
""" Remove node from tree and return whether it was successful or not """
55
return self.__tree.remove_node(node_id, recursive)
57
def refresh_node(self, node_id):
58
""" Send a request for updating the node """
59
self.__tree.modify_node(node_id)
61
def refresh_all(self):
62
""" Refresh all nodes """
63
self.__tree.refresh_all()
65
def move_node(self, node_id, new_parent_id=None):
66
""" Move the node to a new parent (dismissing all other parents)
67
use pid None to move it to the root """
68
if self.has_node(node_id):
69
node = self.get_node(node_id)
70
node.set_parent(new_parent_id)
78
def add_parent(self, node_id, new_parent_id=None):
79
""" Add the node to a new parent. Return whether operation was
80
successful or not. If the node does not exists, return False """
82
if self.has_node(node_id):
83
node = self.get_node(node_id)
84
return node.add_parent(new_parent_id)
88
##### VIEWS ###################################################################
89
def get_main_view(self):
90
""" Return the special view "main" which is without any filters on it."""
91
return self.views['main']
93
def get_viewtree(self, name=None, refresh=True):
94
""" Returns a viewtree by the name:
95
* a viewtree with that name exists => return it
96
* a viewtree with that name does not exist => create a new one and return it
97
* name is None => create an anonymous tree (do not remember it)
99
If refresh is False, the view is not initialized. This is useful as
100
an optimization if you plan to apply a filter.
103
if name is not None and self.views.has_key(name):
104
view_tree = self.views[name]
106
view_tree = ViewTree(self,self.__tree,self.__fbank,refresh=refresh)
108
self.views[name] = view_tree
111
##### FILTERS ##################################################################
112
def list_filters(self):
113
""" Return a list of all available filters by name """
114
return self.__fbank.list_filters()
116
def add_filter(self, filter_name, filter_func, parameters=None):
117
""" Adds a filter to the filter bank.
119
@filter_name : name to give to the filter
120
@filter_func : the function that will filter the nodes
121
@parameters : some default parameters fot that filter
122
Return True if the filter was added
123
Return False if the filter_name was already in the bank
125
return self.__fbank.add_filter(filter_name, filter_func, parameters)
127
def remove_filter(self,filter_name):
128
""" Remove a filter from the bank. Only custom filters that were
129
added here can be removed. Return False if the filter was not removed.
131
return self.__fbank.remove_filter(filter_name)
133
# There should be two classes: for static and for dynamic mode
134
# There are many conditions, and also we would prevent unallowed modes
136
def __init__(self, maininterface, maintree, filters_bank,\
137
refresh = True, static = False):
138
"""A ViewTree is the interface that should be used to display Tree(s).
140
In static mode, FilteredTree layer is not created. (There is no need)
142
We connect to MainTree or FilteredTree to get informed about changes.
143
If FilteredTree is used, it is connected to MainTree to handle changes
144
and then send id to ViewTree if it applies.
146
@param maintree: a Tree object, cointaining all the nodes
147
@param filters_bank: a FiltersBank object. Filters can be added
149
@param refresh: if True, this ViewTree is automatically refreshed
150
after applying a filter.
151
@param static: if True, this is the view of the complete maintree.
152
Filters cannot be added to such a view.
154
self.maininterface = maininterface
155
self.__maintree = maintree
157
self.__fbank = filters_bank
161
self._tree = self.__maintree
163
self.__maintree.register_callback('node-added', \
164
functools.partial(self.__emit, 'node-added'))
165
self.__maintree.register_callback('node-deleted', \
166
functools.partial(self.__emit, 'node-deleted'))
167
self.__maintree.register_callback('node-modified', \
168
functools.partial(self.__emit, 'node-modified'))
170
self.__ft = FilteredTree(maintree, filters_bank, refresh = refresh)
171
self._tree = self.__ft
172
self.__ft.set_callback('added', \
173
functools.partial(self.__emit, 'node-added-inview'))
174
self.__ft.set_callback('deleted', \
175
functools.partial(self.__emit, 'node-deleted-inview'))
176
self.__ft.set_callback('modified', \
177
functools.partial(self.__emit, 'node-modified-inview'))
178
self.__ft.set_callback('reordered', \
179
functools.partial(self.__emit, 'node-children-reordered'))
181
def queue_action(self, node_id,func,param=None):
182
self.__ft.set_callback('runonce',func,node_id=node_id,param=param)
184
def get_basetree(self):
185
""" Return Tree object """
186
return self.maininterface
188
def register_cllbck(self, event, func):
189
""" Store function and return unique key which can be used to
190
unregister the callback later """
192
if not self.__cllbcks.has_key(event):
193
self.__cllbcks[event] = {}
195
callbacks = self.__cllbcks[event]
197
while callbacks.has_key(key):
200
callbacks[key] = func
203
def deregister_cllbck(self, event, key):
204
""" Remove the callback identifed by key (from register_cllbck) """
206
del self.__cllbcks[event][key]
210
def __emit(self, event, node_id, path=None, neworder=None):
211
""" Handle a new event from MainTree or FilteredTree
212
by passing it to other objects, e.g. TreeWidget """
213
callbacks = dict(self.__cllbcks.get(event, {}))
215
for func in callbacks.itervalues():
217
func(node_id, path, neworder)
221
def get_node(self, node_id):
222
""" Get a node from MainTree """
223
return self.__maintree.get_node(node_id)
226
def __get_static_node(self,node_id):
229
if not node_id or node_id == 'root':
230
toreturn = self.__maintree.get_root()
232
toreturn = self.__maintree.get_node(node_id)
234
raise Exception("You should not get a static node"+\
240
return self.__maintree.get_root()
243
def refresh_all(self):
244
self.__maintree.refresh_all()
246
def get_current_state(self):
247
""" Request current state to be send by signals/callbacks.
249
This allow LibLarch widget to connect on fly (e.g. after FilteredTree
250
is up and has some nodes). """
253
self.__maintree.refresh_all()
255
self.__ft.get_current_state()
257
def print_tree(self, string=None):
258
""" Print the shown tree, i.e. MainTree or FilteredTree """
259
return self._tree.print_tree(string)
261
def get_all_nodes(self):
262
""" Return list of node_id of displayed nodes """
263
return self._tree.get_all_nodes()
265
def get_n_nodes(self, withfilters=[], include_transparent=True):
266
""" Returns quantity of displayed nodes in this tree
268
@withfilters => Additional filters are applied before counting,
269
i.e. the currently applied filters are also taken into account
271
@inclde_transparent => if it is False, filters which don't have
272
the transparent parameters are skipped, not takend into account
276
self.__ft = FilteredTree(self.__maintree, self.__fbank, refresh = True)
277
return self.__ft.get_n_nodes(withfilters=withfilters,\
278
include_transparent=include_transparent)
280
# FIXME WTF? do wee need it?
281
def get_node_for_path(self, path):
282
""" Convert path to node_id.
284
I am not sure what this is for... """
285
return self._tree.get_node_for_path(path)
287
# FIXME WTF? do wee need it?
288
def get_paths_for_node(self, node_id=None):
289
""" If node_id is none, return root path
291
*Almost* reverse function to get_node_for_path
292
(1 node can have many paths, 1:M)
294
return self._tree.get_paths_for_node(node_id)
296
# FIXME WTF? do wee need it?
297
# FIXME change pid => parent_id
298
def next_node(self, node_id, pid=None):
299
""" Return the next node to node_id.
301
@parent_id => identify which instance of node_id to work.
302
If None, random instance is used """
304
return self._tree.next_node(node_id, pid)
306
def node_has_child(self, node_id):
308
# FIXME use the same interface
311
toreturn = self.__maintree.get_node(node_id).has_child()
313
toreturn = self.__ft.node_has_child(node_id)
316
def node_all_children(self, node_id=None):
318
# FIXME use the same interface
319
# FIXME name? returns really all children? recursive?
321
if not node_id or self.__maintree.has_node(node_id):
322
toreturn = self.__maintree.get_node(node_id).get_children()
326
toreturn = self._tree.node_all_children(node_id)
329
def node_n_children(self, node_id=None, recursive=False):
330
""" Return quantity of children of node_id.
331
If node_id is None, use the root node.
332
Every instance of node has the same children"""
334
self.__ft = FilteredTree(self.__maintree, self.__fbank, refresh = True)
335
return self.__ft.node_n_children(node_id,recursive)
337
def node_nth_child(self, node_id, n):
338
""" Return nth child of the node. """
339
# FIXME the same interface
343
# FIXME Shouldnt be solved in MainTree?
344
if not node_id or node_id == 'root':
345
node = self.__maintree.get_root()
347
node = self.__maintree.get_node(node_id)
349
if node and node.get_n_children() > n:
350
toreturn = node.get_nth_child(n)
352
raise ValueError("node %s has less than %s nodes" %(node_id, n))
354
# FIXME Shouldnt be solved in FilteredTree?
355
realn = self.__ft.node_n_children(node_id)
357
raise ValueError("viewtree has %s nodes, no node %s" %(realn, n))
358
toreturn = self.__ft.node_nth_child(node_id, n)
361
def node_has_parent(self, node_id):
362
""" Has node parents? Is it child of root? """
363
return len(self.node_parents(node_id)) > 0
365
def node_parents(self, node_id):
366
""" Returns displayed parents of the given node, or [] if there is no
367
parent (such as if the node is a child of the virtual root),
368
or if the parent is not displayable.
369
Doesn't check wheter node node_id is displayed or not. (we only care about
372
# FIXME the same interface
374
toreturn = self.__maintree.get_node(node_id).get_parents()
376
toreturn = self.__ft.node_parents(node_id)
379
def is_displayed(self, node_id):
380
""" Is the node displayed? """
383
return self.__maintree.has_node(node_id)
385
return self.__ft.is_displayed(node_id)
387
####### FILTERS ###############################################################
388
def list_applied_filters(self):
389
return self.__ft.list_applied_filters()
391
def apply_filter(self, filter_name, parameters=None, \
392
reset=False, refresh=True):
393
""" Applies a new filter to the tree.
395
@param filter_name: The name of an already registered filter to apply
396
@param parameters: Optional parameters to pass to the filter
397
@param reset : optional boolean. Should we remove other filters?
398
@param refresh : should we refresh after applying this filter ?
401
raise Exception("WARNING: filters cannot be applied" + \
402
"to a static tree\n")
404
self.__ft.apply_filter(filter_name, parameters, reset, refresh)
406
def unapply_filter(self,filter_name,refresh=True):
407
""" Removes a filter from the tree.
409
@param filter_name: The name of filter to remove
412
raise Exception("WARNING: filters cannot be unapplied" +\
413
"from a static tree\n")
415
self.__ft.unapply_filter(filter_name, refresh)
417
def reset_filters(self, refresh=True, transparent_only=False):
418
""" Remove all filters currently set on the tree. """
420
raise Exception("WARNING: filters cannot be reset" +\
421
"on a static tree\n")
423
self.__ft.reset_filters(refresh, transparent_only)