OpenCores
URL https://opencores.org/ocsvn/c0or1k/c0or1k/trunk

Subversion Repositories c0or1k

[/] [c0or1k/] [trunk/] [tools/] [cml2-tools/] [tree.py] - Blame information for rev 2

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 2 drasko
# tree.py -- highly optimized tkinter tree control
2
# by Charles E. "Gene" Cash (gcash@magicnet.net)
3
#
4
# 98/12/02 CEC started
5
# 99/??/?? CEC release to comp.lang.python.announce
6
# Trimmed for CML2 by ESR, December 2001 (cut-paste support removed).
7
import os, string
8
from Tkinter import *
9
 
10
# this is initialized later, after Tkinter is started
11
open_icon=None
12
 
13
# tree node helper class
14
class Node:
15
    # initialization creates node, draws it, and binds mouseclicks
16
    def __init__(self, parent, name, id, myclosed_icon, myopen_icon, x, y,
17
                 parentwidget):
18
        self.parent=parent              # immediate parent node
19
        self.name=name                  # name displayed on the label
20
        self.id=id                      # internal id used to manipulate things
21
        self.open_icon=myopen_icon      # bitmaps to be displayed
22
        self.closed_icon=myclosed_icon
23
        self.widget=parentwidget        # tree widget we belong to
24
        self.subnodes=[]                # our list of child nodes
25
        self.spinlock=0                  # cheap mutex spinlock
26
        self.open_flag=0         # closed to start with
27
        # draw horizontal connecting lines
28
        if self.widget.lineflag:
29
            self.line=self.widget.create_line(x-self.widget.distx, y, x, y)
30
        # draw approprate image
31
        if self.open_flag:
32
            self.symbol=self.widget.create_image(x, y, image=self.open_icon)
33
        else:
34
            self.symbol=self.widget.create_image(x, y, image=self.closed_icon)
35
        # add label
36
        self.label=self.widget.create_text(x+self.widget.textoff, y,
37
                                           text=self.name, justify='left',
38
                                           anchor='w' )
39
        # single-click to expand/collapse
40
        self.widget.tag_bind(self.symbol, '<1>', self.click)
41
        self.widget.tag_bind(self.label, '<1>', self.click) # njr
42
        # call customization hook
43
        if self.widget.init_hook:
44
            self.widget.init_hook(self)
45
 
46
#    def __repr__(self):
47
#        return 'Node: %s  Parent: %s  (%d children)' % \
48
#               (self.name, self.parent.name, len(self.subnodes))
49
 
50
    # recursively delete subtree & clean up cyclic references
51
    def _delete(self):
52
        for i in self.subnodes:
53
            if i.open_flag and i.subnodes:
54
                # delete vertical connecting line
55
                if self.widget.lineflag:
56
                    self.widget.delete(i.tree)
57
            # delete node's subtree, if any
58
            i._delete()
59
            # the following unbinding hassle is because tkinter
60
            # keeps a callback reference for each binding
61
            # so if we want things GC'd...
62
            for j in (i.symbol, i.label):
63
                for k in self.widget.tag_bind(j):
64
                    self.widget.tag_unbind(j, k)
65
                try:
66
                    for k in self.widget._tagcommands.get(j, []):
67
                        self.widget.deletecommand(k)
68
                        self.widget._tagcommands[j].remove(k)
69
                except: # XXX not needed in >= 1.6?
70
                    pass
71
            # delete widgets from canvas
72
            self.widget.delete(i.symbol, i.label)
73
            if self.widget.lineflag:
74
                self.widget.delete(i.line)
75
            # break cyclic reference
76
            i.parent=None
77
        # move cursor if it's in deleted subtree
78
        if self.widget.pos in self.subnodes:
79
            self.widget.move_cursor(self)
80
        # now subnodes will be properly garbage collected
81
        self.subnodes=[]
82
 
83
    # move everything below current icon, to make room for subtree
84
    # using the magic of item tags
85
    def _tagmove(self, dist):
86
        # mark everything below current node as movable
87
        bbox1=self.widget.bbox(self.widget.root.symbol, self.label)
88
        bbox2=self.widget.bbox('all')
89
        self.widget.dtag('move')
90
        self.widget.addtag('move', 'overlapping',
91
                           bbox2[0], bbox1[3], bbox2[2], bbox2[3])
92
        # untag cursor & node so they don't get moved too
93
        # this has to be done under Tk on X11
94
        self.widget.dtag(self.widget.cursor_box, 'move')
95
        self.widget.dtag(self.symbol, 'move')
96
        self.widget.dtag(self.label, 'move')
97
        # now do the move of all the tagged objects
98
        self.widget.move('move', 0, dist)
99
        # fix up connecting lines
100
        if self.widget.lineflag:
101
            n=self
102
            while n:
103
                if len(n.subnodes):
