~ubuntu-branches/ubuntu/trusty/liblarch/trusty

« back to all changes in this revision

Viewing changes to liblarch/filteredtree.py

  • Committer: Package Import Robot
  • Author(s): Luca Falavigna
  • Date: 2013-05-05 14:22:10 UTC
  • mfrom: (2.1.1 experimental)
  • Revision ID: package-import@ubuntu.com-20130505142210-scyeprq1t3cnjbsr
Tags: 2.1.0-2
* Upload to unstable.
* debian/control:
  - Use canonical URIs for VCS fields.
* debian/watch:
  - Point to GitHub repository, thanks Bart Martens!

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from __future__ import with_statement
2
1
# -*- coding: utf-8 -*-
3
2
# -----------------------------------------------------------------------------
4
3
# Liblarch - a library to handle directed acyclic graphs
19
18
# -----------------------------------------------------------------------------
20
19
 
21
20
import gobject
22
 
import processqueue
23
 
 
24
 
ASYNC_MODIFY = True
25
21
 
26
22
class FilteredTree():
27
23
    """ FilteredTree is the most important and also the most buggy part of
38
34
    by a simple recursion.
39
35
    """
40
36
 
41
 
    def __init__(self, tree, filtersbank, refresh=True):
 
37
    def __init__(self, tree, filtersbank, name=None, refresh=True):
42
38
        """ Construct a layer where filters could by applied
43
39
 
44
40
        @param tree: Original tree to filter.
50
46
        """
51
47
 
52
48
        self.cllbcks = {}
53
 
        self.callcount = {'up':0,'down':0,'both':0}
54
 
        self._queue = processqueue.SyncQueue()
55
49
 
56
50
        # Cache
57
51
        self.nodes = {}
58
 
        #Trying to have an unique string here.
59
 
        #Note that it was "None" before 
60
 
        self.root_id = "root*90124810293810238*!@ébpo@@+épboébpon/*»«%«»(+-¡a"
 
52
        # Set root_id by the name of FilteredTree
 
53
        if name is None:
 
54
            self.root_id = "anonymous_root"
 
55
        else:
 
56
            self.root_id = "root_%s" % name
 
57
 
61
58
        self.nodes[self.root_id] = {'parents': [], 'children': []}
62
59
        self.cache_paths = {}
 
60
        self.filter_cache = {}
63
61
 
64
62
        # Connect to signals from MainTree
65
63
        self.tree = tree
96
94
        else:
97
95
            self.cllbcks[event] = [func,node_id,param]
98
96
        
99
 
    def callback(self, event, node_id, path, neworder=None,async=False):
 
97
    def callback(self, event, node_id, path, neworder=None):
100
98
        """ Run a callback.
101
99
 
102
100
        To call callback, the object must be initialized and function exists.
108
106
        
109
107
        The runonce event is actually only run once, when a given task appears.
110
108
        """
 
109
 
111
110
        
112
111
        if event == 'added':
113
112
            for func,nid,param in self.cllbcks.get(node_id,[]):
120
119
        func,nid,param = self.cllbcks.get(event, (None,None,None))
121
120
        if func:
122
121
            if neworder:
123
 
                if async:
124
 
                    self._queue.push(func, node_id, path, neworder)
125
 
                else:
126
 
                    func(node_id,path,neworder)
 
122
                func(node_id,path,neworder)
127
123
            else:
128
 
                if async:
129
 
                    self._queue.push(func, node_id, path)
130
 
                else:
131
 
                    func(node_id,path)
 
124
                func(node_id,path)
132
125
                    
133
 
    def flush(self):
134
 
        return self._queue.flush()
135
 
            
136
126
 
137
127
#### EXTERNAL MODIFICATION ####################################################
138
128
    def __external_modify(self, node_id):
139
129
        return self.__update_node(node_id,direction="both")
140
130
        
141
 
    def __update_node(self, node_id,direction):
 
131
    def __update_node(self, node_id, direction):
142
132
        '''update the node node_id and propagate the 
143
133
        change in direction (up|down|both) '''
 
