# # 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("
# 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("
# 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)