symbian-qemu-0.9.1-12/python-2.6.1/Lib/idlelib/TreeWidget.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 # XXX TO DO:
       
     2 # - popup menu
       
     3 # - support partial or total redisplay
       
     4 # - key bindings (instead of quick-n-dirty bindings on Canvas):
       
     5 #   - up/down arrow keys to move focus around
       
     6 #   - ditto for page up/down, home/end
       
     7 #   - left/right arrows to expand/collapse & move out/in
       
     8 # - more doc strings
       
     9 # - add icons for "file", "module", "class", "method"; better "python" icon
       
    10 # - callback for selection???
       
    11 # - multiple-item selection
       
    12 # - tooltips
       
    13 # - redo geometry without magic numbers
       
    14 # - keep track of object ids to allow more careful cleaning
       
    15 # - optimize tree redraw after expand of subnode
       
    16 
       
    17 import os
       
    18 from Tkinter import *
       
    19 import imp
       
    20 
       
    21 import ZoomHeight
       
    22 from configHandler import idleConf
       
    23 
       
    24 ICONDIR = "Icons"
       
    25 
       
    26 # Look for Icons subdirectory in the same directory as this module
       
    27 try:
       
    28     _icondir = os.path.join(os.path.dirname(__file__), ICONDIR)
       
    29 except NameError:
       
    30     _icondir = ICONDIR
       
    31 if os.path.isdir(_icondir):
       
    32     ICONDIR = _icondir
       
    33 elif not os.path.isdir(ICONDIR):
       
    34     raise RuntimeError, "can't find icon directory (%r)" % (ICONDIR,)
       
    35 
       
    36 def listicons(icondir=ICONDIR):
       
    37     """Utility to display the available icons."""
       
    38     root = Tk()
       
    39     import glob
       
    40     list = glob.glob(os.path.join(icondir, "*.gif"))
       
    41     list.sort()
       
    42     images = []
       
    43     row = column = 0
       
    44     for file in list:
       
    45         name = os.path.splitext(os.path.basename(file))[0]
       
    46         image = PhotoImage(file=file, master=root)
       
    47         images.append(image)
       
    48         label = Label(root, image=image, bd=1, relief="raised")
       
    49         label.grid(row=row, column=column)
       
    50         label = Label(root, text=name)
       
    51         label.grid(row=row+1, column=column)
       
    52         column = column + 1
       
    53         if column >= 10:
       
    54             row = row+2
       
    55             column = 0
       
    56     root.images = images
       
    57 
       
    58 
       
    59 class TreeNode:
       
    60 
       
    61     def __init__(self, canvas, parent, item):
       
    62         self.canvas = canvas
       
    63         self.parent = parent
       
    64         self.item = item
       
    65         self.state = 'collapsed'
       
    66         self.selected = False
       
    67         self.children = []
       
    68         self.x = self.y = None
       
    69         self.iconimages = {} # cache of PhotoImage instances for icons
       
    70 
       
    71     def destroy(self):
       
    72         for c in self.children[:]:
       
    73             self.children.remove(c)
       
    74             c.destroy()
       
    75         self.parent = None
       
    76 
       
    77     def geticonimage(self, name):
       
    78         try:
       
    79             return self.iconimages[name]
       
    80         except KeyError:
       
    81             pass
       
    82         file, ext = os.path.splitext(name)
       
    83         ext = ext or ".gif"
       
    84         fullname = os.path.join(ICONDIR, file + ext)
       
    85         image = PhotoImage(master=self.canvas, file=fullname)
       
    86         self.iconimages[name] = image
       
    87         return image
       
    88 
       
    89     def select(self, event=None):
       
    90         if self.selected:
       
    91             return
       
    92         self.deselectall()
       
    93         self.selected = True
       
    94         self.canvas.delete(self.image_id)
       
    95         self.drawicon()
       
    96         self.drawtext()
       
    97 
       
    98     def deselect(self, event=None):
       
    99         if not self.selected:
       
   100             return
       
   101         self.selected = False
       
   102         self.canvas.delete(self.image_id)
       
   103         self.drawicon()
       
   104         self.drawtext()
       
   105 
       
   106     def deselectall(self):
       
   107         if self.parent:
       
   108             self.parent.deselectall()
       
   109         else:
       
   110             self.deselecttree()
       
   111 
       
   112     def deselecttree(self):
       
   113         if self.selected:
       
   114             self.deselect()
       
   115         for child in self.children:
       
   116             child.deselecttree()
       
   117 
       
   118     def flip(self, event=None):
       
   119         if self.state == 'expanded':
       
   120             self.collapse()
       
   121         else:
       
   122             self.expand()
       
   123         self.item.OnDoubleClick()
       
   124         return "break"
       
   125 
       
   126     def expand(self, event=None):
       
   127         if not self.item._IsExpandable():
       
   128             return
       
   129         if self.state != 'expanded':
       
   130             self.state = 'expanded'
       
   131             self.update()
       
   132             self.view()
       
   133 
       
   134     def collapse(self, event=None):
       
   135         if self.state != 'collapsed':
       
   136             self.state = 'collapsed'
       
   137             self.update()
       
   138 
       
   139     def view(self):
       
   140         top = self.y - 2
       
   141         bottom = self.lastvisiblechild().y + 17
       
   142         height = bottom - top
       
   143         visible_top = self.canvas.canvasy(0)
       
   144         visible_height = self.canvas.winfo_height()
       
   145         visible_bottom = self.canvas.canvasy(visible_height)
       
   146         if visible_top <= top and bottom <= visible_bottom:
       
   147             return
       
   148         x0, y0, x1, y1 = self.canvas._getints(self.canvas['scrollregion'])
       
   149         if top >= visible_top and height <= visible_height:
       
   150             fraction = top + height - visible_height
       
   151         else:
       
   152             fraction = top
       
   153         fraction = float(fraction) / y1
       
   154         self.canvas.yview_moveto(fraction)
       
   155 
       
   156     def lastvisiblechild(self):
       
   157         if self.children and self.state == 'expanded':
       
   158             return self.children[-1].lastvisiblechild()
       
   159         else:
       
   160             return self
       
   161 
       
   162     def update(self):
       
   163         if self.parent:
       
   164             self.parent.update()
       
   165         else:
       
   166             oldcursor = self.canvas['cursor']
       
   167             self.canvas['cursor'] = "watch"
       
   168             self.canvas.update()
       
   169             self.canvas.delete(ALL)     # XXX could be more subtle
       
   170             self.draw(7, 2)
       
   171             x0, y0, x1, y1 = self.canvas.bbox(ALL)
       
   172             self.canvas.configure(scrollregion=(0, 0, x1, y1))
       
   173             self.canvas['cursor'] = oldcursor
       
   174 
       
   175     def draw(self, x, y):
       
   176         # XXX This hard-codes too many geometry constants!
       
   177         self.x, self.y = x, y
       
   178         self.drawicon()
       
   179         self.drawtext()
       
   180         if self.state != 'expanded':
       
   181             return y+17
       
   182         # draw children
       
   183         if not self.children:
       
   184             sublist = self.item._GetSubList()
       
   185             if not sublist:
       
   186                 # _IsExpandable() was mistaken; that's allowed
       
   187                 return y+17
       
   188             for item in sublist:
       
   189                 child = self.__class__(self.canvas, self, item)
       
   190                 self.children.append(child)
       
   191         cx = x+20
       
   192         cy = y+17
       
   193         cylast = 0
       
   194         for child in self.children:
       
   195             cylast = cy
       
   196             self.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50")
       
   197             cy = child.draw(cx, cy)
       
   198             if child.item._IsExpandable():
       
   199                 if child.state == 'expanded':
       
   200                     iconname = "minusnode"
       
   201                     callback = child.collapse
       
   202                 else:
       
   203                     iconname = "plusnode"
       
   204                     callback = child.expand
       
   205                 image = self.geticonimage(iconname)
       
   206                 id = self.canvas.create_image(x+9, cylast+7, image=image)
       
   207                 # XXX This leaks bindings until canvas is deleted:
       
   208                 self.canvas.tag_bind(id, "<1>", callback)
       
   209                 self.canvas.tag_bind(id, "<Double-1>", lambda x: None)
       
   210         id = self.canvas.create_line(x+9, y+10, x+9, cylast+7,
       
   211             ##stipple="gray50",     # XXX Seems broken in Tk 8.0.x
       
   212             fill="gray50")
       
   213         self.canvas.tag_lower(id) # XXX .lower(id) before Python 1.5.2
       
   214         return cy
       
   215 
       
   216     def drawicon(self):
       
   217         if self.selected:
       
   218             imagename = (self.item.GetSelectedIconName() or
       
   219                          self.item.GetIconName() or
       
   220                          "openfolder")
       
   221         else:
       
   222             imagename = self.item.GetIconName() or "folder"
       
   223         image = self.geticonimage(imagename)
       
   224         id = self.canvas.create_image(self.x, self.y, anchor="nw", image=image)
       
   225         self.image_id = id
       
   226         self.canvas.tag_bind(id, "<1>", self.select)
       
   227         self.canvas.tag_bind(id, "<Double-1>", self.flip)
       
   228 
       
   229     def drawtext(self):
       
   230         textx = self.x+20-1
       
   231         texty = self.y-1
       
   232         labeltext = self.item.GetLabelText()
       
   233         if labeltext:
       
   234             id = self.canvas.create_text(textx, texty, anchor="nw",
       
   235                                          text=labeltext)
       
   236             self.canvas.tag_bind(id, "<1>", self.select)
       
   237             self.canvas.tag_bind(id, "<Double-1>", self.flip)
       
   238             x0, y0, x1, y1 = self.canvas.bbox(id)
       
   239             textx = max(x1, 200) + 10
       
   240         text = self.item.GetText() or "<no text>"
       
   241         try:
       
   242             self.entry
       
   243         except AttributeError:
       
   244             pass
       
   245         else:
       
   246             self.edit_finish()
       
   247         try:
       
   248             label = self.label
       
   249         except AttributeError:
       
   250             # padding carefully selected (on Windows) to match Entry widget:
       
   251             self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
       
   252         theme = idleConf.GetOption('main','Theme','name')
       
   253         if self.selected:
       
   254             self.label.configure(idleConf.GetHighlight(theme, 'hilite'))
       
   255         else:
       
   256             self.label.configure(idleConf.GetHighlight(theme, 'normal'))
       
   257         id = self.canvas.create_window(textx, texty,
       
   258                                        anchor="nw", window=self.label)
       
   259         self.label.bind("<1>", self.select_or_edit)
       
   260         self.label.bind("<Double-1>", self.flip)
       
   261         self.text_id = id
       
   262 
       
   263     def select_or_edit(self, event=None):
       
   264         if self.selected and self.item.IsEditable():
       
   265             self.edit(event)
       
   266         else:
       
   267             self.select(event)
       
   268 
       
   269     def edit(self, event=None):
       
   270         self.entry = Entry(self.label, bd=0, highlightthickness=1, width=0)
       
   271         self.entry.insert(0, self.label['text'])
       
   272         self.entry.selection_range(0, END)
       
   273         self.entry.pack(ipadx=5)
       
   274         self.entry.focus_set()
       
   275         self.entry.bind("<Return>", self.edit_finish)
       
   276         self.entry.bind("<Escape>", self.edit_cancel)
       
   277 
       
   278     def edit_finish(self, event=None):
       
   279         try:
       
   280             entry = self.entry
       
   281             del self.entry
       
   282         except AttributeError:
       
   283             return
       
   284         text = entry.get()
       
   285         entry.destroy()
       
   286         if text and text != self.item.GetText():
       
   287             self.item.SetText(text)
       
   288         text = self.item.GetText()
       
   289         self.label['text'] = text
       
   290         self.drawtext()
       
   291         self.canvas.focus_set()
       
   292 
       
   293     def edit_cancel(self, event=None):
       
   294         try:
       
   295             entry = self.entry
       
   296             del self.entry
       
   297         except AttributeError:
       
   298             return
       
   299         entry.destroy()
       
   300         self.drawtext()
       
   301         self.canvas.focus_set()
       
   302 
       
   303 
       
   304 class TreeItem:
       
   305 
       
   306     """Abstract class representing tree items.
       
   307 
       
   308     Methods should typically be overridden, otherwise a default action
       
   309     is used.
       
   310 
       
   311     """
       
   312 
       
   313     def __init__(self):
       
   314         """Constructor.  Do whatever you need to do."""
       
   315 
       
   316     def GetText(self):
       
   317         """Return text string to display."""
       
   318 
       
   319     def GetLabelText(self):
       
   320         """Return label text string to display in front of text (if any)."""
       
   321 
       
   322     expandable = None
       
   323 
       
   324     def _IsExpandable(self):
       
   325         """Do not override!  Called by TreeNode."""
       
   326         if self.expandable is None:
       
   327             self.expandable = self.IsExpandable()
       
   328         return self.expandable
       
   329 
       
   330     def IsExpandable(self):
       
   331         """Return whether there are subitems."""
       
   332         return 1
       
   333 
       
   334     def _GetSubList(self):
       
   335         """Do not override!  Called by TreeNode."""
       
   336         if not self.IsExpandable():
       
   337             return []
       
   338         sublist = self.GetSubList()
       
   339         if not sublist:
       
   340             self.expandable = 0
       
   341         return sublist
       
   342 
       
   343     def IsEditable(self):
       
   344         """Return whether the item's text may be edited."""
       
   345 
       
   346     def SetText(self, text):
       
   347         """Change the item's text (if it is editable)."""
       
   348 
       
   349     def GetIconName(self):
       
   350         """Return name of icon to be displayed normally."""
       
   351 
       
   352     def GetSelectedIconName(self):
       
   353         """Return name of icon to be displayed when selected."""
       
   354 
       
   355     def GetSubList(self):
       
   356         """Return list of items forming sublist."""
       
   357 
       
   358     def OnDoubleClick(self):
       
   359         """Called on a double-click on the item."""
       
   360 
       
   361 
       
   362 # Example application
       
   363 
       
   364 class FileTreeItem(TreeItem):
       
   365 
       
   366     """Example TreeItem subclass -- browse the file system."""
       
   367 
       
   368     def __init__(self, path):
       
   369         self.path = path
       
   370 
       
   371     def GetText(self):
       
   372         return os.path.basename(self.path) or self.path
       
   373 
       
   374     def IsEditable(self):
       
   375         return os.path.basename(self.path) != ""
       
   376 
       
   377     def SetText(self, text):
       
   378         newpath = os.path.dirname(self.path)
       
   379         newpath = os.path.join(newpath, text)
       
   380         if os.path.dirname(newpath) != os.path.dirname(self.path):
       
   381             return
       
   382         try:
       
   383             os.rename(self.path, newpath)
       
   384             self.path = newpath
       
   385         except os.error:
       
   386             pass
       
   387 
       
   388     def GetIconName(self):
       
   389         if not self.IsExpandable():
       
   390             return "python" # XXX wish there was a "file" icon
       
   391 
       
   392     def IsExpandable(self):
       
   393         return os.path.isdir(self.path)
       
   394 
       
   395     def GetSubList(self):
       
   396         try:
       
   397             names = os.listdir(self.path)
       
   398         except os.error:
       
   399             return []
       
   400         names.sort(lambda a, b: cmp(os.path.normcase(a), os.path.normcase(b)))
       
   401         sublist = []
       
   402         for name in names:
       
   403             item = FileTreeItem(os.path.join(self.path, name))
       
   404             sublist.append(item)
       
   405         return sublist
       
   406 
       
   407 
       
   408 # A canvas widget with scroll bars and some useful bindings
       
   409 
       
   410 class ScrolledCanvas:
       
   411     def __init__(self, master, **opts):
       
   412         if not opts.has_key('yscrollincrement'):
       
   413             opts['yscrollincrement'] = 17
       
   414         self.master = master
       
   415         self.frame = Frame(master)
       
   416         self.frame.rowconfigure(0, weight=1)
       
   417         self.frame.columnconfigure(0, weight=1)
       
   418         self.canvas = Canvas(self.frame, **opts)
       
   419         self.canvas.grid(row=0, column=0, sticky="nsew")
       
   420         self.vbar = Scrollbar(self.frame, name="vbar")
       
   421         self.vbar.grid(row=0, column=1, sticky="nse")
       
   422         self.hbar = Scrollbar(self.frame, name="hbar", orient="horizontal")
       
   423         self.hbar.grid(row=1, column=0, sticky="ews")
       
   424         self.canvas['yscrollcommand'] = self.vbar.set
       
   425         self.vbar['command'] = self.canvas.yview
       
   426         self.canvas['xscrollcommand'] = self.hbar.set
       
   427         self.hbar['command'] = self.canvas.xview
       
   428         self.canvas.bind("<Key-Prior>", self.page_up)
       
   429         self.canvas.bind("<Key-Next>", self.page_down)
       
   430         self.canvas.bind("<Key-Up>", self.unit_up)
       
   431         self.canvas.bind("<Key-Down>", self.unit_down)
       
   432         #if isinstance(master, Toplevel) or isinstance(master, Tk):
       
   433         self.canvas.bind("<Alt-Key-2>", self.zoom_height)
       
   434         self.canvas.focus_set()
       
   435     def page_up(self, event):
       
   436         self.canvas.yview_scroll(-1, "page")
       
   437         return "break"
       
   438     def page_down(self, event):
       
   439         self.canvas.yview_scroll(1, "page")
       
   440         return "break"
       
   441     def unit_up(self, event):
       
   442         self.canvas.yview_scroll(-1, "unit")
       
   443         return "break"
       
   444     def unit_down(self, event):
       
   445         self.canvas.yview_scroll(1, "unit")
       
   446         return "break"
       
   447     def zoom_height(self, event):
       
   448         ZoomHeight.zoom_height(self.master)
       
   449         return "break"
       
   450 
       
   451 
       
   452 # Testing functions
       
   453 
       
   454 def test():
       
   455     import PyShell
       
   456     root = Toplevel(PyShell.root)
       
   457     root.configure(bd=0, bg="yellow")
       
   458     root.focus_set()
       
   459     sc = ScrolledCanvas(root, bg="white", highlightthickness=0, takefocus=1)
       
   460     sc.frame.pack(expand=1, fill="both")
       
   461     item = FileTreeItem("C:/windows/desktop")
       
   462     node = TreeNode(sc.canvas, None, item)
       
   463     node.expand()
       
   464 
       
   465 def test2():
       
   466     # test w/o scrolling canvas
       
   467     root = Tk()
       
   468     root.configure(bd=0)
       
   469     canvas = Canvas(root, bg="white", highlightthickness=0)
       
   470     canvas.pack(expand=1, fill="both")
       
   471     item = FileTreeItem(os.curdir)
       
   472     node = TreeNode(canvas, None, item)
       
   473     node.update()
       
   474     canvas.focus_set()
       
   475 
       
   476 if __name__ == '__main__':
       
   477     test()