####################################################################### # 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')