ยปCore Development>Code coverage>Lib/idlelib/TreeWidget.py

Python code coverage for Lib/idlelib/TreeWidget.py

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