blob: 0db4d490880e35a5b0e6caa4dde0e47d61b3e876 [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:
Guido van Rossum986abac1998-04-06 14:29:28 +000010 widget = event.widget
11 widget.delete(0, INSERT)
Guido van Rossumde99d311997-01-31 18:58:53 +000012 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_:
Guido van Rossum986abac1998-04-06 14:29:28 +000026 widget = Toplevel(master, class_=class_)
Guido van Rossumde99d311997-01-31 18:58:53 +000027 else:
Guido van Rossum986abac1998-04-06 14:29:28 +000028 widget = Toplevel(master)
Guido van Rossumde99d311997-01-31 18:58:53 +000029 if title:
Guido van Rossum986abac1998-04-06 14:29:28 +000030 widget.title(title)
31 widget.iconname(title)
Guido van Rossumde99d311997-01-31 18:58:53 +000032 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():
Guido van Rossum986abac1998-04-06 14:29:28 +000046 m_width = master.winfo_width()
47 m_height = master.winfo_height()
48 m_x = master.winfo_rootx()
49 m_y = master.winfo_rooty()
Guido van Rossumde99d311997-01-31 18:58:53 +000050 else:
Guido van Rossum986abac1998-04-06 14:29:28 +000051 m_width = master.winfo_screenwidth()
52 m_height = master.winfo_screenheight()
53 m_x = m_y = 0
Guido van Rossumde99d311997-01-31 18:58:53 +000054 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:
Guido van Rossum986abac1998-04-06 14:29:28 +000060 widget.deiconify() # Become visible at the desired location
Guido van Rossumde99d311997-01-31 18:58:53 +000061 return widget
62
63
64def make_scrollbars(parent, hbar, vbar, pack=1, class_=None, name=None,
Guido van Rossum986abac1998-04-06 14:29:28 +000065 takefocus=0):
Guido van Rossumde99d311997-01-31 18:58:53 +000066
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_:
Guido van Rossum986abac1998-04-06 14:29:28 +000079 if name: frame = Frame(parent, class_=class_, name=name)
80 else: frame = Frame(parent, class_=class_)
Guido van Rossumde99d311997-01-31 18:58:53 +000081 else:
Guido van Rossum986abac1998-04-06 14:29:28 +000082 if name: frame = Frame(parent, name=name)
83 else: frame = Frame(parent)
Guido van Rossumde99d311997-01-31 18:58:53 +000084
85 if pack:
Guido van Rossum986abac1998-04-06 14:29:28 +000086 frame.pack(fill=BOTH, expand=1)
Guido van Rossumde99d311997-01-31 18:58:53 +000087
88 corner = None
89 if vbar:
Guido van Rossum986abac1998-04-06 14:29:28 +000090 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)
Guido van Rossumde99d311997-01-31 18:58:53 +0000102 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000103 vbar = None
Guido van Rossumde99d311997-01-31 18:58:53 +0000104
105 if hbar:
Guido van Rossum986abac1998-04-06 14:29:28 +0000106 hbar = Scrollbar(frame, orient=HORIZONTAL, name="hbar",
107 takefocus=takefocus)
108 hbar.pack(fill=X, side=BOTTOM)
Guido van Rossumde99d311997-01-31 18:58:53 +0000109 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000110 hbar = None
Guido van Rossumde99d311997-01-31 18:58:53 +0000111
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:
Guido van Rossum986abac1998-04-06 14:29:28 +0000124 widget['yscrollcommand'] = (vbar, 'set')
125 vbar['command'] = (widget, 'yview')
Guido van Rossumde99d311997-01-31 18:58:53 +0000126
127 if hbar:
Guido van Rossum986abac1998-04-06 14:29:28 +0000128 widget['xscrollcommand'] = (hbar, 'set')
129 hbar['command'] = (widget, 'xview')
Guido van Rossumde99d311997-01-31 18:58:53 +0000130
131 widget.vbar = vbar
132 widget.hbar = hbar
133
134
135def make_text_box(parent, width=0, height=0, hbar=0, vbar=1,
Guido van Rossum986abac1998-04-06 14:29:28 +0000136 fill=BOTH, expand=1, wrap=WORD, pack=1,
137 class_=None, name=None, takefocus=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000138
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,
Guido van Rossum986abac1998-04-06 14:29:28 +0000151 class_=class_, name=name,
152 takefocus=takefocus)
Guido van Rossumde99d311997-01-31 18:58:53 +0000153
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,
Guido van Rossum986abac1998-04-06 14:29:28 +0000165 fill=BOTH, expand=1, pack=1, class_=None, name=None,
166 takefocus=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000167
168 """Subroutine to create a list box.
169
170 Like make_text_box().
171 """
172 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
Guido van Rossum986abac1998-04-06 14:29:28 +0000173 class_=class_, name=name,
174 takefocus=takefocus)
Guido van Rossumde99d311997-01-31 18:58:53 +0000175
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,
Guido van Rossum986abac1998-04-06 14:29:28 +0000187 fill=BOTH, expand=1, pack=1, class_=None, name=None,
188 takefocus=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000189
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,
Guido van Rossum986abac1998-04-06 14:29:28 +0000197 class_=class_, name=name,
198 takefocus=takefocus)
Guido van Rossumde99d311997-01-31 18:58:53 +0000199
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:
Guido van Rossum986abac1998-04-06 14:29:28 +0000231 entry = Entry(frame, relief=SUNKEN)
Guido van Rossumde99d311997-01-31 18:58:53 +0000232 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000233 entry = Entry(frame, relief=SUNKEN, borderwidth=borderwidth)
Guido van Rossumde99d311997-01-31 18:58:53 +0000234 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,
Guido van Rossum986abac1998-04-06 14:29:28 +0000246 labelwidth=0, borderwidth=None,
247 takefocus=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000248 """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:
Guido van Rossum986abac1998-04-06 14:29:28 +0000264 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)
Guido van Rossumde99d311997-01-31 18:58:53 +0000271 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000272 entry = make_text_box(frame, entrywidth, entryheight, 1, 1,
273 takefocus=takefocus)
274 frame.pack(fill=BOTH, expand=1)
Guido van Rossumde99d311997-01-31 18:58:53 +0000275
276 return entry, frame, label
277
278
279def make_double_frame(master=None, class_=None, name=None, relief=RAISED,
Guido van Rossum986abac1998-04-06 14:29:28 +0000280 borderwidth=1):
Guido van Rossumde99d311997-01-31 18:58:53 +0000281 """Create a pair of frames suitable for 'hosting' a dialog."""
282 if name:
Guido van Rossum986abac1998-04-06 14:29:28 +0000283 if class_: frame = Frame(master, class_=class_, name=name)
284 else: frame = Frame(master, name=name)
Guido van Rossumde99d311997-01-31 18:58:53 +0000285 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000286 if class_: frame = Frame(master, class_=class_)
287 else: frame = Frame(master)
Guido van Rossumde99d311997-01-31 18:58:53 +0000288 top = Frame(frame, name="topframe", relief=relief,
Guido van Rossum986abac1998-04-06 14:29:28 +0000289 borderwidth=borderwidth)
Guido van Rossumde99d311997-01-31 18:58:53 +0000290 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,
Guido van Rossum986abac1998-04-06 14:29:28 +0000301 side=None, expand=None, font=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000302 """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:
Guido van Rossum986abac1998-04-06 14:29:28 +0000314 Label(outer, text=label, font=font, anchor=W).pack(fill=X)
Guido van Rossumde99d311997-01-31 18:58:53 +0000315 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:
Guido van Rossum986abac1998-04-06 14:29:28 +0000329 wid = max(wid, len(btn["text"]))
Guido van Rossumde99d311997-01-31 18:58:53 +0000330 for btn in buttons:
Guido van Rossum986abac1998-04-06 14:29:28 +0000331 btn["width"] = wid
Guido van Rossumde99d311997-01-31 18:58:53 +0000332
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):
Guido van Rossum986abac1998-04-06 14:29:28 +0000338 msg = string.join(map(flatten, msg))
Guido van Rossumde99d311997-01-31 18:58:53 +0000339 elif t is ClassType:
Guido van Rossum986abac1998-04-06 14:29:28 +0000340 msg = msg.__name__
Guido van Rossumde99d311997-01-31 18:58:53 +0000341 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000342 msg = str(msg)
Guido van Rossumde99d311997-01-31 18:58:53 +0000343 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):
Guido van Rossum986abac1998-04-06 14:29:28 +0000359 s = boolean(entry.get()) and '\nyes' or '\nno'
360 text.insert('end', s)
Guido van Rossumde99d311997-01-31 18:58:53 +0000361 entry.bind('<Return>', enter)
362 entry.insert(END, flatten(sys.argv))
363 root.mainloop()
364
365
366if __name__ == '__main__':
367 test()