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