Package nltk :: Package draw :: Module util
[hide private]
[frames] | no frames]

Source Code for Module nltk.draw.util

   1  # Natural Language Toolkit: Drawing utilities 
   2  # 
   3  # Copyright (C) 2001-2011 NLTK Project 
   4  # Author: Edward Loper <edloper@gradient.cis.upenn.edu> 
   5  # URL: <http://www.nltk.org/> 
   6  # For license information, see LICENSE.TXT 
   7  # 
   8  # $Id: util.py 6339 2008-07-30 01:54:47Z stevenbird $ 
   9   
  10  """ 
  11  Tools for graphically displaying and interacting with the objects and 
  12  processing classes defined by the Toolkit.  These tools are primarily 
  13  intended to help students visualize the objects that they create. 
  14   
  15  The graphical tools are typically built using X{canvas widgets}, each 
  16  of which encapsulates the graphical elements and bindings used to 
  17  display a complex object on a Tkinter C{Canvas}.  For example, NLTK 
  18  defines canvas widgets for displaying trees and directed graphs, as 
  19  well as a number of simpler widgets.  These canvas widgets make it 
  20  easier to build new graphical tools and demos.  See the class 
  21  documentation for L{CanvasWidget} for more information. 
  22   
  23  The C{nltk.draw} module defines the abstract C{CanvasWidget} base 
  24  class, and a number of simple canvas widgets.  The remaining canvas 
  25  widgets are defined by submodules, such as L{nltk.draw.tree}. 
  26   
  27  The C{nltk.draw} module also defines L{CanvasFrame}, which 
  28  encapsulates a C{Canvas} and its scrollbars.  It uses a 
  29  L{ScrollWatcherWidget} to ensure that all canvas widgets contained on 
  30  its canvas are within the scroll region. 
  31   
  32  Acknowledgements: Many of the ideas behind the canvas widget system 
  33  are derived from C{CLIG}, a Tk-based grapher for linguistic data 
  34  structures.  For more information, see U{the CLIG 
  35  homepage<http://www.ags.uni-sb.de/~konrad/clig.html>}. 
  36   
  37  """ 
  38   
  39  from Tkinter import * 
  40  import tkFont, tkMessageBox, tkFileDialog 
  41   
  42  from nltk.util import in_idle 
  43       
  44  ##////////////////////////////////////////////////////// 
  45  ##  CanvasWidget 
  46  ##////////////////////////////////////////////////////// 
  47   
