# # The Widget Construction Kit # $Id: Utils.py 2557 2005-10-16 20:37:26Z Fredrik $ # # widget helpers # # history: # 2001-01-07 fl added scroll mixin; rename internal mixin methods # 2001-01-26 fl added event controller/mixin (for simplied event handling) # 2001-02-03 fl moved from tk3.py # 2003-05-12 fl added Observable mixin # # Copyright (c) 2000-2005 by Secret Labs AB # Copyright (c) 2000-2005 by Fredrik Lundh # # info@pythonware.com # http://www.pythonware.com # # -------------------------------------------------------------------- # The Tkinter 3000 Widget Construction Kit is # # Copyright (c) 2000-2005 by Secret Labs AB # Copyright (c) 2000-2005 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and its # associated documentation for any purpose and without fee is hereby # granted, provided that the above copyright notice appears in all # copies, and that both that copyright notice and this permission notice # appear in supporting documentation, and that the name of Secret Labs # AB or the author not be used in advertising or publicity pertaining to # distribution of the software without specific, written prior # permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR # ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -------------------------------------------------------------------- import WCK ## # Text mixin. This mixin class maps foreground and font # options to a font resource attribute, and handles width and # height in character units. #

# To define what should be drawn, override the ui_text_get # method. class TextMixin: ui_option_foreground = WCK.FOREGROUND ui_option_font = WCK.FONT ui_option_height = 0 ui_option_width = 0 ## # Default config method. This implementation sets up the font # attribute and the widget size, based on the mixin options. If you # override this method, you must remember call the mixin version. def ui_handle_config(self): # fonts self.font = font = self.ui_font( self.ui_option_foreground, self.ui_option_font ) # size (default is actual size) option_width = float(self.ui_option_width) option_height = float(self.ui_option_height) if option_width <= 0 or option_height <= 0: try: width, height = self.ui_draw.textsize( self.ui_text_get(), self.font ) except NotImplementedError: raise ValueError, "width and height must both be non-zero" if option_width > 0: width = self.ui_draw.textsize(None, font)[0] * option_width if option_height > 0: height = height * option_height else: width, height = self.ui_draw.textsize(None, font) width = option_width * width height = option_height * height return int(width), int(height) ## # Default repair method. This implementation calls the {@link # TextMixin.ui_text_draw} method. def ui_handle_repair(self, draw, x0, y0, x1, y1): self.ui_text_draw(draw) ## # Draws centered text. The default implementation draws centered # text, and uses the ui_text_get method to determine what # to draw. # # @param draw Drawing context, as passed to ui_handle_repair. # @param text Optional text. If omitted or None, this method calls # the {@link TextMixin.ui_text_get} method to fetch the text. def ui_text_draw(self, draw, text=None): size = self.ui_size() if text is None: text = self.ui_text_get() width, height = draw.textsize(text, self.font) draw.text(((size[0]-width)/2, (size[1]-height)/2), text, self.font) ## # Get text to draw. This method is only called if # {@link TextMixin.ui_text_draw} is called without a text argument. # # @return The string to draw. def ui_text_get(self): raise NotImplementedError, "must override text_get" ## # Standard controller for the EventMixin class. class EventController(WCK.Controller): def create(self, handle): handle("", self.handle_key) handle("", self.handle_press) handle("", self.handle_release) handle("", self.handle_enter) handle("", self.handle_motion) handle("", self.handle_leave) def handle_key(self, event): event.widget.onkey(event) def handle_press(self, event): event.widget.onmousedown(event) def handle_release(self, event): # FIXME: only generate click event if the mouse hasn't moved event.widget.onclick(event) event.widget.onmouseup(event) def handle_enter(self, event): event.widget.onmouseover(event) def handle_motion(self, event): event.widget.onmousemove(event) def handle_leave(self, event): event.widget.onmouseout(event) ## # Event mixin. This mixin simplifies user event handling, by mapping # mouse and keyboard events to DOM/DHTML-style method calls. #

# Note that this mixin overrides the ui_controller attribute. class EventMixin: ui_controller = EventController ## # Called for keyboard events. # # @param event A keyboard event (Key). Use event.char to # get the character string, event.keysym to get the # keyboard symbol, and event.keycode to get the key # code. def onkey(self, event): pass ## # Called for mouse clicks. # # @param event A button press event (ButtonPress). Use # event.num to get the button number, event.x # and event.y to get the mouse coordinate, relative # to the parent widget. def onclick(self, event): pass ## # Called for mouse button press events. # # @param event A button press event (ButtonPress). Use # event.num to get the button number, event.x # and event.y to get the mouse coordinate, relative # to the parent widget. def onmousedown(self, event): pass ## # Called for mouse button release events. # # @param event A button release event (ButtonRelease). Use # event.num to get the button number, event.x # and event.y to get the mouse coordinate, relative # to the parent widget. def onmouseup(self, event): pass ## # Called for window enter events. # # @param event An enter event (Enter). def onmouseover(self, event): pass ## # Called for mouse motion events. # # @param event A motion event (Motion). Use event.x # and event.y to get the mouse coordinate, relative # to the parent widget. def onmousemove(self, event): pass ## # Called for window leave events. # # @param event A leave event (Leave). def onmouseout(self, event): pass ## # Standard controller for the ButtonMixin class and other # button-like widgets. class ButtonController(WCK.Controller): active = None inside = None def create(self, handle): handle("", self.handle_enter) handle("", self.handle_leave) handle("", self.handle_button_1) handle("", self.handle_button_release_1) handle("", self.handle_space) def handle_enter(self, event): widget = event.widget widget.ui_button_enter() if widget.cget("state") != "disabled": if widget is self.active: # arm if user moves pointer back into active button widget.ui_button_arm() self.inside = widget def handle_leave(self, event): widget = event.widget widget.ui_button_leave() if widget is self.active: # disarm button if user moves the pointer outside # the widget widget.ui_button_disarm() self.inside = None def handle_button_1(self, event): widget = event.widget if widget.cget("state") != "disabled": widget.focus_set() widget.ui_button_arm() self.active = widget def handle_button_release_1(self, event): widget = event.widget if self.active is widget and widget.cget("state") != "disabled": self.active = None if widget is self.inside: widget.ui_button_disarm() widget.invoke() def handle_space(self, event): widget = event.widget if widget.cget("state") != "disabled": # simulate a mouse click widget.ui_button_arm() widget.update_idletasks() widget.after(100) widget.ui_button_disarm() widget.invoke() ## # Button mixin. This mixin implements basic button widget behaviour # (arm/click). class ButtonMixin: ui_option_state = "normal" ui_option_command = None ui_controller = ButtonController ## # Called when the user presses the button, either by clicking the # mouse button over the button, or by pressing the space bar. #

