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

Source Code for Package nltk.draw

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