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