blob: 6734530a02b84864841465f8f89a318a6e87f9ea [file] [log] [blame]
Guido van Rossumde99d311997-01-31 18:58:53 +00001"""Assorted Tk-related subroutines used in Grail."""
2
3
4import string
5from types import *
6from Tkinter import *
7
8def _clear_entry_widget(event):
9 try:
10 widget = event.widget
11 widget.delete(0, INSERT)
12 except: pass
13def install_keybindings(root):
14 root.bind_class('Entry', '<Control-u>', _clear_entry_widget)
15
16
17def make_toplevel(master, title=None, class_=None):
18 """Create a Toplevel widget.
19
20 This is a shortcut for a Toplevel() instantiation plus calls to
21 set the title and icon name of the widget.
22
23 """
24
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
33
34def set_transient(widget, master, relx=0.5, rely=0.3, expose=1):
35 """Make an existing toplevel widget transient for a master.
36
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 """
41
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
62
63
64def make_scrollbars(parent, hbar, vbar, pack=1, class_=None, name=None,
65 takefocus=0):
66
67 """Subroutine to create a frame with scrollbars.
68
69 This is used by make_text_box and similar routines.
70
71 Note: the caller is responsible for setting the x/y scroll command
72 properties (e.g. by calling set_scroll_commands()).
73
74 Return a tuple containing the hbar, the vbar, and the frame, where
75 hbar and vbar are None if not requested.
76
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)
84
85 if pack:
86 frame.pack(fill=BOTH, expand=1)
87
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
104
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
111
112 return hbar, vbar, frame
113
114
115def set_scroll_commands(widget, hbar, vbar):
116
117 """Link a scrollable widget to its scroll bars.
118
119 The scroll bars may be empty.
120
121 """
122
123 if vbar:
124 widget['yscrollcommand'] = (vbar, 'set')
125 vbar['command'] = (widget, 'yview')
126
127 if hbar:
128 widget['xscrollcommand'] = (hbar, 'set')
129 hbar['command'] = (widget, 'xview')
130
131 widget.vbar = vbar
132 widget.hbar = hbar
133
134
135def 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):
138
139 """Subroutine to create a text box.
140
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.
146
147 Return the text widget and the frame widget.
148
149 """
150 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
151 class_=class_, name=name,
152 takefocus=takefocus)
153
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)
158
159 set_scroll_commands(widget, hbar, vbar)
160
161 return widget, frame
162
163
164def 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):
167
168 """Subroutine to create a list box.
169
170 Like make_text_box().
171 """
172 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
173 class_=class_, name=name,
174 takefocus=takefocus)
175
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)
180
181 set_scroll_commands(widget, hbar, vbar)
182
183 return widget, frame
184
185
186def 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):
189
190 """Subroutine to create a canvas.
191
192 Like make_text_box().
193
194 """
195
196 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
197 class_=class_, name=name,
198 takefocus=takefocus)
199
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)
204
205 set_scroll_commands(widget, hbar, vbar)
206
207 return widget, frame
208
209
210
211def make_form_entry(parent, label, borderwidth=None):
212
213 """Subroutine to create a form entry.
214
215 Create:
216 - a horizontally filling and expanding frame, containing:
217 - a label on the left, and
218 - a text entry on the right.
219
220 Return the entry widget and the frame widget.
221
222 """
223
224 frame = Frame(parent)
225 frame.pack(fill=X)
226
227 label = Label(frame, text=label)
228 label.pack(side=LEFT)
229
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)
235
236 return entry, frame
237
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.
241#
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.
244#
245def 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.
249
250 Create:
251 - a horizontally filling and expanding frame, containing:
252 - a label on the left, and
253 - a text entry on the right.
254
255 Return the entry widget and the frame widget.
256 """
257 if label and label[-1] != ':': label = label + ':'
258
259 frame = Frame(parent)
260
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)
275
276 return entry, frame, label
277
278
279def 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')
296
297 return frame, top, bottom
298
299
300def 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.
303
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.
308
309 """
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
319
320
321def unify_button_widths(*buttons):
322 """Make buttons passed in all have the same width.
323
324 Works for labels and other widgets with the 'text' option.
325
326 """
327 wid = 0
328 for btn in buttons:
329 wid = max(wid, len(btn["text"]))
330 for btn in buttons:
331 btn["width"] = wid
332
333
334def 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
344
345
346def 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
350
351
352def 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()
364
365
366if __name__ == '__main__':
367 test()