Require implementations for warnings.showwarning() support the 'line' argument.
[python.git] / Tools / webchecker / tktools.py
blob3a68f9ac639fcfd7e05eed55b1a029fc1ca62ed7
1 """Assorted Tk-related subroutines used in Grail."""
4 from types import *
5 from Tkinter import *
7 def _clear_entry_widget(event):
8 try:
9 widget = event.widget
10 widget.delete(0, INSERT)
11 except: pass
12 def install_keybindings(root):
13 root.bind_class('Entry', '<Control-u>', _clear_entry_widget)
16 def make_toplevel(master, title=None, class_=None):
17 """Create a Toplevel widget.
19 This is a shortcut for a Toplevel() instantiation plus calls to
20 set the title and icon name of the widget.
22 """
24 if class_:
25 widget = Toplevel(master, class_=class_)
26 else:
27 widget = Toplevel(master)
28 if title:
29 widget.title(title)
30 widget.iconname(title)
31 return widget
33 def set_transient(widget, master, relx=0.5, rely=0.3, expose=1):
34 """Make an existing toplevel widget transient for a master.
36 The widget must exist but should not yet have been placed; in
37 other words, this should be called after creating all the
38 subwidget but before letting the user interact.
39 """
41 widget.withdraw() # Remain invisible while we figure out the geometry
42 widget.transient(master)
43 widget.update_idletasks() # Actualize geometry information
44 if master.winfo_ismapped():
45 m_width = master.winfo_width()
46 m_height = master.winfo_height()
47 m_x = master.winfo_rootx()
48 m_y = master.winfo_rooty()
49 else:
50 m_width = master.winfo_screenwidth()
51 m_height = master.winfo_screenheight()
52 m_x = m_y = 0
53 w_width = widget.winfo_reqwidth()
54 w_height = widget.winfo_reqheight()
55 x = m_x + (m_width - w_width) * relx
56 y = m_y + (m_height - w_height) * rely
57 widget.geometry("+%d+%d" % (x, y))
58 if expose:
59 widget.deiconify() # Become visible at the desired location
60 return widget
63 def make_scrollbars(parent, hbar, vbar, pack=1, class_=None, name=None,
64 takefocus=0):
66 """Subroutine to create a frame with scrollbars.
68 This is used by make_text_box and similar routines.
70 Note: the caller is responsible for setting the x/y scroll command
71 properties (e.g. by calling set_scroll_commands()).
73 Return a tuple containing the hbar, the vbar, and the frame, where
74 hbar and vbar are None if not requested.
76 """
77 if class_:
78 if name: frame = Frame(parent, class_=class_, name=name)
79 else: frame = Frame(parent, class_=class_)
80 else:
81 if name: frame = Frame(parent, name=name)
82 else: frame = Frame(parent)
84 if pack:
85 frame.pack(fill=BOTH, expand=1)
87 corner = None
88 if vbar:
89 if not hbar:
90 vbar = Scrollbar(frame, takefocus=takefocus)
91 vbar.pack(fill=Y, side=RIGHT)
92 else:
93 vbarframe = Frame(frame, borderwidth=0)
94 vbarframe.pack(fill=Y, side=RIGHT)
95 vbar = Scrollbar(frame, name="vbar", takefocus=takefocus)
96 vbar.pack(in_=vbarframe, expand=1, fill=Y, side=TOP)
97 sbwidth = vbar.winfo_reqwidth()
98 corner = Frame(vbarframe, width=sbwidth, height=sbwidth)
99 corner.propagate(0)
100 corner.pack(side=BOTTOM)
101 else:
102 vbar = None
104 if hbar:
105 hbar = Scrollbar(frame, orient=HORIZONTAL, name="hbar",
106 takefocus=takefocus)
107 hbar.pack(fill=X, side=BOTTOM)
108 else:
109 hbar = None
111 return hbar, vbar, frame
114 def set_scroll_commands(widget, hbar, vbar):
116 """Link a scrollable widget to its scroll bars.
118 The scroll bars may be empty.
122 if vbar:
123 widget['yscrollcommand'] = (vbar, 'set')
124 vbar['command'] = (widget, 'yview')
126 if hbar:
127 widget['xscrollcommand'] = (hbar, 'set')
128 hbar['command'] = (widget, 'xview')
130 widget.vbar = vbar
131 widget.hbar = hbar
134 def make_text_box(parent, width=0, height=0, hbar=0, vbar=1,
135 fill=BOTH, expand=1, wrap=WORD, pack=1,
136 class_=None, name=None, takefocus=None):
138 """Subroutine to create a text box.
140 Create:
141 - a both-ways filling and expanding frame, containing:
142 - a text widget on the left, and
143 - possibly a vertical scroll bar on the right.
144 - possibly a horizonta; scroll bar at the bottom.
146 Return the text widget and the frame widget.
149 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
150 class_=class_, name=name,
151 takefocus=takefocus)
153 widget = Text(frame, wrap=wrap, name="text")
154 if width: widget.config(width=width)
155 if height: widget.config(height=height)
156 widget.pack(expand=expand, fill=fill, side=LEFT)
158 set_scroll_commands(widget, hbar, vbar)
160 return widget, frame
163 def make_list_box(parent, width=0, height=0, hbar=0, vbar=1,
164 fill=BOTH, expand=1, pack=1, class_=None, name=None,
165 takefocus=None):
167 """Subroutine to create a list box.
169 Like make_text_box().
171 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
172 class_=class_, name=name,
173 takefocus=takefocus)
175 widget = Listbox(frame, name="listbox")
176 if width: widget.config(width=width)
177 if height: widget.config(height=height)
178 widget.pack(expand=expand, fill=fill, side=LEFT)
180 set_scroll_commands(widget, hbar, vbar)
182 return widget, frame
185 def make_canvas(parent, width=0, height=0, hbar=1, vbar=1,
186 fill=BOTH, expand=1, pack=1, class_=None, name=None,
187 takefocus=None):
189 """Subroutine to create a canvas.
191 Like make_text_box().
195 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
196 class_=class_, name=name,
197 takefocus=takefocus)
199 widget = Canvas(frame, scrollregion=(0, 0, width, height), name="canvas")
200 if width: widget.config(width=width)
201 if height: widget.config(height=height)
202 widget.pack(expand=expand, fill=fill, side=LEFT)
204 set_scroll_commands(widget, hbar, vbar)
206 return widget, frame
210 def make_form_entry(parent, label, borderwidth=None):
212 """Subroutine to create a form entry.
214 Create:
215 - a horizontally filling and expanding frame, containing:
216 - a label on the left, and
217 - a text entry on the right.
219 Return the entry widget and the frame widget.
223 frame = Frame(parent)
224 frame.pack(fill=X)
226 label = Label(frame, text=label)
227 label.pack(side=LEFT)
229 if borderwidth is None:
230 entry = Entry(frame, relief=SUNKEN)
231 else:
232 entry = Entry(frame, relief=SUNKEN, borderwidth=borderwidth)
233 entry.pack(side=LEFT, fill=X, expand=1)
235 return entry, frame
237 # This is a slightly modified version of the function above. This
238 # version does the proper alighnment of labels with their fields. It
239 # should probably eventually replace make_form_entry altogether.
241 # The one annoying bug is that the text entry field should be
242 # expandable while still aligning the colons. This doesn't work yet.
244 def make_labeled_form_entry(parent, label, entrywidth=20, entryheight=1,
245 labelwidth=0, borderwidth=None,
246 takefocus=None):
247 """Subroutine to create a form entry.
249 Create:
250 - a horizontally filling and expanding frame, containing:
251 - a label on the left, and
252 - a text entry on the right.
254 Return the entry widget and the frame widget.
256 if label and label[-1] != ':': label = label + ':'
258 frame = Frame(parent)
260 label = Label(frame, text=label, width=labelwidth, anchor=E)
261 label.pack(side=LEFT)
262 if entryheight == 1:
263 if borderwidth is None:
264 entry = Entry(frame, relief=SUNKEN, width=entrywidth)
265 else:
266 entry = Entry(frame, relief=SUNKEN, width=entrywidth,
267 borderwidth=borderwidth)
268 entry.pack(side=RIGHT, expand=1, fill=X)
269 frame.pack(fill=X)
270 else:
271 entry = make_text_box(frame, entrywidth, entryheight, 1, 1,
272 takefocus=takefocus)
273 frame.pack(fill=BOTH, expand=1)
275 return entry, frame, label
278 def make_double_frame(master=None, class_=None, name=None, relief=RAISED,
279 borderwidth=1):
280 """Create a pair of frames suitable for 'hosting' a dialog."""
281 if name:
282 if class_: frame = Frame(master, class_=class_, name=name)
283 else: frame = Frame(master, name=name)
284 else:
285 if class_: frame = Frame(master, class_=class_)
286 else: frame = Frame(master)
287 top = Frame(frame, name="topframe", relief=relief,
288 borderwidth=borderwidth)
289 bottom = Frame(frame, name="bottomframe")
290 bottom.pack(fill=X, padx='1m', pady='1m', side=BOTTOM)
291 top.pack(expand=1, fill=BOTH, padx='1m', pady='1m')
292 frame.pack(expand=1, fill=BOTH)
293 top = Frame(top)
294 top.pack(expand=1, fill=BOTH, padx='2m', pady='2m')
296 return frame, top, bottom
299 def make_group_frame(master, name=None, label=None, fill=Y,
300 side=None, expand=None, font=None):
301 """Create nested frames with a border and optional label.
303 The outer frame is only used to provide the decorative border, to
304 control packing, and to host the label. The inner frame is packed
305 to fill the outer frame and should be used as the parent of all
306 sub-widgets. Only the inner frame is returned.
309 font = font or "-*-helvetica-medium-r-normal-*-*-100-*-*-*-*-*-*"
310 outer = Frame(master, borderwidth=2, relief=GROOVE)
311 outer.pack(expand=expand, fill=fill, side=side)
312 if label:
313 Label(outer, text=label, font=font, anchor=W).pack(fill=X)
314 inner = Frame(master, borderwidth='1m', name=name)
315 inner.pack(expand=1, fill=BOTH, in_=outer)
316 inner.forget = outer.forget
317 return inner
320 def unify_button_widths(*buttons):
321 """Make buttons passed in all have the same width.
323 Works for labels and other widgets with the 'text' option.
326 wid = 0
327 for btn in buttons:
328 wid = max(wid, len(btn["text"]))
329 for btn in buttons:
330 btn["width"] = wid
333 def flatten(msg):
334 """Turn a list or tuple into a single string -- recursively."""
335 t = type(msg)
336 if t in (ListType, TupleType):
337 msg = ' '.join(map(flatten, msg))
338 elif t is ClassType:
339 msg = msg.__name__
340 else:
341 msg = str(msg)
342 return msg
345 def boolean(s):
346 """Test whether a string is a Tk boolean, without error checking."""
347 if s.lower() in ('', '0', 'no', 'off', 'false'): return 0
348 else: return 1
351 def test():
352 """Test make_text_box(), make_form_entry(), flatten(), boolean()."""
353 import sys
354 root = Tk()
355 entry, eframe = make_form_entry(root, 'Boolean:')
356 text, tframe = make_text_box(root)
357 def enter(event, entry=entry, text=text):
358 s = boolean(entry.get()) and '\nyes' or '\nno'
359 text.insert('end', s)
360 entry.bind('<Return>', enter)
361 entry.insert(END, flatten(sys.argv))
362 root.mainloop()
365 if __name__ == '__main__':
366 test()