134
 
144
135
        if node_id == self.root_id:
145
136
            return None
146
 
        
147
 
        #Updating the node itself.
 
137
 
148
138
        current_display = self.is_displayed(node_id)
149
139
        new_display = self.__is_displayed(node_id)
150
140
 
 
141
        for fcname in self.filter_cache:
 
142
            if node_id in self.filter_cache[fcname]['nodes']:
 
143
                self.filter_cache[fcname]['nodes'].remove(node_id)
 
144
                self.filter_cache[fcname]['count'] = len(self.filter_cache[fcname]['nodes'])
 
145
 
151
146
        completely_updated = True
152
147
 
153
148
        if not current_display and not new_display:
165
160
            action = 'deleted'
166
161
        else:
167
162
            action = 'modified'
168
 
            
 
163
 
169
164
        # Create node info for new node
170
165
        if action == 'added':
171
166
            self.nodes[node_id] = {'parents':[], 'children':[]}
173
168
        # Make sure parents are okay if we adding or updating
174
169
        if action == 'added' or action == 'modified':
175
170
            current_parents = self.nodes[node_id]['parents']
176
 
            
177
 
            new_parents = self.__node_parents(node_id)
178
 
#            new_parents = []
179
 
#            for pp in self.__node_parents(node_id):
180
 
#                if pp in self.nodes:
181
 
#                    new_parents.append(pp)
 
171
            new_parents = self.__node_parents(node_id)
 
172
 
 
173
            # When using flat filter or a recursive filter, FilteredTree
 
174
            # might not recognize a parent correctly, make sure to check them
 
175
            if action == 'added':
 
176
                node = self.tree.get_node(node_id)
 
177
                for parent_id in node.get_parents():
 
178
                    if parent_id not in new_parents and parent_id not in current_parents:
 
179
                        self.__update_node(parent_id, direction="up")
 
180
 
 
181
            # Refresh list of parents after doing checkup once again
 
182
            current_parents = self.nodes[node_id]['parents']
 
183
            new_parents = self.__node_parents(node_id)
182
184
                    
183
185
            self.nodes[node_id]['parents'] = [parent_id for parent_id in new_parents
184
186
                if parent_id in self.nodes]
192
194
            if direction == "down" and self.root_id in add_to:
193
195
                direction = "both"
194
196
 
195
 
            #We update the parents
196
 
            if action == 'added':
197
 
                #This check is for "phantom parents", for example
198
 
                #If we have a flat or leave-only filter, we have to update the
199
 
                #real parents!
200
 
                node = self.tree.get_node(node_id)
201
 
                for parent in node.get_parents():
202
 
                    if parent not in new_parents and parent not in current_parents:
203
 
                        self.__update_node(parent,direction="up")
204
197
            for parent_id in remove_from:
205
198
                self.send_remove_tree(node_id, parent_id)
206
199
                self.nodes[parent_id]['children'].remove(node_id)
223
216
            #We update the node itself     
224
217
            #Why should we call the callback only for modify?
225
218
            if action == 'modified':
226
 
                self.callcount[direction] += 1
227
219
                for path in self.get_paths_for_node(node_id):
228
 
                    self.callback(action, node_id, path,async=ASYNC_MODIFY) 
 
220
                    self.callback(action, node_id, path) 
229
221
            
230
222
            #We update the children
231
223
            current_children = self.nodes[node_id]['children']
241
233
            for child_id in children:
242
234
                self.send_remove_tree(child_id, node_id)
243
235
                self.nodes[child_id]['parents'].remove(node_id)
244
 
            # Remove node from cache
245
 
            for parent_id in self.nodes[node_id]['parents']:
246
 
                self.nodes[parent_id]['children'].remove(node_id)
247
 
                self.__update_node(parent_id,direction="up")
248
 
 
249
 
            del self.nodes[node_id]
250
 
 
251
 
            for child_id in children:
252
236
                self.__update_node(child_id,direction="down")
253
 
            
 
237
 
254
238
            for path in paths:
