Ditched '_find_SET()', since it was a no-value-added wrapper around
[python/dscho.git] / Tools / webchecker / tktools.py
blob0db4d490880e35a5b0e6caa4dde0e47d61b3e876
1 """Assorted Tk-related subroutines used in Grail."""
4 import string
5 from types import *
6 from Tkinter import *
8 def _clear_entry_widget(event):
9 try:
10 widget = event.widget
11 widget.delete(0, INSERT)
12 except: pass
13 def install_keybindings(root):
14 root.bind_class('Entry', '<Control-u>', _clear_entry_widget)
17 def make_toplevel(master, title=None, class_=None):
18 """Create a Toplevel widget.
20 This is a shortcut for a Toplevel() instantiation plus calls to
21 set the title and icon name of the widget.
23 """
25 if class_:
26 widget = Toplevel(master, class_=class_)
27 else:
28 widget = Toplevel(master)
29 if title:
30 widget.title(title)
31 widget.iconname(title)
32 return widget
34 def set_transient(widget, master, relx=0.5, rely=0.3, expose=1):
35 """Make an existing toplevel widget transient for a master.
37 The widget must exist but should not yet have been placed; in
38 other words, this should be called after creating all the
39 subwidget but before letting the user interact.
40 """
42 widget.withdraw() # Remain invisible while we figure out the geometry
43 widget.transient(master)
44 widget.update_idletasks() # Actualize geometry information
45 if master.winfo_ismapped():
46 m_width = master.winfo_width()
47 m_height = master.winfo_height()
48 m_x = master.winfo_rootx()
49 m_y = master.winfo_rooty()
50 else:
51 m_width = master.winfo_screenwidth()
52 m_height = master.winfo_screenheight()
53 m_x = m_y = 0
54 w_width = widget.winfo_reqwidth()
55 w_height = widget.winfo_reqheight()
56 x = m_x + (m_width - w_width) * relx
57 y = m_y + (m_height - w_height) * rely
58 widget.geometry("+%d+%d" % (x, y))
59 if expose:
60 widget.deiconify() # Become visible at the desired location
61 return widget
64 def make_scrollbars(parent, hbar, vbar, pack=1, class_=None, name=None,
65 takefocus=0):
67 """Subroutine to create a frame with scrollbars.
69 This is used by make_text_box and similar routines.
71 Note: the caller is responsible for setting the x/y scroll command
72 properties (e.g. by calling set_scroll_commands()).
74 Return a tuple containing the hbar, the vbar, and the frame, where
75 hbar and vbar are None if not requested.
77 """
78 if class_:
79 if name: frame = Frame(parent, class_=class_, name=name)
80 else: frame = Frame(parent, class_=class_)
81 else:
82 if name: frame = Frame(parent, name=name)
83 else: frame = Frame(parent)
85 if pack:
86 frame.pack(fill=BOTH, expand=1)
88 corner = None
89 if vbar:
90 if not hbar:
91 vbar = Scrollbar(frame, takefocus=takefocus)
92 vbar.pack(fill=Y, side=RIGHT)
93 else:
94 vbarframe = Frame(frame, borderwidth=0)
95 vbarframe.pack(fill=Y, side=RIGHT)
96 vbar = Scrollbar(frame, name="vbar", takefocus=takefocus)
97 vbar.pack(in_=vbarframe, expand=1, fill=Y, side=TOP)
98 sbwidth = vbar.winfo_reqwidth()
99 corner = Frame(vbarframe, width=sbwidth, height=sbwidth)
100 corner.propagate(0)
101 corner.pack(side=BOTTOM)
102 else:
103 vbar = None
105 if hbar:
106 hbar = Scrollbar(frame, orient=HORIZONTAL, name="hbar",
107 takefocus=takefocus)
108 hbar.pack(fill=X, side=BOTTOM)
109 else:
110 hbar = None
112 return hbar, vbar, frame
115 def set_scroll_commands(widget, hbar, vbar):
117 """Link a scrollable widget to its scroll bars.
119 The scroll bars may be empty.
123 if vbar:
124 widget['yscrollcommand'] = (vbar, 'set')
125 vbar['command'] = (widget, 'yview')
127 if hbar:
128 widget['xscrollcommand'] = (hbar, 'set')
129 hbar['command'] = (widget, 'xview')
131 widget.vbar = vbar
132 widget.hbar = hbar
135 def make_text_box(parent, width=0, height=0, hbar=0, vbar=1,
136 fill=BOTH, expand=1, wrap=WORD, pack=1,
137 class_=None, name=None, takefocus=None):
139 """Subroutine to create a text box.
141 Create:
142 - a both-ways filling and expanding frame, containing:
143 - a text widget on the left, and
144 - possibly a vertical scroll bar on the right.
145 - possibly a horizonta; scroll bar at the bottom.
147 Return the text widget and the frame widget.
150 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
151 class_=class_, name=name,
152 takefocus=takefocus)
154 widget = Text(frame, wrap=wrap, name="text")
155 if width: widget.config(width=width)
156 if height: widget.config(height=height)
157 widget.pack(expand=expand, fill=fill, side=LEFT)
159 set_scroll_commands(widget, hbar, vbar)
161 return widget, frame
164 def make_list_box(parent, width=0, height=0, hbar=0, vbar=1,
165 fill=BOTH, expand=1, pack=1, class_=None, name=None,
166 takefocus=None):
168 """Subroutine to create a list box.
170 Like make_text_box().
172 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
173 class_=class_, name=name,
174 takefocus=takefocus)
176 widget = Listbox(frame, name="listbox")
177 if width: widget.config(width=width)
178 if height: widget.config(height=height)
179 widget.pack(expand=expand, fill=fill, side=LEFT)
181 set_scroll_commands(widget, hbar, vbar)
183 return widget, frame
186 def make_canvas(parent, width=0, height=0, hbar=1, vbar=1,
187 fill=BOTH, expand=1, pack=1, class_=None, name=None,
188 takefocus=None):
190 """Subroutine to create a canvas.
192 Like make_text_box().
196 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
197 class_=class_, name=name,
198 takefocus=takefocus)
200 widget = Canvas(frame, scrollregion=(0, 0, width, height), name="canvas")
201 if width: widget.config(width=width)
202 if height: widget.config(height=height)
203 widget.pack(expand=expand, fill=fill, side=LEFT)
205 set_scroll_commands(widget, hbar, vbar)
207 return widget, frame
211 def make_form_entry(parent, label, borderwidth=None):
213 """Subroutine to create a form entry.
215 Create:
216 - a horizontally filling and expanding frame, containing:
217 - a label on the left, and
218 - a text entry on the right.
220 Return the entry widget and the frame widget.
224 frame = Frame(parent)
225 frame.pack(fill=X)
227 label = Label(frame, text=label)
228 label.pack(side=LEFT)
230 if borderwidth is None:
231 entry = Entry(frame, relief=SUNKEN)
232 else:
233 entry = Entry(frame, relief=SUNKEN, borderwidth=borderwidth)
234 entry.pack(side=LEFT, fill=X, expand=1)
236 return entry, frame
238 # This is a slightly modified version of the function above. This
239 # version does the proper alighnment of labels with their fields. It
240 # should probably eventually replace make_form_entry altogether.
242 # The one annoying bug is that the text entry field should be
243 # expandable while still aligning the colons. This doesn't work yet.
245 def make_labeled_form_entry(parent, label, entrywidth=20, entryheight=1,
246 labelwidth=0, borderwidth=None,
247 takefocus=None):
248 """Subroutine to create a form entry.
250 Create:
251 - a horizontally filling and expanding frame, containing:
252 - a label on the left, and
253 - a text entry on the right.
255 Return the entry widget and the frame widget.
257 if label and label[-1] != ':': label = label + ':'
259 frame = Frame(parent)
261 label = Label(frame, text=label, width=labelwidth, anchor=E)
262 label.pack(side=LEFT)
263 if entryheight == 1:
264 if borderwidth is None:
265 entry = Entry(frame, relief=SUNKEN, width=entrywidth)
266 else:
267 entry = Entry(frame, relief=SUNKEN, width=entrywidth,
268 borderwidth=borderwidth)
269 entry.pack(side=RIGHT, expand=1, fill=X)
270 frame.pack(fill=X)
271 else:
272 entry = make_text_box(frame, entrywidth, entryheight, 1, 1,
273 takefocus=takefocus)
274 frame.pack(fill=BOTH, expand=1)
276 return entry, frame, label
279 def make_double_frame(master=None, class_=None, name=None, relief=RAISED,
280 borderwidth=1):
281 """Create a pair of frames suitable for 'hosting' a dialog."""
282 if name:
283 if class_: frame = Frame(master, class_=class_, name=name)
284 else: frame = Frame(master, name=name)
285 else:
286 if class_: frame = Frame(master, class_=class_)
287 else: frame = Frame(master)
288 top = Frame(frame, name="topframe", relief=relief,
289 borderwidth=borderwidth)
290 bottom = Frame(frame, name="bottomframe")
291 bottom.pack(fill=X, padx='1m', pady='1m', side=BOTTOM)
292 top.pack(expand=1, fill=BOTH, padx='1m', pady='1m')
293 frame.pack(expand=1, fill=BOTH)
294 top = Frame(top)
295 top.pack(expand=1, fill=BOTH, padx='2m', pady='2m')
297 return frame, top, bottom
300 def make_group_frame(master, name=None, label=None, fill=Y,
301 side=None, expand=None, font=None):
302 """Create nested frames with a border and optional label.
304 The outer frame is only used to provide the decorative border, to
305 control packing, and to host the label. The inner frame is packed
306 to fill the outer frame and should be used as the parent of all
307 sub-widgets. Only the inner frame is returned.
310 font = font or "-*-helvetica-medium-r-normal-*-*-100-*-*-*-*-*-*"
311 outer = Frame(master, borderwidth=2, relief=GROOVE)
312 outer.pack(expand=expand, fill=fill, side=side)
313 if label:
314 Label(outer, text=label, font=font, anchor=W).pack(fill=X)
315 inner = Frame(master, borderwidth='1m', name=name)
316 inner.pack(expand=1, fill=BOTH, in_=outer)
317 inner.forget = outer.forget
318 return inner
321 def unify_button_widths(*buttons):
322 """Make buttons passed in all have the same width.
324 Works for labels and other widgets with the 'text' option.
327 wid = 0
328 for btn in buttons:
329 wid = max(wid, len(btn["text"]))
330 for btn in buttons:
331 btn["width"] = wid
334 def flatten(msg):
335 """Turn a list or tuple into a single string -- recursively."""
336 t = type(msg)
337 if t in (ListType, TupleType):
338 msg = string.join(map(flatten, msg))
339 elif t is ClassType:
340 msg = msg.__name__
341 else:
342 msg = str(msg)
343 return msg
346 def boolean(s):
347 """Test whether a string is a Tk boolean, without error checking."""
348 if string.lower(s) in ('', '0', 'no', 'off', 'false'): return 0
349 else: return 1
352 def test():
353 """Test make_text_box(), make_form_entry(), flatten(), boolean()."""
354 import sys
355 root = Tk()
356 entry, eframe = make_form_entry(root, 'Boolean:')
357 text, tframe = make_text_box(root)
358 def enter(event, entry=entry, text=text):
359 s = boolean(entry.get()) and '\nyes' or '\nno'
360 text.insert('end', s)
361 entry.bind('<Return>', enter)
362 entry.insert(END, flatten(sys.argv))
363 root.mainloop()
366 if __name__ == '__main__':
367 test()