104
                    # position of current icon
105
                    x1, y1=self.widget.coords(n.symbol)
106
                    # position of last node in subtree
107
                    x2, y2=self.widget.coords(n.subnodes[-1:][0].symbol)
108
                    self.widget.coords(n.tree, x1, y1, x1, y2)
109
                n=n.parent
110
 
111
    # return list of subnodes that are expanded (not including self)
112
    # only includes unique leaf nodes (e.g. /home and /home/root won't
113
    # both be included) so expand() doesn't get called unnecessarily
114
    # thank $DEITY for Dr. Dutton's Data Structures classes at UCF!
115
    def expanded(self):
116
        # push initial node into stack
117
        stack=[(self, (self.id,))]
118
        list=[]
119
        while stack:
120
            # pop from stack
121
            p, i=stack[-1:][0]
122
            del stack[-1:]
123
            # flag to discard non-unique sub paths
124
            flag=1
125
            # check all children
126
            for n in p.subnodes:
127
                # if expanded, push onto stack
128
                if n.open_flag:
129
                    flag=0
130
                    stack.append((n, i+(n.id,)))
131
            # if we reached end of path, add to list
132
            if flag:
133
                list.append(i[1:])
134
        return list
135
 
136
    # get full name, including names of all parents
137
    def full_id(self):
138
        if self.parent:
139
            return self.parent.full_id()+(self.id,)
140
        else:
141
            return (self.id,)
142
 
143
    # expanding/collapsing folders
144
    def toggle_state(self, state=None):
145
        if self.widget.toggle_init_hook:
146
            self.widget.toggle_init_hook(self)
147
        if not self.open_icon:
148
            return                      # not an expandable folder
149
        if state == None:
150
            state = not self.open_flag  # toggle to other state
151
        else:
152
            # are we already in the state we want to be?
153
            if (not state) == (not self.open_flag):
154
                return
155
        # not re-entrant
156
        # acquire mutex
157
        while self.spinlock:
158
            pass
159
        self.spinlock=1
160
        # call customization hook
161
        if self.widget.before_hook:
162
            self.widget.before_hook(self)
163
        # if we're closed, expand & draw our subtrees
164
        if not self.open_flag:
165
            self.open_flag=1
166
            self.widget.itemconfig(self.symbol, image=self.open_icon)
167
            # get contents of subdirectory or whatever
168
            contents=self.widget.get_contents(self)
169
            # move stuff to make room
170
            self._tagmove(self.widget.disty*len(contents))
171
            # now draw subtree
172
            self.subnodes=[]
173
            # get current position of icon
174
            x, y=self.widget.coords(self.symbol)
175
            yp=y
176
            for i in contents:
177
                # add new subnodes, they'll draw themselves
178
                yp=yp+self.widget.disty
179
                self.subnodes.append(Node(self, i[0], i[1], i[2], i[3],
180
                                          x+self.widget.distx, yp,
181
                                          self.widget))
182
            # the vertical line spanning the subtree
183
            if self.subnodes and self.widget.lineflag:
184
                self.tree=self.widget.create_line(x, y,
185
                                     x, y+self.widget.disty*len(self.subnodes))
186
                self.widget.lower(self.tree, self.symbol)
187
        # if we're open, collapse and delete subtrees
188
        elif self.open_flag:
189
            self.open_flag=0
190
            self.widget.itemconfig(self.symbol, image=self.closed_icon)
191
            # if we have any children
192
            if self.subnodes:
193
                # recursively delete subtree icons
194
                self._delete()
195
                # delete vertical line
196
                if self.widget.lineflag:
197
                    self.widget.delete(self.tree)
198
                # find next (vertically-speaking) node
199
                n=self
200
                while n.parent:
201
                    # position of next sibling in parent's list
202
                    i=n.parent.subnodes.index(n)+1
203
                    if i < len(n.parent.subnodes):
204
                        n=n.parent.subnodes[i]
205
                        break
206
                    n=n.parent
207
                if n.parent:
208
                    # move everything up so that distance to next subnode is
209
                    # correct
210
                    x1, y1=self.widget.coords(self.symbol)
211
                    x2, y2=self.widget.coords(n.symbol)
212
                    dist=y2-y1-self.widget.disty
213
                    self._tagmove(-dist)
214
        # update scroll region for new size
215
        x1, y1, x2, y2=self.widget.bbox('all')
216
        self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5))
217
        # call customization hook
218
        if self.widget.after_hook:
219
            print 'calling after_hook'
220
            self.widget.after_hook(self)
221
        # release mutex
222
        self.spinlock=0
223
 
224
    # expand this subnode
225
    # doesn't have to exist, it expands what part of the path DOES exist
226
    def expand(self, dirs):
227
        # if collapsed, then expand
228
        self.toggle_state(1)
229
        # find next subnode
