blob: 3a68f9ac639fcfd7e05eed55b1a029fc1ca62ed7 [file] [log] [blame]
Guido van Rossumde99d311997-01-31 18:58:53 +00001"""Assorted Tk-related subroutines used in Grail."""
2
3
Guido van Rossumde99d311997-01-31 18:58:53 +00004from types import *
5from Tkinter import *
6
7def _clear_entry_widget(event):
8 try:
Guido van Rossum986abac1998-04-06 14:29:28 +00009 widget = event.widget
10 widget.delete(0, INSERT)
Guido van Rossumde99d311997-01-31 18:58:53 +000011 except: pass
12def install_keybindings(root):
13 root.bind_class('Entry', '<Control-u>', _clear_entry_widget)
14
15
16def make_toplevel(master, title=None, class_=None):
17 """Create a Toplevel widget.
18
19 This is a shortcut for a Toplevel() instantiation plus calls to
20 set the title and icon name of the widget.
21
22 """
23
24 if class_:
Guido van Rossum986abac1998-04-06 14:29:28 +000025 widget = Toplevel(master, class_=class_)
Guido van Rossumde99d311997-01-31 18:58:53 +000026 else:
Guido van Rossum986abac1998-04-06 14:29:28 +000027 widget = Toplevel(master)
Guido van Rossumde99d311997-01-31 18:58:53 +000028 if title:
Guido van Rossum986abac1998-04-06 14:29:28 +000029 widget.title(title)
30 widget.iconname(title)
Guido van Rossumde99d311997-01-31 18:58:53 +000031 return widget
32
33def set_transient(widget, master, relx=0.5, rely=0.3, expose=1):
34 """Make an existing toplevel widget transient for a master.
35
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 """
40
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():
Guido van Rossum986abac1998-04-06 14:29:28 +000045 m_width = master.winfo_width()
46 m_height = master.winfo_height()
47 m_x = master.winfo_rootx()
48 m_y = master.winfo_rooty()
Guido van Rossumde99d311997-01-31 18:58:53 +000049 else:
Guido van Rossum986abac1998-04-06 14:29:28 +000050 m_width = master.winfo_screenwidth()
51 m_height = master.winfo_screenheight()
52 m_x = m_y = 0
Guido van Rossumde99d311997-01-31 18:58:53 +000053 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:
Guido van Rossum986abac1998-04-06 14:29:28 +000059 widget.deiconify() # Become visible at the desired location
Guido van Rossumde99d311997-01-31 18:58:53 +000060 return widget
61
62
63def make_scrollbars(parent, hbar, vbar, pack=1, class_=None, name=None,
Guido van Rossum986abac1998-04-06 14:29:28 +000064 takefocus=0):
Guido van Rossumde99d311997-01-31 18:58:53 +000065
66 """Subroutine to create a frame with scrollbars.
67
68 This is used by make_text_box and similar routines.
69
70 Note: the caller is responsible for setting the x/y scroll command
71 properties (e.g. by calling set_scroll_commands()).
72
73 Return a tuple containing the hbar, the vbar, and the frame, where
74 hbar and vbar are None if not requested.
75
76 """
77 if class_:
Guido van Rossum986abac1998-04-06 14:29:28 +000078 if name: frame = Frame(parent, class_=class_, name=name)
79 else: frame = Frame(parent, class_=class_)
Guido van Rossumde99d311997-01-31 18:58:53 +000080 else:
Guido van Rossum986abac1998-04-06 14:29:28 +000081 if name: frame = Frame(parent, name=name)
82 else: frame = Frame(parent)
Guido van Rossumde99d311997-01-31 18:58:53 +000083
84 if pack:
Guido van Rossum986abac1998-04-06 14:29:28 +000085 frame.pack(fill=BOTH, expand=1)
Guido van Rossumde99d311997-01-31 18:58:53 +000086
87 corner = None
88 if vbar:
Guido van Rossum986abac1998-04-06 14:29:28 +000089 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)
Guido van Rossumde99d311997-01-31 18:58:53 +0000101 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000102 vbar = None
Guido van Rossumde99d311997-01-31 18:58:53 +0000103
104 if hbar:
Guido van Rossum986abac1998-04-06 14:29:28 +0000105 hbar = Scrollbar(frame, orient=HORIZONTAL, name="hbar",
106 takefocus=takefocus)
107 hbar.pack(fill=X, side=BOTTOM)
Guido van Rossumde99d311997-01-31 18:58:53 +0000108 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000109 hbar = None
Guido van Rossumde99d311997-01-31 18:58:53 +0000110
111 return hbar, vbar, frame
112
113
114def set_scroll_commands(widget, hbar, vbar):
115
116 """Link a scrollable widget to its scroll bars.
117
118 The scroll bars may be empty.
119
120 """
121
122 if vbar:
Guido van Rossum986abac1998-04-06 14:29:28 +0000123 widget['yscrollcommand'] = (vbar, 'set')
124 vbar['command'] = (widget, 'yview')
Guido van Rossumde99d311997-01-31 18:58:53 +0000125
126 if hbar:
Guido van Rossum986abac1998-04-06 14:29:28 +0000127 widget['xscrollcommand'] = (hbar, 'set')
128 hbar['command'] = (widget, 'xview')
Guido van Rossumde99d311997-01-31 18:58:53 +0000129
130 widget.vbar = vbar
131 widget.hbar = hbar
132
133
134def make_text_box(parent, width=0, height=0, hbar=0, vbar=1,
Guido van Rossum986abac1998-04-06 14:29:28 +0000135 fill=BOTH, expand=1, wrap=WORD, pack=1,
136 class_=None, name=None, takefocus=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000137
138 """Subroutine to create a text box.
139
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.
145
146 Return the text widget and the frame widget.
147
148 """
149 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
Guido van Rossum986abac1998-04-06 14:29:28 +0000150 class_=class_, name=name,
151 takefocus=takefocus)
Guido van Rossumde99d311997-01-31 18:58:53 +0000152
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)
157
158 set_scroll_commands(widget, hbar, vbar)
159
160 return widget, frame
161
162
163def make_list_box(parent, width=0, height=0, hbar=0, vbar=1,
Guido van Rossum986abac1998-04-06 14:29:28 +0000164 fill=BOTH, expand=1, pack=1, class_=None, name=None,
165 takefocus=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000166
167 """Subroutine to create a list box.
168
169 Like make_text_box().
170 """
171 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
Guido van Rossum986abac1998-04-06 14:29:28 +0000172 class_=class_, name=name,
173 takefocus=takefocus)
Guido van Rossumde99d311997-01-31 18:58:53 +0000174
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)
179
180 set_scroll_commands(widget, hbar, vbar)
181
182 return widget, frame
183
184
185def make_canvas(parent, width=0, height=0, hbar=1, vbar=1,
Guido van Rossum986abac1998-04-06 14:29:28 +0000186 fill=BOTH, expand=1, pack=1, class_=None, name=None,
187 takefocus=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000188
189 """Subroutine to create a canvas.
190
191 Like make_text_box().
192
193 """
194
195 hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
Guido van Rossum986abac1998-04-06 14:29:28 +0000196 class_=class_, name=name,
197 takefocus=takefocus)
Guido van Rossumde99d311997-01-31 18:58:53 +0000198
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)
203
204 set_scroll_commands(widget, hbar, vbar)
205
206 return widget, frame
207
208
209
210def make_form_entry(parent, label, borderwidth=None):
211
212 """Subroutine to create a form entry.
213
214 Create:
215 - a horizontally filling and expanding frame, containing:
216 - a label on the left, and
217 - a text entry on the right.
218
219 Return the entry widget and the frame widget.
220
221 """
222
223 frame = Frame(parent)
224 frame.pack(fill=X)
225
226 label = Label(frame, text=label)
227 label.pack(side=LEFT)
228
229 if borderwidth is None:
Guido van Rossum986abac1998-04-06 14:29:28 +0000230 entry = Entry(frame, relief=SUNKEN)
Guido van Rossumde99d311997-01-31 18:58:53 +0000231 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000232 entry = Entry(frame, relief=SUNKEN, borderwidth=borderwidth)
Guido van Rossumde99d311997-01-31 18:58:53 +0000233 entry.pack(side=LEFT, fill=X, expand=1)
234
235 return entry, frame
236
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.
240#
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.
243#
244def make_labeled_form_entry(parent, label, entrywidth=20, entryheight=1,
Guido van Rossum986abac1998-04-06 14:29:28 +0000245 labelwidth=0, borderwidth=None,
246 takefocus=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000247 """Subroutine to create a form entry.
248
249 Create:
250 - a horizontally filling and expanding frame, containing:
251 - a label on the left, and
252 - a text entry on the right.
253
254 Return the entry widget and the frame widget.
255 """
256 if label and label[-1] != ':': label = label + ':'
257
258 frame = Frame(parent)
259
260 label = Label(frame, text=label, width=labelwidth, anchor=E)
261 label.pack(side=LEFT)
262 if entryheight == 1:
Guido van Rossum986abac1998-04-06 14:29:28 +0000263 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)
Guido van Rossumde99d311997-01-31 18:58:53 +0000270 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000271 entry = make_text_box(frame, entrywidth, entryheight, 1, 1,
272 takefocus=takefocus)
273 frame.pack(fill=BOTH, expand=1)
Guido van Rossumde99d311997-01-31 18:58:53 +0000274
275 return entry, frame, label
276
277
278def make_double_frame(master=None, class_=None, name=None, relief=RAISED,
Guido van Rossum986abac1998-04-06 14:29:28 +0000279 borderwidth=1):
Guido van Rossumde99d311997-01-31 18:58:53 +0000280 """Create a pair of frames suitable for 'hosting' a dialog."""
281 if name:
Guido van Rossum986abac1998-04-06 14:29:28 +0000282 if class_: frame = Frame(master, class_=class_, name=name)
283 else: frame = Frame(master, name=name)
Guido van Rossumde99d311997-01-31 18:58:53 +0000284 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000285 if class_: frame = Frame(master, class_=class_)
286 else: frame = Frame(master)
Guido van Rossumde99d311997-01-31 18:58:53 +0000287 top = Frame(frame, name="topframe", relief=relief,
Guido van Rossum986abac1998-04-06 14:29:28 +0000288 borderwidth=borderwidth)
Guido van Rossumde99d311997-01-31 18:58:53 +0000289 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')
295
296 return frame, top, bottom
297
298
299def make_group_frame(master, name=None, label=None, fill=Y,
Guido van Rossum986abac1998-04-06 14:29:28 +0000300 side=None, expand=None, font=None):
Guido van Rossumde99d311997-01-31 18:58:53 +0000301 """Create nested frames with a border and optional label.
302
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.
307
308 """
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:
Guido van Rossum986abac1998-04-06 14:29:28 +0000313 Label(outer, text=label, font=font, anchor=W).pack(fill=X)
Guido van Rossumde99d311997-01-31 18:58:53 +0000314 inner = Frame(master, borderwidth='1m', name=name)
315 inner.pack(expand=1, fill=BOTH, in_=outer)
316 inner.forget = outer.forget
317 return inner
318
319
320def unify_button_widths(*buttons):
321 """Make buttons passed in all have the same width.
322
323 Works for labels and other widgets with the 'text' option.
324
325 """
326 wid = 0
327 for btn in buttons:
Guido van Rossum986abac1998-04-06 14:29:28 +0000328 wid = max(wid, len(btn["text"]))
Guido van Rossumde99d311997-01-31 18:58:53 +0000329 for btn in buttons:
Guido van Rossum986abac1998-04-06 14:29:28 +0000330 btn["width"] = wid
Guido van Rossumde99d311997-01-31 18:58:53 +0000331
332
333def flatten(msg):
334 """Turn a list or tuple into a single string -- recursively."""
335 t = type(msg)
336 if t in (ListType, TupleType):
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000337 msg = ' '.join(map(flatten, msg))
Guido van Rossumde99d311997-01-31 18:58:53 +0000338 elif t is ClassType:
Guido van Rossum986abac1998-04-06 14:29:28 +0000339 msg = msg.__name__
Guido van Rossumde99d311997-01-31 18:58:53 +0000340 else:
Guido van Rossum986abac1998-04-06 14:29:28 +0000341 msg = str(msg)
Guido van Rossumde99d311997-01-31 18:58:53 +0000342 return msg
343
344
345def boolean(s):
346 """Test whether a string is a Tk boolean, without error checking."""
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000347 if s.lower() in ('', '0', 'no', 'off', 'false'): return 0
Guido van Rossumde99d311997-01-31 18:58:53 +0000348 else: return 1
349
350
351def 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):
Guido van Rossum986abac1998-04-06 14:29:28 +0000358 s = boolean(entry.get()) and '\nyes' or '\nno'
359 text.insert('end', s)
Guido van Rossumde99d311997-01-31 18:58:53 +0000360 entry.bind('<Return>', enter)
361 entry.insert(END, flatten(sys.argv))
362 root.mainloop()
363
364
365if __name__ == '__main__':
366 test()