1 | """Assorted Tk-related subroutines used in Grail."""
|
---|
2 |
|
---|
3 |
|
---|
4 | from types import *
|
---|
5 | from Tkinter import *
|
---|
6 |
|
---|
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)
|
---|
14 |
|
---|
15 |
|
---|
16 | def 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_:
|
---|
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
|
---|
32 |
|
---|
33 | def 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():
|
---|
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
|
---|
61 |
|
---|
62 |
|
---|
63 | def make_scrollbars(parent, hbar, vbar, pack=1, class_=None, name=None,
|
---|
64 | takefocus=0):
|
---|
65 |
|
---|
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_:
|
---|
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)
|
---|
83 |
|
---|
84 | if pack:
|
---|
85 | frame.pack(fill=BOTH, expand=1)
|
---|
86 |
|
---|
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
|
---|
103 |
|
---|
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
|
---|
110 |
|
---|
111 | return hbar, vbar, frame
|
---|
112 |
|
---|
113 |
|
---|
114 | def 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:
|
---|
123 | widget['yscrollcommand'] = (vbar, 'set')
|
---|
124 | vbar['command'] = (widget, 'yview')
|
---|
125 |
|
---|
126 | if hbar:
|
---|
127 | widget['xscrollcommand'] = (hbar, 'set')
|
---|
128 | hbar['command'] = (widget, 'xview')
|
---|
129 |
|
---|
130 | widget.vbar = vbar
|
---|
131 | widget.hbar = hbar
|
---|
132 |
|
---|
133 |
|
---|
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):
|
---|
137 |
|
---|
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,
|
---|
150 | class_=class_, name=name,
|
---|
151 | takefocus=takefocus)
|
---|
152 |
|
---|
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 |
|
---|
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):
|
---|
166 |
|
---|
167 | """Subroutine to create a list box.
|
---|
168 |
|
---|
169 | Like make_text_box().
|
---|
170 | """
|
---|
171 | hbar, vbar, frame = make_scrollbars(parent, hbar, vbar, pack,
|
---|
172 | class_=class_, name=name,
|
---|
173 | takefocus=takefocus)
|
---|
174 |
|
---|
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 |
|
---|
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):
|
---|
188 |
|
---|
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,
|
---|
196 | class_=class_, name=name,
|
---|
197 | takefocus=takefocus)
|
---|
198 |
|
---|
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 |
|
---|
210 | def 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:
|
---|
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)
|
---|
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 | #
|
---|
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.
|
---|
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:
|
---|
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)
|
---|
274 |
|
---|
275 | return entry, frame, label
|
---|
276 |
|
---|
277 |
|
---|
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')
|
---|
295 |
|
---|
296 | return frame, top, bottom
|
---|
297 |
|
---|
298 |
|
---|
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.
|
---|
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:
|
---|
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
|
---|
318 |
|
---|
319 |
|
---|
320 | def 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:
|
---|
328 | wid = max(wid, len(btn["text"]))
|
---|
329 | for btn in buttons:
|
---|
330 | btn["width"] = wid
|
---|
331 |
|
---|
332 |
|
---|
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
|
---|
343 |
|
---|
344 |
|
---|
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
|
---|
349 |
|
---|
350 |
|
---|
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()
|
---|
363 |
|
---|
364 |
|
---|
365 | if __name__ == '__main__':
|
---|
366 | test()
|
---|