blob: 9af626cca9c92c5b006f39a077a3488947ddbe12 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import os
2import bdb
Georg Brandl14fc4272008-05-17 18:39:55 +00003from tkinter import *
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04004from idlelib.windows import ListedToplevel
5from idlelib.scrolledlist import ScrolledList
6from idlelib import macosx
David Scherer7aced172000-08-15 01:13:23 +00007
8
Chui Tey5d2af632002-05-26 13:36:41 +00009class Idb(bdb.Bdb):
10
11 def __init__(self, gui):
12 self.gui = gui
13 bdb.Bdb.__init__(self)
14
15 def user_line(self, frame):
Kurt B. Kaiser57bfe5d2003-05-10 00:09:52 +000016 if self.in_rpc_code(frame):
17 self.set_step()
18 return
Chui Tey5d2af632002-05-26 13:36:41 +000019 message = self.__frame2message(frame)
Terry Jan Reedy6a904c12015-11-20 19:36:43 -050020 try:
21 self.gui.interaction(message, frame)
Terry Jan Reedye1b02e02015-11-21 00:05:03 -050022 except TclError: # When closing debugger window with [x] in 3.x
Terry Jan Reedy6a904c12015-11-20 19:36:43 -050023 pass
Chui Tey5d2af632002-05-26 13:36:41 +000024
25 def user_exception(self, frame, info):
Kurt B. Kaiser57bfe5d2003-05-10 00:09:52 +000026 if self.in_rpc_code(frame):
27 self.set_step()
28 return
Chui Tey5d2af632002-05-26 13:36:41 +000029 message = self.__frame2message(frame)
30 self.gui.interaction(message, frame, info)
31
Kurt B. Kaiser57bfe5d2003-05-10 00:09:52 +000032 def in_rpc_code(self, frame):
33 if frame.f_code.co_filename.count('rpc.py'):
34 return True
35 else:
36 prev_frame = frame.f_back
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040037 prev_name = prev_frame.f_code.co_filename
38 if 'idlelib' in prev_name and 'debugger' in prev_name:
39 # catch both idlelib/debugger.py and idlelib/debugger_r.py
40 # on both posix and windows
Kurt B. Kaiser57bfe5d2003-05-10 00:09:52 +000041 return False
42 return self.in_rpc_code(prev_frame)
43
Chui Tey5d2af632002-05-26 13:36:41 +000044 def __frame2message(self, frame):
45 code = frame.f_code
46 filename = code.co_filename
47 lineno = frame.f_lineno
48 basename = os.path.basename(filename)
49 message = "%s:%s" % (basename, lineno)
50 if code.co_name != "?":
51 message = "%s: %s()" % (message, code.co_name)
52 return message
53
54
55class Debugger:
David Scherer7aced172000-08-15 01:13:23 +000056
David Scherer7aced172000-08-15 01:13:23 +000057 vstack = vsource = vlocals = vglobals = None
58
Chui Tey5d2af632002-05-26 13:36:41 +000059 def __init__(self, pyshell, idb=None):
60 if idb is None:
61 idb = Idb(self)
David Scherer7aced172000-08-15 01:13:23 +000062 self.pyshell = pyshell
Chui Tey5d2af632002-05-26 13:36:41 +000063 self.idb = idb
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000064 self.frame = None
David Scherer7aced172000-08-15 01:13:23 +000065 self.make_gui()
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +000066 self.interacting = 0
Terry Jan Reedy6a904c12015-11-20 19:36:43 -050067 self.nesting_level = 0
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000068
Chui Tey5d2af632002-05-26 13:36:41 +000069 def run(self, *args):
Terry Jan Reedy6a904c12015-11-20 19:36:43 -050070 # Deal with the scenario where we've already got a program running
71 # in the debugger and we want to start another. If that is the case,
72 # our second 'run' was invoked from an event dispatched not from
73 # the main event loop, but from the nested event loop in 'interaction'
74 # below. So our stack looks something like this:
75 # outer main event loop
76 # run()
77 # <running program with traces>
78 # callback to debugger's interaction()
79 # nested event loop
80 # run() for second command
81 #
82 # This kind of nesting of event loops causes all kinds of problems
83 # (see e.g. issue #24455) especially when dealing with running as a
84 # subprocess, where there's all kinds of extra stuff happening in
85 # there - insert a traceback.print_stack() to check it out.
86 #
87 # By this point, we've already called restart_subprocess() in
88 # ScriptBinding. However, we also need to unwind the stack back to
89 # that outer event loop. To accomplish this, we:
90 # - return immediately from the nested run()
91 # - abort_loop ensures the nested event loop will terminate
92 # - the debugger's interaction routine completes normally
93 # - the restart_subprocess() will have taken care of stopping
94 # the running program, which will also let the outer run complete
95 #
96 # That leaves us back at the outer main event loop, at which point our
97 # after event can fire, and we'll come back to this routine with a
98 # clean stack.
99 if self.nesting_level > 0:
100 self.abort_loop()
101 self.root.after(100, lambda: self.run(*args))
102 return
Chui Tey5d2af632002-05-26 13:36:41 +0000103 try:
104 self.interacting = 1
105 return self.idb.run(*args)
106 finally:
107 self.interacting = 0
David Scherer7aced172000-08-15 01:13:23 +0000108
109 def close(self, event=None):
Terry Jan Reedye1b02e02015-11-21 00:05:03 -0500110 try:
111 self.quit()
112 except Exception:
113 pass
David Scherer7aced172000-08-15 01:13:23 +0000114 if self.interacting:
115 self.top.bell()
116 return
117 if self.stackviewer:
118 self.stackviewer.close(); self.stackviewer = None
Kurt B. Kaiserf8096fb2002-06-25 03:28:38 +0000119 # Clean up pyshell if user clicked debugger control close widget.
120 # (Causes a harmless extra cycle through close_debugger() if user
121 # toggled debugger from pyshell Debug menu)
David Scherer7aced172000-08-15 01:13:23 +0000122 self.pyshell.close_debugger()
Kurt B. Kaiserf8096fb2002-06-25 03:28:38 +0000123 # Now close the debugger control window....
David Scherer7aced172000-08-15 01:13:23 +0000124 self.top.destroy()
125
David Scherer7aced172000-08-15 01:13:23 +0000126 def make_gui(self):
127 pyshell = self.pyshell
128 self.flist = pyshell.flist
129 self.root = root = pyshell.root
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000130 self.top = top = ListedToplevel(root)
David Scherer7aced172000-08-15 01:13:23 +0000131 self.top.wm_title("Debug Control")
132 self.top.wm_iconname("Debug")
133 top.wm_protocol("WM_DELETE_WINDOW", self.close)
134 self.top.bind("<Escape>", self.close)
135 #
136 self.bframe = bframe = Frame(top)
137 self.bframe.pack(anchor="w")
138 self.buttons = bl = []
139 #
140 self.bcont = b = Button(bframe, text="Go", command=self.cont)
141 bl.append(b)
142 self.bstep = b = Button(bframe, text="Step", command=self.step)
143 bl.append(b)
144 self.bnext = b = Button(bframe, text="Over", command=self.next)
145 bl.append(b)
146 self.bret = b = Button(bframe, text="Out", command=self.ret)
147 bl.append(b)
148 self.bret = b = Button(bframe, text="Quit", command=self.quit)
149 bl.append(b)
150 #
151 for b in bl:
152 b.configure(state="disabled")
153 b.pack(side="left")
154 #
155 self.cframe = cframe = Frame(bframe)
156 self.cframe.pack(side="left")
157 #
158 if not self.vstack:
159 self.__class__.vstack = BooleanVar(top)
160 self.vstack.set(1)
161 self.bstack = Checkbutton(cframe,
162 text="Stack", command=self.show_stack, variable=self.vstack)
163 self.bstack.grid(row=0, column=0)
164 if not self.vsource:
165 self.__class__.vsource = BooleanVar(top)
David Scherer7aced172000-08-15 01:13:23 +0000166 self.bsource = Checkbutton(cframe,
167 text="Source", command=self.show_source, variable=self.vsource)
168 self.bsource.grid(row=0, column=1)
169 if not self.vlocals:
170 self.__class__.vlocals = BooleanVar(top)
171 self.vlocals.set(1)
172 self.blocals = Checkbutton(cframe,
173 text="Locals", command=self.show_locals, variable=self.vlocals)
174 self.blocals.grid(row=1, column=0)
175 if not self.vglobals:
176 self.__class__.vglobals = BooleanVar(top)
David Scherer7aced172000-08-15 01:13:23 +0000177 self.bglobals = Checkbutton(cframe,
178 text="Globals", command=self.show_globals, variable=self.vglobals)
179 self.bglobals.grid(row=1, column=1)
180 #
181 self.status = Label(top, anchor="w")
182 self.status.pack(anchor="w")
183 self.error = Label(top, anchor="w")
184 self.error.pack(anchor="w", fill="x")
185 self.errorbg = self.error.cget("background")
186 #
187 self.fstack = Frame(top, height=1)
188 self.fstack.pack(expand=1, fill="both")
189 self.flocals = Frame(top)
190 self.flocals.pack(expand=1, fill="both")
191 self.fglobals = Frame(top, height=1)
192 self.fglobals.pack(expand=1, fill="both")
193 #
194 if self.vstack.get():
195 self.show_stack()
196 if self.vlocals.get():
197 self.show_locals()
198 if self.vglobals.get():
199 self.show_globals()
200
Chui Tey5d2af632002-05-26 13:36:41 +0000201 def interaction(self, message, frame, info=None):
David Scherer7aced172000-08-15 01:13:23 +0000202 self.frame = frame
David Scherer7aced172000-08-15 01:13:23 +0000203 self.status.configure(text=message)
204 #
205 if info:
206 type, value, tb = info
207 try:
208 m1 = type.__name__
209 except AttributeError:
210 m1 = "%s" % str(type)
211 if value is not None:
212 try:
213 m1 = "%s: %s" % (m1, str(value))
214 except:
215 pass
216 bg = "yellow"
217 else:
218 m1 = ""
219 tb = None
220 bg = self.errorbg
221 self.error.configure(text=m1, background=bg)
222 #
223 sv = self.stackviewer
224 if sv:
Chui Tey5d2af632002-05-26 13:36:41 +0000225 stack, i = self.idb.get_stack(self.frame, tb)
David Scherer7aced172000-08-15 01:13:23 +0000226 sv.load_stack(stack, i)
227 #
228 self.show_variables(1)
229 #
230 if self.vsource.get():
231 self.sync_source_line()
232 #
233 for b in self.buttons:
234 b.configure(state="normal")
235 #
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000236 self.top.wakeup()
Terry Jan Reedy6a904c12015-11-20 19:36:43 -0500237 # Nested main loop: Tkinter's main loop is not reentrant, so use
238 # Tcl's vwait facility, which reenters the event loop until an
239 # event handler sets the variable we're waiting on
240 self.nesting_level += 1
241 self.root.tk.call('vwait', '::idledebugwait')
242 self.nesting_level -= 1
David Scherer7aced172000-08-15 01:13:23 +0000243 #
244 for b in self.buttons:
245 b.configure(state="disabled")
246 self.status.configure(text="")
247 self.error.configure(text="", background=self.errorbg)
248 self.frame = None
249
250 def sync_source_line(self):
251 frame = self.frame
252 if not frame:
253 return
Chui Tey5d2af632002-05-26 13:36:41 +0000254 filename, lineno = self.__frame2fileline(frame)
255 if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
256 self.flist.gotofileline(filename, lineno)
257
258 def __frame2fileline(self, frame):
David Scherer7aced172000-08-15 01:13:23 +0000259 code = frame.f_code
Chui Tey5d2af632002-05-26 13:36:41 +0000260 filename = code.co_filename
David Scherer7aced172000-08-15 01:13:23 +0000261 lineno = frame.f_lineno
Chui Tey5d2af632002-05-26 13:36:41 +0000262 return filename, lineno
David Scherer7aced172000-08-15 01:13:23 +0000263
264 def cont(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000265 self.idb.set_continue()
Terry Jan Reedy6a904c12015-11-20 19:36:43 -0500266 self.abort_loop()
David Scherer7aced172000-08-15 01:13:23 +0000267
268 def step(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000269 self.idb.set_step()
Terry Jan Reedy6a904c12015-11-20 19:36:43 -0500270 self.abort_loop()
David Scherer7aced172000-08-15 01:13:23 +0000271
272 def next(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000273 self.idb.set_next(self.frame)
Terry Jan Reedy6a904c12015-11-20 19:36:43 -0500274 self.abort_loop()
David Scherer7aced172000-08-15 01:13:23 +0000275
276 def ret(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000277 self.idb.set_return(self.frame)
Terry Jan Reedy6a904c12015-11-20 19:36:43 -0500278 self.abort_loop()
David Scherer7aced172000-08-15 01:13:23 +0000279
280 def quit(self):
Chui Tey5d2af632002-05-26 13:36:41 +0000281 self.idb.set_quit()
Terry Jan Reedy6a904c12015-11-20 19:36:43 -0500282 self.abort_loop()
283
284 def abort_loop(self):
285 self.root.tk.call('set', '::idledebugwait', '1')
David Scherer7aced172000-08-15 01:13:23 +0000286
287 stackviewer = None
288
289 def show_stack(self):
290 if not self.stackviewer and self.vstack.get():
unknowned813bf2002-07-05 22:05:24 +0000291 self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
David Scherer7aced172000-08-15 01:13:23 +0000292 if self.frame:
Chui Tey5d2af632002-05-26 13:36:41 +0000293 stack, i = self.idb.get_stack(self.frame, None)
David Scherer7aced172000-08-15 01:13:23 +0000294 sv.load_stack(stack, i)
295 else:
296 sv = self.stackviewer
297 if sv and not self.vstack.get():
298 self.stackviewer = None
299 sv.close()
300 self.fstack['height'] = 1
301
302 def show_source(self):
303 if self.vsource.get():
304 self.sync_source_line()
305
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000306 def show_frame(self, stackitem):
Terry Jan Reedy1e402952014-01-28 23:13:45 -0500307 self.frame = stackitem[0] # lineno is stackitem[1]
David Scherer7aced172000-08-15 01:13:23 +0000308 self.show_variables()
309
310 localsviewer = None
311 globalsviewer = None
312
313 def show_locals(self):
314 lv = self.localsviewer
315 if self.vlocals.get():
316 if not lv:
unknowned813bf2002-07-05 22:05:24 +0000317 self.localsviewer = NamespaceViewer(self.flocals, "Locals")
David Scherer7aced172000-08-15 01:13:23 +0000318 else:
319 if lv:
320 self.localsviewer = None
321 lv.close()
322 self.flocals['height'] = 1
323 self.show_variables()
324
325 def show_globals(self):
326 gv = self.globalsviewer
327 if self.vglobals.get():
328 if not gv:
unknowned813bf2002-07-05 22:05:24 +0000329 self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")
David Scherer7aced172000-08-15 01:13:23 +0000330 else:
331 if gv:
332 self.globalsviewer = None
333 gv.close()
334 self.fglobals['height'] = 1
335 self.show_variables()
336
337 def show_variables(self, force=0):
338 lv = self.localsviewer
339 gv = self.globalsviewer
340 frame = self.frame
341 if not frame:
342 ldict = gdict = None
343 else:
344 ldict = frame.f_locals
345 gdict = frame.f_globals
346 if lv and gv and ldict is gdict:
347 ldict = None
348 if lv:
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000349 lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)
David Scherer7aced172000-08-15 01:13:23 +0000350 if gv:
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000351 gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
David Scherer7aced172000-08-15 01:13:23 +0000352
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000353 def set_breakpoint_here(self, filename, lineno):
Kurt B. Kaiser491427d2002-12-02 04:41:29 +0000354 self.idb.set_break(filename, lineno)
David Scherer7aced172000-08-15 01:13:23 +0000355
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000356 def clear_breakpoint_here(self, filename, lineno):
Kurt B. Kaiser491427d2002-12-02 04:41:29 +0000357 self.idb.clear_break(filename, lineno)
Kurt B. Kaiser669f4c32002-06-20 04:01:47 +0000358
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000359 def clear_file_breaks(self, filename):
Kurt B. Kaiser491427d2002-12-02 04:41:29 +0000360 self.idb.clear_all_file_breaks(filename)
unknowned813bf2002-07-05 22:05:24 +0000361
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000362 def load_breakpoints(self):
363 "Load PyShellEditorWindow breakpoints into subprocess debugger"
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000364 for editwin in self.pyshell.flist.inversedict:
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000365 filename = editwin.io.filename
366 try:
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000367 for lineno in editwin.breakpoints:
Kurt B. Kaiser45186c42002-10-23 04:48:08 +0000368 self.set_breakpoint_here(filename, lineno)
369 except AttributeError:
370 continue
unknowned813bf2002-07-05 22:05:24 +0000371
372class StackViewer(ScrolledList):
373
374 def __init__(self, master, flist, gui):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400375 if macosx.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000376 # At least on with the stock AquaTk version on OSX 10.4 you'll
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +0300377 # get a shaking GUI that eventually kills IDLE if the width
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000378 # argument is specified.
379 ScrolledList.__init__(self, master)
380 else:
381 ScrolledList.__init__(self, master, width=80)
unknowned813bf2002-07-05 22:05:24 +0000382 self.flist = flist
383 self.gui = gui
384 self.stack = []
385
386 def load_stack(self, stack, index=None):
387 self.stack = stack
388 self.clear()
389 for i in range(len(stack)):
390 frame, lineno = stack[i]
391 try:
392 modname = frame.f_globals["__name__"]
393 except:
394 modname = "?"
395 code = frame.f_code
396 filename = code.co_filename
397 funcname = code.co_name
398 import linecache
399 sourceline = linecache.getline(filename, lineno)
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000400 sourceline = sourceline.strip()
unknowned813bf2002-07-05 22:05:24 +0000401 if funcname in ("?", "", None):
402 item = "%s, line %d: %s" % (modname, lineno, sourceline)
403 else:
404 item = "%s.%s(), line %d: %s" % (modname, funcname,
405 lineno, sourceline)
406 if i == index:
407 item = "> " + item
408 self.append(item)
409 if index is not None:
410 self.select(index)
411
412 def popup_event(self, event):
413 "override base method"
414 if self.stack:
415 return ScrolledList.popup_event(self, event)
416
417 def fill_menu(self):
418 "override base method"
419 menu = self.menu
420 menu.add_command(label="Go to source line",
421 command=self.goto_source_line)
422 menu.add_command(label="Show stack frame",
423 command=self.show_stack_frame)
424
425 def on_select(self, index):
426 "override base method"
427 if 0 <= index < len(self.stack):
428 self.gui.show_frame(self.stack[index])
429
430 def on_double(self, index):
431 "override base method"
432 self.show_source(index)
433
434 def goto_source_line(self):
435 index = self.listbox.index("active")
436 self.show_source(index)
437
438 def show_stack_frame(self):
439 index = self.listbox.index("active")
440 if 0 <= index < len(self.stack):
441 self.gui.show_frame(self.stack[index])
442
443 def show_source(self, index):
444 if not (0 <= index < len(self.stack)):
445 return
446 frame, lineno = self.stack[index]
447 code = frame.f_code
448 filename = code.co_filename
449 if os.path.isfile(filename):
450 edit = self.flist.open(filename)
451 if edit:
452 edit.gotoline(lineno)
453
454
455class NamespaceViewer:
456
457 def __init__(self, master, title, dict=None):
458 width = 0
459 height = 40
460 if dict:
461 height = 20*len(dict) # XXX 20 == observed height of Entry widget
462 self.master = master
463 self.title = title
Alexandre Vassalotti1f2ba4b2008-05-16 07:12:44 +0000464 import reprlib
465 self.repr = reprlib.Repr()
unknowned813bf2002-07-05 22:05:24 +0000466 self.repr.maxstring = 60
467 self.repr.maxother = 60
468 self.frame = frame = Frame(master)
469 self.frame.pack(expand=1, fill="both")
470 self.label = Label(frame, text=title, borderwidth=2, relief="groove")
471 self.label.pack(fill="x")
472 self.vbar = vbar = Scrollbar(frame, name="vbar")
473 vbar.pack(side="right", fill="y")
474 self.canvas = canvas = Canvas(frame,
475 height=min(300, max(40, height)),
476 scrollregion=(0, 0, width, height))
477 canvas.pack(side="left", fill="both", expand=1)
478 vbar["command"] = canvas.yview
479 canvas["yscrollcommand"] = vbar.set
480 self.subframe = subframe = Frame(canvas)
481 self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
482 self.load_dict(dict)
483
484 dict = -1
485
486 def load_dict(self, dict, force=0, rpc_client=None):
487 if dict is self.dict and not force:
488 return
489 subframe = self.subframe
490 frame = self.frame
Guido van Rossum4c269c52007-08-13 17:39:20 +0000491 for c in list(subframe.children.values()):
unknowned813bf2002-07-05 22:05:24 +0000492 c.destroy()
493 self.dict = None
494 if not dict:
495 l = Label(subframe, text="None")
496 l.grid(row=0, column=0)
497 else:
Kurt B. Kaisercf3c42172007-08-29 18:44:24 +0000498 #names = sorted(dict)
499 ###
500 # Because of (temporary) limitations on the dict_keys type (not yet
501 # public or pickleable), have the subprocess to send a list of
502 # keys, not a dict_keys object. sorted() will take a dict_keys
503 # (no subprocess) or a list.
504 #
505 # There is also an obscure bug in sorted(dict) where the
506 # interpreter gets into a loop requesting non-existing dict[0],
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400507 # dict[1], dict[2], etc from the debugger_r.DictProxy.
Kurt B. Kaisercf3c42172007-08-29 18:44:24 +0000508 ###
509 keys_list = dict.keys()
510 names = sorted(keys_list)
511 ###
unknowned813bf2002-07-05 22:05:24 +0000512 row = 0
513 for name in names:
514 value = dict[name]
515 svalue = self.repr.repr(value) # repr(value)
516 # Strip extra quotes caused by calling repr on the (already)
517 # repr'd value sent across the RPC interface:
518 if rpc_client:
519 svalue = svalue[1:-1]
520 l = Label(subframe, text=name)
521 l.grid(row=row, column=0, sticky="nw")
unknowned813bf2002-07-05 22:05:24 +0000522 l = Entry(subframe, width=0, borderwidth=0)
523 l.insert(0, svalue)
unknowned813bf2002-07-05 22:05:24 +0000524 l.grid(row=row, column=1, sticky="nw")
525 row = row+1
526 self.dict = dict
527 # XXX Could we use a <Configure> callback for the following?
528 subframe.update_idletasks() # Alas!
529 width = subframe.winfo_reqwidth()
530 height = subframe.winfo_reqheight()
531 canvas = self.canvas
532 self.canvas["scrollregion"] = (0, 0, width, height)
533 if height > 300:
534 canvas["height"] = 300
535 frame.pack(expand=1)
536 else:
537 canvas["height"] = height
538 frame.pack(expand=0)
539
540 def close(self):
541 self.frame.destroy()