230
        if dirs:
231
            for n in self.subnodes:
232
                if n.id == dirs[0]:
233
                    return n.expand(dirs[1:])
234
            print "Can't find path %s in %s" % (dirs, self.id)
235
            print "- Available subnodes: %s" % map(lambda n: n.id, self.subnodes)
236
        return self
237
 
238
    # handle mouse clicks by moving cursor and toggling folder state
239
    def click(self, dummy):
240
        self.widget.move_cursor(self)
241
        self.toggle_state()
242
 
243
    # return next lower visible node
244
    def next(self):
245
        n=self
246
        if n.subnodes:
247
            # if you can go right, do so
248
            return n.subnodes[0]
249
        while n.parent:
250
            # move to next sibling
251
            i=n.parent.subnodes.index(n)+1
252
            if i < len(n.parent.subnodes):
253
                return n.parent.subnodes[i]
254
            # if no siblings, move to parent's sibling
255
            n=n.parent
256
        # we're at bottom
257
        return self
258
 
259
    # return next higher visible node
260
    def prev(self):
261
        n=self
262
        if n.parent:
263
            # move to previous sibling
264
            i=n.parent.subnodes.index(n)-1
265
            if i >= 0:
266
                # move to last child
267
                n=n.parent.subnodes[i]
268
                while n.subnodes:
269
                    n=n.subnodes[-1]
270
            else:
271
                # punt if there's no previous sibling
272
                if n.parent:
273
                    n=n.parent
274
        return n
275
 
276
class Tree(Canvas):
277
    def __init__(self, master, rootname, rootlabel=None, openicon=None,
278
                 shuticon=None, getcontents=None, init=None,
279
                 toggle_init=None,before=None, after=None, cut=None, paste=None,
280
                 distx=15, disty=15, textoff=10, lineflag=1, **kw_args):
281
        global open_icon, shut_icon, file_icon,yes_icon,no_icon
282
        # pass args to superclass
283
        apply(Canvas.__init__, (self, master), kw_args)
284
        # try creating an image, work around Tkinter bug
285
        # ('global' should do it, but it doesn't)
286
        if open_icon is not None:
287
            try:
288
                item = self.create_image(0,0,image=open_icon)
289
                self.delete(item)
290
            except:
291
                print "recreating Tree PhotoImages"
292
                open_icon = None # need to recreate PhotoImages
293
        # default images (BASE64-encoded GIF files)
294
        # we have to delay initialization until Tk starts up or PhotoImage()
295
        # complains (otherwise I'd just put it up top)
296
        if open_icon == None:
297
            open_icon=PhotoImage(
298
                data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \
299
                'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \
300
                'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7')
301
            shut_icon=PhotoImage(
302
                data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \
303
                'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \
304
                'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==')
305
            file_icon=PhotoImage(
306
                data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \
307
                'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \
308
                'wbzuJrIHgw1WgAAOw==')
309
            yes_icon=PhotoImage(
310
                data='R0lGODlhDAAPAKEAAP////9FRAAAAP///yH5BAEKAAMALAAA' \
311
                'AAAMAA8AAAIrhI8zyKAWUARCQGnqPVODuXlg0FkQ+WUmUzYpZYKv9'\
312
                '5Eg7VZKxffC7usVAAA7')
313
            no_icon=PhotoImage(
314
                data='R0lGODlhDAAPAKEAAP///wAAAERe/////yH+FUNyZWF0ZWQgd' \
315
            '2l0aCBUaGUgR0lNUAAh+QQBCgADACwAAAAADAAPAAACLISPM8i' \
316
            'gjUIAolILpDB70zxxnieJBxl6n1FiGLiuW4tgJcSGuKRI/h/oAX8FADs=')
317
        # function to return subnodes (not very much use w/o this)
318
        if not getcontents:
319
            raise ValueError, 'must have "get_contents" function'
320
        self.get_contents=getcontents
321
        # horizontal distance that subtrees are indented
322
        self.distx=distx
323
        # vertical distance between rows
324
        self.disty=disty
325
        # how far to offset text label
326
        self.textoff=textoff
327
        # called after new node initialization
328
        self.init_hook=init
329
        # called right after toggle state 
330
        self.toggle_init_hook=toggle_init
331
        # called just before subtree expand/collapse
332
        self.before_hook=before
333
        # called just after subtree expand/collapse
334
        self.after_hook=after
335
        # flag to display lines
336
        self.lineflag=lineflag
337
        # create root node to get the ball rolling
338
        if openicon:
339
            oi = openicon
340
        else:
341
            oi = open_icon
342
        if shuticon:
343
            si = shuticon
344
        else:
345
            si = shut_icon
346
        if rootlabel:
347
            self.root=Node(None, rootlabel, rootname, si, oi, 11, 11, self)
348
        else:
