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