# The default implementation calls the object given by the # command option, if callable. def invoke(self): "Invoke button command" command = self.ui_option_command if callable(command): command() ## # Called when the mouse button is pressed with the mouse placed # over the button. def ui_button_arm(self): pass ## # Called when the mouse button is released after the mouse has # been moved out of the button. def ui_button_disarm(self): pass ## # Called when the mouse pointer is moved over the button. def ui_button_enter(self): pass ## # Called when the mouse pointer is moved out from the button. def ui_button_leave(self): pass ## # Scroll mixin. This mixin implements vertical and horizontal # scrolling. class ScrollMixin: # mixin user must override these to enable scrolling ui_option_xscrollcommand = WCK.NOT_INHERITED ui_option_yscrollcommand = WCK.NOT_INHERITED ## # Get total/left/right indexes. def ui_scroll_xinfo(self): raise NotImplementedError, "must override ui_scroll_xinfo" ## # Get total/top/bottom indexes. def ui_scroll_yinfo(self): raise NotImplementedError, "must override ui_scroll_yinfo" ## # Set left margin to left + units + pages. def ui_scroll_xset(self, left, units, pages): raise NotImplementedError, "must override ui_scroll_xset" ## # Set top margin to left + units + pages. def ui_scroll_yset(self, top, units, pages): raise NotImplementedError, "must override ui_scroll_yset" ## # Update scrollbar. def ui_scroll_update(self): xscrollcommand = self.ui_option_xscrollcommand if callable(xscrollcommand): total, left, right = self.ui_scroll_xinfo() if total > 0: ratio1 = float(left) / total ratio2 = float(right) / total xscrollcommand(max(0.0, ratio1), min(ratio2, 1.0)) else: xscrollcommand(0.0, 1.0) yscrollcommand = self.ui_option_yscrollcommand if callable(yscrollcommand): total, top, bottom = self.ui_scroll_yinfo() if total > 0: ratio1 = float(top) / total ratio2 = float(bottom) / total yscrollcommand(max(0.0, ratio1), min(ratio2, 1.0)) else: yscrollcommand(0.0, 1.0) ## # Change the horizonal view. def xview(self, command, value, unit=None): "Change horizontal view" total, left, right = self.ui_scroll_xinfo() if command == "moveto": self.ui_scroll_xset(int(total * float(value) + 0.5), 0, 0) elif command == "scroll": if unit == "units": self.ui_scroll_xset(left, int(value), 0) elif unit == "pages": self.ui_scroll_xset(left, 0, int(value)) else: raise ValueError, "command should be 'moveto' or 'scroll'" ## # Change the vertical view. def yview(self, command, value, unit=None): "Change vertical view" total, top, bottom = self.ui_scroll_yinfo() if command == "moveto": self.ui_scroll_yset(int(total * float(value) + 0.5), 0, 0) elif command == "scroll": if unit == "units": self.ui_scroll_yset(top, int(value), 0) elif unit == "pages": self.ui_scroll_yset(top, 0, int(value)) else: raise ValueError, "command should be 'moveto' or 'scroll'" # -------------------------------------------------------------------- # convenience widgets ## # Simple scrolled event-handling widget base class. This class # inherits from {@link ScrollMixin} and {@link EventMixin}. class SimpleWidget(ScrollMixin, EventMixin, WCK.Widget): pass ## # Standard button widget base class. This widget inherits from {@link # ButtonMixin}, and adds a relief option, which is changed to # "sunken" whenever the button is armed. class ButtonWidget(ButtonMixin, WCK.Widget): # button widget ui_option_relief = "raised" ui_option_borderwidth = 2 ui_option_takefocus = 1 def ui_button_arm(self): self.__relief = self.cget("relief") self.config(relief="sunken") def ui_button_disarm(self): self.config(relief=self.__relief) ## # Standard observable mixin. This mixin class can be used to make # container objects (including widget models) "observable". class Observable: __observers = None ## # Add an observer object to the container. The observer # should be a callable object which takes two arguments (the # event code and associated data). # # @param observer Observer object. def addobserver(self, observer): if not self.__observers: self.__observers = [] self.__observers.append(observer) ## # Remove the given observer object from the container. The # observer must exist. # # @param observer Observer object. def removeobserver(self, observer): self.__observers.remove(observer) ## # Send the event and associated data to all observers. If an # exception occurs in an observer, notification is aborted and the # exception is propagated back to the caller. # # @param event Observer event. # @param data Optional data associated with the event. def notify(self, event, data=None): for o in self.__observers or (): o(event, data)