349
            self.root=Node(None, rootname, rootname, si, oi, 11, 11, self)
350
        # configure for scrollbar(s)
351
        x1, y1, x2, y2=self.bbox('all')
352
        self.configure(scrollregion=(x1, y1, x2+5, y2+5))
353
        # add a cursor
354
        self.cursor_box=self.create_rectangle(0, 0, 0, 0)
355
        self.move_cursor(self.root)
356
        # make it easy to point to control
357
        self.bind('<Enter>', self.mousefocus)
358
        # bindings similar to those used by Microsoft tree control
359
        # page-up/page-down
360
        self.bind('<Next>', self.pagedown)
361
        self.bind('<Prior>', self.pageup)
362
        # arrow-up/arrow-down
363
        self.bind('<Down>', self.next)
364
        self.bind('<Up>', self.prev)
365
        # arrow-left/arrow-right
366
        self.bind('<Left>', self.ascend)
367
        # (hold this down and you expand the entire tree)
368
        self.bind('<Right>', self.descend)
369
        # home/end
370
        self.bind('<Home>', self.first)
371
        self.bind('<End>', self.last)
372
        # space bar
373
        self.bind('<Key-space>', self.toggle)
374
 
375
    # scroll (in a series of nudges) so items are visible
376
    def see(self, *items):
377
        x1, y1, x2, y2=apply(self.bbox, items)
378
        while x2 > self.canvasx(0)+self.winfo_width():
379
            old=self.canvasx(0)
380
            self.xview('scroll', 1, 'units')
381
            # avoid endless loop if we can't scroll
382
            if old == self.canvasx(0):
383
                break
384
        while y2 > self.canvasy(0)+self.winfo_height():
385
            old=self.canvasy(0)
386
            self.yview('scroll', 1, 'units')
387
            if old == self.canvasy(0):
388
                break
389
        # done in this order to ensure upper-left of object is visible
390
        while x1 < self.canvasx(0):
391
            old=self.canvasx(0)
392
            self.xview('scroll', -1, 'units')
393
            if old == self.canvasx(0):
394
                break
395
        while y1 < self.canvasy(0):
396
            old=self.canvasy(0)
397
            self.yview('scroll', -1, 'units')
398
            if old == self.canvasy(0):
399
                break
400
 
401
    # move cursor to node
402
    def move_cursor(self, node):
403
        self.pos=node
404
        x1, y1, x2, y2=self.bbox(node.symbol, node.label)
405
        self.coords(self.cursor_box, x1-1, y1-1, x2+1, y2+1)
406
        self.see(node.symbol, node.label)
407
 
408
    # expand given path
409
    # note that the convention used in this program to identify a
410
    # particular node is to give a tuple listing it's id and parent ids
411
    # so you probably want to use os.path.split() a lot
412
    def expand(self, path):
413
        return self.root.expand(path[1:])
414
 
415
    # soak up event argument when moused-over
416
    # could've used lambda but didn't...
417
    def mousefocus(self, event):
418
        self.focus_set()
419
 
420
    # open/close subtree
421
    def toggle(self, event=None):
422
        self.pos.toggle_state()
423
 
424
    # move to next lower visible node
425
    def next(self, event=None):
426
        self.move_cursor(self.pos.next())
427
 
428
    # move to next higher visible node
429
    def prev(self, event=None):
430
        self.move_cursor(self.pos.prev())
431
 
432
    # move to immediate parent
433
    def ascend(self, event=None):
434
        if self.pos.parent:
435
            # move to parent
436
            self.move_cursor(self.pos.parent)
437
 
438
    # move right, expanding as we go
439
    def descend(self, event=None):
440
        self.pos.toggle_state(1)
441
        if self.pos.subnodes:
442
            # move to first subnode
443
            self.move_cursor(self.pos.subnodes[0])
444
        else:
445
            # if no subnodes, move to next sibling
446
            self.next()
447
 
448
    # go to root
449
    def first(self, event=None):
450
        # move to root node
451
        self.move_cursor(self.root)
452
 
453
    # go to last visible node
454
    def last(self, event=None):
455
        # move to bottom-most node
456
        n=self.root
457
        while n.subnodes:
458
            n=n.subnodes[-1]
459
        self.move_cursor(n)
460
 
461
    # previous page
462
    def pageup(self, event=None):
463
        n=self.pos
464
        j=self.winfo_height()/self.disty
465
        for i in range(j-3):
466
            n=n.prev()
467
        self.yview('scroll', -1, 'pages')
468
        self.move_cursor(n)
469
 
470
    # next page
471
    def pagedown(self, event=None):
472
        n=self.pos
473
        j=self.winfo_height()/self.disty
474
        for i in range(j-3):
475
            n=n.next()
476
        self.yview('scroll', 1, 'pages')
477
        self.move_cursor(n)
478
 
479
# End

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.