historical/toontown-classic.git/panda/Pmw/Pmw_1_3/lib/PmwNoteBook.py
2024-01-16 11:20:27 -06:00

617 lines
24 KiB
Python

import string
import types
import Tkinter
import Pmw
class NoteBook(Pmw.MegaArchetype):
def __init__(self, parent = None, **kw):
# Define the megawidget options.
INITOPT = Pmw.INITOPT
optiondefs = (
('hull_highlightthickness', 0, None),
('hull_borderwidth', 0, None),
('arrownavigation', 1, INITOPT),
('borderwidth', 2, INITOPT),
('createcommand', None, None),
('lowercommand', None, None),
('pagemargin', 4, INITOPT),
('raisecommand', None, None),
('tabpos', 'n', INITOPT),
)
self.defineoptions(kw, optiondefs, dynamicGroups = ('Page', 'Tab'))
# Initialise the base class (after defining the options).
Pmw.MegaArchetype.__init__(self, parent, Tkinter.Canvas)
self.bind('<Map>', self._handleMap)
self.bind('<Configure>', self._handleConfigure)
tabpos = self['tabpos']
if tabpos is not None and tabpos != 'n':
raise ValueError, \
'bad tabpos option %s: should be n or None' % repr(tabpos)
self._withTabs = (tabpos is not None)
self._pageMargin = self['pagemargin']
self._borderWidth = self['borderwidth']
# Use a dictionary as a set of bits indicating what needs to
# be redisplayed the next time _layout() is called. If
# dictionary contains 'topPage' key, the value is the new top
# page to be displayed. None indicates that all pages have
# been deleted and that _layout() should draw a border under where
# the tabs should be.
self._pending = {}
self._pending['size'] = 1
self._pending['borderColor'] = 1
self._pending['topPage'] = None
if self._withTabs:
self._pending['tabs'] = 1
self._canvasSize = None # This gets set by <Configure> events
# Set initial height of space for tabs
if self._withTabs:
self.tabBottom = 35
else:
self.tabBottom = 0
self._lightBorderColor, self._darkBorderColor = \
Pmw.Color.bordercolors(self, self['hull_background'])
self._pageNames = [] # List of page names
# Map from page name to page info. Each item is itself a
# dictionary containing the following items:
# page the Tkinter.Frame widget for the page
# created set to true the first time the page is raised
# tabbutton the Tkinter.Button widget for the button (if any)
# tabreqwidth requested width of the tab
# tabreqheight requested height of the tab
# tabitems the canvas items for the button: the button
# window item, the lightshadow and the darkshadow
# left the left and right canvas coordinates of the tab
# right
self._pageAttrs = {}
# Name of page currently on top (actually displayed, using
# create_window, not pending). Ignored if current top page
# has been deleted or new top page is pending. None indicates
# no pages in notebook.
self._topPageName = None
# Canvas items used:
# Per tab:
# top and left shadow
# right shadow
# button
# Per notebook:
# page
# top page
# left shadow
# bottom and right shadow
# top (one or two items)
# Canvas tags used:
# lighttag - top and left shadows of tabs and page
# darktag - bottom and right shadows of tabs and page
# (if no tabs then these are reversed)
# (used to color the borders by recolorborders)
# Create page border shadows.
if self._withTabs:
self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
fill = self._lightBorderColor, tags = 'lighttag')
self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
fill = self._darkBorderColor, tags = 'darktag')
self._pageTop1Border = self.create_polygon(0, 0, 0, 0, 0, 0,
fill = self._darkBorderColor, tags = 'lighttag')
self._pageTop2Border = self.create_polygon(0, 0, 0, 0, 0, 0,
fill = self._darkBorderColor, tags = 'lighttag')
else:
self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
fill = self._darkBorderColor, tags = 'darktag')
self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
fill = self._lightBorderColor, tags = 'lighttag')
self._pageTopBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
fill = self._darkBorderColor, tags = 'darktag')
# Check keywords and initialise options.
self.initialiseoptions()
def insert(self, pageName, before = 0, **kw):
if self._pageAttrs.has_key(pageName):
msg = 'Page "%s" already exists.' % pageName
raise ValueError, msg
# Do this early to catch bad <before> spec before creating any items.
beforeIndex = self.index(before, 1)
pageOptions = {}
if self._withTabs:
# Default tab button options.
tabOptions = {
'text' : pageName,
'borderwidth' : 0,
}
# Divide the keyword options into the 'page_' and 'tab_' options.
for key in kw.keys():
if key[:5] == 'page_':
pageOptions[key[5:]] = kw[key]
del kw[key]
elif self._withTabs and key[:4] == 'tab_':
tabOptions[key[4:]] = kw[key]
del kw[key]
else:
raise KeyError, 'Unknown option "' + key + '"'
# Create the frame to contain the page.
page = apply(self.createcomponent, (pageName,
(), 'Page',
Tkinter.Frame, self._hull), pageOptions)
attributes = {}
attributes['page'] = page
attributes['created'] = 0
if self._withTabs:
# Create the button for the tab.
def raiseThisPage(self = self, pageName = pageName):
self.selectpage(pageName)
tabOptions['command'] = raiseThisPage
tab = apply(self.createcomponent, (pageName + '-tab',
(), 'Tab',
Tkinter.Button, self._hull), tabOptions)
if self['arrownavigation']:
# Allow the use of the arrow keys for Tab navigation:
def next(event, self = self, pageName = pageName):
self.nextpage(pageName)
def prev(event, self = self, pageName = pageName):
self.previouspage(pageName)
tab.bind('<Left>', prev)
tab.bind('<Right>', next)
attributes['tabbutton'] = tab
attributes['tabreqwidth'] = tab.winfo_reqwidth()
attributes['tabreqheight'] = tab.winfo_reqheight()
# Create the canvas item to manage the tab's button and the items
# for the tab's shadow.
windowitem = self.create_window(0, 0, window = tab, anchor = 'nw')
lightshadow = self.create_polygon(0, 0, 0, 0, 0, 0,
tags = 'lighttag', fill = self._lightBorderColor)
darkshadow = self.create_polygon(0, 0, 0, 0, 0, 0,
tags = 'darktag', fill = self._darkBorderColor)
attributes['tabitems'] = (windowitem, lightshadow, darkshadow)
self._pending['tabs'] = 1
self._pageAttrs[pageName] = attributes
self._pageNames.insert(beforeIndex, pageName)
# If this is the first page added, make it the new top page
# and call the create and raise callbacks.
if self.getcurselection() is None:
self._pending['topPage'] = pageName
self._raiseNewTop(pageName)
self._layout()
return page
def add(self, pageName, **kw):
return apply(self.insert, (pageName, len(self._pageNames)), kw)
def delete(self, *pageNames):
newTopPage = 0
for page in pageNames:
pageIndex = self.index(page)
pageName = self._pageNames[pageIndex]
pageInfo = self._pageAttrs[pageName]
if self.getcurselection() == pageName:
if len(self._pageNames) == 1:
newTopPage = 0
self._pending['topPage'] = None
elif pageIndex == len(self._pageNames) - 1:
newTopPage = 1
self._pending['topPage'] = self._pageNames[pageIndex - 1]
else:
newTopPage = 1
self._pending['topPage'] = self._pageNames[pageIndex + 1]
if self._topPageName == pageName:
self._hull.delete(self._topPageItem)
self._topPageName = None
if self._withTabs:
self.destroycomponent(pageName + '-tab')
apply(self._hull.delete, pageInfo['tabitems'])
self.destroycomponent(pageName)
del self._pageAttrs[pageName]
del self._pageNames[pageIndex]
# If the old top page was deleted and there are still pages
# left in the notebook, call the create and raise callbacks.
if newTopPage:
pageName = self._pending['topPage']
self._raiseNewTop(pageName)
if self._withTabs:
self._pending['tabs'] = 1
self._layout()
def page(self, pageIndex):
pageName = self._pageNames[self.index(pageIndex)]
return self._pageAttrs[pageName]['page']
def pagenames(self):
return list(self._pageNames)
def getcurselection(self):
if self._pending.has_key('topPage'):
return self._pending['topPage']
else:
return self._topPageName
def tab(self, pageIndex):
if self._withTabs:
pageName = self._pageNames[self.index(pageIndex)]
return self._pageAttrs[pageName]['tabbutton']
else:
return None
def index(self, index, forInsert = 0):
listLength = len(self._pageNames)
if type(index) == types.IntType:
if forInsert and index <= listLength:
return index
elif not forInsert and index < listLength:
return index
else:
raise ValueError, 'index "%s" is out of range' % index
elif index is Pmw.END:
if forInsert:
return listLength
elif listLength > 0:
return listLength - 1
else:
raise ValueError, 'NoteBook has no pages'
elif index is Pmw.SELECT:
if listLength == 0:
raise ValueError, 'NoteBook has no pages'
return self._pageNames.index(self.getcurselection())
else:
if index in self._pageNames:
return self._pageNames.index(index)
validValues = 'a name, a number, Pmw.END or Pmw.SELECT'
raise ValueError, \
'bad index "%s": must be %s' % (index, validValues)
def selectpage(self, page):
pageName = self._pageNames[self.index(page)]
oldTopPage = self.getcurselection()
if pageName != oldTopPage:
self._pending['topPage'] = pageName
if oldTopPage == self._topPageName:
self._hull.delete(self._topPageItem)
cmd = self['lowercommand']
if cmd is not None:
cmd(oldTopPage)
self._raiseNewTop(pageName)
self._layout()
# Set focus to the tab of new top page:
if self._withTabs and self['arrownavigation']:
self._pageAttrs[pageName]['tabbutton'].focus_set()
def previouspage(self, pageIndex = None):
if pageIndex is None:
curpage = self.index(Pmw.SELECT)
else:
curpage = self.index(pageIndex)
if curpage > 0:
self.selectpage(curpage - 1)
def nextpage(self, pageIndex = None):
if pageIndex is None:
curpage = self.index(Pmw.SELECT)
else:
curpage = self.index(pageIndex)
if curpage < len(self._pageNames) - 1:
self.selectpage(curpage + 1)
def setnaturalsize(self, pageNames = None):
self.update_idletasks()
maxPageWidth = 1
maxPageHeight = 1
if pageNames is None:
pageNames = self.pagenames()
for pageName in pageNames:
pageInfo = self._pageAttrs[pageName]
page = pageInfo['page']
w = page.winfo_reqwidth()
h = page.winfo_reqheight()
if maxPageWidth < w:
maxPageWidth = w
if maxPageHeight < h:
maxPageHeight = h
pageBorder = self._borderWidth + self._pageMargin
width = maxPageWidth + pageBorder * 2
height = maxPageHeight + pageBorder * 2
if self._withTabs:
maxTabHeight = 0
for pageInfo in self._pageAttrs.values():
if maxTabHeight < pageInfo['tabreqheight']:
maxTabHeight = pageInfo['tabreqheight']
height = height + maxTabHeight + self._borderWidth * 1.5
# Note that, since the hull is a canvas, the width and height
# options specify the geometry *inside* the borderwidth and
# highlightthickness.
self.configure(hull_width = width, hull_height = height)
def recolorborders(self):
self._pending['borderColor'] = 1
self._layout()
def _handleMap(self, event):
self._layout()
def _handleConfigure(self, event):
self._canvasSize = (event.width, event.height)
self._pending['size'] = 1
self._layout()
def _raiseNewTop(self, pageName):
if not self._pageAttrs[pageName]['created']:
self._pageAttrs[pageName]['created'] = 1
cmd = self['createcommand']
if cmd is not None:
cmd(pageName)
cmd = self['raisecommand']
if cmd is not None:
cmd(pageName)
# This is the vertical layout of the notebook, from top (assuming
# tabpos is 'n'):
# hull highlightthickness (top)
# hull borderwidth (top)
# borderwidth (top border of tabs)
# borderwidth * 0.5 (space for bevel)
# tab button (maximum of requested height of all tab buttons)
# borderwidth (border between tabs and page)
# pagemargin (top)
# the page itself
# pagemargin (bottom)
# borderwidth (border below page)
# hull borderwidth (bottom)
# hull highlightthickness (bottom)
#
# canvasBorder is sum of top two elements.
# tabBottom is sum of top five elements.
#
# Horizontal layout (and also vertical layout when tabpos is None):
# hull highlightthickness
# hull borderwidth
# borderwidth
# pagemargin
# the page itself
# pagemargin
# borderwidth
# hull borderwidth
# hull highlightthickness
#
def _layout(self):
if not self.winfo_ismapped() or self._canvasSize is None:
# Don't layout if the window is not displayed, or we
# haven't yet received a <Configure> event.
return
hullWidth, hullHeight = self._canvasSize
borderWidth = self._borderWidth
canvasBorder = string.atoi(self._hull['borderwidth']) + \
string.atoi(self._hull['highlightthickness'])
if not self._withTabs:
self.tabBottom = canvasBorder
oldTabBottom = self.tabBottom
if self._pending.has_key('borderColor'):
self._lightBorderColor, self._darkBorderColor = \
Pmw.Color.bordercolors(self, self['hull_background'])
# Draw all the tabs.
if self._withTabs and (self._pending.has_key('tabs') or
self._pending.has_key('size')):
# Find total requested width and maximum requested height
# of tabs.
sumTabReqWidth = 0
maxTabHeight = 0
for pageInfo in self._pageAttrs.values():
sumTabReqWidth = sumTabReqWidth + pageInfo['tabreqwidth']
if maxTabHeight < pageInfo['tabreqheight']:
maxTabHeight = pageInfo['tabreqheight']
if maxTabHeight != 0:
# Add the top tab border plus a bit for the angled corners
self.tabBottom = canvasBorder + maxTabHeight + borderWidth * 1.5
# Prepare for drawing the border around each tab button.
tabTop = canvasBorder
tabTop2 = tabTop + borderWidth
tabTop3 = tabTop + borderWidth * 1.5
tabBottom2 = self.tabBottom
tabBottom = self.tabBottom + borderWidth
numTabs = len(self._pageNames)
availableWidth = hullWidth - 2 * canvasBorder - \
numTabs * 2 * borderWidth
x = canvasBorder
cumTabReqWidth = 0
cumTabWidth = 0
# Position all the tabs.
for pageName in self._pageNames:
pageInfo = self._pageAttrs[pageName]
(windowitem, lightshadow, darkshadow) = pageInfo['tabitems']
if sumTabReqWidth <= availableWidth:
tabwidth = pageInfo['tabreqwidth']
else:
# This ugly calculation ensures that, when the
# notebook is not wide enough for the requested
# widths of the tabs, the total width given to
# the tabs exactly equals the available width,
# without rounding errors.
cumTabReqWidth = cumTabReqWidth + pageInfo['tabreqwidth']
tmp = (2*cumTabReqWidth*availableWidth + sumTabReqWidth) \
/ (2 * sumTabReqWidth)
tabwidth = tmp - cumTabWidth
cumTabWidth = tmp
# Position the tab's button canvas item.
self.coords(windowitem, x + borderWidth, tabTop3)
self.itemconfigure(windowitem,
width = tabwidth, height = maxTabHeight)
# Make a beautiful border around the tab.
left = x
left2 = left + borderWidth
left3 = left + borderWidth * 1.5
right = left + tabwidth + 2 * borderWidth
right2 = left + tabwidth + borderWidth
right3 = left + tabwidth + borderWidth * 0.5
self.coords(lightshadow,
left, tabBottom2, left, tabTop2, left2, tabTop,
right2, tabTop, right3, tabTop2, left3, tabTop2,
left2, tabTop3, left2, tabBottom,
)
self.coords(darkshadow,
right2, tabTop, right, tabTop2, right, tabBottom2,
right2, tabBottom, right2, tabTop3, right3, tabTop2,
)
pageInfo['left'] = left
pageInfo['right'] = right
x = x + tabwidth + 2 * borderWidth
# Redraw shadow under tabs so that it appears that tab for old
# top page is lowered and that tab for new top page is raised.
if self._withTabs and (self._pending.has_key('topPage') or
self._pending.has_key('tabs') or self._pending.has_key('size')):
if self.getcurselection() is None:
# No pages, so draw line across top of page area.
self.coords(self._pageTop1Border,
canvasBorder, self.tabBottom,
hullWidth - canvasBorder, self.tabBottom,
hullWidth - canvasBorder - borderWidth,
self.tabBottom + borderWidth,
borderWidth + canvasBorder, self.tabBottom + borderWidth,
)
# Ignore second top border.
self.coords(self._pageTop2Border, 0, 0, 0, 0, 0, 0)
else:
# Draw two lines, one on each side of the tab for the
# top page, so that the tab appears to be raised.
pageInfo = self._pageAttrs[self.getcurselection()]
left = pageInfo['left']
right = pageInfo['right']
self.coords(self._pageTop1Border,
canvasBorder, self.tabBottom,
left, self.tabBottom,
left + borderWidth, self.tabBottom + borderWidth,
canvasBorder + borderWidth, self.tabBottom + borderWidth,
)
self.coords(self._pageTop2Border,
right, self.tabBottom,
hullWidth - canvasBorder, self.tabBottom,
hullWidth - canvasBorder - borderWidth,
self.tabBottom + borderWidth,
right - borderWidth, self.tabBottom + borderWidth,
)
# Prevent bottom of dark border of tabs appearing over
# page top border.
self.tag_raise(self._pageTop1Border)
self.tag_raise(self._pageTop2Border)
# Position the page border shadows.
if self._pending.has_key('size') or oldTabBottom != self.tabBottom:
self.coords(self._pageLeftBorder,
canvasBorder, self.tabBottom,
borderWidth + canvasBorder,
self.tabBottom + borderWidth,
borderWidth + canvasBorder,
hullHeight - canvasBorder - borderWidth,
canvasBorder, hullHeight - canvasBorder,
)
self.coords(self._pageBottomRightBorder,
hullWidth - canvasBorder, self.tabBottom,
hullWidth - canvasBorder, hullHeight - canvasBorder,
canvasBorder, hullHeight - canvasBorder,
borderWidth + canvasBorder,
hullHeight - canvasBorder - borderWidth,
hullWidth - canvasBorder - borderWidth,
hullHeight - canvasBorder - borderWidth,
hullWidth - canvasBorder - borderWidth,
self.tabBottom + borderWidth,
)
if not self._withTabs:
self.coords(self._pageTopBorder,
canvasBorder, self.tabBottom,
hullWidth - canvasBorder, self.tabBottom,
hullWidth - canvasBorder - borderWidth,
self.tabBottom + borderWidth,
borderWidth + canvasBorder, self.tabBottom + borderWidth,
)
# Color borders.
if self._pending.has_key('borderColor'):
self.itemconfigure('lighttag', fill = self._lightBorderColor)
self.itemconfigure('darktag', fill = self._darkBorderColor)
newTopPage = self._pending.get('topPage')
pageBorder = borderWidth + self._pageMargin
# Raise new top page.
if newTopPage is not None:
self._topPageName = newTopPage
self._topPageItem = self.create_window(
pageBorder + canvasBorder, self.tabBottom + pageBorder,
window = self._pageAttrs[newTopPage]['page'],
anchor = 'nw',
)
# Change position of top page if tab height has changed.
if self._topPageName is not None and oldTabBottom != self.tabBottom:
self.coords(self._topPageItem,
pageBorder + canvasBorder, self.tabBottom + pageBorder)
# Change size of top page if,
# 1) there is a new top page.
# 2) canvas size has changed, but not if there is no top
# page (eg: initially or when all pages deleted).
# 3) tab height has changed, due to difference in the height of a tab
if (newTopPage is not None or \
self._pending.has_key('size') and self._topPageName is not None
or oldTabBottom != self.tabBottom):
self.itemconfigure(self._topPageItem,
width = hullWidth - 2 * canvasBorder - pageBorder * 2,
height = hullHeight - 2 * canvasBorder - pageBorder * 2 -
(self.tabBottom - canvasBorder),
)
self._pending = {}
# Need to do forwarding to get the pack, grid, etc methods.
# Unfortunately this means that all the other canvas methods are also
# forwarded.
Pmw.forwardmethods(NoteBook, Tkinter.Canvas, '_hull')