255
239
                self.callback(action, node_id, path)
256
 
                
257
 
            #We update parents who are not displayed
258
 
            #If the node is only hidden and still exists in the tree
 
240
 
 
241
            # Remove node from cache
 
242
            for parent_id in self.nodes[node_id]['parents']:
 
243
                self.nodes[parent_id]['children'].remove(node_id)
 
244
                self.__update_node(parent_id,direction="up")
 
245
 
 
246
            self.nodes.pop(node_id)
 
247
 
 
248
            # We update parents who are not displayed
 
249
            # If the node is only hidden and still exists in the tree
259
250
            if self.tree.has_node(node_id):
260
251
                node = self.tree.get_node(node_id)
261
252
                for parent in node.get_parents():
262
253
                    if parent not in self.nodes:
263
 
                        self.tree.modify_node(parent)
 
254
                        self.__update_node(parent, direction="up")
264
255
                
265
256
        return completely_updated
266
257
 
298
289
    def test_validity(self):
299
290
        for node_id in self.nodes:
300
291
            for parent_id in self.nodes[node_id]['parents']:
301
 
                assert node_id in self.nodes[parent_id]['children']
 
292
                assert node_id in self.nodes[parent_id]['children'], "Node '%s' is not in children of '%s'" % (node_id, parent_id)
302
293
 
303
294
            if self.nodes[node_id]['parents'] == []:
304
 
                assert node_id == self.root_id
 
295
                assert node_id == self.root_id, "Node '%s' does not have parents" % (node_id)
305
296
 
306
297
            for parent_id in self.nodes[node_id]['children']:
307
 
                assert node_id in self.nodes[parent_id]['parents']
 
298
                assert node_id in self.nodes[parent_id]['parents'], "Node '%s' is not in parents of '%s'" % (node_id, parent_id)
308
299
 
309
300
 
310
301
#### OTHER ####################################################################
311
302
    def refilter(self):
312
303
        # Find out it there is at least one flat filter
 
304
        self.filter_cache = {}
313
305
        self.__flat = False
314
306
        for filter_name in self.applied_filters:
315
307
            filt = self.fbank.get_filter(filter_name)
329
321
 
330
322
        while queue != []:
331
323
            node_id = queue.pop(0)
332
 
            #FIXME: decide which is the best direction
 
324
            # FIXME: decide which is the best direction
333
325
            self.__update_node(node_id, direction="both")
334
326
 
335
327
            node = self.tree.get_node(node_id)
439
431
                if parent_id not in self.nodes:
440
432
                    raise Exception("Parent %s does not exists" % parent_id)
441
433
                if node_id not in self.nodes[parent_id]['children']:
442
 
                    raise Exception("%s is not children of %s\n%s" % (node_id, parent_id,str(self.nodes)))
 
434
                    # Dump also state of FilteredTree => useful for debugging
 
435
                    s = "\nCurrent tree:\n"
 
436
                    for key in self.nodes:
 
437
                        s += key + "\n"
 
438
                        s += "\t parents" + str(self.nodes[key]['parents']) + "\n"
 
439
                        s += "\t children" + str(self.nodes[key]['children']) + "\n"
 
440
                    raise Exception("%s is not children of %s\n%s" % (node_id, parent_id, s))
443
441
 
444
442
                for parent_path in self.get_paths_for_node(parent_id):
445
443
                    mypath = parent_path + (node_id,)
480
478
        nodes.remove(self.root_id)
481
479
        return nodes
482
480
 
483
 
    def get_n_nodes(self, withfilters=[], include_transparent=True):
484
 
        """
485
 
        returns quantity of displayed nodes in this tree
486
 
        if the withfilters is set, returns the quantity of nodes
487
 
        that will be displayed if we apply those filters to the current
488
 
        tree. It means that the currently applied filters are also taken into
489
 
        account.
490
 
        If include_transparent=False, we only take into account the applied filters
491
 
        that doesn't have the transparent parameters.
492
 
        """
493
 
        if withfilters == [] and include_transparent:
 