48 -class CanvasWidget(object):
49 """ 50 A collection of graphical elements and bindings used to display a 51 complex object on a Tkinter C{Canvas}. A canvas widget is 52 responsible for managing the C{Canvas} tags and callback bindings 53 necessary to display and interact with the object. Canvas widgets 54 are often organized into hierarchies, where parent canvas widgets 55 control aspects of their child widgets. 56 57 Each canvas widget is bound to a single C{Canvas}. This C{Canvas} 58 is specified as the first argument to the C{CanvasWidget}'s 59 constructor. 60 61 Attributes 62 ========== 63 Each canvas widget can support a variety of X{attributes}, which 64 control how the canvas widget is displayed. Some typical examples 65 attributes are C{color}, C{font}, and C{radius}. Each attribute 66 has a default value. This default value can be overridden in the 67 constructor, using keyword arguments of the form 68 C{attribute=value}: 69 70 >>> cn = CanvasText(c, 'test', color='red') 71 72 Attribute values can also be changed after a canvas widget has 73 been constructed, using the C{__setitem__} operator: 74 75 >>> cn['font'] = 'times' 76 77 The current value of an attribute value can be queried using the 78 C{__getitem__} operator: 79 80 >>> cn['color'] 81 red 82 83 For a list of the attributes supported by a type of canvas widget, 84 see its class documentation. 85 86 Interaction 87 =========== 88 The attribute C{'draggable'} controls whether the user can drag a 89 canvas widget around the canvas. By default, canvas widgets 90 are not draggable. 91 92 C{CanvasWidget} provides callback support for two types of user 93 interaction: clicking and dragging. The method C{bind_click} 94 registers a callback function that is called whenever the canvas 95 widget is clicked. The method C{bind_drag} registers a callback 96 function that is called after the canvas widget is dragged. If 97 the user clicks or drags a canvas widget with no registered 98 callback function, then the interaction event will propagate to 99 its parent. For each canvas widget, only one callback function 100 may be registered for an interaction event. Callback functions 101 can be deregistered with the C{unbind_click} and C{unbind_drag} 102 methods. 103 104 Subclassing 105 =========== 106 C{CanvasWidget} is an abstract class. Subclasses are required to 107 implement the following methods: 108 109 - C{__init__}: Builds a new canvas widget. It must perform the 110 following three tasks (in order): 111 - Create any new graphical elements. 112 - Call C{_add_child_widget} on each child widget. 113 - Call the C{CanvasWidget} constructor. 114 - C{_tags}: Returns a list of the canvas tags for all graphical 115 elements managed by this canvas widget, not including 116 graphical elements managed by its child widgets. 117 - C{_manage}: Arranges the child widgets of this canvas widget. 118 This is typically only called when the canvas widget is 119 created. 120 - C{_update}: Update this canvas widget in response to a 121 change in a single child. 122 123 For C{CanvasWidget}s with no child widgets, the default 124 definitions for C{_manage} and C{_update} may be used. 125 126 If a subclass defines any attributes, then it should implement 127 C{__getitem__} and C{__setitem__}. If either of these methods is 128 called with an unknown attribute, then they should propagate the 129 request to C{CanvasWidget}. 130 131 Most subclasses implement a number of additional methods that 132 modify the C{CanvasWidget} in some way. These methods must call 133 C{parent.update(self)} after making any changes to the canvas 134 widget's graphical elements. The canvas widget must also call 135 C{parent.update(self)} after changing any attribute value that 136 affects the shape or position of the canvas widget's graphical 137 elements. 138 139 @type __canvas: C{Tkinter.Canvas} 140 @ivar __canvas: This C{CanvasWidget}'s canvas. 141 142 @type __parent: C{CanvasWidget} or C{None} 143 @ivar __parent: This C{CanvasWidget}'s hierarchical parent widget. 144 @type __children: C{list} of C{CanvasWidget} 145 @ivar __children: This C{CanvasWidget}'s hierarchical child widgets. 146 147 @type __updating: C{boolean} 148 @ivar __updating: Is this canvas widget currently performing an 149 update? If it is, then it will ignore any new update requests 150 from child widgets. 151 152 @type __draggable: C{boolean} 153 @ivar __draggable: Is this canvas widget draggable? 154 @type __press: C{event} 155 @ivar __press: The ButtonPress event that we're currently handling. 156 @type __drag_x: C{int} 157 @ivar __drag_x: Where it's been moved to (to find dx) 158 @type __drag_y: C{int} 159 @ivar __drag_y: Where it's been moved to (to find dy) 160 @type __callbacks: C{dictionary} 161 @ivar __callbacks: Registered callbacks. Currently, four keys are 162 used: C{1}, C{2}, C{3}, and C{'drag'}. The values are 163 callback functions. Each callback function takes a single 164 argument, which is the C{CanvasWidget} that triggered the 165 callback. 166 """
167 - def __init__(self, canvas, parent=None, **attribs):
168 """ 169 Create a new canvas widget. This constructor should only be 170 called by subclass constructors; and it should be called only 171 X{after} the subclass has constructed all graphical canvas 172 objects and registered all child widgets. 173 174 @param canvas: This canvas widget's canvas. 175 @type canvas: C{Tkinter.Canvas} 176 @param parent: This canvas widget's hierarchical parent. 177 @type parent: C{CanvasWidget} 178 @param attribs: The new canvas widget's attributes. 179 """ 180 if self.__class__ == CanvasWidget: 181 raise TypeError, 'CanvasWidget is an abstract base class' 182 183 if not isinstance(canvas, Canvas): 184 raise TypeError('Expected a canvas!') 185 186 self.__canvas = canvas 187 self.__parent = parent 188 189 # If the subclass constructor called _add_child_widget, then 190 # self.__children will already exist. 191 if not hasattr(self, '_CanvasWidget__children'): self.__children = [] 192 193 # Is this widget hidden? 194 self.__hidden = 0 195 196 # Update control (prevents infinite loops) 197 self.__updating = 0 198 199 # Button-press and drag callback handling. 200 self.__press = None 201 self.__drag_x = self.__drag_y = 0 202 self.__callbacks = {} 203 self.__draggable = 0 204 205 # Set up attributes. 206 for (attr, value) in attribs.items(): self[attr] = value 207 208 # Manage this canvas widget 209 self._manage() 210 211 # Register any new bindings 212 for tag in self._tags(): 213 self.__canvas.tag_bind(tag, '<ButtonPress-1>', 214 self.__press_cb) 215 self.__canvas.tag_bind(tag, '<ButtonPress-2>', 216 self.__press_cb) 217 self.__canvas.tag_bind(tag, '<ButtonPress-3>', 218 self.__press_cb)
219 220 ##////////////////////////////////////////////////////// 221 ## Inherited methods. 222 ##////////////////////////////////////////////////////// 223
224 - def bbox(self):
225 """ 226 @return: A bounding box for this C{CanvasWidget}. The bounding 227 box is a tuple of four coordinates, M{(xmin, ymin, xmax, 228 ymax)}, for a rectangle which encloses all of the canvas 229 widget's graphical elements. Bounding box coordinates are 230 specified with respect to the C{Canvas}'s coordinate 231 space. 232 @rtype: C{4-tuple} of C{int}s 233 """ 234 if self.__hidden: return (0,0,0,0) 235 if len(self.tags()) == 0: raise ValueError('No tags') 236 return self.__canvas.bbox(*self.tags())
237
238 - def width(self):
239 """ 240 @return: The width of this canvas widget's bounding box, in 241 its C{Canvas}'s coordinate space. 242 @rtype: C{int} 243 """ 244 if len(self.tags()) == 0: raise ValueError('No tags') 245 bbox = self.__canvas.bbox(*self.tags()) 246 return bbox[2]-bbox[0]
247
248 - def height(self):
249 """ 250 @return: The height of this canvas widget's bounding box, in 251 its C{Canvas}'s coordinate space. 252 @rtype: C{int} 253 """ 254 if len(self.tags()) == 0: raise ValueError('No tags') 255 bbox = self.__canvas.bbox(*self.tags()) 256 return bbox[3]-bbox[1]
257
258 - def parent(self):
259 """ 260 @return: The hierarchical parent of this canvas widget. 261 C{self} is considered a subpart of its parent for 262 purposes of user interaction. 263 @rtype: C{CanvasWidget} or C{None} 264 """ 265 return self.__parent
266
267 - def child_widgets(self):
268 """ 269 @return: A list of the hierarchical children of this canvas 270 widget. These children are considered part of C{self} 271 for purposes of user interaction. 272 @rtype: C{list} of C{CanvasWidget} 273 """ 274 return self.__children
275
276 - def canvas(self):
277 """ 278 @return: The canvas that this canvas widget is bound to. 279 @rtype: C{Tkinter.Canvas} 280 """ 281 return self.__canvas
282
283 - def move(self, dx, dy):
284 """ 285 Move this canvas widget by a given distance. In particular, 286 shift the canvas widget right by C{dx} pixels, and down by 287 C{dy} pixels. Both C{dx} and C{dy} may be negative, resulting 288 in leftward or upward movement. 289 290 @type dx: C{int} 291 @param dx: The number of pixels to move this canvas widget 292 rightwards. 293 @type dy: C{int} 294 @param dy: The number of pixels to move this canvas widget 295 downwards. 296 @rtype: C{None} 297 """ 298 if dx == dy == 0: return 299 for tag in self.tags(): 300 self.__canvas.move(tag, dx, dy) 301 if self.__parent: self.__parent.update(self)
302
303 - def moveto(self, x, y, anchor='NW'):
304 """ 305 Move this canvas widget to the given location. In particular, 306 shift the canvas widget such that the corner or side of the 307 bounding box specified by C{anchor} is at location (C{x}, 308 C{y}). 309 310 @param x,y: The location that the canvas widget should be moved 311 to. 312 @param anchor: The corner or side of the canvas widget that 313 should be moved to the specified location. C{'N'} 314 specifies the top center; C{'NE'} specifies the top right 315 corner; etc. 316 """ 317 x1,y1,x2,y2 = self.bbox() 318 if anchor == 'NW': self.move(x-x1, y-y1) 319 if anchor == 'N': self.move(x-x1/2-x2/2, y-y1) 320 if anchor == 'NE': self.move(x-x2, y-y1) 321 if anchor == 'E': self.move(x-x2, y-y1/2-y2/2) 322 if anchor == 'SE': self.move(x-x2, y-y2) 323 if anchor == 'S': self.move(x-x1/2-x2/2, y-y2) 324 if anchor == 'SW': self.move(x-x1, y-y2) 325 if anchor == 'W': self.move(x-x1, y-y1/2-y2/2)
326
327 - def destroy(self):
328 """ 329 Remove this C{CanvasWidget} from its C{Canvas}. After a 330 C{CanvasWidget} has been destroyed, it should not be accessed. 331 332 Note that you only need to destroy a top-level 333 C{CanvasWidget}; its child widgets will be destroyed 334 automatically. If you destroy a non-top-level 335 C{CanvasWidget}, then the entire top-level widget will be 336 destroyed. 337 338 @raise ValueError: if this C{CanvasWidget} has a parent. 339 @rtype: C{None} 340 """ 341 if self.__parent is not None: 342 self.__parent.destroy() 343 return 344 345 for tag in self.tags(): 346 self.__canvas.tag_unbind(tag, '<ButtonPress-1>') 347 self.__canvas.tag_unbind(tag, '<ButtonPress-2>') 348 self.__canvas.tag_unbind(tag, '<ButtonPress-3>') 349 self.__canvas.delete(*self.tags()) 350 self.__canvas = None
351
352 - def update(self, child):
353 """ 354 Update the graphical display of this canvas widget, and all of 355 its ancestors, in response to a change in one of this canvas 356 widget's children. 357 358 @param child: The child widget that changed. 359 @type child: C{CanvasWidget} 360 """ 361 if self.__hidden or child.__hidden: return 362 # If we're already updating, then do nothing. This prevents 363 # infinite loops when _update modifies its children. 364 if self.__updating: return 365 self.__updating = 1 366 367 # Update this CanvasWidget. 368 self._update(child) 369 370 # Propagate update request to the parent. 371 if self.__parent: self.__parent.update(self) 372 373 # We're done updating. 374 self.__updating = 0
375
376 - def manage(self):
377 """ 378 Arrange this canvas widget and all of its descendants. 379 380 @rtype: C{None} 381 """ 382 if self.__hidden: return 383 for child in self.__children: child.manage() 384 self._manage()
385
386 - def tags(self):
387 """ 388 @return: a list of the canvas tags for all graphical 389 elements managed by this canvas widget, including 390 graphical elements managed by its child widgets. 391 @rtype: C{list} of C{int} 392 """ 393 if self.__canvas is None: 394 raise ValueError('Attempt to access a destroyed canvas widget') 395 tags = [] 396 tags += self._tags() 397 for child in self.__children: 398 tags += child.tags() 399 return tags
400
401 - def __setitem__(self, attr, value):
402 """ 403 Set the value of the attribute C{attr} to C{value}. See the 404 class documentation for a list of attributes supported by this 405 canvas widget. 406 407 @rtype: C{None} 408 """ 409 if attr == 'draggable': 410 self.__draggable = value 411 else: 412 raise ValueError('Unknown attribute %r' % attr)
413
414 - def __getitem__(self, attr):
415 """ 416 @return: the value of the attribute C{attr}. See the class 417 documentation for a list of attributes supported by this 418 canvas widget. 419 @rtype: (any) 420 """ 421 if attr == 'draggable': 422 return self.__draggable 423 else: 424 raise ValueError('Unknown attribute %r' % attr)
425
426 - def __repr__(self):
427 """ 428 @return: a string representation of this canvas widget. 429 @rtype: C{string} 430 """ 431 return '<%s>' % self.__class__.__name__
432
433 - def hide(self):
434 """ 435 Temporarily hide this canvas widget. 436 437 @rtype: C{None} 438 """ 439 self.__hidden = 1 440 for tag in self.tags(): 441 self.__canvas.itemconfig(tag, state='hidden')
442
443 - def show(self):
444 """ 445 Show a hidden canvas widget. 446 447 @rtype: C{None} 448 """ 449 self.__hidden = 0 450 for tag in self.tags(): 451 self.__canvas.itemconfig(tag, state='normal')
452
453 - def hidden(self):
454 """ 455 @return: True if this canvas widget is hidden. 456 @rtype: C{boolean} 457 """ 458 return self.__hidden
459 460 ##////////////////////////////////////////////////////// 461 ## Callback interface 462 ##////////////////////////////////////////////////////// 463
464 - def bind_click(self, callback, button=1):
465 """ 466 Register a new callback that will be called whenever this 467 C{CanvasWidget} is clicked on. 468 469 @type callback: C{function} 470 @param callback: The callback function that will be called 471 whenever this C{CanvasWidget} is clicked. This function 472 will be called with this C{CanvasWidget} as its argument. 473 @type button: C{int} 474 @param button: Which button the user should use to click on 475 this C{CanvasWidget}. Typically, this should be 1 (left 476 button), 3 (right button), or 2 (middle button). 477 """ 478 self.__callbacks[button] = callback
479
480 - def bind_drag(self, callback):
481 """ 482 Register a new callback that will be called after this 483 C{CanvasWidget} is dragged. This implicitly makes this 484 C{CanvasWidget} draggable. 485 486 @type callback: C{function} 487 @param callback: The callback function that will be called 488 whenever this C{CanvasWidget} is clicked. This function 489 will be called with this C{CanvasWidget} as its argument. 490 """ 491 self.__draggable = 1 492 self.__callbacks['drag'] = callback
493
494 - def unbind_click(self, button=1):
495 """ 496 Remove a callback that was registered with C{bind_click}. 497 498 @type button: C{int} 499 @param button: Which button the user should use to click on 500 this C{CanvasWidget}. Typically, this should be 1 (left 501 button), 3 (right button), or 2 (middle button). 502 """ 503 try: del self.__callbacks[button] 504 except: pass
505
506 - def unbind_drag(self):
507 """ 508 Remove a callback that was registered with C{bind_drag}. 509 """ 510 try: del self.__callbacks['drag'] 511 except: pass
512 513 ##////////////////////////////////////////////////////// 514 ## Callback internals 515 ##////////////////////////////////////////////////////// 516
517 - def __press_cb(self, event):
518 """ 519 Handle a button-press event: 520 - record the button press event in C{self.__press} 521 - register a button-release callback. 522 - if this CanvasWidget or any of its ancestors are 523 draggable, then register the appropriate motion callback. 524 """ 525 # If we're already waiting for a button release, then ignore 526 # this new button press. 527 if (self.__canvas.bind('<ButtonRelease-1>') or 528 self.__canvas.bind('<ButtonRelease-2>') or 529 self.__canvas.bind('<ButtonRelease-3>')): 530 return 531 532 # Unbind motion (just in case; this shouldn't be necessary) 533 self.__canvas.unbind('<Motion>') 534 535 # Record the button press event. 536 self.__press = event 537 538 # If any ancestor is draggable, set up a motion callback. 539 # (Only if they pressed button number 1) 540 if event.num == 1: 541 widget = self 542 while widget is not None: 543 if widget['draggable']: 544 widget.__start_drag(event) 545 break 546 widget = widget.parent() 547 548 # Set up the button release callback. 549 self.__canvas.bind('<ButtonRelease-%d>' % event.num, 550 self.__release_cb)
551
552 - def __start_drag(self, event):
553 """ 554 Begin dragging this object: 555 - register a motion callback 556 - record the drag coordinates 557 """ 558 self.__canvas.bind('<Motion>', self.__motion_cb) 559 self.__drag_x = event.x 560 self.__drag_y = event.y
561
562 - def __motion_cb(self, event):
563 """ 564 Handle a motion event: 565 - move this object to the new location 566 - record the new drag coordinates 567 """ 568 self.move(event.x-self.__drag_x, event.y-self.__drag_y) 569 self.__drag_x = event.x 570 self.__drag_y = event.y
571
572 - def __release_cb(self, event):
573 """ 574 Handle a release callback: 575 - unregister motion & button release callbacks. 576 - decide whether they clicked, dragged, or cancelled 577 - call the appropriate handler. 578 """ 579 # Unbind the button release & motion callbacks. 580 self.__canvas.unbind('<ButtonRelease-%d>' % event.num) 581 self.__canvas.unbind('<Motion>') 582 583 # Is it a click or a drag? 584 if (event.time - self.__press.time < 100 and 585 abs(event.x-self.__press.x) + abs(event.y-self.__press.y) < 5): 586 # Move it back, if we were dragging. 587 if self.__draggable and event.num == 1: 588 self.move(self.__press.x - self.__drag_x, 589 self.__press.y - self.__drag_y) 590 self.__click(event.num) 591 elif event.num == 1: 592 self.__drag() 593 594 self.__press = None
595
596 - def __drag(self):
597 """ 598 If this C{CanvasWidget} has a drag callback, then call it; 599 otherwise, find the closest ancestor with a drag callback, and 600 call it. If no ancestors have a drag callback, do nothing. 601 """ 602 if self.__draggable: 603 if self.__callbacks.has_key('drag'): 604 cb = self.__callbacks['drag'] 605 try: 606 cb(self) 607 except: 608 print 'Error in drag callback for %r' % self 609 elif self.__parent is not None: 610 self.__parent.__drag()
611
612 - def __click(self, button):
613 """ 614 If this C{CanvasWidget} has a drag callback, then call it; 615 otherwise, find the closest ancestor with a click callback, and 616 call it. If no ancestors have a click callback, do nothing. 617 """ 618 if self.__callbacks.has_key(button): 619 cb = self.__callbacks[button] 620 #try: 621 cb(self) 622 #except: 623 # print 'Error in click callback for %r' % self 624 # raise 625 elif self.__parent is not None: 626 self.__parent.__click(button)
627 628 ##////////////////////////////////////////////////////// 629 ## Child/parent Handling 630 ##////////////////////////////////////////////////////// 631
632 - def _add_child_widget(self, child):
633 """ 634 Register a hierarchical child widget. The child will be 635 considered part of this canvas widget for purposes of user 636 interaction. C{_add_child_widget} has two direct effects: 637 - It sets C{child}'s parent to this canvas widget. 638 - It adds C{child} to the list of canvas widgets returned by 639 the C{child_widgets} member function. 640 641 @param child: The new child widget. C{child} must not already 642 have a parent. 643 @type child: C{CanvasWidget} 644 """ 645 if not hasattr(self, '_CanvasWidget__children'): self.__children = [] 646 if child.__parent is not None: 647 raise ValueError('%s already has a parent', child) 648 child.__parent = self 649 self.__children.append(child)
650
651 - def _remove_child_widget(self, child):
652 """ 653 Remove a hierarchical child widget. This child will no longer 654 be considered part of this canvas widget for purposes of user 655 interaction. C{_add_child_widget} has two direct effects: 656 - It sets C{child}'s parent to C{None}. 657 - It removes C{child} from the list of canvas widgets 658 returned by the C{child_widgets} member function. 659 660 @param child: The child widget to remove. C{child} must be a 661 child of this canvas widget. 662 @type child: C{CanvasWidget} 663 """ 664 self.__children.remove(child) 665 child.__parent = None
666 667 ##////////////////////////////////////////////////////// 668 ## Defined by subclass 669 ##////////////////////////////////////////////////////// 670
671 - def _tags(self):
672 """ 673 @return: a list of canvas tags for all graphical elements 674 managed by this canvas widget, not including graphical 675 elements managed by its child widgets. 676 @rtype: C{list} of C{int} 677 """ 678 raise AssertionError()
679
680 - def _manage(self):
681 """ 682 Arrange the child widgets of this canvas widget. This method 683 is called when the canvas widget is initially created. It is 684 also called if the user calls the C{manage} method on this 685 canvas widget or any of its ancestors. 686 687 @rtype: C{None} 688 """ 689 pass
690
691 - def _update(self, child):
692 """ 693 Update this canvas widget in response to a change in one of 694 its children. 695 696 @param child: The child that changed. 697 @type child: C{CanvasWidget} 698 @rtype: C{None} 699 """ 700 pass
701 702 ##////////////////////////////////////////////////////// 703 ## Basic widgets. 704 ##////////////////////////////////////////////////////// 705
706 -class TextWidget(CanvasWidget):
707 """ 708 A canvas widget that displays a single string of text. 709 710 Attributes: 711 - C{color}: the color of the text. 712 - C{font}: the font used to display the text. 713 - C{justify}: justification for multi-line texts. Valid values 714 are C{left}, C{center}, and C{right}. 715 - C{width}: the width of the text. If the text is wider than 716 this width, it will be line-wrapped at whitespace. 717 - C{draggable}: whether the text can be dragged by the user. 718 """
719 - def __init__(self, canvas, text, **attribs):
720 """ 721 Create a new text widget. 722 723 @type canvas: C{Tkinter.Canvas} 724 @param canvas: This canvas widget's canvas. 725 @type text: C{string} 726 @param text: The string of text to display. 727 @param attribs: The new canvas widget's attributes. 728 """ 729 self._text = text 730 self._tag = canvas.create_text(1, 1, text=text) 731 CanvasWidget.__init__(self, canvas, **attribs)
732
733 - def __setitem__(self, attr, value):
734 if attr in ('color', 'font', 'justify', 'width'): 735 if attr == 'color': attr = 'fill' 736 self.canvas().itemconfig(self._tag, {attr:value}) 737 else: 738 CanvasWidget.__setitem__(self, attr, value)
739
740 - def __getitem__(self, attr):
741 if attr == 'width': 742 return int(self.canvas().itemcget(self._tag, attr)) 743 elif attr in ('color', 'font', 'justify'): 744 if attr == 'color': attr = 'fill' 745 return self.canvas().itemcget(self._tag, attr) 746 else: 747 return CanvasWidget.__getitem__(self, attr)
748
749 - def _tags(self): return [self._tag]
750
751 - def text(self):
752 """ 753 @return: The text displayed by this text widget. 754 @rtype: C{string} 755 """ 756 return self.canvas().itemcget(self._tag, 'TEXT')
757
758 - def set_text(self, text):
759 """ 760 Change the text that is displayed by this text widget. 761 762 @type text: C{string} 763 @param text: The string of text to display. 764 @rtype: C{None} 765 """ 766 self.canvas().itemconfig(self._tag, text=text) 767 if self.parent() is not None: 768 self.parent().update(self)
769
770 - def __repr__(self):
771 return '[Text: %r]' % self._text
772
773 -class SymbolWidget(TextWidget):
774 """ 775 A canvas widget that displays special symbols, such as the 776 negation sign and the exists operator. Symbols are specified by 777 name. Currently, the following symbol names are defined: C{neg}, 778 C{disj}, C{conj}, C{lambda}, C{merge}, C{forall}, C{exists}, 779 C{subseteq}, C{subset}, C{notsubset}, C{emptyset}, C{imp}, 780 C{rightarrow}, C{equal}, C{notequal}, C{epsilon}. 781 782 Attributes: 783 - C{color}: the color of the text. 784 - C{draggable}: whether the text can be dragged by the user. 785 786 @cvar SYMBOLS: A dictionary mapping from symbols to the character 787 in the C{symbol} font used to render them. 788 """ 789 SYMBOLS = {'neg':'\330', 'disj':'\332', 'conj': '\331', 790 'lambda': '\154', 'merge': '\304', 791 'forall': '\042', 'exists': '\044', 792 'subseteq': '\315', 'subset': '\314', 793 'notsubset': '\313', 'emptyset': '\306', 794 'imp': '\336', 'rightarrow': chr(222), #'\256', 795 'equal': '\75', 'notequal': '\271', 796 'intersection': '\307', 'union': '\310', 797 'epsilon': 'e', 798 } 799
800 - def __init__(self, canvas, symbol, **attribs):
801 """ 802 Create a new symbol widget. 803 804 @type canvas: C{Tkinter.Canvas} 805 @param canvas: This canvas widget's canvas. 806 @type symbol: C{string} 807 @param symbol: The name of the symbol to display. 808 @param attribs: The new canvas widget's attributes. 809 """ 810 attribs['font'] = 'symbol' 811 TextWidget.__init__(self, canvas, '', **attribs) 812 self.set_symbol(symbol)
813
814 - def symbol(self):
815 """ 816 @return: the name of the symbol that is displayed by this 817 symbol widget. 818 @rtype: C{string} 819 """ 820 return self._symbol
821
822 - def set_symbol(self, symbol):
823 """ 824 Change the symbol that is displayed by this symbol widget. 825 826 @type symbol: C{string} 827 @param symbol: The name of the symbol to display. 828 """ 829 if not SymbolWidget.SYMBOLS.has_key(symbol): 830 raise ValueError('Unknown symbol: %s' % symbol) 831 self._symbol = symbol 832 self.set_text(SymbolWidget.SYMBOLS[symbol])
833
834 - def __repr__(self):
835 return '[Symbol: %r]' % self._symbol
836 837 # A staticmethod that displays all symbols.
838 - def symbolsheet(size=20):
839 """ 840 Open a new Tkinter window that displays the entire alphabet 841 for the symbol font. This is useful for constructing the 842 L{SymbolWidget.SYMBOLS} dictionary. 843 """ 844 top = Tk() 845 def destroy(e, top=top): top.destroy() 846 top.bind('q', destroy) 847 Button(top, text='Quit', command=top.destroy).pack(side='bottom') 848 text = Text(top, font=('helvetica', -size), width=20, height=30) 849 text.pack(side='left') 850 sb=Scrollbar(top, command=text.yview) 851 text['yscrollcommand']=sb.set 852 sb.pack(side='right', fill='y') 853 text.tag_config('symbol', font=('symbol', -size)) 854 for i in range(256): 855 if i in (0,10): continue # null and newline 856 for k,v in SymbolWidget.SYMBOLS.items(): 857 if v == chr(i): 858 text.insert('end', '%-10s\t' % k) 859 break 860 else: 861 text.insert('end', '%-10d \t' % i) 862 text.insert('end', '[%s]\n' % chr(i), 'symbol') 863 top.mainloop()
864 symbolsheet = staticmethod(symbolsheet)
865 866
867 -class AbstractContainerWidget(CanvasWidget):
868 """ 869 An abstract class for canvas widgets that contain a single child, 870 such as C{BoxWidget} and C{OvalWidget}. Subclasses must define 871 a constructor, which should create any new graphical elements and 872 then call the C{AbstractCanvasContainer} constructor. Subclasses 873 must also define the C{_update} method and the C{_tags} method; 874 and any subclasses that define attributes should define 875 C{__setitem__} and C{__getitem__}. 876 """
877 - def __init__(self, canvas, child, **attribs):
878 """ 879 Create a new container widget. This constructor should only 880 be called by subclass constructors. 881 882 @type canvas: C{Tkinter.Canvas} 883 @param canvas: This canvas widget's canvas. 884 @param child: The container's child widget. C{child} must not 885 have a parent. 886 @type child: C{CanvasWidget} 887 @param attribs: The new canvas widget's attributes. 888 """ 889 self._child = child 890 self._add_child_widget(child) 891 CanvasWidget.__init__(self, canvas, **attribs)
892
893 - def _manage(self):
894 self._update(self._child)
895
896 - def child(self):
897 """ 898 @return: The child widget contained by this container widget. 899 @rtype: C{CanvasWidget} 900 """ 901 return self._child
902
903 - def set_child(self, child):
904 """ 905 Change the child widget contained by this container widget. 906 907 @param child: The new child widget. C{child} must not have a 908 parent. 909 @type child: C{CanvasWidget} 910 @rtype: C{None} 911 """ 912 self._remove_child_widget(self._child) 913 self._add_child_widget(child) 914 self._child = child 915 self.update(child)
916
917 - def __repr__(self):
918 name = self.__class__.__name__ 919 if name[-6:] == 'Widget': name = name[:-6] 920 return '[%s: %r]' % (name, self._child)
921
922 -class BoxWidget(AbstractContainerWidget):
923 """ 924 A canvas widget that places a box around a child widget. 925 926 Attributes: 927 - C{fill}: The color used to fill the interior of the box. 928 - C{outline}: The color used to draw the outline of the box. 929 - C{width}: The width of the outline of the box. 930 - C{margin}: The number of pixels space left between the child 931 and the box. 932 - C{draggable}: whether the text can be dragged by the user. 933 """
934 - def __init__(self, canvas, child, **attribs):
935 """ 936 Create a new box widget. 937 938 @type canvas: C{Tkinter.Canvas} 939 @param canvas: This canvas widget's canvas. 940 @param child: The child widget. C{child} must not have a 941 parent. 942 @type child: C{CanvasWidget} 943 @param attribs: The new canvas widget's attributes. 944 """ 945 self._child = child 946 self._margin = 1 947 self._box = canvas.create_rectangle(1,1,1,1) 948 canvas.tag_lower(self._box) 949 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
950
951 - def __setitem__(self, attr, value):
952 if attr == 'margin': self._margin = value 953 elif attr in ('outline', 'fill', 'width'): 954 self.canvas().itemconfig(self._box, {attr:value}) 955 else: 956 CanvasWidget.__setitem__(self, attr, value)
957
958 - def __getitem__(self, attr):
959 if attr == 'margin': return self._margin 960 elif attr == 'width': 961 return float(self.canvas().itemcget(self._box, attr)) 962 elif attr in ('outline', 'fill', 'width'): 963 return self.canvas().itemcget(self._box, attr) 964 else: 965 return CanvasWidget.__getitem__(self, attr)
966
967 - def _update(self, child):
968 (x1, y1, x2, y2) = child.bbox() 969 margin = self._margin + self['width']/2 970 self.canvas().coords(self._box, x1-margin, y1-margin, 971 x2+margin, y2+margin)
972
973 - def _tags(self): return [self._box]
974
975 -class OvalWidget(AbstractContainerWidget):
976 """ 977 A canvas widget that places a oval around a child widget. 978 979 Attributes: 980 - C{fill}: The color used to fill the interior of the oval. 981 - C{outline}: The color used to draw the outline of the oval. 982 - C{width}: The width of the outline of the oval. 983 - C{margin}: The number of pixels space left between the child 984 and the oval. 985 - C{draggable}: whether the text can be dragged by the user. 986 - C{double}: If true, then a double-oval is drawn. 987 """
988 - def __init__(self, canvas, child, **attribs):
989 """ 990 Create a new oval widget. 991 992 @type canvas: C{Tkinter.Canvas} 993 @param canvas: This canvas widget's canvas. 994 @param child: The child widget. C{child} must not have a 995 parent. 996 @type child: C{CanvasWidget} 997 @param attribs: The new canvas widget's attributes. 998 """ 999 self._child = child 1000 self._margin = 1 1001 self._oval = canvas.create_oval(1,1,1,1) 1002 self._circle = attribs.pop('circle', False) 1003 self._double = attribs.pop('double', False) 1004 if self._double: 1005 self._oval2 = canvas.create_oval(1,1,1,1) 1006 else: 1007 self._oval2 = None 1008 canvas.tag_lower(self._oval) 1009 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
1010
1011 - def __setitem__(self, attr, value):
1012 c = self.canvas() 1013 if attr == 'margin': self._margin = value 1014 elif attr == 'double': 1015 if value==True and self._oval2 is None: 1016 # Copy attributes & position from self._oval. 1017 x1, y1, x2, y2 = c.bbox(self._oval) 1018 w = self['width']*2 1019 self._oval2 = c.create_oval(x1-w, y1-w, x2+w, y2+w, 1020 outline=c.itemcget(self._oval, 'outline'), 1021 width=c.itemcget(self._oval, 'width')) 1022 c.tag_lower(self._oval2) 1023 if value==False and self._oval2 is not None: 1024 c.delete(self._oval2) 1025 self._oval2 = None 1026 elif attr in ('outline', 'fill', 'width'): 1027 c.itemconfig(self._oval, {attr:value}) 1028 if self._oval2 is not None and attr!='fill': 1029 c.itemconfig(self._oval2, {attr:value}) 1030 if self._oval2 is not None and attr!='fill': 1031 self.canvas().itemconfig(self._oval2, {attr:value}) 1032 else: 1033 CanvasWidget.__setitem__(self, attr, value)
1034
1035 - def __getitem__(self, attr):
1036 if attr == 'margin': return self._margin 1037 elif attr == 'double': return self._double is not None 1038 elif attr == 'width': 1039 return float(self.canvas().itemcget(self._oval, attr)) 1040 elif attr in ('outline', 'fill', 'width'): 1041 return self.canvas().itemcget(self._oval, attr) 1042 else: 1043 return CanvasWidget.__getitem__(self, attr)
1044 1045 # The ratio between inscribed & circumscribed ovals 1046 RATIO = 1.4142135623730949 1047
1048 - def _update(self, child):
1049 R = OvalWidget.RATIO 1050 (x1, y1, x2, y2) = child.bbox() 1051 margin = self._margin 1052 1053 # If we're a circle, pretend our contents are square. 1054 if self._circle: 1055 dx, dy = abs(x1-x2), abs(y1-y2) 1056 if dx > dy: 1057 y = (y1+y2)/2 1058 y1, y2 = y-dx/2, y+dx/2 1059 elif dy > dx: 1060 x = (x1+x2)/2 1061 x1, x2 = x-dy/2, x+dy/2 1062 1063 # Find the four corners. 1064 left = int(( x1*(1+R) + x2*(1-R) ) / 2) 1065 right = left + int((x2-x1)*R) 1066 top = int(( y1*(1+R) + y2*(1-R) ) / 2) 1067 bot = top + int((y2-y1)*R) 1068 self.canvas().coords(self._oval, left-margin, top-margin, 1069 right+margin, bot+margin) 1070 if self._oval2 is not None: 1071 self.canvas().coords(self._oval2, left-margin+2, top-margin+2, 1072 right+margin-2, bot+margin-2)
1073
1074 - def _tags(self):
1075 if self._oval2 is None: 1076 return [self._oval] 1077 else: 1078 return [self._oval, self._oval2]
1079
1080 -class ParenWidget(AbstractContainerWidget):
1081 """ 1082 A canvas widget that places a pair of parenthases around a child 1083 widget. 1084 1085 Attributes: 1086 - C{color}: The color used to draw the parenthases. 1087 - C{width}: The width of the parenthases. 1088 - C{draggable}: whether the text can be dragged by the user. 1089 """
1090 - def __init__(self, canvas, child, **attribs):
1091 """ 1092 Create a new parenthasis widget. 1093 1094 @type canvas: C{Tkinter.Canvas} 1095 @param canvas: This canvas widget's canvas. 1096 @param child: The child widget. C{child} must not have a 1097 parent. 1098 @type child: C{CanvasWidget} 1099 @param attribs: The new canvas widget's attributes. 1100 """ 1101 self._child = child 1102 self._oparen = canvas.create_arc(1,1,1,1, style='arc', 1103 start=90, extent=180) 1104 self._cparen = canvas.create_arc(1,1,1,1, style='arc', 1105 start=-90, extent=180) 1106 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
1107
1108 - def __setitem__(self, attr, value):
1109 if attr == 'color': 1110 self.canvas().itemconfig(self._oparen, outline=value) 1111 self.canvas().itemconfig(self._cparen, outline=value) 1112 elif attr == 'width': 1113 self.canvas().itemconfig(self._oparen, width=value) 1114 self.canvas().itemconfig(self._cparen, width=value) 1115 else: 1116 CanvasWidget.__setitem__(self, attr, value)
1117
1118 - def __getitem__(self, attr):
1119 if attr == 'color': 1120 return self.canvas().itemcget(self._oparen, 'outline') 1121 elif attr == 'width': 1122 return self.canvas().itemcget(self._oparen, 'width') 1123 else: 1124 return CanvasWidget.__getitem__(self, attr)
1125
1126 - def _update(self, child):
1127 (x1, y1, x2, y2) = child.bbox() 1128 width = max((y2-y1)/6, 4) 1129 self.canvas().coords(self._oparen, x1-width, y1, x1+width, y2) 1130 self.canvas().coords(self._cparen, x2-width, y1, x2+width, y2)
1131
1132 - def _tags(self): return [self._oparen, self._cparen]
1133
1134 -class BracketWidget(AbstractContainerWidget):
1135 """ 1136 A canvas widget that places a pair of brackets around a child 1137 widget. 1138 1139 Attributes: 1140 - C{color}: The color used to draw the brackets. 1141 - C{width}: The width of the brackets. 1142 - C{draggable}: whether the text can be dragged by the user. 1143 """
1144 - def __init__(self, canvas, child, **attribs):
1145 """ 1146 Create a new bracket widget. 1147 1148 @type canvas: C{Tkinter.Canvas} 1149 @param canvas: This canvas widget's canvas. 1150 @param child: The child widget. C{child} must not have a 1151 parent. 1152 @type child: C{CanvasWidget} 1153 @param attribs: The new canvas widget's attributes. 1154 """ 1155 self._child = child 1156 self._obrack = canvas.create_line(1,1,1,1,1,1,1,1) 1157 self._cbrack = canvas.create_line(1,1,1,1,1,1,1,1) 1158 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
1159
1160 - def __setitem__(self, attr, value):
1161 if attr == 'color': 1162 self.canvas().itemconfig(self._obrack, fill=value) 1163 self.canvas().itemconfig(self._cbrack, fill=value) 1164 elif attr == 'width': 1165 self.canvas().itemconfig(self._obrack, width=value) 1166 self.canvas().itemconfig(self._cbrack, width=value) 1167 else: 1168 CanvasWidget.__setitem__(self, attr, value)
1169
1170 - def __getitem__(self, attr):
1171 if attr == 'color': 1172 return self.canvas().itemcget(self._obrack, 'outline') 1173 elif attr == 'width': 1174 return self.canvas().itemcget(self._obrack, 'width') 1175 else: 1176 return CanvasWidget.__getitem__(self, attr)
1177
1178 - def _update(self, child):
1179 (x1, y1, x2, y2) = child.bbox() 1180 width = max((y2-y1)/8, 2) 1181 self.canvas().coords(self._obrack, x1, y1, x1-width, y1, 1182 x1-width, y2, x1, y2) 1183 self.canvas().coords(self._cbrack, x2, y1, x2+width, y1, 1184 x2+width, y2, x2, y2)
1185
1186 - def _tags(self): return [self._obrack, self._cbrack]
1187
1188 -class SequenceWidget(CanvasWidget):
1189 """ 1190 A canvas widget that keeps a list of canvas widgets in a 1191 horizontal line. 1192 1193 Attributes: 1194 - C{align}: The vertical alignment of the children. Possible 1195 values are C{'top'}, C{'center'}, and C{'bottom'}. By 1196 default, children are center-aligned. 1197 - C{space}: The amount of horizontal space to place between 1198 children. By default, one pixel of space is used. 1199 - C{ordered}: If true, then keep the children in their 1200 original order. 1201 """
1202 - def __init__(self, canvas, *children, **attribs):
1203 """ 1204 Create a new sequence widget. 1205 1206 @type canvas: C{Tkinter.Canvas} 1207 @param canvas: This canvas widget's canvas. 1208 @param children: The widgets that should be aligned 1209 horizontally. Each child must not have a parent. 1210 @type children: C{list} of C{CanvasWidget} 1211 @param attribs: The new canvas widget's attributes. 1212 """ 1213 self._align = 'center' 1214 self._space = 1 1215 self._ordered = False 1216 self._children = list(children) 1217 for child in children: self._add_child_widget(child) 1218 CanvasWidget.__init__(self, canvas, **attribs)
1219
1220 - def __setitem__(self, attr, value):
1221 if attr == 'align': 1222 if value not in ('top', 'bottom', 'center'): 1223 raise ValueError('Bad alignment: %r' % value) 1224 self._align = value 1225 elif attr == 'space': self._space = value 1226 elif attr == 'ordered': self._ordered = value 1227 else: CanvasWidget.__setitem__(self, attr, value)
1228
1229 - def __getitem__(self, attr):
1230 if attr == 'align': return value 1231 elif attr == 'space': return self._space 1232 elif attr == 'ordered': return self._ordered 1233 else: return CanvasWidget.__getitem__(self, attr)
1234
1235 - def _tags(self): return []
1236
1237 - def _yalign(self, top, bot):
1238 if self._align == 'top': return top 1239 if self._align == 'bottom': return bot 1240 if self._align == 'center': return (top+bot)/2
1241
1242 - def _update(self, child):
1243 # Align all children with child. 1244 (left, top, right, bot) = child.bbox() 1245 y = self._yalign(top, bot) 1246 for c in self._children: 1247 (x1, y1, x2, y2) = c.bbox() 1248 c.move(0, y-self._yalign(y1,y2)) 1249 1250 if self._ordered and len(self._children) > 1: 1251 index = self._children.index(child) 1252 1253 x = right + self._space 1254 for i in range(index+1, len(self._children)): 1255 (x1, y1, x2, y2) = self._children[i].bbox() 1256 if x > x1: 1257 self._children[i].move(x-x1, 0) 1258 x += x2-x1 + self._space 1259 1260 x = left - self._space 1261 for i in range(index-1, -1, -1): 1262 (x1, y1, x2, y2) = self._children[i].bbox() 1263 if x < x2: 1264 self._children[i].move(x-x2, 0) 1265 x -= x2-x1 + self._space
1266
1267 - def _manage(self):
1268 if len(self._children) == 0: return 1269 child = self._children[0] 1270 1271 # Align all children with child. 1272 (left, top, right, bot) = child.bbox() 1273 y = self._yalign(top, bot) 1274 1275 index = self._children.index(child) 1276 1277 # Line up children to the right of child. 1278 x = right + self._space 1279 for i in range(index+1, len(self._children)): 1280 (x1, y1, x2, y2) = self._children[i].bbox() 1281 self._children[i].move(x-x1, y-self._yalign(y1,y2)) 1282 x += x2-x1 + self._space 1283 1284 # Line up children to the left of child. 1285 x = left - self._space 1286 for i in range(index-1, -1, -1): 1287 (x1, y1, x2, y2) = self._children[i].bbox() 1288 self._children[i].move(x-x2, y-self._yalign(y1,y2)) 1289 x -= x2-x1 + self._space
1290
1291 - def __repr__(self):
1292 return '[Sequence: ' + `self._children`[1:-1]+']'
1293 1294 # Provide an alias for the child_widgets() member. 1295 children = CanvasWidget.child_widgets 1296
1297 - def replace_child(self, oldchild, newchild):
1298 """ 1299 Replace the child canvas widget C{oldchild} with C{newchild}. 1300 C{newchild} must not have a parent. C{oldchild}'s parent will 1301 be set to C{None}. 1302 1303 @type oldchild: C{CanvasWidget} 1304 @param oldchild: The child canvas widget to remove. 1305 @type newchild: C{CanvasWidget} 1306 @param newchild: The canvas widget that should replace 1307 C{oldchild}. 1308 """ 1309 index = self._children.index(oldchild) 1310 self._children[index] = newchild 1311 self._remove_child_widget(oldchild) 1312 self._add_child_widget(newchild) 1313 self.update(newchild)
1314
1315 - def remove_child(self, child):
1316 """ 1317 Remove the given child canvas widget. C{child}'s parent will 1318 be set ot None. 1319 1320 @type child: C{CanvasWidget} 1321 @param child: The child canvas widget to remove. 1322 """ 1323 index = self._children.index(child) 1324 del self._children[index] 1325 self._remove_child_widget(child) 1326 if len(self._children) > 0: 1327 self.update(self._children[0])
1328
1329 - def insert_child(self, index, child):
1330 """ 1331 Insert a child canvas widget before a given index. 1332 1333 @type child: C{CanvasWidget} 1334 @param child: The canvas widget that should be inserted. 1335 @type index: C{int} 1336 @param index: The index where the child widget should be 1337 inserted. In particular, the index of C{child} will be 1338 C{index}; and the index of any children whose indices were 1339 greater than equal to C{index} before C{child} was 1340 inserted will be incremented by one. 1341 """ 1342 self._children.insert(index, child) 1343 self._add_child_widget(child)
1344
1345 -class StackWidget(CanvasWidget):
1346 """ 1347 A canvas widget that keeps a list of canvas widgets in a vertical 1348 line. 1349 1350 Attributes: 1351 - C{align}: The horizontal alignment of the children. Possible 1352 values are C{'left'}, C{'center'}, and C{'right'}. By 1353 default, children are center-aligned. 1354 - C{space}: The amount of vertical space to place between 1355 children. By default, one pixel of space is used. 1356 - C{ordered}: If true, then keep the children in their 1357 original order. 1358 """
1359 - def __init__(self, canvas, *children, **attribs):
1360 """ 1361 Create a new stack widget. 1362 1363 @type canvas: C{Tkinter.Canvas} 1364 @param canvas: This canvas widget's canvas. 1365 @param children: The widgets that should be aligned 1366 vertically. Each child must not have a parent. 1367 @type children: C{list} of C{CanvasWidget} 1368 @param attribs: The new canvas widget's attributes. 1369 """ 1370 self._align = 'center' 1371 self._space = 1 1372 self._ordered = False 1373 self._children = list(children) 1374 for child in children: self._add_child_widget(child) 1375 CanvasWidget.__init__(self, canvas, **attribs)
1376
1377 - def __setitem__(self, attr, value):
1378 if attr == 'align': 1379 if value not in ('left', 'right', 'center'): 1380 raise ValueError('Bad alignment: %r' % value) 1381 self._align = value 1382 elif attr == 'space': self._space = value 1383 elif attr == 'ordered': self._ordered = value 1384 else: CanvasWidget.__setitem__(self, attr, value)
1385
1386 - def __getitem__(self, attr):
1387 if attr == 'align': return value 1388 elif attr == 'space': return self._space 1389 elif attr == 'ordered': return self._ordered 1390 else: return CanvasWidget.__getitem__(self, attr)
1391
1392 - def _tags(self): return []
1393
1394 - def _xalign(self, left, right):
1395 if self._align == 'left': return left 1396 if self._align == 'right': return right 1397 if self._align == 'center': return (left+right)/2
1398
1399 - def _update(self, child):
1400 # Align all children with child. 1401 (left, top, right, bot) = child.bbox() 1402 x = self._xalign(left, right) 1403 for c in self._children: 1404 (x1, y1, x2, y2) = c.bbox() 1405 c.move(x-self._xalign(x1,x2), 0) 1406 1407 if self._ordered and len(self._children) > 1: 1408 index = self._children.index(child) 1409 1410 y = bot + self._space 1411 for i in range(index+1, len(self._children)): 1412 (x1, y1, x2, y2) = self._children[i].bbox() 1413 if y > y1: 1414 self._children[i].move(0, y-y1) 1415 y += y2-y1 + self._space 1416 1417 y = top - self._space 1418 for i in range(index-1, -1, -1): 1419 (x1, y1, x2, y2) = self._children[i].bbox() 1420 if y < y2: 1421 self._children[i].move(0, y-y2) 1422 y -= y2-y1 + self._space
1423
1424 - def _manage(self):
1425 if len(self._children) == 0: return 1426 child = self._children[0] 1427 1428 # Align all children with child. 1429 (left, top, right, bot) = child.bbox() 1430 x = self._xalign(left, right) 1431 1432 index = self._children.index(child) 1433 1434 # Line up children below the child. 1435 y = bot + self._space 1436 for i in range(index+1, len(self._children)): 1437 (x1, y1, x2, y2) = self._children[i].bbox() 1438 self._children[i].move(x-self._xalign(x1,x2), y-y1) 1439 y += y2-y1 + self._space 1440 1441 # Line up children above the child. 1442 y = top - self._space 1443 for i in range(index-1, -1, -1): 1444 (x1, y1, x2, y2) = self._children[i].bbox() 1445 self._children[i].move(x-self._xalign(x1,x2), y-y2) 1446 y -= y2-y1 + self._space
1447
1448 - def __repr__(self):
1449 return '[Stack: ' + `self._children`[1:-1]+']'
1450 1451 # Provide an alias for the child_widgets() member. 1452 children = CanvasWidget.child_widgets 1453
1454 - def replace_child(self, oldchild, newchild):
1455 """ 1456 Replace the child canvas widget C{oldchild} with C{newchild}. 1457 C{newchild} must not have a parent. C{oldchild}'s parent will 1458 be set to C{None}. 1459 1460 @type oldchild: C{CanvasWidget} 1461 @param oldchild: The child canvas widget to remove. 1462 @type newchild: C{CanvasWidget} 1463 @param newchild: The canvas widget that should replace 1464 C{oldchild}. 1465 """ 1466 index = self._children.index(oldchild) 1467 self._children[index] = newchild 1468 self._remove_child_widget(oldchild) 1469 self._add_child_widget(newchild) 1470 self.update(newchild)
1471
1472 - def remove_child(self, child):
1473 """ 1474 Remove the given child canvas widget. C{child}'s parent will 1475 be set ot None. 1476 1477 @type child: C{CanvasWidget} 1478 @param child: The child canvas widget to remove. 1479 """ 1480 index = self._children.index(child) 1481 del self._children[index] 1482 self._remove_child_widget(child) 1483 if len(self._children) > 0: 1484 self.update(self._children[0])
1485
1486 - def insert_child(self, index, child):
1487 """ 1488 Insert a child canvas widget before a given index. 1489 1490 @type child: C{CanvasWidget} 1491 @param child: The canvas widget that should be inserted. 1492 @type index: C{int} 1493 @param index: The index where the child widget should be 1494 inserted. In particular, the index of C{child} will be 1495 C{index}; and the index of any children whose indices were 1496 greater than equal to C{index} before C{child} was 1497 inserted will be incremented by one. 1498 """ 1499 self._children.insert(index, child) 1500 self._add_child_widget(child)
1501
1502 -class SpaceWidget(CanvasWidget):
1503 """ 1504 A canvas widget that takes up space but does not display 1505 anything. C{SpaceWidget}s can be used to add space between 1506 elements. Each space widget is characterized by a width and a 1507 height. If you wish to only create horizontal space, then use a 1508 height of zero; and if you wish to only create vertical space, use 1509 a width of zero. 1510 """
1511 - def __init__(self, canvas, width, height, **attribs):
1512 """ 1513 Create a new space widget. 1514 1515 @type canvas: C{Tkinter.Canvas} 1516 @param canvas: This canvas widget's canvas. 1517 @type width: C{int} 1518 @param width: The width of the new space widget. 1519 @type height: C{int} 1520 @param height: The height of the new space widget. 1521 @param attribs: The new canvas widget's attributes. 1522 """ 1523 # For some reason, 1524 if width > 4: width -= 4 1525 if height > 4: height -= 4 1526 self._tag = canvas.create_line(1, 1, width, height, fill='') 1527 CanvasWidget.__init__(self, canvas, **attribs)
1528 1529 # note: width() and height() are already defined by CanvasWidget.
1530 - def set_width(self, width):
1531 """ 1532 Change the width of this space widget. 1533 1534 @param width: The new width. 1535 @type width: C{int} 1536 @rtype: C{None} 1537 """ 1538 [x1, y1, x2, y2] = self.bbox() 1539 self.canvas().coords(self._tag, x1, y1, x1+width, y2)
1540
1541 - def set_height(self, height):
1542 """ 1543 Change the height of this space widget. 1544 1545 @param height: The new height. 1546 @type height: C{int} 1547 @rtype: C{None} 1548 """ 1549 [x1, y1, x2, y2] = self.bbox() 1550 self.canvas().coords(self._tag, x1, y1, x2, y1+height)
1551
1552 - def _tags(self): return [self._tag]
1553
1554 - def __repr__(self): return '[Space]'
1555
1556 -class ScrollWatcherWidget(CanvasWidget):
1557 """ 1558 A special canvas widget that adjusts its C{Canvas}'s scrollregion 1559 to always include the bounding boxes of all of its children. The 1560 scroll-watcher widget will only increase the size of the 1561 C{Canvas}'s scrollregion; it will never decrease it. 1562 """
1563 - def __init__(self, canvas, *children, **attribs):
1564 """ 1565 Create a new scroll-watcher widget. 1566 1567 @type canvas: C{Tkinter.Canvas} 1568 @param canvas: This canvas widget's canvas. 1569 @type children: C{list} of C{CanvasWidget} 1570 @param children: The canvas widgets watched by the 1571 scroll-watcher. The scroll-watcher will ensure that these 1572 canvas widgets are always contained in their canvas's 1573 scrollregion. 1574 @param attribs: The new canvas widget's attributes. 1575 """ 1576 for child in children: self._add_child_widget(child) 1577 CanvasWidget.__init__(self, canvas, **attribs)
1578
1579 - def add_child(self, canvaswidget):
1580 """ 1581 Add a new canvas widget to the scroll-watcher. The 1582 scroll-watcher will ensure that the new canvas widget is 1583 always contained in its canvas's scrollregion. 1584 1585 @param canvaswidget: The new canvas widget. 1586 @type canvaswidget: C{CanvasWidget} 1587 @rtype: C{None} 1588 """ 1589 self._add_child_widget(canvaswidget) 1590 self.update(canvaswidget)
1591
1592 - def remove_child(self, canvaswidget):
1593 """ 1594 Remove a canvas widget from the scroll-watcher. The 1595 scroll-watcher will no longer ensure that the new canvas 1596 widget is always contained in its canvas's scrollregion. 1597 1598 @param canvaswidget: The canvas widget to remove. 1599 @type canvaswidget: C{CanvasWidget} 1600 @rtype: C{None} 1601 """ 1602 self._remove_child_widget(canvaswidget)
1603
1604 - def _tags(self): return []
1605
1606 - def _update(self, child):
1608
1609 - def _adjust_scrollregion(self):
1610 """ 1611 Adjust the scrollregion of this scroll-watcher's C{Canvas} to 1612 include the bounding boxes of all of its children. 1613 """ 1614 bbox = self.bbox() 1615 canvas = self.canvas() 1616 scrollregion = [int(n) for n in canvas['scrollregion'].split()] 1617 if len(scrollregion) != 4: return 1618 if (bbox[0] < scrollregion[0] or bbox[1] < scrollregion[1] or 1619 bbox[2] > scrollregion[2] or bbox[3] > scrollregion[3]): 1620 scrollregion = ('%d %d %d %d' % 1621 (min(bbox[0], scrollregion[0]), 1622 min(bbox[1], scrollregion[1]), 1623 max(bbox[2], scrollregion[2]), 1624 max(bbox[3], scrollregion[3]))) 1625 canvas['scrollregion'] = scrollregion
1626 1627 ##////////////////////////////////////////////////////// 1628 ## Canvas Frame 1629 ##////////////////////////////////////////////////////// 1630
1631 -class CanvasFrame(object):
1632 """ 1633 A C{Tkinter} frame containing a canvas and scrollbars. 1634 C{CanvasFrame} uses a C{ScrollWatcherWidget} to ensure that all of 1635 the canvas widgets contained on its canvas are within its 1636 scrollregion. In order for C{CanvasFrame} to make these checks, 1637 all canvas widgets must be registered with C{add_widget} when they 1638 are added to the canvas; and destroyed with C{destroy_widget} when 1639 they are no longer needed. 1640 1641 If a C{CanvasFrame} is created with no parent, then it will create 1642 its own main window, including a "Done" button and a "Print" 1643 button. 1644 """
1645 - def __init__(self, parent=None, **kw):
1646 """ 1647 Create a new C{CanvasFrame}. 1648 1649 @type parent: C{Tkinter.BaseWidget} or C{Tkinter.Tk} 1650 @param parent: The parent C{Tkinter} widget. If no parent is 1651 specified, then C{CanvasFrame} will create a new main 1652 window. 1653 @param kw: Keyword arguments for the new C{Canvas}. See the 1654 documentation for C{Tkinter.Canvas} for more information. 1655 """ 1656 # If no parent was given, set up a top-level window. 1657 if parent is None: 1658 self._parent = Tk() 1659 self._parent.title('NLTK') 1660 self._parent.bind('<Control-p>', lambda e: self.print_to_file()) 1661 self._parent.bind('<Control-x>', self.destroy) 1662 self._parent.bind('<Control-q>', self.destroy) 1663 else: 1664 self._parent = parent 1665 1666 # Create a frame for the canvas & scrollbars 1667 self._frame = frame = Frame(self._parent) 1668 self._canvas = canvas = Canvas(frame, **kw) 1669 xscrollbar = Scrollbar(self._frame, orient='horizontal') 1670 yscrollbar = Scrollbar(self._frame, orient='vertical') 1671 xscrollbar['command'] = canvas.xview 1672 yscrollbar['command'] = canvas.yview 1673 canvas['xscrollcommand'] = xscrollbar.set 1674 canvas['yscrollcommand'] = yscrollbar.set 1675 yscrollbar.pack(fill='y', side='right') 1676 xscrollbar.pack(fill='x', side='bottom') 1677 canvas.pack(expand=1, fill='both', side='left') 1678 1679 # Set initial scroll region. 1680 scrollregion = '0 0 %s %s' % (canvas['width'], canvas['height']) 1681 canvas['scrollregion'] = scrollregion 1682 1683 self._scrollwatcher = ScrollWatcherWidget(canvas) 1684 1685 # If no parent was given, pack the frame, and add a menu. 1686 if parent is None: 1687 self.pack(expand=1, fill='both') 1688 self._init_menubar()
1689
1690 - def _init_menubar(self):
1691 menubar = Menu(self._parent) 1692 1693 filemenu = Menu(menubar, tearoff=0) 1694 filemenu.add_command(label='Print to Postscript', underline=0, 1695 command=self.print_to_file, accelerator='Ctrl-p') 1696 filemenu.add_command(label='Exit', underline=1, 1697 command=self.destroy, accelerator='Ctrl-x') 1698 menubar.add_cascade(label='File', underline=0, menu=filemenu) 1699 1700 self._parent.config(menu=menubar)
1701
1702 - def print_to_file(self, filename=None):
1703 """ 1704 Print the contents of this C{CanvasFrame} to a postscript 1705 file. If no filename is given, then prompt the user for one. 1706 1707 @param filename: The name of the file to print the tree to. 1708 @type filename: C{string} 1709 @rtype: C{None} 1710 """ 1711 if filename is None: 1712 from tkFileDialog import asksaveasfilename 1713 ftypes = [('Postscript files', '.ps'), 1714 ('All files', '*')] 1715 filename = asksaveasfilename(filetypes=ftypes, 1716 defaultextension='.ps') 1717 if not filename: return 1718 (x0, y0, w, h) = self.scrollregion() 1719 self._canvas.postscript(file=filename, x=x0, y=y0, 1720 width=w+2, height=h+2, 1721 pagewidth=w+2, # points = 1/72 inch 1722 pageheight=h+2, # points = 1/72 inch 1723 pagex=0, pagey=0)
1724
1725 - def scrollregion(self):
1726 """ 1727 @return: The current scroll region for the canvas managed by 1728 this C{CanvasFrame}. 1729 @rtype: 4-tuple of C{int} 1730 """ 1731 (x1, y1, x2, y2) = self._canvas['scrollregion'].split() 1732 return (int(x1), int(y1), int(x2), int(y2))
1733
1734 - def canvas(self):
1735 """ 1736 @return: The canvas managed by this C{CanvasFrame}. 1737 @rtype: C{Tkinter.Canvas} 1738 """ 1739 return self._canvas
1740
1741 - def add_widget(self, canvaswidget, x=None, y=None):
1742 """ 1743 Register a canvas widget with this C{CanvasFrame}. The 1744 C{CanvasFrame} will ensure that this canvas widget is always 1745 within the C{Canvas}'s scrollregion. If no coordinates are 1746 given for the canvas widget, then the C{CanvasFrame} will 1747 attempt to find a clear area of the canvas for it. 1748 1749 @type canvaswidget: C{CanvasWidget} 1750 @param canvaswidget: The new canvas widget. C{canvaswidget} 1751 must have been created on this C{CanvasFrame}'s canvas. 1752 @type x: C{int} 1753 @param x: The initial x coordinate for the upper left hand 1754 corner of C{canvaswidget}, in the canvas's coordinate 1755 space. 1756 @type y: C{int} 1757 @param y: The initial y coordinate for the upper left hand 1758 corner of C{canvaswidget}, in the canvas's coordinate 1759 space. 1760 """ 1761 if x is None or y is None: 1762 (x, y) = self._find_room(canvaswidget, x, y) 1763 1764 # Move to (x,y) 1765 (x1,y1,x2,y2) = canvaswidget.bbox() 1766 canvaswidget.move(x-x1,y-y1) 1767 1768 # Register with scrollwatcher. 1769 self._scrollwatcher.add_child(canvaswidget)
1770
1771 - def _find_room(self, widget, desired_x, desired_y):
1772 """ 1773 Try to find a space for a given widget. 1774 """ 1775 (left, top, right, bot) = self.scrollregion() 1776 w = widget.width() 1777 h = widget.height() 1778 1779 if w >= (right-left): return (0,0) 1780 if h >= (bot-top): return (0,0) 1781 1782 # Move the widget out of the way, for now. 1783 (x1,y1,x2,y2) = widget.bbox() 1784 widget.move(left-x2-50, top-y2-50) 1785 1786 if desired_x is not None: 1787 x = desired_x 1788 for y in range(top, bot-h, (bot-top-h)/10): 1789 if not self._canvas.find_overlapping(x-5, y-5, x+w+5, y+h+5): 1790 return (x,y) 1791 1792 if desired_y is not None: 1793 y = desired_y 1794 for x in range(left, right-w, (right-left-w)/10): 1795 if not self._canvas.find_overlapping(x-5, y-5, x+w+5, y+h+5): 1796 return (x,y) 1797 1798 for y in range(top, bot-h, (bot-top-h)/10): 1799 for x in range(left, right-w, (right-left-w)/10): 1800 if not self._canvas.find_overlapping(x-5, y-5, x+w+5, y+h+5): 1801 return (x,y) 1802 return (0,0)
1803
1804 - def destroy_widget(self, canvaswidget):
1805 """ 1806 Remove a canvas widget from this C{CanvasFrame}. This 1807 deregisters the canvas widget, and destroys it. 1808 """ 1809 self.remove_widget(canvaswidget) 1810 canvaswidget.destroy()
1811
1812 - def remove_widget(self, canvaswidget):
1813 # Deregister with scrollwatcher. 1814 self._scrollwatcher.remove_child(canvaswidget)
1815
1816 - def pack(self, cnf={}, **kw):
1817 """ 1818 Pack this C{CanvasFrame}. See the documentation for 1819 C{Tkinter.Pack} for more information. 1820 """ 1821 self._frame.pack(cnf, **kw)
1822 # Adjust to be big enough for kids? 1823
1824 - def destroy(self, *e):
1825 """ 1826 Destroy this C{CanvasFrame}. If this C{CanvasFrame} created a 1827 top-level window, then this will close that window. 1828 """ 1829 if self._parent is None: return 1830 self._parent.destroy() 1831 self._parent = None
1832
1833 - def mainloop(self, *args, **kwargs):
1834 """ 1835 Enter the Tkinter mainloop. This function must be called if 1836 this frame is created from a non-interactive program (e.g. 1837 from a secript); otherwise, the frame will close as soon as 1838 the script completes. 1839 """ 1840 if in_idle(): return 1841 self._parent.mainloop(*args, **kwargs)
1842 1843 ##////////////////////////////////////////////////////// 1844 ## Text display 1845 ##////////////////////////////////////////////////////// 1846
1847 -class ShowText(object):
1848 """ 1849 A C{Tkinter} window used to display a text. C{ShowText} is 1850 typically used by graphical tools to display help text, or similar 1851 information. 1852 """
1853 - def __init__(self, root, title, text, width=None, height=None, 1854 **textbox_options):
1855 if width is None or height is None: 1856 (width, height) = self.find_dimentions(text, width, height) 1857 1858 # Create the main window. 1859 if root is None: 1860 self._top = top = Tk() 1861 else: 1862 self._top = top = Toplevel(root) 1863 top.title(title) 1864 1865 b = Button(top, text='Ok', command=self.destroy) 1866 b.pack(side='bottom') 1867 1868 tbf = Frame(top) 1869 tbf.pack(expand=1, fill='both') 1870 scrollbar = Scrollbar(tbf, orient='vertical') 1871 scrollbar.pack(side='right', fill='y') 1872 textbox = Text(tbf, wrap='word', width=width, 1873 height=height, **textbox_options) 1874 textbox.insert('end', text) 1875 textbox['state'] = 'disabled' 1876 textbox.pack(side='left', expand=1, fill='both') 1877 scrollbar['command'] = textbox.yview 1878 textbox['yscrollcommand'] = scrollbar.set 1879 1880 # Make it easy to close the window. 1881 top.bind('q', self.destroy) 1882 top.bind('x', self.destroy) 1883 top.bind('c', self.destroy) 1884 top.bind('<Return>', self.destroy) 1885 top.bind('<Escape>', self.destroy) 1886 1887 # Focus the scrollbar, so they can use up/down, etc. 1888 scrollbar.focus()
1889
1890 - def find_dimentions(self, text, width, height):
1891 lines = text.split('\n') 1892 if width is None: 1893 maxwidth = max([len(line) for line in lines]) 1894 width = min(maxwidth, 80) 1895 1896 # Now, find height. 1897 height = 0 1898 for line in lines: 1899 while len(line) > width: 1900 brk = line[:width].rfind(' ') 1901 line = line[brk:] 1902 height += 1 1903 height += 1 1904 height = min(height, 25) 1905 1906 return (width, height)
1907
1908 - def destroy(self, *e):
1909 if self._top is None: return 1910 self._top.destroy() 1911 self._top = None
1912
1913 - def mainloop(self, *args, **kwargs):
1914 """ 1915 Enter the Tkinter mainloop. This function must be called if 1916 this window is created from a non-interactive program (e.g. 1917 from a secript); otherwise, the window will close as soon as 1918 the script completes. 1919 """ 1920 if in_idle(): return 1921 self._top.mainloop(*args, **kwargs)
1922 1923 ##////////////////////////////////////////////////////// 1924 ## Entry dialog 1925 ##////////////////////////////////////////////////////// 1926
1927 -class EntryDialog(object):
1928 """ 1929 A dialog box for entering 1930 """
1931 - def __init__(self, parent, original_text='', instructions='', 1932 set_callback=None, title=None):
1933 self._parent = parent 1934 self._original_text = original_text 1935 self._set_callback = set_callback 1936 1937 width = max(30, len(original_text)*3/2) 1938 self._top = Toplevel(parent) 1939 1940 if title: self._top.title(title) 1941 1942 # The text entry box. 1943 entryframe = Frame(self._top) 1944 entryframe.pack(expand=1, fill='both', padx=5, pady=5,ipady=10) 1945 if instructions: 1946 l=Label(entryframe, text=instructions) 1947 l.pack(side='top', anchor='w', padx=30) 1948 self._entry = Entry(entryframe, width=width) 1949 self._entry.pack(expand=1, fill='x', padx=30) 1950 self._entry.insert(0, original_text) 1951 1952 # A divider 1953 divider = Frame(self._top, borderwidth=1, relief='sunken') 1954 divider.pack(fill='x', ipady=1, padx=10) 1955 1956 # The buttons. 1957 buttons = Frame(self._top) 1958 buttons.pack(expand=0, fill='x', padx=5, pady=5) 1959 b = Button(buttons, text='Cancel', command=self._cancel, width=8) 1960 b.pack(side='right', padx=5) 1961 b = Button(buttons, text='Ok', command=self._ok, 1962 width=8, default='active') 1963 b.pack(side='left', padx=5) 1964 b = Button(buttons, text='Apply', command=self._apply, width=8) 1965 b.pack(side='left') 1966 1967 self._top.bind('<Return>', self._ok) 1968 self._top.bind('<Control-q>', self._cancel) 1969 self._top.bind('<Escape>', self._cancel) 1970 1971 self._entry.focus()
1972
1973 - def _reset(self, *e):
1974 self._entry.delete(0,'end') 1975 self._entry.insert(0, self._original_text) 1976 if self._set_callback: 1977 self._set_callback(self._original_text)
1978
1979 - def _cancel(self, *e):
1980 try: self._reset() 1981 except: pass 1982 self._destroy()
1983
1984 - def _ok(self, *e):
1985 self._apply() 1986 self._destroy()
1987
1988 - def _apply(self, *e):
1989 if self._set_callback: 1990 self._set_callback(self._entry.get())
1991
1992 - def _destroy(self, *e):
1993 if self._top is None: return 1994 self._top.destroy() 1995 self._top = None
1996 1997 ##////////////////////////////////////////////////////// 1998 ## Colorized List 1999 ##////////////////////////////////////////////////////// 2000
2001 -class ColorizedList(object):
2002 """ 2003 An abstract base class for displaying a colorized list of items. 2004 Subclasses should define: 2005 - L{_init_colortags}, which sets up Text color tags that 2006 will be used by the list. 2007 - L{_item_repr}, which returns a list of (text,colortag) 2008 tuples that make up the colorized representation of the 2009 item. 2010 @note: Typically, you will want to register a callback for 2011 C{'select'} that calls L{mark} on the given item. 2012 """
2013 - def __init__(self, parent, items=[], **options):
2014 """ 2015 Construct a new list. 2016 2017 @param parent: The Tk widget that contains the colorized list 2018 @param items: The initial contents of the colorized list. 2019 @param options: 2020 """ 2021 self._parent = parent 2022 self._callbacks = {} 2023 2024 # Which items are marked? 2025 self._marks = {} 2026 2027 # Initialize the Tkinter frames. 2028 self._init_itemframe(options.copy()) 2029 2030 # Set up key & mouse bindings. 2031 self._textwidget.bind('<KeyPress>', self._keypress) 2032 self._textwidget.bind('<ButtonPress>', self._buttonpress) 2033 2034 # Fill in the given CFG's items. 2035 self._items = None