Package teamwork :: Package widgets :: Module bigwidgets
[hide private]
[frames] | no frames]

Source Code for Module teamwork.widgets.bigwidgets

   1  # Natural Language Toolkit: graphical representations package 
   2  # 
   3  # Copyright (C) 2001 University of Pennsylvania 
   4  # Author: Edward Loper <edloper@gradient.cis.upenn.edu> 
   5  # URL: <http://nltk.sf.net> 
   6  # For license information, see LICENSE.TXT 
   7  # 
   8  # $Id: bigwidgets.py 3006 2008-06-19 21:16:02Z pynadath $ 
   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 C{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 C{nltk.draw.tree}. 
  26   
  27  The C{nltk.draw} module also defines C{CanvasFrame}, which 
  28  encapsulates a C{Canvas} and its scrollbars.  It uses a 
  29  C{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 the CLIG homepage at:: 
  35   
  36      http://www.ags.uni-sb.de/~konrad/clig.html 
  37   
  38  """ 
  39   
  40  from Tkinter import * 
  41   
  42  __all__ = ( 
  43      'CanvasFrame', 'CanvasWidget', 
  44      'TextWidget', 'SymbolWidget','ImageWidget', 
  45      'BoxWidget', 'OvalWidget', 'ParenWidget', 'BracketWidget', 'PolygonWidget', 
  46      'SequenceWidget', 'StackWidget', 'SpaceWidget', 
  47      'ScrollWatcherWidget', 'AbstractContainerWidget', 
  48       
  49      'ShowText') 
  50   
  51  # Including these causes circular dependancy trouble??? 
  52  #    'tree', 'chart', 'fas', 'srparser', 'plot', 
  53  #    'plot_graph', 'tree_edit' 
  54  #    ) 
  55   
  56  ##////////////////////////////////////////////////////// 
  57  ##  CanvasWidget 
  58  ##////////////////////////////////////////////////////// 
  59   
60 -class CanvasWidget:
61 """ 62 A collection of graphical elements and bindings used to display a 63 complex object on a Tkinter C{Canvas}. A canvas widget is 64 responsible for managing the C{Canvas} tags and callback bindings 65 necessary to display and interact with the object. Canvas widgets 66 are often organized into hierarchies, where parent canvas widgets 67 control aspects of their child widgets. 68 69 Each canvas widget is bound to a single C{Canvas}. This C{Canvas} 70 is specified as the first argument to the C{CanvasWidget}'s 71 constructor. 72 73 Attributes 74 ========== 75 Each canvas widget can support a variety of X{attributes}, which 76 control how the canvas widget is displayed. Some typical examples 77 attributes are C{color}, C{font}, and C{radius}. Each attribute 78 has a default value. This default value can be overridden in the 79 constructor, using keyword arguments of the form 80 C{attribute=value}: 81 82 >>> cn = CanvasText(c, 'test', color='red') 83 84 Attribute values can also be changed after a canvas widget has 85 been constructed, using the C{__setitem__} operator: 86 87 >>> cn['font'] = 'times' 88 89 The current value of an attribute value can be queried using the 90 C{__getitem__} operator: 91 92 >>> cn['color'] 93 red 94 95 For a list of the attributes supported by a type of canvas widget, 96 see its class documentation. 97 98 Interaction 99 =========== 100 The attribute C{'draggable'} controls whether the user can drag a 101 canvas widget around the canvas. By default, canvas widgets 102 are not draggable. 103 104 C{CanvasWidget} provides callback support for two types of user 105 interaction: clicking and dragging. The method C{bind_click} 106 registers a callback function that is called whenever the canvas 107 widget is clicked. The method C{bind_drag} registers a callback 108 function that is called after the canvas widget is dragged. If 109 the user clicks or drags a canvas widget with no registered 110 callback function, then the interaction event will propagate to 111 its parent. For each canvas widget, only one callback function 112 may be registered for an interaction event. Callback functions 113 can be deregistered with the C{unbind_click} and C{unbind_drag} 114 methods. 115 116 Subclassing 117 =========== 118 C{CanvasWidget} is an abstract class. Subclasses are required to 119 implement the following methods: 120 121 - C{__init__}: Builds a new canvas widget. It must perform the 122 following three tasks (in order): 123 - Create any new graphical elements. 124 - Call C{_add_child_widget} on each child widget. 125 - Call the C{CanvasWidget} constructor. 126 - C{_tags}: Returns a list of the canvas tags for all graphical 127 elements managed by this canvas widget, not including 128 graphical elements managed by its child widgets. 129 - C{_manage}: Arranges the child widgets of this canvas widget. 130 This is typically only called when the canvas widget is 131 created. 132 - C{_update}: Update this canvas widget in response to a 133 change in a single child. 134 135 For C{CanvasWidget}s with no child widgets, the default 136 definitions for C{_manage} and C{_update} may be used. 137 138 If a subclass defines any attributes, then it should implement 139 C{__getitem__} and C{__setitem__}. If either of these methods is 140 called with an unknown attribute, then they should propagate the 141 request to C{CanvasWidget}. 142 143 Most subclasses implement a number of additional methods that 144 modify the C{CanvasWidget} in some way. These methods must call 145 C{parent.update(self)} after making any changes to the canvas 146 widget's graphical elements. The canvas widget must also call 147 C{parent.update(self)} after changing any attribute value that 148 affects the shape or position of the canvas widget's graphical 149 elements. 150 151 @type __canvas: C{Tkinter.Canvas} 152 @ivar __canvas: This C{CanvasWidget}'s canvas. 153 154 @type __parent: C{CanvasWidget} or C{None} 155 @ivar __parent: This C{CanvasWidget}'s hierarchical parent widget. 156 @type __children: C{list} of C{CanvasWidget} 157 @ivar __children: This C{CanvasWidget}'s hierarchical child widgets. 158 159 @type __updating: C{boolean} 160 @ivar __updating: Is this canvas widget currently performing an 161 update? If it is, then it will ignore any new update requests 162 from child widgets. 163 164 @type __draggable: C{boolean} 165 @ivar __draggable: Is this canvas widget draggable? 166 @type __press: C{event} 167 @ivar __press: The ButtonPress event that we're currently handling. 168 @type __drag_x: C{int} 169 @ivar __drag_x: Where it's been moved to (to find dx) 170 @type __drag_y: C{int} 171 @ivar __drag_y: Where it's been moved to (to find dy) 172 @type __callbacks: C{dictionary} 173 @ivar __callbacks: Registered callbacks. Currently, four keys are 174 used: C{1}, C{2}, C{3}, and C{'drag'}. The values are 175 callback functions. Each callback function takes a single 176 argument, which is the C{CanvasWidget} that triggered the 177 callback. 178 """
179 - def __init__(self, canvas, parent=None, **attribs):
180 """ 181 Create a new canvas widget. This constructor should only be 182 called by subclass constructors; and it should be called only 183 X{after} the subclass has constructed all graphical canvas 184 objects and registered all child widgets. 185 186 @param canvas: This canvas widget's canvas. 187 @type canvas: C{Tkinter.Canvas} 188 @param parent: This canvas widget's hierarchical parent. 189 @type parent: C{CanvasWidget} 190 @param attribs: The new canvas widget's attributes. 191 """ 192 if not isinstance(canvas, Canvas): 193 raise TypeError('Expected a canvas!') 194 195 self.__canvas = canvas 196 self.__parent = parent 197 if not hasattr(self, '_CanvasWidget__children'): self.__children = [] 198 199 self.__hidden = 0 200 201 # Update control (prevents infinite loops) 202 self.__updating = 0 203 204 # Button-press and drag callback handling. 205 self.__press = None 206 self.__drag_x = self.__drag_y = 0 207 self.__callbacks = {} 208 self.__draggable = 0 209 # dp: double-click callback handling. 210 self.__double = None 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 destroy(self):
311 """ 312 Remove this C{CanvasWidget} from its C{Canvas}. After a 313 C{CanvasWidget} has been destroyed, it should not be accessed. 314 315 Note that you only need to destroy a top-level 316 C{CanvasWidget}; its child widgets will be destroyed 317 automatically. If you destroy a non-top-level 318 C{CanvasWidget}, then the entire top-level widget will be 319 destroyed. 320 321 @raise ValueError: if this C{CanvasWidget} has a parent. 322 @rtype: C{None} 323 """ 324 if self.__parent is not None: 325 self.__parent.destroy() 326 return 327 328 for tag in self.tags(): 329 self.__canvas.tag_unbind(tag, '<ButtonPress-1>') 330 self.__canvas.tag_unbind(tag, '<ButtonPress-2>') 331 self.__canvas.tag_unbind(tag, '<ButtonPress-3>') 332 self.__canvas.delete(*self.tags()) 333 self.__canvas = None
334
335 - def update(self, child):
336 """ 337 Update the graphical display of this canvas widget, and all of 338 its ancestors, in response to a change in one of this canvas 339 widget's children. 340 341 @param child: The child widget that changed. 342 @type child: C{CanvasWidget} 343 """ 344 if self.__hidden or child.__hidden: return 345 # If we're already updating, then do nothing. This prevents 346 # infinite loops when _update modifies its children. 347 if self.__updating: return 348 self.__updating = 1 349 350 # Update this CanvasWidget. 351 self._update(child) 352 353 # Propagate update request to the parent. 354 if self.__parent: self.__parent.update(self) 355 356 # We're done updating. 357 self.__updating = 0
358
359 - def manage(self):
360 """ 361 Arrange this canvas widget and all of its descendants. 362 363 @rtype: C{None} 364 """ 365 if self.__hidden: return 366 for child in self.__children: child.manage() 367 self._manage()
368
369 - def tags(self):
370 """ 371 @return: a list of the canvas tags for all graphical 372 elements managed by this canvas widget, including 373 graphical elements managed by its child widgets. 374 @rtype: C{list} of C{int} 375 """ 376 if self.__canvas is None: 377 raise ValueError('Attempt to access a destroyed canvas widget') 378 tags = [] 379 tags += self._tags() 380 for child in self.__children: 381 tags += child.tags() 382 return tags
383
384 - def __setitem__(self, attr, value):
385 """ 386 Set the value of the attribute C{attr} to C{value}. See the 387 class documentation for a list of attributes supported by this 388 canvas widget. 389 390 @rtype: C{None} 391 """ 392 if attr == 'draggable': 393 self.__draggable = value 394 else: 395 raise ValueError('Unknown attribute %r' % attr)
396
397 - def __getitem__(self, attr):
398 """ 399 @return: the value of the attribute C{attr}. See the class 400 documentation for a list of attributes supported by this 401 canvas widget. 402 @rtype: (any) 403 """ 404 if attr == 'draggable': 405 return self.__draggable 406 else: 407 raise ValueError('Unknown attribute %r' % attr)
408
409 - def __repr__(self):
410 """ 411 @return: a string representation of this canvas widget. 412 @rtype: C{string} 413 """ 414 return '<%s>' % self.__class__.__name__
415
416 - def hide(self):
417 """ 418 Temporarily hide this canvas widget. 419 420 @rtype: C{None} 421 """ 422 self.__hidden = 1 423 for tag in self.tags(): 424 self.__canvas.itemconfig(tag, state='hidden')
425
426 - def show(self):
427 """ 428 Show a hidden canvas widget. 429 430 @rtype: C{None} 431 """ 432 self.__hidden = 0 433 for tag in self.tags(): 434 self.__canvas.itemconfig(tag, state='normal')
435
436 - def hidden(self):
437 """ 438 @return: True if this canvas widget is hidden. 439 @rtype: C{boolean} 440 """ 441 return self.__hidden
442 443 ##////////////////////////////////////////////////////// 444 ## Callback interface 445 ##////////////////////////////////////////////////////// 446
447 - def bind_click(self, callback, button=1):
448 """ 449 Register a new callback that will be called whenever this 450 C{CanvasWidget} is clicked on. 451 452 @type callback: C{function} 453 @param callback: The callback function that will be called 454 whenever this C{CanvasWidget} is clicked. This function 455 will be called with the event and this C{CanvasWidget} 456 as its argument. (dp: added the event as argument) 457 @type button: C{int} 458 @param button: Which button the user should use to click on 459 this C{CanvasWidget}. Typically, this should be 1 (left 460 button), 3 (right button), or 2 (middle button). 461 """ 462 self.__callbacks[button] = callback
463
464 - def bind_drag(self, callback):
465 """ 466 Register a new callback that will be called after this 467 C{CanvasWidget} is dragged. This implicitly makes this 468 C{CanvasWidget} draggable. 469 470 @type callback: C{function} 471 @param callback: The callback function that will be called 472 whenever this C{CanvasWidget} is clicked. This function 473 will be called with this C{CanvasWidget} as its argument. 474 """ 475 self.__draggable = 1 476 self.__callbacks['drag'] = callback
477
478 - def unbind_click(self, button=1):
479 """ 480 Remove a callback that was registered with C{bind_click}. 481 482 @type button: C{int} 483 @param button: Which button the user should use to click on 484 this C{CanvasWidget}. Typically, this should be 1 (left 485 button), 3 (right button), or 2 (middle button). 486 """ 487 try: del self.__callbacks[button] 488 except: pass
489
490 - def unbind_drag(self):
491 """ 492 Remove a callback that was registered with C{bind_drag}. 493 """ 494 try: del self.__callbacks['drag'] 495 except: pass
496 497 ##////////////////////////////////////////////////////// 498 ## Callback internals 499 ##////////////////////////////////////////////////////// 500
501 - def __press_cb(self, event):
502 """ 503 Handle a button-press event: 504 - record the button press event in C{self.__press} 505 - register a button-release callback. 506 - if this CanvasWidget or any of its ancestors are 507 draggable, then register the appropriate motion callback. 508 """ 509 # If we're already waiting for a button release, then ignore 510 # this new button press. 511 if (self.__canvas.bind('<ButtonRelease-1>') or 512 self.__canvas.bind('<ButtonRelease-2>') or 513 self.__canvas.bind('<ButtonRelease-3>')): 514 return 515 516 # Unbind motion (just in case; this shouldn't be necessary) 517 self.__canvas.unbind('<Motion>') 518 519 # dp: check for double-click 520 if self.__press: 521 if abs(event.time-self.__press.time) < 1000: 522 self.__double = self.__press 523 else: 524 self.__double = None 525 526 # Record the button press event. 527 self.__press = event 528 529 # If any ancestor is draggable, set up a motion callback. 530 # (Only if they pressed button number 1) 531 if event.num == 1: 532 widget = self 533 while widget is not None: 534 if widget['draggable']: 535 widget.__start_drag(event) 536 break 537 widget = widget.parent() 538 539 # Set up the button release callback. 540 self.__canvas.bind('<ButtonRelease-%d>' % event.num, 541 self.__release_cb)
542
543 - def __start_drag(self, event):
544 """ 545 Begin dragging this object: 546 - register a motion callback 547 - record the drag coordinates 548 """ 549 self.__canvas.bind('<Motion>', self.__motion_cb) 550 self.__drag_x = event.x 551 self.__drag_y = event.y
552
553 - def __motion_cb(self, event):
554 """ 555 Handle a motion event: 556 - move this object to the new location 557 - record the new drag coordinates 558 """ 559 self.move(event.x-self.__drag_x, event.y-self.__drag_y) 560 self.__drag_x = event.x 561 self.__drag_y = event.y
562
563 - def __release_cb(self, event):
564 """ 565 Handle a release callback: 566 - unregister motion & button release callbacks. 567 - decide whether they clicked, dragged, or cancelled 568 - call the appropriate handler. 569 """ 570 # Unbind the button release & motion callbacks. 571 self.__canvas.unbind('<ButtonRelease-%d>' % event.num) 572 self.__canvas.unbind('<Motion>') 573 # Is it a click or a drag? 574 if (event.time - self.__press.time < 1000 and 575 abs(event.x-self.__press.x) + abs(event.y-self.__press.y) < 5): 576 # Move it back, if we were dragging. 577 if self.__draggable and event.num == 1: 578 self.move(self.__press.x - self.__drag_x, 579 self.__press.y - self.__drag_y) 580 self.__click(event) 581 elif event.num == 1: 582 self.__drag()
583 584 ## self.__press = None 585
586 - def __drag(self):
587 """ 588 If this C{CanvasWidget} has a drag callback, then call it; 589 otherwise, find the closest ancestor with a drag callback, and 590 call it. If no ancestors have a drag callback, do nothing. 591 """ 592 if self.__draggable: 593 if self.__callbacks.has_key('drag'): 594 cb = self.__callbacks['drag'] 595 ## try: 596 cb(self) 597 ## except: 598 ## print 'Error in drag callback for %r' % self 599 elif self.__parent is not None: 600 self.__parent.__drag()
601
602 - def __click(self, event):
603 """ 604 If this C{CanvasWidget} has a click callback, then call it; 605 otherwise, find the closest ancestor with a click callback, and 606 call it. If no ancestors have a click callback, do nothing. 607 """ 608 if self.__double: 609 if self.__callbacks.has_key('double'): 610 self.__callbacks['double'](event,self) 611 self.__double = None 612 self.__press = None 613 else: 614 self.__parent.__double = self.__double 615 self.__parent.__click(event) 616 elif self.__callbacks.has_key(event.num): 617 cb = self.__callbacks[event.num] 618 #try: 619 cb(event,self) 620 #except: 621 # print 'Error in click callback for %r' % self 622 # raise 623 elif self.__parent is not None: 624 self.__parent.__click(event)
625 626 ##////////////////////////////////////////////////////// 627 ## Child/parent Handling 628 ##////////////////////////////////////////////////////// 629
630 - def _add_child_widget(self, child):
631 """ 632 Register a hierarchical child widget. The child will be 633 considered part of this canvas widget for purposes of user 634 interaction. C{_add_child_widget} has two direct effects: 635 - It sets C{child}'s parent to this canvas widget. 636 - It adds C{child} to the list of canvas widgets returned by 637 the C{child_widgets} member function. 638 639 @param child: The new child widget. C{child} must not already 640 have a parent. 641 @type child: C{CanvasWidget} 642 """ 643 if not hasattr(self, '_CanvasWidget__children'): self.__children = [] 644 if child.__parent is not None: 645 raise ValueError('%s already has a parent', child) 646 child.__parent = self 647 self.__children.append(child)
648
649 - def _remove_child_widget(self, child):
650 """ 651 Remove a hierarchical child widget. This child will no longer 652 be considered part of this canvas widget for purposes of user 653 interaction. C{_add_child_widget} has two direct effects: 654 - It sets C{child}'s parent to C{None}. 655 - It removes C{child} from the list of canvas widgets 656 returned by the C{child_widgets} member function. 657 658 @param child: The child widget to remove. C{child} must be a 659 child of this canvas widget. 660 @type child: C{CanvasWidget} 661 """ 662 self.__children.remove(child) 663 child.__parent = None
664 665 ##////////////////////////////////////////////////////// 666 ## Defined by subclass 667 ##////////////////////////////////////////////////////// 668
669 - def _tags(self):
670 """ 671 @return: a list of canvas tags for all graphical elements 672 managed by this canvas widget, not including graphical 673 elements managed by its child widgets. 674 @rtype: C{list} of C{int} 675 """ 676 raise AssertionError()
677
678 - def _manage(self):
679 """ 680 Arrange the child widgets of this canvas widget. This method 681 is called when the canvas widget is initially created. It is 682 also called if the user calls the C{manage} method on this 683 canvas widget or any of its ancestors. 684 685 @rtype: C{None} 686 """ 687 pass
688
689 - def _update(self, child):
690 """ 691 Update this canvas widget in response to a change in one of 692 its children. 693 694 @param child: The child that changed. 695 @type child: C{CanvasWidget} 696 @rtype: C{None} 697 """ 698 pass
699 700 ##////////////////////////////////////////////////////// 701 ## Basic widgets. 702 ##////////////////////////////////////////////////////// 703
704 -class TextWidget(CanvasWidget):
705 """ 706 A canvas widget that displays a single string of text. 707 708 Attributes: 709 - C{color}: the color of the text. 710 - C{font}: the font used to display the text. 711 - C{justify}: justification for multi-line texts. Valid values 712 are C{left}, C{center}, and C{right}. 713 - C{width}: the width of the text. If the text is wider than 714 this width, it will be line-wrapped at whitespace. 715 - C{draggable}: whether the text can be dragged by the user. 716 """
717 - def __init__(self, canvas, text, **attribs):
718 """ 719 Create a new text widget. 720 721 @type canvas: C{Tkinter.Canvas} 722 @param canvas: This canvas widget's canvas. 723 @type text: C{string} 724 @param text: The string of text to display. 725 @param attribs: The new canvas widget's attributes. 726 """ 727 self._text = text 728 self._tag = canvas.create_text(1, 1, text=text) 729 CanvasWidget.__init__(self, canvas, **attribs)
730
731 - def __setitem__(self, attr, value):
732 if attr in ('color', 'font', 'justify', 'width'): 733 if attr == 'color': attr = 'fill' 734 self.canvas().itemconfig(self._tag, {attr:value}) 735 else: 736 CanvasWidget.__setitem__(self, attr, value)
737
738 - def __getitem__(self, attr):
739 if attr == 'width': 740 return int(self.canvas().itemcget(self._tag, attr)) 741 elif attr in ('color', 'font', 'justify'): 742 if attr == 'color': attr = 'fill' 743 return self.canvas().itemcget(self._tag, attr) 744 else: 745 return CanvasWidget.__getitem__(self, attr)
746
747 - def _tags(self): return [self._tag]
748
749 - def text(self):
750 """ 751 @return: The text displayed by this text widget. 752 @rtype: C{string} 753 """ 754 return self.canvas().itemcget(self._tag, 'text')
755
756 - def set_text(self, text):
757 """ 758 Change the text that is displayed by this text widget. 759 760 @type text: C{string} 761 @param text: The string of text to display. 762 @rtype: C{None} 763 """ 764 self.canvas().itemconfig(self._tag, text=text) 765 if self.parent() is not None: 766 self.parent().update(self)
767
768 - def __repr__(self):
769 return '[Text: %r]' % self._text
770
771 -class SymbolWidget(TextWidget):
772 """ 773 A canvas widget that displays special symbols, such as the 774 negation sign and the exists operator. Symbols are specified by 775 name. Currently, the following symbol names are defined: C{neg}, 776 C{disj}, C{conj}, C{lambda}, C{merge}, C{forall}, C{exists}, 777 C{subseteq}, C{subset}, C{notsubset}, C{emptyset}, C{imp}, 778 C{rightarrow}, C{equal}, C{notequal}, C{epsilon}. 779 780 Attributes: 781 - C{color}: the color of the text. 782 - C{draggable}: whether the text can be dragged by the user. 783 784 @cvar _SYMBOLS: A dictionary mapping from symbols to the character 785 in the C{symbol} font used to render them. 786 """ 787 _SYMBOLS = {'neg':'\330', 'disj':'\332', 'conj': '\331', 788 'lambda': '\154', 'merge': '\304', 789 'forall': '\042', 'exists': '\044', 790 'subseteq': '\315', 'subset': '\314', 791 'notsubset': '\313', 'emptyset': '\306', 792 'imp': '\336', 'rightarrow': '\256', 793 'equal': '\75', 'notequal': '\271', 794 'epsilon': 'e'} 795
796 - def __init__(self, canvas, symbol, **attribs):
797 """ 798 Create a new symbol widget. 799 800 @type canvas: C{Tkinter.Canvas} 801 @param canvas: This canvas widget's canvas. 802 @type symbol: C{string} 803 @param symbol: The name of the symbol to display. 804 @param attribs: The new canvas widget's attributes. 805 """ 806 attribs['font'] = 'symbol' 807 TextWidget.__init__(self, canvas, '', **attribs) 808 self.set_symbol(symbol)
809
810 - def symbol(self):
811 """ 812 @return: the name of the symbol that is displayed by this 813 symbol widget. 814 @rtype: C{string} 815 """ 816 return self._symbol
817
818 - def set_symbol(self, symbol):
819 """ 820 Change the symbol that is displayed by this symbol widget. 821 822 @type symbol: C{string} 823 @param symbol: The name of the symbol to display. 824 """ 825 if not SymbolWidget._SYMBOLS.has_key(symbol): 826 raise ValueError('Unknown symbol: %s' % symbol) 827 self._symbol = symbol 828 self.set_text(SymbolWidget._SYMBOLS[symbol])
829
830 - def __repr__(self):
831 return '[Symbol: %r]' % self._symbol
832
833 -class AbstractContainerWidget(CanvasWidget):
834 """ 835 An abstract class for canvas widgets that contain a single child, 836 such as C{CanvasBox} and C{CanvasOval}. Subclasses must define 837 a constructor, which should create any new graphical elements and 838 then call the C{AbstractCanvasContainer} constructor. Subclasses 839 must also define the C{_update} method and the C{_tags} method; 840 and any subclasses that define attributes should define 841 C{__setitem__} and C{__getitem__}. 842 """
843 - def __init__(self, canvas, child, **attribs):
844 """ 845 Create a new container widget. This constructor should only 846 be called by subclass constructors. 847 848 @type canvas: C{Tkinter.Canvas} 849 @param canvas: This canvas widget's canvas. 850 @param child: The container's child widget. C{child} must not 851 have a parent. 852 @type child: C{CanvasWidget} 853 @param attribs: The new canvas widget's attributes. 854 """ 855 self._child = child 856 self._add_child_widget(child) 857 CanvasWidget.__init__(self, canvas, **attribs)
858
859 - def _manage(self):
860 self._update(self._child)
861
862 - def child(self):
863 """ 864 @return: The child widget contained by this container widget. 865 @rtype: C{CanvasWidget} 866 """ 867 return self._child
868
869 - def set_child(self, child):
870 """ 871 Change the child widget contained by this container widget. 872 873 @param child: The new child widget. C{child} must not have a 874 parent. 875 @type child: C{CanvasWidget} 876 @rtype: C{None} 877 """ 878 self._remove_child_widget(self._child) 879 self._add_child_widget(child) 880 self._child = child 881 self.update(child)
882
883 - def __repr__(self):
884 name = self.__class__.__name__ 885 if name[-6:] == 'Widget': name = name[:-6] 886 return '[%s: %r]' % (name, self._child)
887
888 -class BoxWidget(AbstractContainerWidget):
889 """ 890 A canvas widget that places a box around a child widget. 891 892 Attributes: 893 - C{fill}: The color used to fill the interior of the box. 894 - C{outline}: The color used to draw the outline of the box. 895 - C{width}: The width of the outline of the box. 896 - C{margin}: The number of pixels space left between the child 897 and the box. 898 - C{draggable}: whether the text can be dragged by the user. 899 """
900 - def __init__(self, canvas, child, **attribs):
901 """ 902 Create a new box widget. 903 904 @type canvas: C{Tkinter.Canvas} 905 @param canvas: This canvas widget's canvas. 906 @param child: The child widget. C{child} must not have a 907 parent. 908 @type child: C{CanvasWidget} 909 @param attribs: The new canvas widget's attributes. 910 """ 911 self._child = child 912 self._margin = 1 913 self._box = canvas.create_rectangle(1,1,1,1) 914 canvas.tag_lower(self._box) 915 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
916
917 - def __setitem__(self, attr, value):
918 if attr == 'margin': self._margin = value 919 elif attr in ('outline', 'fill', 'width'): 920 self.canvas().itemconfig(self._box, {attr:value}) 921 else: 922 CanvasWidget.__setitem__(self, attr, value)
923
924 - def __getitem__(self, attr):
925 if attr == 'margin': return self._margin 926 elif attr == 'width': 927 return float(self.canvas().itemcget(self._box, attr)) 928 elif attr in ('outline', 'fill', 'width'): 929 return self.canvas().itemcget(self._box, attr) 930 else: 931 return CanvasWidget.__getitem__(self, attr)
932
933 - def _update(self, child):
934 (x1, y1, x2, y2) = child.bbox() 935 margin = self._margin + self['width']/2 936 self.canvas().coords(self._box, x1-margin, y1-margin, 937 x2+margin, y2+margin)
938
939 - def _tags(self): return [self._box]
940
941 -class PolygonWidget(AbstractContainerWidget):
942 """ 943 A canvas widget that places a box around a child widget. 944 945 Attributes: 946 - C{fill}: The color used to fill the interior of the box. 947 - C{outline}: The color used to draw the outline of the box. 948 - C{width}: The width of the outline of the box. 949 - C{margin}: The number of pixels space left between the child 950 and the box. 951 - C{draggable}: whether the text can be dragged by the user. 952 """
953 - def __init__(self, canvas, child, **attribs):
954 """ 955 Create a new box widget. 956 957 @type canvas: C{Tkinter.Canvas} 958 @param canvas: This canvas widget's canvas. 959 @param child: The child widget. C{child} must not have a 960 parent. 961 @type child: C{CanvasWidget} 962 @param attribs: The new canvas widget's attributes. 963 """ 964 self._child = child 965 self._margin = 1 966 self._box = canvas.create_polygon(1,1,1,1,1,1,1,1,1,1,1,1) 967 canvas.tag_lower(self._box) 968 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
969
970 - def __setitem__(self, attr, value):
971 if attr == 'margin': self._margin = value 972 elif attr in ('outline', 'fill', 'width'): 973 self.canvas().itemconfig(self._box, {attr:value}) 974 else: 975 CanvasWidget.__setitem__(self, attr, value)
976
977 - def __getitem__(self, attr):
978 if attr == 'margin': return self._margin 979 elif attr == 'width': 980 return float(self.canvas().itemcget(self._box, attr)) 981 elif attr in ('outline', 'fill', 'width'): 982 return self.canvas().itemcget(self._box, attr) 983 else: 984 return CanvasWidget.__getitem__(self, attr)
985
986 - def _update(self, child):
987 (x1, y1, x2, y2) = child.bbox() 988 margin = self._margin + self['width']/2 989 self.canvas().coords(self._box, x1-margin, y1-margin, 990 x2+margin, y1-margin,(x2+margin)+6, 991 ((y1-margin) + (y2+margin))/2, 992 x2+margin, y2+margin, 993 x1-margin,y2+margin,(x1-margin)-6, 994 ((y1-margin) + (y2+margin))/2)
995
996 - def _tags(self): return [self._box]
997
998 -class OvalWidget(AbstractContainerWidget):
999 """ 1000 A canvas widget that places a oval around a child widget. 1001 1002 Attributes: 1003 - C{fill}: The color used to fill the interior of the oval. 1004 - C{outline}: The color used to draw the outline of the oval. 1005 - C{width}: The width of the outline of the oval. 1006 - C{margin}: The number of pixels space left between the child 1007 and the oval. 1008 - C{draggable}: whether the text can be dragged by the user. 1009 """
1010 - def __init__(self, canvas, child, **attribs):
1011 """ 1012 Create a new oval widget. 1013 1014 @type canvas: C{Tkinter.Canvas} 1015 @param canvas: This canvas widget's canvas. 1016 @param child: The child widget. C{child} must not have a 1017 parent. 1018 @type child: C{CanvasWidget} 1019 @param attribs: The new canvas widget's attributes. 1020 """ 1021 self._child = child 1022 self._margin = 1 1023 self._oval = canvas.create_oval(1,1,1,1) 1024 canvas.tag_lower(self._oval) 1025 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
1026
1027 - def __setitem__(self, attr, value):
1028 if attr == 'margin': self._margin = value 1029 elif attr in ('outline', 'fill', 'width'): 1030 self.canvas().itemconfig(self._oval, {attr:value}) 1031 else: 1032 CanvasWidget.__setitem__(self, attr, value)
1033
1034 - def __getitem__(self, attr):
1035 if attr == 'margin': return self._margin 1036 elif attr == 'width': 1037 return float(self.canvas().itemcget(self._oval, attr)) 1038 elif attr in ('outline', 'fill', 'width'): 1039 return self.canvas().itemcget(self._oval, attr) 1040 else: 1041 return CanvasWidget.__getitem__(self, attr)
1042 1043 # The ratio between inscribed & circumscribed ovals 1044 RATIO = 2. 1045 ## RATIO = 2.4142135623730949 1046
1047 - def _update(self, child):
1048 R = OvalWidget.RATIO 1049 (x1, y1, x2, y2) = child.bbox() 1050 margin = self._margin + self['width']/2 1051 left = int(( x1*(1+R) + x2*(1-R) ) / 2 - margin) 1052 right = left + int((x2-x1)*R) 1053 top = int(( y1*(1+R) + y2*(1-R) ) / 2 - margin) 1054 bot = top + int((y2-y1)*R) 1055 self.canvas().coords(self._oval, left, top, right, bot)
1056
1057 - def _tags(self): return [self._oval]
1058
1059 -class ImageWidget(CanvasWidget):
1060 """ 1061 A canvas widget that contains an image widget. 1062 1063 Attributes: 1064 - C{fill}: The color used to fill the interior of the oval. 1065 - C{outline}: The color used to draw the outline of the oval. 1066 - C{width}: The width of the outline of the oval. 1067 - C{margin}: The number of pixels space left between the child 1068 and the oval. 1069 - C{draggable}: whether the text can be dragged by the user. 1070 """
1071 - def __init__(self, canvas, child, **attribs):
1072 """ 1073 Create a new image widget. 1074 1075 @type canvas: C{Tkinter.Canvas} 1076 @param canvas: This canvas widget's canvas. 1077 @param child: The child widget. C{child} must not have a 1078 parent. 1079 @type child: C{CanvasWidget} 1080 @param attribs: The new canvas widget's attributes. 1081 """ 1082 self._image = canvas.create_image(1,1,image=child) 1083 canvas.tag_lower(self._image) 1084 CanvasWidget.__init__(self, canvas, **attribs)
1085
1086 - def _tags(self): return [self._image]
1087
1088 -class ParenWidget(AbstractContainerWidget):
1089 """ 1090 A canvas widget that places a pair of parenthases around a child 1091 widget. 1092 1093 Attributes: 1094 - C{color}: The color used to draw the parenthases. 1095 - C{width}: The width of the parenthases. 1096 - C{draggable}: whether the text can be dragged by the user. 1097 """
1098 - def __init__(self, canvas, child, **attribs):
1099 """ 1100 Create a new parenthasis widget. 1101 1102 @type canvas: C{Tkinter.Canvas} 1103 @param canvas: This canvas widget's canvas. 1104 @param child: The child widget. C{child} must not have a 1105 parent. 1106 @type child: C{CanvasWidget} 1107 @param attribs: The new canvas widget's attributes. 1108 """ 1109 self._child = child 1110 self._oparen = canvas.create_arc(1,1,1,1, style='arc', 1111 start=90, extent=180) 1112 self._cparen = canvas.create_arc(1,1,1,1, style='arc', 1113 start=-90, extent=180) 1114 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
1115
1116 - def __setitem__(self, attr, value):
1117 if attr == 'color': 1118 self.canvas().itemconfig(self._oparen, outline=value) 1119 self.canvas().itemconfig(self._cparen, outline=value) 1120 elif attr == 'width': 1121 self.canvas().itemconfig(self._oparen, width=value) 1122 self.canvas().itemconfig(self._cparen, width=value) 1123 else: 1124 CanvasWidget.__setitem__(self, attr, value)
1125
1126 - def __getitem__(self, attr):
1127 if attr == 'color': 1128 return self.canvas().itemcget(self._oparen, 'outline') 1129 elif attr == 'width': 1130 return self.canvas().itemcget(self._oparen, 'width') 1131 else: 1132 return CanvasWidget.__getitem__(self, attr)
1133
1134 - def _update(self, child):
1135 (x1, y1, x2, y2) = child.bbox() 1136 width = max((y2-y1)/6, 4) 1137 self.canvas().coords(self._oparen, x1-width, y1, x1+width, y2) 1138 self.canvas().coords(self._cparen, x2-width, y1, x2+width, y2)
1139
1140 - def _tags(self): return [self._oparen, self._cparen]
1141
1142 -class BracketWidget(AbstractContainerWidget):
1143 """ 1144 A canvas widget that places a pair of brackets around a child 1145 widget. 1146 1147 Attributes: 1148 - C{color}: The color used to draw the brackets. 1149 - C{width}: The width of the brackets. 1150 - C{draggable}: whether the text can be dragged by the user. 1151 """
1152 - def __init__(self, canvas, child, **attribs):
1153 """ 1154 Create a new bracket widget. 1155 1156 @type canvas: C{Tkinter.Canvas} 1157 @param canvas: This canvas widget's canvas. 1158 @param child: The child widget. C{child} must not have a 1159 parent. 1160 @type child: C{CanvasWidget} 1161 @param attribs: The new canvas widget's attributes. 1162 """ 1163 self._child = child 1164 self._obrack = canvas.create_line(1,1,1,1,1,1,1,1) 1165 self._cbrack = canvas.create_line(1,1,1,1,1,1,1,1) 1166 AbstractContainerWidget.__init__(self, canvas, child, **attribs)
1167
1168 - def __setitem__(self, attr, value):
1169 if attr == 'color': 1170 self.canvas().itemconfig(self._obrack, fill=value) 1171 self.canvas().itemconfig(self._cbrack, fill=value) 1172 elif attr == 'width': 1173 self.canvas().itemconfig(self._obrack, width=value) 1174 self.canvas().itemconfig(self._cbrack, width=value) 1175 else: 1176 CanvasWidget.__setitem__(self, attr, value)
1177
1178 - def __getitem__(self, attr):
1179 if attr == 'color': 1180 return self.canvas().itemcget(self._obrack, 'outline') 1181 elif attr == 'width': 1182 return self.canvas().itemcget(self._obrack, 'width') 1183 else: 1184 return CanvasWidget.__getitem__(self, attr)
1185
1186 - def _update(self, child):
1187 (x1, y1, x2, y2) = child.bbox() 1188 width = max((y2-y1)/8, 2) 1189 self.canvas().coords(self._obrack, x1, y1, x1-width, y1, 1190 x1-width, y2, x1, y2) 1191 self.canvas().coords(self._cbrack, x2, y1, x2+width, y1, 1192 x2+width, y2, x2, y2)
1193
1194 - def _tags(self): return [self._obrack, self._cbrack]
1195
1196 -class SequenceWidget(CanvasWidget):
1197 """ 1198 A canvas widget that keeps a list of canvas widgets in a 1199 horizontal line. 1200 1201 Attributes: 1202 - C{align}: The vertical alignment of the children. Possible 1203 values are C{'top'}, C{'center'}, and C{'bottom'}. By 1204 default, children are center-aligned. 1205 - C{space}: The amount of horizontal space to place between 1206 children. By default, one pixel of space is used. 1207 """
1208 - def __init__(self, canvas, *children, **attribs):
1209 """ 1210 Create a new sequence widget. 1211 1212 @type canvas: C{Tkinter.Canvas} 1213 @param canvas: This canvas widget's canvas. 1214 @param children: The widgets that should be aligned 1215 horizontally. Each child must not have a parent. 1216 @type children: C{list} of C{CanvasWidget} 1217 @param attribs: The new canvas widget's attributes. 1218 """ 1219 self._align = 'center' 1220 self._space = 1 1221 self._children = list(children) 1222 for child in children: self._add_child_widget(child) 1223 CanvasWidget.__init__(self, canvas, **attribs)
1224
1225 - def __setitem__(self, attr, value):
1226 if attr == 'align': 1227 if value not in ('top', 'bottom', 'center'): 1228 raise ValueError('Bad alignment: %r' % value) 1229 self._align = value 1230 elif attr == 'space': self._space = value 1231 else: CanvasWidget.__setitem__(self, attr, value)
1232
1233 - def __getitem__(self, attr):
1234 if attr == 'align': return value 1235 elif attr == 'space': return self._space 1236 else: return CanvasWidget.__getitem__(self, attr)
1237
1238 - def _tags(self): return []
1239
1240 - def _manage(self):
1241 if len(self._children) == 0: return 1242 self._update(self._children[0])
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 1254 index = self._children.index(child) 1255 1256 # Line up children to the right of child. 1257 x = right + self._space 1258 for i in range(index+1, len(self._children)): 1259 (x1, y1, x2, y2) = self._children[i].bbox() 1260 self._children[i].move(x-x1, y-self._yalign(y1,y2)) 1261 x += x2-x1 + self._space 1262 1263 # Line up children to the left of child. 1264 x = left - self._space 1265 for i in range(index-1, -1, -1): 1266 (x1, y1, x2, y2) = self._children[i].bbox() 1267 self._children[i].move(x-x2, y-self._yalign(y1,y2)) 1268 x -= x2-x1 + self._space
1269
1270 - def __repr__(self):
1271 return '[Sequence: ' + `self._children`[1:-1]+']'
1272
1273 -class StackWidget(CanvasWidget):
1274 """ 1275 A canvas widget that keeps a list of canvas widgets in a vertical 1276 line. 1277 1278 Attributes: 1279 - C{align}: The horizontal alignment of the children. Possible 1280 values are C{'left'}, C{'center'}, and C{'right'}. By 1281 default, children are center-aligned. 1282 - C{space}: The amount of vertical space to place between 1283 children. By default, one pixel of space is used. 1284 """
1285 - def __init__(self, canvas, *children, **attribs):
1286 """ 1287 Create a new stack widget. 1288 1289 @type canvas: C{Tkinter.Canvas} 1290 @param canvas: This canvas widget's canvas. 1291 @param children: The widgets that should be aligned 1292 vertically. Each child must not have a parent. 1293 @type children: C{list} of C{CanvasWidget} 1294 @param attribs: The new canvas widget's attributes. 1295 """ 1296 self._align = 'center' 1297 self._space = 1 1298 self._children = list(children) 1299 for child in children: self._add_child_widget(child) 1300 CanvasWidget.__init__(self, canvas, **attribs)
1301
1302 - def __setitem__(self, attr, value):
1303 if attr == 'align': 1304 if value not in ('left', 'right', 'center'): 1305 raise ValueError('Bad alignment: %r' % value) 1306 self._align = value 1307 elif attr == 'space': self._space = value 1308 else: CanvasWidget.__setitem__(self, attr, value)
1309
1310 - def __getitem__(self, attr):
1311 if attr == 'align': return value 1312 elif attr == 'space': return self._space 1313 else: return CanvasWidget.__getitem__(self, attr)
1314
1315 - def _tags(self): return []
1316
1317 - def _manage(self):
1318 if len(self._children) == 0: return 1319 self._update(self._children[0])
1320
1321 - def _xalign(self, left, right):
1322 if self._align == 'left': return left 1323 if self._align == 'right': return right 1324 if self._align == 'center': return (left+right)/2
1325
1326 - def _update(self, child):
1327 # Align all children with child. 1328 (left, top, right, bot) = child.bbox() 1329 x = self._xalign(left, right) 1330 1331 index = self._children.index(child) 1332 1333 # Line up children below the child. 1334 y = bot + self._space 1335 for i in range(index+1, len(self._children)): 1336 (x1, y1, x2, y2) = self._children[i].bbox() 1337 self._children[i].move(x-self._xalign(x1,x2), y-y1) 1338 y += y2-y1 + self._space 1339 1340 # Line up children above the child. 1341 y = top - self._space 1342 for i in range(index-1, -1, -1): 1343 (x1, y1, x2, y2) = self._children[i].bbox() 1344 self._children[i].move(x-self._xalign(x1,x2), y-y2) 1345 y -= y2-y1 + self._space
1346
1347 - def __repr__(self):
1348 return '[Stack: ' + `self._children`[1:-1]+']'
1349
1350 -class SpaceWidget(CanvasWidget):
1351 """ 1352 A canvas widget that takes up space but does not display 1353 anything. C{SpaceWidget}s can be used to add space between 1354 elements. Each space widget is characterized by a width and a 1355 height. If you wish to only create horizontal space, then use a 1356 height of zero; and if you wish to only create vertical space, use 1357 a width of zero. 1358 """
1359 - def __init__(self, canvas, width, height, **attribs):
1360 """ 1361 Create a new space widget. 1362 1363 @type canvas: C{Tkinter.Canvas} 1364 @param canvas: This canvas widget's canvas. 1365 @type width: C{int} 1366 @param width: The width of the new space widget. 1367 @type height: C{int} 1368 @param height: The height of the new space widget. 1369 @param attribs: The new canvas widget's attributes. 1370 """ 1371 # For some reason, 1372 if width > 4: width -= 4 1373 if height > 4: height -= 4 1374 self._tag = canvas.create_line(1, 1, width, height, fill='') 1375 CanvasWidget.__init__(self, canvas, **attribs)
1376 1377 # note: width() and height() are already defined by CanvasWidget.
1378 - def set_width(self, width):
1379 """ 1380 Change the width of this space widget. 1381 1382 @param width: The new width. 1383 @type width: C{int} 1384 @rtype: C{None} 1385 """ 1386 [x1, y1, x2, y2] = self._tag.bbox() 1387 self._canvas.coords(self._tag, x1, y1, x1+width, y2)
1388
1389 - def set_height(self, height):
1390 """ 1391 Change the height of this space widget. 1392 1393 @param height: The new height. 1394 @type height: C{int} 1395 @rtype: C{None} 1396 """ 1397 [x1, y1, x2, y2] = self._tag.bbox() 1398 self._canvas.coords(self._tag, x1, y1, x2, y1+height)
1399
1400 - def _tags(self): return [self._tag]
1401
1402 - def __repr__(self): return '[Space]'
1403
1404 -class ScrollWatcherWidget(CanvasWidget):
1405 """ 1406 A special canvas widget that adjusts its C{Canvas}'s scrollregion 1407 to always include the bounding boxes of all of its children. The 1408 scroll-watcher widget will only increase the size of the 1409 C{Canvas}'s scrollregion; it will never decrease it. 1410 """
1411 - def __init__(self, canvas, *children, **attribs):
1412 """ 1413 Create a new scroll-watcher widget. 1414 1415 @type canvas: C{Tkinter.Canvas} 1416 @param canvas: This canvas widget's canvas. 1417 @type children: C{list} of C{CanvasWidget} 1418 @param children: The canvas widgets watched by the 1419 scroll-watcher. The scroll-watcher will ensure that these 1420 canvas widgets are always contained in their canvas's 1421 scrollregion. 1422 @param attribs: The new canvas widget's attributes. 1423 """ 1424 for child in children: self._add_child_widget(child) 1425 CanvasWidget.__init__(self, canvas, **attribs)
1426
1427 - def add_child(self, canvaswidget):
1428 """ 1429 Add a new canvas widget to the scroll-watcher. The 1430 scroll-watcher will ensure that the new canvas widget is 1431 always contained in its canvas's scrollregion. 1432 1433 @param canvaswidget: The new canvas widget. 1434 @type canvaswidget: C{CanvasWidget} 1435 @rtype: C{None} 1436 """ 1437 self._add_child_widget(canvaswidget) 1438 self.update(canvaswidget)
1439
1440 - def remove_child(self, canvaswidget):
1441 """ 1442 Remove a canvas widget from the scroll-watcher. The 1443 scroll-watcher will no longer ensure that the new canvas 1444 widget is always contained in its canvas's scrollregion. 1445 1446 @param canvaswidget: The canvas widget to remove. 1447 @type canvaswidget: C{CanvasWidget} 1448 @rtype: C{None} 1449 """ 1450 self._remove_child_widget(canvaswidget)
1451
1452 - def _tags(self): return []
1453
1454 - def _update(self, child):
1456
1457 - def _adjust_scrollregion(self):
1458 """ 1459 Adjust the scrollregion of this scroll-watcher's C{Canvas} to 1460 include the bounding boxes of all of its children. 1461 """ 1462 bbox = self.bbox() 1463 canvas = self.canvas() 1464 scrollregion = [int(n) for n in canvas['scrollregion'].split()] 1465 if len(scrollregion) != 4: return 1466 if (bbox[0] < scrollregion[0] or bbox[1] < scrollregion[1] or 1467 bbox[2] > scrollregion[2] or bbox[3] > scrollregion[3]): 1468 scrollregion = ('%d %d %d %d' % 1469 (min(bbox[0], scrollregion[0]), 1470 min(bbox[1], scrollregion[1]), 1471 max(bbox[2], scrollregion[2]), 1472 max(bbox[3], scrollregion[3]))) 1473 canvas['scrollregion'] = scrollregion
1474 1475 ##////////////////////////////////////////////////////// 1476 ## Canvas Frame 1477 ##////////////////////////////////////////////////////// 1478
1479 -class CanvasFrame:
1480 """ 1481 A C{Tkinter} frame containing a canvas and scrollbars. 1482 C{CanvasFrame} uses a C{ScrollWatcherWidget} to ensure that all of 1483 the canvas widgets contained on its canvas are within its 1484 scrollregion. In order for C{CanvasFrame} to make these checks, 1485 all canvas widgets must be registered with C{add_widget} when they 1486 are added to the canvas; and destroyed with C{destroy_widget} when 1487 they are no longer needed. 1488 1489 If a C{CanvasFrame} is created with no parent, then it will create 1490 its own main window, including a "Done" button and a "Print" 1491 button. 1492 """
1493 - def __init__(self, parent=None, **kw):
1494 """ 1495 Create a new C{CanvasFrame}. 1496 1497 @type parent: C{Tkinter.BaseWidget} or C{Tkinter.Tk} 1498 @param parent: The parent C{Tkinter} widget. If no parent is 1499 specified, then C{CanvasFrame} will create a new main 1500 window. 1501 @param kw: Keyword arguments for the new C{Canvas}. See the 1502 documentation for C{Tkinter.Canvas} for more information. 1503 """ 1504 # If no parent was given, set up a top-level window. 1505 if parent is None: 1506 self._parent = Tk() 1507 self._parent.title('NLTK') 1508 self._parent.bind('q', self.destroy) 1509 else: 1510 self._parent = parent 1511 1512 # Create a frame for the canvas & scrollbars 1513 self._frame = frame = Frame(self._parent) 1514 self._canvas = canvas = Canvas(frame, **kw) 1515 xscrollbar = Scrollbar(self._frame, orient='horizontal') 1516 yscrollbar = Scrollbar(self._frame, orient='vertical') 1517 xscrollbar['command'] = canvas.xview 1518 yscrollbar['command'] = canvas.yview 1519 canvas['xscrollcommand'] = xscrollbar.set 1520 canvas['yscrollcommand'] = yscrollbar.set 1521 yscrollbar.pack(fill='y', side='right') 1522 xscrollbar.pack(fill='x', side='bottom') 1523 canvas.pack(expand=1, fill='both', side='left') 1524 1525 # Set initial scroll region. 1526 scrollregion = '0 0 %s %s' % (canvas['width'], canvas['height']) 1527 canvas['scrollregion'] = scrollregion 1528 1529 self._scrollwatcher = ScrollWatcherWidget(canvas) 1530 1531 # If no parent was given, pack the frame. 1532 if parent is None: 1533 self.pack(expand=1, fill='both') 1534 1535 # Done button. 1536 buttonframe = Frame(self._parent) 1537 buttonframe.pack(fill='x', side='bottom') 1538 ok = Button(buttonframe, text='Done', command=self.destroy) 1539 ok.pack(side='right') 1540 ps = Button(buttonframe, text='Print', 1541 command=self.print_to_file) 1542 ps.pack(side='left')
1543 #help = Button(buttonframe, text='Help') 1544 #help.pack(side='right') 1545
1546 - def print_to_file(self, filename=None):
1547 """ 1548 Print the contents of this C{CanvasFrame} to a postscript 1549 file. If no filename is given, then prompt the user for one. 1550 1551 @param filename: The name of the file to print the tree to. 1552 @type filename: C{string} 1553 @rtype: C{None} 1554 """ 1555 if filename is None: 1556 from tkFileDialog import asksaveasfilename 1557 ftypes = [('Postscript files', '.ps'), 1558 ('All files', '*')] 1559 filename = asksaveasfilename(filetypes=ftypes, 1560 defaultextension='.ps') 1561 if not filename: return 1562 (x0, y0, w, h) = self.scrollregion() 1563 self._canvas.postscript(file=filename, x=x0, y=y0, 1564 width=w+2, height=h+2)
1565
1566 - def scrollregion(self):
1567 """ 1568 @return: The current scroll region for the canvas managed by 1569 this C{CanvasFrame}. 1570 @rtype: 4-tuple of C{int} 1571 """ 1572 (x1, y1, x2, y2) = self._canvas['scrollregion'].split() 1573 return (int(x1), int(y1), int(x2), int(y2))
1574
1575 - def canvas(self):
1576 """ 1577 @return: The canvas managed by this C{CanvasFrame}. 1578 @rtype: C{Tkinter.Canvas} 1579 """ 1580 return self._canvas
1581
1582 - def add_widget(self, canvaswidget, x=None, y=None):
1583 """ 1584 Register a canvas widget with this C{CanvasFrame}. The 1585 C{CanvasFrame} will ensure that this canvas widget is always 1586 within the C{Canvas}'s scrollregion. If no coordinates are 1587 given for the canvas widget, then the C{CanvasFrame} will 1588 attempt to find a clear area of the canvas for it. 1589 1590 @type canvaswidget: C{CanvasWidget} 1591 @param canvaswidget: The new canvas widget. C{canvaswidget} 1592 must have been created on this C{CanvasFrame}'s canvas. 1593 @type x: C{int} 1594 @param x: The initial x coordinate for the upper left hand 1595 corner of C{canvaswidget}, in the canvas's coordinate 1596 space. 1597 @type y: C{int} 1598 @param y: The initial y coordinate for the upper left hand 1599 corner of C{canvaswidget}, in the canvas's coordinate 1600 space. 1601 """ 1602 if x is None or y is None: 1603 (x, y) = self._find_room(canvaswidget) 1604 1605 # Move to (x,y) 1606 (x1,y1,x2,y2) = canvaswidget.bbox() 1607 canvaswidget.move(x-x1,y-y1) 1608 1609 # Register with scrollwatcher. 1610 self._scrollwatcher.add_child(canvaswidget)
1611
1612 - def _find_room(self, widget):
1613 """ 1614 Try to find a space for a given widget. 1615 """ 1616 (left, top, right, bot) = self.scrollregion() 1617 1618 w = widget.width() 1619 h = widget.height() 1620 1621 # Move the widget out of the way, for now. 1622 (x1,y1,x2,y2) = widget.bbox() 1623 widget.move(left-x2-50, top-y2-50) 1624 1625 if abs(right-left-w) < 10 or abs(bot-top-h) < 10: 1626 # dp: Not sure why this happens, but it must be stopped 1627 return (0,0) 1628 for y in range(top, bot-h, (bot-top-h)/10): 1629 for x in range(left, right-w, (right-left-w)/10): 1630 if not self._canvas.find_overlapping(x-5, y-5, x+w+5, y+h+5): 1631 return (x,y) 1632 return (0,0)
1633
1634 - def destroy_widget(self, canvaswidget):
1635 """ 1636 Remove a canvas widget from this C{CanvasFrame}. This 1637 deregisters the canvas widget, and destroys it. 1638 """ 1639 # Deregister with scrollwatcher. 1640 self._scrollwatcher.remove_child(canvaswidget) 1641 canvaswidget.destroy()
1642
1643 - def pack(self, cnf={}, **kw):
1644 """ 1645 Pack this C{CanvasFrame}. See the documentation for 1646 C{Tkinter.Pack} for more information. 1647 """ 1648 self._frame.pack(cnf, **kw)
1649 # Adjust to be big enough for kids? 1650
1651 - def destroy(self, *e):
1652 """ 1653 Destroy this C{CanvasFrame}. If this C{CanvasFrame} created a 1654 top-level window, then this will close that window. 1655 """ 1656 if self._parent is None: return 1657 self._parent.destroy() 1658 self._parent = None
1659
1660 - def mainloop(self, *args, **kwargs):
1661 self._parent.mainloop(*args, **kwargs)
1662
1663 -class ShowText:
1664 """ 1665 A C{Tkinter} window used to display a text. C{ShowText} is 1666 typically used by graphical tools to display help text, or similar 1667 information. 1668 """
1669 - def __init__(self, root, title, text, width=None, height=None, 1670 **textbox_options):
1671 if width is None or height is None: 1672 (width, height) = self.find_dimensions(text, width, height) 1673 1674 # Create the main window. 1675 if root is None: 1676 self._top = top = Tk() 1677 else: 1678 self._top = top = Toplevel(root) 1679 top.title(title) 1680 1681 b = Button(top, text='Ok', command=self.destroy) 1682 b.pack(side='bottom') 1683 1684 tbf = Frame(top) 1685 tbf.pack(expand=1, fill='both') 1686 scrollbar = Scrollbar(tbf, orient='vertical') 1687 scrollbar.pack(side='right', fill='y') 1688 textbox = Text(tbf, wrap='word', width=width, 1689 height=height, **textbox_options) 1690 textbox.insert('end', text) 1691 textbox['state'] = 'disabled' 1692 textbox.pack(side='left', expand=1, fill='both') 1693 scrollbar['command'] = textbox.yview 1694 textbox['yscrollcommand'] = scrollbar.set 1695 1696 # Make it easy to close the window. 1697 top.bind('q', self.destroy) 1698 top.bind('x', self.destroy) 1699 top.bind('c', self.destroy) 1700 top.bind('<Return>', self.destroy) 1701 top.bind('<Escape>', self.destroy) 1702 1703 # Focus the scrollbar, so they can use up/down, etc. 1704 scrollbar.focus()
1705
1706 - def find_dimensions(self, text, width, height):
1707 lines = text.split('\n') 1708 if width is None: 1709 maxwidth = max([len(line) for line in lines]) 1710 width = min(maxwidth, 80) 1711 1712 # Now, find height. 1713 height = 0 1714 for line in lines: 1715 while len(line) > width: 1716 brk = line[:width].rfind(' ') 1717 line = line[brk:] 1718 height += 1 1719 height += 1 1720 height = min(height, 25) 1721 1722 return (width, height)
1723
1724 - def destroy(self, *e):
1725 if self._top is None: return 1726 self._top.destroy() 1727 self._top = None
1728
1729 - def mainloop(self):
1730 self._top.mainloop()
1731 1732 ##////////////////////////////////////////////////////// 1733 ## Test code. 1734 ##////////////////////////////////////////////////////// 1735
1736 -def odemo(root=None):
1737 """ 1738 A simple demonstration showing how to use canvas widgets. 1739 """ 1740 def fill(cw): 1741 from random import randint 1742 cw['fill'] = '#00%04d' % randint(0,9999)
1743 def color(cw): 1744 from random import randint 1745 cw['color'] = '#ff%04d' % randint(0,9999) 1746 1747 cf = CanvasFrame(root,closeenough=10, width=300, height=300) 1748 c = cf.canvas() 1749 ct3 = TextWidget(c, 'hiya there', draggable=1) 1750 ct2 = TextWidget(c, 'o o\n||\n___\n U', draggable=1, justify='center') 1751 co = OvalWidget(c, ct2, outline='red') 1752 ct = TextWidget(c, 'o o\n||\n\\___/', draggable=1, justify='center') 1753 cp = ParenWidget(c, ct, color='red') 1754 cb = BoxWidget(c, cp, fill='cyan', draggable=1, width=3, margin=10) 1755 equation = SequenceWidget(c, 1756 SymbolWidget(c, 'forall'), TextWidget(c, 'x'), 1757 SymbolWidget(c, 'exists'), TextWidget(c, 'y: '), 1758 TextWidget(c, 'x'), SymbolWidget(c, 'notequal'), 1759 TextWidget(c, 'y')) 1760 space = SpaceWidget(c, 0, 30) 1761 cstack = StackWidget(c, cb, ct3, space, co, equation, align='center') 1762 foo = TextWidget(c, 'try clicking\nand dragging', 1763 draggable=1, justify='center') 1764 cs = SequenceWidget(c, cstack, foo) 1765 zz = BracketWidget(c, cs, color='green4', width=3) 1766 cf.add_widget(zz, 60, 30) 1767 1768 cb.bind_click(fill) 1769 ct.bind_click(color) 1770 co.bind_click(fill) 1771 ct2.bind_click(color) 1772 ct3.bind_click(color) 1773 1774 #ShowText(None, 'title', ((('this is text'*150)+'\n')*5)) 1775 1776 1777 if __name__ == '__main__': 1778 root = Tk() 1779 odemo() 1780 root.mainloop() 1781