481
    def get_n_nodes(self, withfilters=[]):
 
482
        """
 
483
        returns quantity of displayed nodes in this tree
 
484
        if the withfilters is set, returns the quantity of nodes
 
485
        that will be displayed if we apply those filters to the current
 
486
        tree. It means that the currently applied filters are also taken into
 
487
        account.
 
488
        """
 
489
        return len(self.get_nodes(withfilters=withfilters))
 
490
 
 
491
    def get_nodes(self, withfilters=[]):
 
492
        """
 
493
        returns quantity of displayed nodes in this tree
 
494
        if the withfilters is set, returns the quantity of nodes
 
495
        that will be displayed if we apply those filters to the current
 
496
        tree. It means that the currently applied filters are also taken into
 
497
        account.
 
498
        """
 
499
        if withfilters == []:
494
500
            # Use current cache
495
 
            return len(self.nodes) - 1
496
 
        elif withfilters != [] and include_transparent:
 
501
            return self.get_all_nodes()
 
502
        #FIXME maybe allow caching multiple withfilters...
 
503
        elif len(withfilters) == 1 and withfilters[0] in self.filter_cache:
 
504
            return self.filter_cache[withfilters[0]]['nodes']
 
505
            
 
506
#        elif withfilters != []:
 
507
        else:
497
508
            # Filter on the current nodes
498
509
 
499
510
            filters = []
502
513
                if filt:
503
514
                    filters.append(filt)
504
515
 
505
 
            total_count = 0
 
516
            nodes = []
506
517
            for node_id in self.nodes:
507
518
                if node_id == self.root_id:
508
519
                    continue
514
525
                        break
515
526
                
516
527
                if displayed:
517
 
                    total_count += 1
518
 
 
519
 
            return total_count
520
 
        else:
521
 
            # Recompute every node
522
 
 
523
 
            # 1st step: build list of filters
524
 
            filters = []
525
 
            for filter_name in self.applied_filters:
526
 
                filt = self.fbank.get_filter(filter_name)
527
 
                if not filt:
528
 
                    continue
529
 
 
530
 
                # Skip transparent filters if needed
531
 
                transparent = filt.is_transparent()
532
 
                if not include_transparent and transparent:
533
 
                    continue
534
 
 
535
 
                filters.append(filt)
536
 
 
537
 
            for filter_name in withfilters:
538
 
                filt = self.fbank.get_filter(filter_name)
539
 
                if filt:
540
 
                    filters.append(filt)
541
 
 
542
 
            total_count = 0
543
 
            for node_id in self.tree.get_all_nodes():
544
 
                displayed = True
545
 
                for filt in filters:
546
 
                    displayed = filt.is_displayed(node_id)
547
 
                    if not displayed:
548
 
                        break
549
 
                
550
 
                if displayed:
551
 
                    total_count += 1
552
 
 
553
 
            return total_count
 
528
                    nodes.append(node_id)
 
529
 
 
530
            return nodes
554
531
 
555
532
    def get_node_for_path(self, path):
556
533
        if not path or path == ():
677
654
        else:
678
655
            return False
679
656
 
680
 
    def reset_filters(self, refresh=True, transparent_only=False):
 
657
    def reset_filters(self, refresh=True):
681
658
        """
682
659
        Clears all filters currently set on the tree.  Can't be called on 
683
660
        the main tree.
684
 
        Remove only transparents filters if transparent_only is True
685
661
        """
686
 
        if transparent_only:
687
 
            for f in list(self.applied_filters):
688
 
                filt = self.fbank.get_filter(f)
689
 
                if filt:
690
 
                    if filt.get_parameters('transparent'):
691
 
                        self.applied_filters.remove(f)
692
 
                else:
693
 
                    print "bank is %s" % self.applied_filters
694
 
                    raise IndexError('Applied filter %s doesnt' %f +\
695
 
                                    'exist anymore in the bank')
696
 
        else:
697
 
            self.applied_filters = []
 
662
        self.applied_filters = []
698
663
        if refresh:
699
664
            self.refilter()