1"""Assorted Tk-related subroutines used in Grail.""" 2 3 4from types import * 5from Tkinter import * 6 7def _clear_entry_widget(event): 8 try: 9 widget = event.widget 10 widget.delete(0, INSERT) 11 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_: 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 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(): 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 63def 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 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: 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 134def 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 163def 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 185def 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 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: 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# 244def 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 278def 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 299def 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 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: 328 wid = max(wid, len(btn["text"])) 329 for btn in buttons: 330 btn["width"] = wid 331 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): 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 345def 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 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): 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 365if __name__ == '__main__': 366 test() 367