#######################################################################
# File: PmwScrolledCanvas.py
# Author: Joseph A. Saltiel - jsaltiel@lucent.com
# References: Python Mega Widgets (Pmw)
# Modules: Tkinter, Pmw, string
# PmwScrolledCanvas - This is a ScrolledCanvas Mega Widget made for
# Python. It is based on the framework and
# is an extension of PMW (Python Mega Widgets). It
# allows the user to create a scrollable canvas.
# The user can get the subcomponent 'canvas' in which
# to add more elements.
#
# Copyright (C) 1997 by Lucent Technologies, Inc. & Joseph Saltiel
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#######################################################################
import Tkinter
import Pmw
import string
##################################################################
# Class: ScrolledCanvas
# Description: This defines the scrolledCanvas mega-widget. This
# includes its subcomponents and attributes. To add
# additional elements, use 'canvas' component as the
# parent (master).
##################################################################
class ScrolledCanvas(Pmw.MegaWidget):
def __init__(self, parent = None, **kw):
# Define the megawidget options.
optiondefs = (
('height', 100, None),
('width', 100, None),
('background', 'white', None),
('bg', 'white', None),
('vscrollmode', 'dynamic', self._vscrollMode),
('hscrollmode', 'dynamic', self._hscrollMode),
)
self.defineoptions(kw, optiondefs)
# Initialise the base class (after defining the options).
Pmw.MegaWidget.__init__(self, parent)
# Create the components.
interior = self.interior()
#set bg color
if self['background'] != 'white':
self['bg'] = self['background']
# Create the frame Canvas widget. This what is actually scrolled
self._frameCanvas = self.createcomponent('frameCanvas',
(), None,
Tkinter.Canvas, (interior,),
bg=self['bg'], highlightthickness=0,
scrollregion=(0,0, self['width'], self['height']),
xscrollcommand=self._scrollCanvasX,
yscrollcommand=self._scrollCanvasY)
self._frameCanvas.grid(row = 0, column = 0, sticky = 'news')
interior.grid_rowconfigure(0, weight = 1, minsize = 0)
interior.grid_columnconfigure(0, weight = 1, minsize = 0)
#Create a window on the frameCanvas. This should be the
#only element on it.
self._imageCanvas = self.createcomponent('imageCanvas',
(), None,
Tkinter.Canvas, (interior,),
bg=self['bg'], highlightthickness=0)
self._imageCanvas.grid_rowconfigure(0, weight=1)
self._imageCanvas.grid_columnconfigure(0, weight=1)
#Create a canvas on the window. This is what the user should
#use to add elements.
self._canvas = self.createcomponent('canvas',
(), None,
Tkinter.Canvas, (self._imageCanvas,),
bg=self['bg'], highlightthickness=0,
width=self['width'], height=self['height'])
self._canvas.grid(sticky = 'news')
tag = 'SCSelect' + str(self)
self.bind_class(tag, '<Configure>', self._updateScroll, "+")
self._canvas.bindtags(self._canvas.bindtags() + (tag,))
# Create the vertical scrollbar
self._vertScrollbar = self.createcomponent('vertscrollbar',
(), 'Scrollbar',
Tkinter.Scrollbar, (interior,),
orient='vertical', command=self._frameCanvas.yview,
bg=self['bg'], highlightthickness=0)
# Create the horizontal scrollbar
self._horizScrollbar = self.createcomponent('horizscrollbar',
(), 'Scrollbar',
Tkinter.Scrollbar, (interior,),
orient='horizontal', command=self._frameCanvas.xview,
bg=self['bg'], highlightthickness=0)
#Create a dummy frame to fill hole in the grid
self._dummyFrame = self.createcomponent('dummyFrame',
(), 'Frame',
Tkinter.Frame, (interior,),
bg=self['bg'])
self._dummyFrame.grid(row=1, column=1, sticky='news')
interior.configure(bg=self['bg'])
# Initialise instance variables.
self._vertMode = None
self._horizMode = None
self._horizRecurse = 0
self._scrollerX = 0
self._scrollerY = 0
self._canvasWin = self._frameCanvas.create_window(0, 0,
width=self['width'], height=self['height'],
anchor='nw', window=self._imageCanvas)
# Check keywords and initialise options.
self.initialiseoptions(ScrolledCanvas)
#**********************************************************************
# CONFIGURATION METHODS
#**********************************************************************
##################################################################
# Method: _updateScroll
# Description: The Scroll bar only seems to update on change of
# window size. This will force it to update.
#################################################################
def _updateScroll(self, event=None):
try:
self._scrollCanvasX()
self._scrollCanvasY()
except:
pass
##################################################################
# Method: _vscrollMode
# Description: This sets the vertical scrollbar mode
##################################################################
def _vscrollMode(self):
mode = self['vscrollmode']
if mode == 'static':
self._vertScrollbarDisplay(1)
elif mode == 'dynamic' or mode == 'none':
self._vertScrollbarDisplay(0)
else:
message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
raise ValueError, message
##################################################################
# Method: _hscrollMode
# Description: This sets the horizontal scrollbar mode.
##################################################################
def _hscrollMode(self):
mode = self['hscrollmode']
if mode == 'static':
self._horizScrollbarDisplay(1)
elif mode == 'dynamic' or mode == 'none':
self._horizScrollbarDisplay(0)
else:
message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
raise ValueError, message
#**********************************************************************
# PRIVATE METHODS
#**********************************************************************
##################################################################
# Method: _vertScrollbarDisplay
# Description: This displays or erases the vertical scrollbar
##################################################################
def _vertScrollbarDisplay(self, mode):
if mode != self._vertMode:
self._vertMode = mode
if self._vertMode:
self._scrollerY = 1
self._vertScrollbar.grid(row = 0, column = 1, sticky = 'ns')
else:
self._scrollerY = 0
self._vertScrollbar.grid_forget()
##################################################################
# Method: _horizScrollbarDisplay
# Description: This displays or erases the horizontal scrollbar
##################################################################
def _horizScrollbarDisplay(self, mode):
if mode != self._horizMode:
self._horizMode = mode
if self._horizMode:
self._scrollerX = 1
self._horizScrollbar.grid(row = 1, column = 0, sticky = 'ew')
else:
self._scrollerX = 0
self._horizScrollbar.grid_forget()
##################################################################
# Method: _scrollCanvasX
# Description: This controls the X scroll bar
##################################################################
def _scrollCanvasX(self, first=None, last=None):
if first:
self._horizScrollbar.set(first, last)
interior = self.interior()
newX = self._canvas.winfo_reqwidth()
fixedX = self._frameCanvas.winfo_width()
if (fixedX >= newX) and (self['hscrollmode'] == 'dynamic') and self._scrollerX:
self._horizScrollbarDisplay(0)
elif (fixedX < newX) and (self['hscrollmode'] == 'dynamic') and not(self._scrollerX):
self._horizScrollbarDisplay(1)
elements = self._canvas.find_all()
if elements:
self._adjustCanvasSize(sumX = max(fixedX, newX, apply(self._canvas.bbox, elements)[2]))
else:
self._adjustCanvasSize(sumX = max(fixedX, newX))
##################################################################
# Method: _scrollCanvasY
# Description: This controls the Y scroll bar
##################################################################
def _scrollCanvasY(self, first=None, last=None):
if first:
self._vertScrollbar.set(first, last)
interior = self.interior()
newY = self._canvas.winfo_reqheight()
fixedY = self._frameCanvas.winfo_height()
if (fixedY >= newY) and (self['vscrollmode'] == 'dynamic') and self._scrollerY:
self._vertScrollbarDisplay(0)
elif (fixedY < newY) and (self['vscrollmode'] == 'dynamic') and not(self._scrollerY):
self._vertScrollbarDisplay(1)
elements = self._canvas.find_all()
if elements:
self._adjustCanvasSize(sumY = max(fixedY, newY, apply(self._canvas.bbox, elements)[3]))
else:
self._adjustCanvasSize(sumY = max(fixedY, newY))
##################################################################
# Method: _adjustCanvasSize
# Description: This adjust the scroll region to cover the entire display
##################################################################
def _adjustCanvasSize(self, sumX=None, sumY=None):
change = 0
region = string.splitfields(self._frameCanvas['scrollregion'])
if sumY:
newY = repr(sumY)
if self._frameCanvas.itemcget(self._canvasWin, 'height') != newY:
region[3] = newY
change = 1
self._frameCanvas.itemconfigure(self._canvasWin, height=sumY)
if sumX:
newX = repr(sumX)
if self._frameCanvas.itemcget(self._canvasWin, 'width') != newX:
change = 1
region[2] = newX
self._frameCanvas.itemconfigure(self._canvasWin, width=sumX)
if change:
newRegion = tuple(region)
self._frameCanvas.configure(scrollregion=newRegion)
#**********************************************************************
# PUBLIC METHODS
#**********************************************************************
##################################################################
# Method: updateScrollbars
# Description: Public method to force the scrollbars/canvas to update
##################################################################
def updateScrollbars(self):
self._frameCanvas.update_idletasks()
self. _updateScroll()
##################################################################
# Method: getCanvas
# Description: This returns the canvas to use
##################################################################
def getCanvas(self):
return self._canvas
##################################################################
# Method: bbox
# Description: Need to explicitly forward this to override the
# stupid (grid_)bbox method inherited from
# Tkinter.Frame.Grid
##################################################################
def bbox(self, index):
return self._canvas.bbox(index)
Pmw.forwardmethods(ScrolledCanvas, Tkinter.Canvas, '_canvas')