📄 tree.py
字号:
def insert_before(self, nodes): """Insert list of nodes as siblings before this node. Call parent node's add_node() function to generate the list of nodes.""" i=self.parent_node.child_nodes.index(self) self.parent_node.PVT_insert(nodes, i, self.prev_visible()) def insert_after(self, nodes): """Insert list of nodes as siblings after this node. Call parent node's add_node() function to generate the list of nodes.""" i=self.parent_node.child_nodes.index(self)+1 self.parent_node.PVT_insert(nodes, i, self.PVT_last()) def insert_children(self, nodes): """Insert list of nodes as children of this node. Call node's add_node() function to generate the list of nodes.""" self.PVT_insert(nodes, 0, self) def toggle_state(self): """Toggle node's state between expanded and collapsed, if possible""" if self.expandable_flag: if self.expanded_flag: self.PVT_set_state(0) else: self.PVT_set_state(1) # ----- functions for drag'n'drop support ----- def PVT_enter(self, event): """detect mouse hover for drag'n'drop""" self.widget.target=self def dnd_end(self, target, event): """Notification that dnd processing has been ended. It DOES NOT imply that we've been dropped somewhere useful, we could have just been dropped into deep space and nothing happened to any data structures, or it could have been just a plain mouse-click w/o any dragging.""" if not self.widget.drag: # if there's been no dragging, it was just a mouse click self.widget.move_cursor(self) self.toggle_state() self.widget.drag=0 # ----- PRIVATE METHODS (prefixed with "PVT_") ----- # these methods are subject to change, so please try not to use them def PVT_last(self): """Return bottom-most node in subtree""" n=self while n.child_nodes: n=n.child_nodes[-1] return n def PVT_find(self, search): """Used by searching functions""" if self.id != search[0]: # this actually only goes tilt if root doesn't match return None if len(search) == 1: return self # get list of children IDs i=map(lambda x: x.id, self.child_nodes) # if there is a child that matches, search it try: return self.child_nodes[i.index(search[1])].PVT_find(search[1:]) except: return None def PVT_insert(self, nodes, pos, below): """Create and insert new children. "nodes" is list previously created via calls to add_list(). "pos" is index in the list of children where the new nodes are inserted. "below" is node which new children should appear immediately below.""" if not self.expandable_flag: raise TypeError, 'not an expandable node' # for speed sw=self.widget # expand and insert children children=[] self.expanded_flag=1 sw.itemconfig(self.symbol, image=self.expanded_icon) if sw.minus_icon and sw.line_flag: sw.itemconfig(self.indic, image=sw.minus_icon) if len(nodes): # move stuff to make room below.PVT_tag_move(sw.dist_y*len(nodes)) # get position of first new child xp, dummy=sw.coords(self.symbol) dummy, yp=sw.coords(below.symbol) xp=xp+sw.dist_x yp=yp+sw.dist_y # create vertical line if sw.line_flag and not self.v_line: self.v_line=sw.create_line( xp, yp, xp, yp+sw.dist_y*len(nodes)) sw.tag_lower(self.v_line, self.symbol) n=sw.node_class for i in nodes: # add new subnodes, they'll draw themselves # this is a very expensive call children.append( n(parent_node=self, expandable_flag=i.flag, label=i.name, id=i.id, collapsed_icon=i.collapsed_icon, expanded_icon=i.expanded_icon, x=xp, y=yp)) yp=yp+sw.dist_y self.child_nodes[pos:pos]=children self.PVT_cleanup_lines() self.PVT_update_scrollregion() sw.move_cursor(sw.pos) def PVT_set_state(self, state): """Common code forexpanding/collapsing folders. It's not re-entrant, and there are certain cases in which we can be called again before we're done, so we use a mutex.""" while self.widget.spinlock: pass self.widget.spinlock=1 # expand & draw our subtrees if state: self.child_nodes=[] self.widget.new_nodes=[] if self.widget.get_contents_callback: # this callback needs to make multiple calls to add_node() try: self.widget.get_contents_callback(self) except: report_callback_exception() self.PVT_insert(self.widget.new_nodes, 0, self) # collapse and delete subtrees else: self.expanded_flag=0 self.widget.itemconfig(self.symbol, image=self.collapsed_icon) if self.indic: self.widget.itemconfig(self.indic, image=self.widget.plus_icon) self.delete(0) # release mutex self.widget.spinlock=0 def PVT_cleanup_lines(self): """Resize connecting lines""" if self.widget.line_flag: n=self while n: if n.child_nodes: x1, y1=self.widget.coords(n.symbol) x2, y2=self.widget.coords(n.child_nodes[-1].symbol) self.widget.coords(n.v_line, x1, y1, x1, y2) n=n.parent_node def PVT_update_scrollregion(self): """Update scroll region for new size""" x1, y1, x2, y2=self.widget.bbox('all') self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5)) def PVT_delete_subtree(self): """Recursively delete subtree & clean up cyclic references to make garbage collection happy""" sw=self.widget sw.delete(self.v_line) self.v_line=None for i in self.child_nodes: # delete node's subtree, if any i.PVT_delete_subtree() i.PVT_unbind_all() # delete widgets from canvas sw.delete(i.symbol) sw.delete(i.label) sw.delete(i.h_line) sw.delete(i.v_line) sw.delete(i.indic) # break circular reference i.parent_node=None # move cursor if it's in deleted subtree if sw.pos in self.child_nodes: sw.move_cursor(self) # now subnodes will be properly garbage collected self.child_nodes=[] def PVT_unbind_all(self): """Unbind callbacks so node gets garbage-collected. This wasn't easy to figure out the proper way to do this. See also tag_bind() for the Tree widget itself.""" for j in (self.symbol, self.label, self.indic, self.h_line, self.v_line): for k in self.widget.bindings.get(j, ()): self.widget.tag_unbind(j, k[0], k[1]) def PVT_tag_move(self, dist): """Move everything below current icon, to make room for subtree using the Disney magic of item tags. This is the secret of making everything as fast as it is.""" # mark everything below current node as movable bbox1=self.widget.bbox(self.widget.root.symbol, self.label) bbox2=self.widget.bbox('all') self.widget.dtag('move') self.widget.addtag('move', 'overlapping', bbox2[0], bbox1[3], bbox2[2], bbox2[3]) # untag cursor & node so they don't get moved too self.widget.dtag(self.widget.cursor_box, 'move') self.widget.dtag(self.symbol, 'move') self.widget.dtag(self.label, 'move') # now do the move of all the tagged objects self.widget.move('move', 0, dist) def PVT_click(self, event): """Handle mouse clicks by kicking off possible drag'n'drop processing""" if self.widget.drop_callback: if Tkdnd.dnd_start(self, event): x1, y1, x2, y2=self.widget.bbox(self.symbol) self.x_off=(x1-x2)/2 self.y_off=(y1-y2)/2 else: # no callback, don't bother with drag'n'drop self.widget.drag=0 self.dnd_end(None, None)#------------------------------------------------------------------------------class Tree(Canvas): # do we have enough possible arguments?!?!?! def __init__(self, master, root_id, root_label='', get_contents_callback=None, dist_x=15, dist_y=15, text_offset=10, line_flag=1, expanded_icon=None, collapsed_icon=None, regular_icon=None, plus_icon=None, minus_icon=None, node_class=Node, drop_callback=None, *args, **kw_args): # pass args to superclass (new idiom from Python 2.2) Canvas.__init__(self, master, *args, **kw_args) # this allows to subclass Node and pass our class in self.node_class=node_class # keep track of node bindings self.bindings={} # cheap mutex spinlock self.spinlock=0 # flag to see if there's been any d&d dragging self.drag=0 # default images (BASE64-encoded GIF files) if expanded_icon == None: self.expanded_icon=PhotoImage( data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ 'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \ 'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7') else: self.expanded_icon=expanded_icon if collapsed_icon == None: self.collapsed_icon=PhotoImage( data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ 'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \ 'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==') else: self.collapsed_icon=collapsed_icon if regular_icon == None: self.regular_icon=PhotoImage( data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \ 'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \ 'wbzuJrIHgw1WgAAOw==') else: self.regular_icon=regular_icon if plus_icon == None: self.plus_icon=PhotoImage( data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \ 'FIyPoiu2sJyCyoF7W3hxz850CFIA\nADs=') else: self.plus_icon=plus_icon if minus_icon == None: self.minus_icon=PhotoImage( data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \ 'EYyPoivG614LAlg7ZZbxoR8UADs=') else: self.minus_icon=minus_icon # horizontal distance that subtrees are indented self.dist_x=dist_x # vertical distance between rows self.dist_y=dist_y # how far to offset text label self.text_offset=text_offset # flag controlling connecting line display self.line_flag=line_flag # called just before subtree expand/collapse self.get_contents_callback=get_contents_callback # called after drag'n'drop self.drop_callback=drop_callback # create root node to get the ball rolling self.root=node_class(parent_node=None, label=root_label, id=root_id, expandable_flag=1, collapsed_icon=self.collapsed_icon, expanded_icon=self.expanded_icon, x=dist_x, y=dist_y, parent_widget=self) # configure for scrollbar(s) x1, y1, x2, y2=self.bbox('all') self.configure(scrollregion=(x1, y1, x2+5, y2+5)) # add a cursor self.cursor_box=self.create_rectangle(0, 0, 0, 0) self.move_cursor(self.root) # make it easy to point to control self.bind('<Enter>', self.PVT_mousefocus) # totally arbitrary yet hopefully intuitive default keybindings # stole 'em from ones used by microsoft tree control # page-up/page-down self.bind('<Next>', self.pagedown) self.bind('<Prior>', self.pageup) # arrow-up/arrow-down self.bind('<Down>', self.next)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -