"""
Tableau has a couple of base classes for analyze viewers.
 
"""
 
from __future__ import absolute_import
#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
import __init__
 
from skeinforge_tools.analyze_plugins.analyze_utilities import zoom_in
from skeinforge_tools.analyze_plugins.analyze_utilities import zoom_out
from skeinforge_tools.skeinforge_utilities import euclidean
from skeinforge_tools.skeinforge_utilities import gcodec
from skeinforge_tools.skeinforge_utilities import settings
import math
import os
 
__author__ = "Enrique Perez (perez_enrique@yahoo.com)"
__date__ = "$Date: 2008/21/04 $"
__license__ = "GPL 3.0"
 
 
def getGeometricDifference( first, second ):
	"Get the geometric difference of the two numbers."
	return max( first, second ) / min( first, second )
 
def getGridHorizontalFrame( gridPosition ):
	"Get the grid horizontal object with a frame from the grid position."
	gridHorizontal = settings.GridHorizontal( 0, 0 )
	gridHorizontal.master = settings.Tkinter.Frame( gridPosition.master, borderwidth = 1, padx = 3, relief = 'raised' )
	gridHorizontal.master.grid( row = gridPosition.row, column = gridPosition.column, sticky = settings.Tkinter.E )
	return gridHorizontal
 
def getLengthMinusOneMinimumOne( elementList ):
	"Get the length of the length minus one, minimum one."
	return max( 1, len( elementList ) - 1 )
 
def getScrollbarCanvasPortion( scrollbar ):
	"Get the canvas portion of the scrollbar."
	scrollbarBeginEnd = scrollbar.get()
	return scrollbarBeginEnd[ 1 ] - scrollbarBeginEnd[ 0 ]
 
def setStateNormalDisabled( active, widget ):
	"Set the state of the widget to normal if active and disabled if inactive."
	if active:
		widget.config( state = settings.Tkinter.NORMAL )
	else:
		widget.config( state = settings.Tkinter.DISABLED )
 
 
class ColoredLine:
	"A colored index line."
	def __init__( self, begin, colorName, displayString, end, tagString ):
		"Set the color name and corners."
		self.begin = begin
		self.colorName = colorName
		self.displayString = displayString
		self.end = end
		self.tagString = tagString
 
	def __repr__( self ):
		"Get the string representation of this colored index line."
		return '%s, %s, %s, %s' % ( self.colorName, self.begin, self.end, self.tagString )
 
 
class ExportCanvasDialog:
	"A class to display the export canvas repository dialog."
	def addPluginToMenu( self, canvas, fileName, menu, name, suffix ):
		"Add the display command to the menu."
		self.canvas = canvas
		self.fileName = fileName
		self.name = name
		self.suffix = suffix
		menu.add_command( label = settings.getEachWordCapitalized( self.name ), command = self.display )
 
	def display( self ):
		"Display the export canvas repository dialog."
		for repositoryDialog in settings.globalRepositoryDialogListTable:
			if repositoryDialog.repository.lowerName == self.name:
				repositoryDialog.setCanvasFileNameSuffix( self.canvas, self.skein.fileName, self.suffix )
				settings.liftRepositoryDialogs( settings.globalRepositoryDialogListTable[ repositoryDialog ] )
				return
		exportCanvasPluginsFolderPath = gcodec.getAbsoluteFolderPath( os.path.dirname( __file__ ), 'export_canvas_plugins' )
		pluginModule = gcodec.getModuleWithDirectoryPath( exportCanvasPluginsFolderPath, self.name )
		if pluginModule == None:
			return None
		pluginRepository = pluginModule.getNewRepository()
		pluginRepository.setCanvasFileNameSuffix( self.canvas, self.fileName, self.suffix )
		settings.getDisplayedDialogFromConstructor( pluginRepository )
 
 
class TableauRepository:
	"The viewer base repository class."
	def addAnimation( self ):
		"Add the animation settings."
		self.frameList = settings.FrameList().getFromValue( 'Frame List', self, [] )
		self.animationLineQuickening = settings.FloatSpinUpdate().getFromValue( 0.5, 'Animation Line Quickening (ratio):', self, 4.5, 1.0 )
		self.animationSlideShowRate = settings.FloatSpinUpdate().getFromValue( 1.0, 'Animation Slide Show Rate (layers/second):', self, 5.0, 2.0 )
 
	def addScaleScreenSlide( self ):
		"Add the scale, screen and slide show settings."
		self.scale = settings.FloatSpinNotOnMenu().getFromValue( 10.0, 'Scale (pixels per millimeter):', self, 50.0, 15.0 )
		self.screenHorizontalInset = settings.IntSpin().getFromValue( 80, 'Screen Horizontal Inset (pixels):', self, 1000, 100 )
		self.screenVerticalInset = settings.IntSpin().getFromValue( 120, 'Screen Vertical Inset (pixels):', self, 1000, 200 )
 
	def setToDisplaySave( self, event = None ):
		"Set the setting values to the display, save the new values."
		for menuEntity in self.menuEntities:
			if menuEntity in self.archive:
				menuEntity.setToDisplay()
		settings.writeSettings( self )
 
 
class TableauWindow:
	def activateMouseModeTool( self ):
		"Activate the mouse mode tool."
		self.repository.setToDisplaySave()
		self.canvas.focus_set()
		self.createMouseModeTool()
		self.mouseTool.update()
 
	def addCanvasMenuRootScrollSkein( self, repository, skein, suffix, title ):
		"Add the canvas, menu bar, scroll bar, skein panes, tableau repository, root and skein."
		self.imagesDirectoryPath = os.path.join( settings.getSkeinforgeDirectoryPath(), 'images' )
		self.movementTextID = None
		self.mouseInstantButtons = []
		self.photoImages = {}
		self.repository = repository
		self.root = settings.Tkinter.Tk()
		self.gridPosition = settings.GridVertical( 0, 1 )
		self.gridPosition.master = self.root
		self.root.title( os.path.basename( skein.fileName ) + ' - ' + title )
		self.rulingExtent = 24
		self.rulingTargetSeparation = 150.0
		self.screenSize = skein.screenSize
		self.skein = skein
		self.skeinPanes = skein.skeinPanes
		self.suffix = suffix
		self.timerID = None
		repository.animationSlideShowRate.value = max( repository.animationSlideShowRate.value, 0.01 )
		repository.animationSlideShowRate.value = min( repository.animationSlideShowRate.value, 85.0 )
		repository.drawArrows.setUpdateFunction( self.setWindowToDisplaySaveUpdate )
		repository.goAroundExtruderOffTravel.setUpdateFunction( self.setWindowToDisplaySavePhoenixUpdate )
		repository.layerExtraSpan.setUpdateFunction( self.setWindowToDisplaySaveUpdate )
		repository.widthOfSelectionThread.setUpdateFunction( self.setWindowToDisplaySaveUpdate )
		repository.widthOfTravelThread.setUpdateFunction( self.setWindowToDisplaySaveUpdate )
		repository.window = self
		for menuRadio in repository.mouseMode.menuRadios:
			fileName = menuRadio.name.lower()
			fileName = fileName.replace( ' ', '_' ) + '.ppm'
			menuRadio.mouseButton = self.getPhotoButtonGridIncrement( menuRadio.invoke, fileName, self.gridPosition )
		self.gridPosition = settings.GridHorizontal( 1, 99 )
		self.gridPosition.master = self.root
		self.xScrollbar = settings.HiddenScrollbar( self.root, orient = settings.Tkinter.HORIZONTAL )
		self.xScrollbar.grid( row = 98, column = 2, columnspan = 96, sticky = settings.Tkinter.E + settings.Tkinter.W )
		self.yScrollbar = settings.HiddenScrollbar( self.root )
		self.yScrollbar.grid( row = 1, rowspan = 97, column = 99, sticky = settings.Tkinter.N + settings.Tkinter.S )
		self.canvasHeight = min( int( skein.screenSize.imag ), self.root.winfo_screenheight() - repository.screenVerticalInset.value )
		self.canvasWidth = min( int( skein.screenSize.real ), self.root.winfo_screenwidth() - repository.screenHorizontalInset.value )
		scrollRegionBoundingBox = ( 0, 0, int( skein.screenSize.real ), int( skein.screenSize.imag ) )
		self.canvas = settings.Tkinter.Canvas( self.root, highlightthickness = 3, width = self.canvasWidth, height = self.canvasHeight, scrollregion = scrollRegionBoundingBox )
		self.canvas.grid( row = 1, rowspan = 97, column = 2, columnspan = 96, sticky = settings.Tkinter.E + settings.Tkinter.W + settings.Tkinter.N + settings.Tkinter.S )
		self.fileHelpMenuBar = settings.FileHelpMenuBar( self.root )
		self.exportMenu = settings.Tkinter.Menu( self.fileHelpMenuBar.fileMenu, tearoff = 0 )
		self.fileHelpMenuBar.fileMenu.add_cascade( label = "Export", menu = self.exportMenu, underline = 0 )
		exportCanvasPluginsFolderPath = gcodec.getAbsoluteFolderPath( os.path.dirname( __file__ ), 'export_canvas_plugins' )
		exportCanvasPluginFileNames = gcodec.getPluginFileNamesFromDirectoryPath( exportCanvasPluginsFolderPath )
		for exportCanvasPluginFileName in exportCanvasPluginFileNames:
			ExportCanvasDialog().addPluginToMenu( self.canvas, skein.fileName, self.exportMenu, exportCanvasPluginFileName, suffix )
		self.fileHelpMenuBar.fileMenu.add_separator()
		self.fileHelpMenuBar.completeMenu( self.close, repository, self.save, self )
 
	def addLayer( self, gridPosition ):
		"Add the layer frame items."
		self.diveButton = self.getPhotoButtonGridIncrement( self.dive, 'dive.ppm', gridPosition )
		self.soarButton = self.getPhotoButtonGridIncrement( self.soar, 'soar.ppm', gridPosition )
		gridPosition.increment()
		settings.Tkinter.Label( gridPosition.master, text = 'Layer:' ).grid( row = gridPosition.row, column = gridPosition.column, sticky = settings.Tkinter.W )
		gridPosition.increment()
		self.limitIndex()
		self.layerEntry = settings.Tkinter.Spinbox( gridPosition.master, command = self.layerEntryReturnPressed, from_ = 0, increment = 1, to = getLengthMinusOneMinimumOne( self.skeinPanes ) )
		self.layerEntry.bind( '<Return>', self.layerEntryReturnPressed )
		self.layerEntry.grid( row = gridPosition.row, column = gridPosition.column, sticky = settings.Tkinter.W )
 
	def addLine( self, gridPosition ):
		"Add the line frame items."
		self.lineDiveButton = self.getPhotoButtonGridIncrement( self.lineDive, 'dive.ppm', gridPosition )
		self.lineSoarButton = self.getPhotoButtonGridIncrement( self.lineSoar, 'soar.ppm', gridPosition )
		gridPosition.increment()
		settings.Tkinter.Label( gridPosition.master, text = 'Line:' ).grid( row = gridPosition.row, column = gridPosition.column, sticky = settings.Tkinter.W )
		gridPosition.increment()
		self.lineEntry = settings.Tkinter.Spinbox( gridPosition.master, command = self.lineEntryReturnPressed, from_ = 0, increment = 1, to = getLengthMinusOneMinimumOne( self.getColoredLines() ) )
		self.lineEntry.bind( '<Return>', self.lineEntryReturnPressed )
		self.lineEntry.grid( row = gridPosition.row, column = gridPosition.column, sticky = settings.Tkinter.W )
 
	def addMouseInstantTool( self, fileName, gridPosition, mouseInstantTool ):
		"Add the mouse instant tool and derived photo button."
		mouseInstantTool.getReset( self )
		photoButton = self.getPhotoButtonGridIncrement( mouseInstantTool.click, fileName, gridPosition )
		mouseInstantTool.mouseButton = photoButton
		self.mouseInstantButtons.append( photoButton )
 
	def addMouseToolsBind( self ):
		"Add the mouse tool and bind button one clicked, button one released and motion."
		self.xScrollbar.config( command = self.relayXview )
		self.yScrollbar.config( command = self.relayYview )
		self.canvas[ 'xscrollcommand' ] = self.xScrollbar.set
		self.canvas[ 'yscrollcommand' ] = self.yScrollbar.set
		settings.CloseListener( self, self.destroyAllDialogWindows ).listenToWidget( self.canvas )
		self.canvasScreenCenter = 0.5 * complex( float( self.canvasWidth ) / float( self.screenSize.real ), float( self.canvasHeight ) / float( self.screenSize.imag ) )
		self.addPhotoImage( 'stop.ppm', self.gridPosition )
		self.gridPosition.increment()
		self.addLayer( getGridHorizontalFrame( self.gridPosition ) )
		self.gridPosition.increment()
		self.addLine( getGridHorizontalFrame( self.gridPosition ) )
		self.gridPosition.increment()
		self.addScale( getGridHorizontalFrame( self.gridPosition ) )
		self.gridPosition = settings.GridVertical( self.gridPosition.columnStart + 1, self.gridPosition.row )
		self.gridPosition.master = self.root
		for name in self.repository.frameList.value:
			entity = self.getEntityFromName( name )
			if entity != None:
				self.gridPosition.incrementGivenNumberOfColumns( 3 )
				entity.addToDialog( getGridHorizontalFrame( self.gridPosition ) )
		for menuRadio in self.repository.mouseMode.menuRadios:
			menuRadio.mouseTool = menuRadio.getNewMouseToolFunction().getReset( self )
			self.mouseTool = menuRadio.mouseTool
		self.createMouseModeTool()
		self.canvas.bind( '<Button-1>', self.button1 )
		self.canvas.bind( '<ButtonRelease-1>', self.buttonRelease1 )
		self.canvas.bind( '<KeyPress-Down>', self.keyPressDown )
		self.canvas.bind( '<KeyPress-Left>', self.keyPressLeft )
		self.canvas.bind( '<KeyPress-Right>', self.keyPressRight )
		self.canvas.bind( '<KeyPress-Up>', self.keyPressUp )
		self.canvas.bind( '<Motion>', self.motion )
		self.canvas.bind( '<Return>', self.keyPressReturn )
		self.canvas.bind( '<Shift-ButtonRelease-1>', self.shiftButtonRelease1 )
		self.canvas.bind( '<Shift-Motion>', self.shiftMotion )
		self.layerEntry.bind( '<Destroy>', self.cancelTimer )
		self.root.grid_columnconfigure( 44, weight = 1 )
		self.root.grid_rowconfigure( 44, weight = 1 )
		self.resetPeriodicButtonsText()
		self.repository.animationLineQuickening.setUpdateFunction( self.repository.setToDisplaySave )
		self.repository.animationSlideShowRate.setUpdateFunction( self.repository.setToDisplaySave )
		self.repository.screenHorizontalInset.setUpdateFunction( self.redisplayWindowUpdate )
		self.repository.screenVerticalInset.setUpdateFunction( self.redisplayWindowUpdate )
		rankZeroSeperation = self.getRulingSeparationWidthPixels( 0 )
		zoom = self.rulingTargetSeparation / rankZeroSeperation
		self.rank = euclidean.getRank( zoom )
		rankTop = self.rank + 1
		seperationBottom = self.getRulingSeparationWidthPixels( self.rank )
		seperationTop = self.getRulingSeparationWidthPixels( rankTop )
		bottomDifference = getGeometricDifference( self.rulingTargetSeparation, seperationBottom )
		topDifference = getGeometricDifference( self.rulingTargetSeparation, seperationTop )
		if topDifference < bottomDifference:
			self.rank = rankTop
		self.rulingSeparationWidthMillimeters = euclidean.getIncrementFromRank( self.rank )
		self.canvas.focus_set()
 
	def addPhotoImage( self, fileName, gridPosition ):
		"Get a PhotoImage button, grid the button and increment the grid position."
		photoImage = None
		try:
			photoImage = settings.Tkinter.PhotoImage( file = os.path.join( self.imagesDirectoryPath, fileName ), master = gridPosition.master )
		except:
			print( 'Image %s was not found in the images directory, so a text button will be substituted.' % fileName )
		untilDotFileName = gcodec.getUntilDot( fileName )
		self.photoImages[ untilDotFileName ] = photoImage
		return untilDotFileName
 
	def addScale( self, gridPosition ):
		"Add the line frame items."
		self.addMouseInstantTool( 'zoom_out.ppm', gridPosition, zoom_out.getNewMouseTool() )
		self.addMouseInstantTool( 'zoom_in.ppm', gridPosition, zoom_in.getNewMouseTool() )
		gridPosition.increment()
		settings.Tkinter.Label( gridPosition.master, text = 'Scale:' ).grid( row = gridPosition.row, column = gridPosition.column, sticky = settings.Tkinter.W )
		gridPosition.increment()
		self.scaleEntry = settings.Tkinter.Spinbox( gridPosition.master, command = self.scaleEntryReturnPressed, from_ = 10.0, increment = 5.0, to = 100.0 )
		self.scaleEntry.bind( '<Return>', self.scaleEntryReturnPressed )
		self.scaleEntry.grid( row = gridPosition.row, column = gridPosition.column, sticky = settings.Tkinter.W )
 
	def addSettingsMenuSetWindowGeometry( self, center ):
		"Add the settings menu, center the scroll region, update, and set the window geometry."
		self.settingsMenu = settings.Tkinter.Menu( self.fileHelpMenuBar.menuBar, tearoff = 0 )
		self.fileHelpMenuBar.addMenuToMenuBar( "Settings", self.settingsMenu )
		settings.addMenuEntitiesToMenuFrameable( self.settingsMenu, self.repository.menuEntities )
		self.relayXview( settings.Tkinter.MOVETO, center.real - self.canvasScreenCenter.real )
		self.relayYview( settings.Tkinter.MOVETO, center.imag - self.canvasScreenCenter.imag )
		self.root.withdraw()
		self.root.update_idletasks()
		movedGeometryString = '%sx%s+%s' % ( self.root.winfo_reqwidth(), self.root.winfo_reqheight(), '0+0' )
		self.root.geometry( movedGeometryString )
 
	def button1( self, event ):
		"The button was clicked."
		self.mouseTool.button1( event )
 
	def buttonRelease1( self, event ):
		"The button was released."
		self.mouseTool.buttonRelease1( event )
 
	def cancelTimer( self, event = None ):
		"Cancel the timer and set it to none."
		if self.timerID != None:
			self.canvas.after_cancel ( self.timerID )
			self.timerID = None
 
	def cancelTimerResetButtons( self ):
		"Cancel the timer and set it to none."
		self.cancelTimer()
		self.resetPeriodicButtonsText()
 
	def close( self, event = None ):
		"The dialog was closed."
		try:
			self.root.after( 1, self.root.destroy ) # to get around 'Font Helvetica -12 still in cache.' segmentation bug, instead of simply calling self.root.destroy()
		except:
			pass
 
	def createMouseModeTool( self ):
		"Create the mouse mode tool."
		self.destroyMouseToolRaiseMouseButtons()
		for menuRadio in self.repository.mouseMode.menuRadios:
			if menuRadio.value:
				self.mouseTool = menuRadio.mouseTool
				menuRadio.mouseButton[ 'relief' ] = settings.Tkinter.SUNKEN
 
	def destroyAllDialogWindows( self ):
		"Destroy all the dialog windows."
		settings.writeSettings( self.repository )
		return
		for menuEntity in self.repository.menuEntities:
			lowerName = menuEntity.name.lower()
			if lowerName in settings.globalRepositoryDialogListTable:
				globalRepositoryDialogValues = settings.globalRepositoryDialogListTable[ lowerName ]
				for globalRepositoryDialogValue in globalRepositoryDialogValues:
					settings.quitWindow( globalRepositoryDialogValue.root )
 
	def destroyMouseToolRaiseMouseButtons( self ):
		"Destroy the mouse tool and raise the mouse buttons."
		self.mouseTool.destroyEverything()
		for menuRadio in self.repository.mouseMode.menuRadios:
			menuRadio.mouseButton[ 'relief' ] = settings.Tkinter.RAISED
		for mouseInstantButton in self.mouseInstantButtons:
			mouseInstantButton[ 'relief' ] = settings.Tkinter.RAISED
 
	def dive( self ):
		"Dive, go down periodically."
		oldDiveButtonText = self.diveButton[ 'text' ]
		self.cancelTimerResetButtons()
		if oldDiveButtonText == 'stop':
			return
		self.diveCycle()
 
	def diveCycle( self ):
		"Start the dive cycle."
		self.cancelTimer()
		self.repository.layer.value -= 1
		self.update()
		if self.repository.layer.value < 1:
			self.resetPeriodicButtonsText()
			return
		self.setButtonImageText( self.diveButton, 'stop' )
		self.timerID = self.canvas.after( self.getSlideShowDelay(), self.diveCycle )
 
	def getAnimationLineDelay( self, coloredLine ):
		"Get the animation line delay in milliseconds."
#		maybe later, add animation along line
#		nextLayerIndex = self.repository.layer.value
#		nextLineIndex = self.repository.line.value + 1
#		coloredLinesLength = len( self.getColoredLines() )
#		self.skein.feedRateMinute
#		if nextLineIndex >= coloredLinesLength:
#			if nextLayerIndex + 1 < len( self.skeinPanes ):
#				nextLayerIndex += 1
#				nextLineIndex = 0
#			else:
#				nextLineIndex = self.repository.line.value
		splitLine = gcodec.getSplitLineBeforeBracketSemicolon( coloredLine.displayString )
		self.skein.feedRateMinute = gcodec.getFeedRateMinute( self.skein.feedRateMinute, splitLine )
		feedRateSecond = self.skein.feedRateMinute / 60.0
		coloredLineLength = abs( coloredLine.end - coloredLine.begin ) / self.repository.scale.value
		duration = coloredLineLength / feedRateSecond
		animationLineDelay = int( round( 1000.0 * duration / self.repository.animationLineQuickening.value ) )
		return max( animationLineDelay, 1 )
 
	def getDrawnLineText( self, location, tags, text ):
		"Get the line text drawn on the canvas."
		anchorTowardCenter = settings.Tkinter.N
		if location.imag > float( self.canvasHeight ) * 0.1:
			anchorTowardCenter = settings.Tkinter.S
		if location.real > float( self.canvasWidth ) * 0.7:
			anchorTowardCenter += settings.Tkinter.E
		else:
			anchorTowardCenter += settings.Tkinter.W
		return self.canvas.create_text( int( location.real ), int( location.imag ), anchor = anchorTowardCenter, tags = tags, text = text )
 
	def getEntityFromName( self, name ):
		"Get the entity of the given name."
		for entity in self.repository.displayEntities:
			if entity.name == name:
				return entity
		return None
 
	def getPhotoButtonGridIncrement( self, commandFunction, fileName, gridPosition ):
		"Get a PhotoImage button, grid the button and increment the grid position."
		gridPosition.increment()
		untilDotFileName = self.addPhotoImage( fileName, gridPosition )
		photoImage = self.photoImages[ untilDotFileName ]
		photoButton = settings.Tkinter.Button( gridPosition.master, activebackground = 'black', activeforeground = 'white', command = commandFunction, text = untilDotFileName )
		if photoImage != None:
			photoButton[ 'image' ] = photoImage
		photoButton.grid( row = gridPosition.row, column = gridPosition.column, sticky = settings.Tkinter.W )
		return photoButton
 
	def getRoundedRulingText( self, extraDecimalPlaces, number ):
		"Get the rounded ruling text."
		rulingText = euclidean.getRoundedToDecimalPlacesString( extraDecimalPlaces - math.floor( math.log10( self.rulingSeparationWidthMillimeters ) ), number )
		if self.rulingSeparationWidthMillimeters < .99:
			return rulingText
		if rulingText[ - len( '.0' ) : ] == '.0':
			return rulingText[ : - len( '.0' ) ]
		return rulingText
 
	def getRulingSeparationWidthPixels( self, rank ):
		"Get the separation width in pixels."
		return euclidean.getIncrementFromRank( rank ) * self.skein.scale
 
	def getScrollPaneCenter( self ):
		"Get the center of the scroll pane."
		return self.getScrollPaneFraction() + self.canvasScreenCenter
 
	def getScrollPaneFraction( self ):
		"Get the center of the scroll pane."
		return complex( self.xScrollbar.get()[ 0 ], self.yScrollbar.get()[ 0 ] )
 
	def getSlideShowDelay( self ):
		"Get the slide show delay in milliseconds."
		slideShowDelay = int( round( 1000.0 / self.repository.animationSlideShowRate.value ) )
		return max( slideShowDelay, 1 )
 
	def getUpdateSkeinPanes( self ):
		"Get the update skein panes."
		layerPlusExtraSpan = self.repository.layer.value + self.repository.layerExtraSpan.value
		layersFrom = max( 0, min( self.repository.layer.value, layerPlusExtraSpan ) )
		layersTo = min( len( self.skeinPanes ), max( self.repository.layer.value, layerPlusExtraSpan ) + 1 )
		return self.skeinPanes[ layersFrom : layersTo ]
 
	def isLineBelowZeroSetLayer( self ):
		"Determine if the line index is below zero, and if so set the layer index."
		if self.repository.line.value >= 0:
			return False
		self.repository.line.value = 0
		if self.repository.layer.value > 0:
			self.setLayerIndex( self.repository.layer.value - 1 )
			return True
		return False
 
	def isLineBeyondListSetLayer( self ):
		"Determine if the line index is beyond the end of the list, and if so set the layer index."
		coloredLinesLength = len( self.getColoredLines() )
		if self.repository.line.value < coloredLinesLength:
			return False
		self.repository.line.value = coloredLinesLength - 1
		if self.repository.layer.value < len( self.skeinPanes ) - 1:
			self.setLayerIndex( self.repository.layer.value + 1 )
			return True
		return False
 
	def keyPressDown( self, event ):
		"The down arrow was pressed."
		self.mouseTool.keyPressDown( event )
 
	def keyPressLeft( self, event ):
		"The left arrow was pressed."
		self.mouseTool.keyPressLeft( event )
 
	def keyPressReturn( self, event ):
		"The return key was pressed."
		self.mouseTool.keyPressReturn( event )
 
	def keyPressRight( self, event ):
		"The right arrow was pressed."
		self.mouseTool.keyPressRight( event )
 
	def keyPressUp( self, event ):
		"The up arrow was pressed."
		self.mouseTool.keyPressUp( event )
 
	def layerEntryReturnPressed( self, event = None ):
		"The layer index entry return was pressed."
		self.setLayerIndex( int( self.layerEntry.get() ) )
 
	def limitIndex( self ):
		"Limit the index so it is not below zero or above the top."
		self.repository.layer.value = max( 0, self.repository.layer.value )
		self.repository.layer.value = min( len( self.skeinPanes ) - 1, self.repository.layer.value )
 
	def limitIndexSetArrowMouseDeleteCanvas( self ):
		"Limit the index, set the arrow type, and delete all the canvas items."
		self.limitIndex()
		self.arrowType = None
		if self.repository.drawArrows.value:
			self.arrowType = 'last'
		self.canvas.delete( settings.Tkinter.ALL )
 
	def lineEntryReturnPressed( self, event = None ):
		"The line index entry return was pressed."
		self.repository.line.value = int( self.lineEntry.get() )
		if self.isLineBelowZeroSetLayer():
			return
		if self.isLineBeyondListSetLayer():
			return
		self.cancelTimerResetButtons()
		self.updateMouseToolIfSelection()
		self.setLineButtonsState()
 
	def lineDive( self ):
		"Line dive, go down periodically."
		oldLineDiveButtonText = self.lineDiveButton[ 'text' ]
		self.cancelTimerResetButtons()
		if oldLineDiveButtonText == 'stop':
			return
		self.lineDiveCycle()
 
	def lineDiveCycle( self ):
		"Start the line dive cycle."
		self.cancelTimer()
		self.repository.line.value -= 1
		if self.repository.line.value < 0:
			self.repository.line.value = 0
			if self.repository.layer.value == 0:
				self.resetPeriodicButtonsText()
				self.setLineButtonsState()
				return
			self.setLayerIndex( self.repository.layer.value - 1 )
		else:
			self.updateMouseToolIfSelection()
		self.setLineButtonsState()
		self.setButtonImageText( self.lineDiveButton, 'stop' )
		coloredLine = self.getColoredLines()[ self.repository.line.value ]
		self.timerID = self.canvas.after( self.getAnimationLineDelay( coloredLine ), self.lineDiveCycle )
 
	def lineSoar( self ):
		"Line soar, go up periodically."
		oldLineSoarButtonText = self.lineSoarButton[ 'text' ]
		self.cancelTimerResetButtons()
		if oldLineSoarButtonText == 'stop':
			return
		self.lineSoarCycle()
 
	def lineSoarCycle( self ):
		"Start the line soar cycle."
		self.cancelTimer()
		self.repository.line.value += 1
		coloredLinesLength = len( self.getColoredLines() )
		if self.repository.line.value >= coloredLinesLength:
			self.repository.line.value = coloredLinesLength - 1
			if self.repository.layer.value > len( self.skeinPanes ) - 2:
				self.resetPeriodicButtonsText()
				self.setLineButtonsState()
				return
			self.setLayerIndex( self.repository.layer.value + 1 )
		else:
			self.updateMouseToolIfSelection()
		self.setLineButtonsState()
		self.setButtonImageText( self.lineSoarButton, 'stop' )
		coloredLine = self.getColoredLines()[ self.repository.line.value ]
		self.timerID = self.canvas.after( self.getAnimationLineDelay( coloredLine ), self.lineSoarCycle )
 
	def motion( self, event ):
		"The mouse moved."
		self.mouseTool.motion( event )
 
	def phoenixUpdate( self ):
		"Update the skein, and deiconify a new window and destroy the old."
		self.updateNewDestroyOld( self.getScrollPaneCenter() )
 
	def relayXview( self, *args ):
		"Relay xview changes."
		self.canvas.xview( *args )
 
	def relayYview( self, *args ):
		"Relay yview changes."
		self.canvas.yview( *args )
 
	def resetPeriodicButtonsText( self ):
		"Reset the text of the periodic buttons."
		self.setButtonImageText( self.diveButton, 'dive' )
		self.setButtonImageText( self.soarButton, 'soar' )
		self.setButtonImageText( self.lineDiveButton, 'dive' )
		self.setButtonImageText( self.lineSoarButton, 'soar' )
 
	def redisplayWindowUpdate( self, event = None ):
		"Deiconify a new window and destroy the old."
		self.repository.setToDisplaySave()
		self.getCopy().updateDeiconify( self.getScrollPaneCenter() )
		self.root.after( 1, self.root.destroy ) # to get around 'Font Helvetica -12 still in cache.' segmentation bug, instead of simply calling self.root.destroy()
 
	def save( self ):
		"Set the setting values to the display, save the new values."
		for menuEntity in self.repository.menuEntities:
			if menuEntity in self.repository.archive:
				menuEntity.setToDisplay()
		self.setInsetToDisplay()
		settings.writeSettings( self.repository )
 
	def scaleEntryReturnPressed( self, event = None ):
		"The scale entry return was pressed."
		self.repository.scale.value = float( self.scaleEntry.get() )
		self.phoenixUpdate()
 
	def setButtonImageText( self, button, text ):
		"Set the text of the e periodic buttons."
		photoImage = self.photoImages[ text ]
		if photoImage != None:
			button[ 'image' ] = photoImage
		button[ 'text' ] = text
 
	def setDisplayLayerIndex( self ):
		"Set the display of the layer index entry field and buttons."
		coloredLines = self.getColoredLines()
		isAboveFloor = self.repository.layer.value > 0
		isBelowCeiling = self.repository.layer.value < len( self.skeinPanes ) - 1
		setStateNormalDisabled( isAboveFloor, self.diveButton )
		setStateNormalDisabled( isBelowCeiling, self.soarButton )
		self.setLineButtonsState()
		settings.setEntryText( self.layerEntry, self.repository.layer.value )
		settings.setEntryText( self.lineEntry, self.repository.line.value )
		settings.setEntryText( self.scaleEntry, self.repository.scale.value )
		self.mouseTool.update()
		self.setInsetToDisplay()
 
	def setInsetToDisplay( self ):
		"Set the archive to the display."
		if self.root.state() != 'normal':
			return
		xScrollbarCanvasPortion = getScrollbarCanvasPortion( self.xScrollbar )
		newScreenHorizontalInset = int( self.root.winfo_screenwidth() - round( xScrollbarCanvasPortion * self.skein.screenSize.real ) )
		if xScrollbarCanvasPortion < .99:
			self.repository.screenHorizontalInset.value = newScreenHorizontalInset
		else:
			self.repository.screenHorizontalInset.value = min( self.repository.screenHorizontalInset.value, newScreenHorizontalInset )
		yScrollbarCanvasPortion = getScrollbarCanvasPortion( self.yScrollbar )
		newScreenVerticalInset = int( self.root.winfo_screenheight() - round( yScrollbarCanvasPortion * self.skein.screenSize.imag ) )
		if yScrollbarCanvasPortion < .99:
			self.repository.screenVerticalInset.value = newScreenVerticalInset
		else:
			self.repository.screenVerticalInset.value = min( self.repository.screenVerticalInset.value, newScreenVerticalInset )
 
	def setLayerIndex( self, layerIndex ):
		"Set the layer index."
		self.cancelTimerResetButtons()
		oldLayerIndex = self.repository.layer.value
		self.repository.layer.value = layerIndex
		self.limitIndex()
		coloredLines = self.getColoredLines()
		if self.repository.layer.value < oldLayerIndex:
			self.repository.line.value = len( coloredLines ) - 1
			self.lineEntry[ 'to' ] = getLengthMinusOneMinimumOne( coloredLines )
		if self.repository.layer.value > oldLayerIndex:
			self.repository.line.value = 0
			self.lineEntry[ 'to' ] = getLengthMinusOneMinimumOne( coloredLines )
		self.update()
 
	def setLineButtonsState( self ):
		"Set the state of the line buttons."
		coloredLines = self.getColoredLines()
		isAboveFloor = self.repository.layer.value > 0
		isBelowCeiling = self.repository.layer.value < len( self.skeinPanes ) - 1
		setStateNormalDisabled( isAboveFloor or self.repository.line.value > 0, self.lineDiveButton )
		setStateNormalDisabled( isBelowCeiling or self.repository.line.value < len( coloredLines ) - 1, self.lineSoarButton )
 
	def setWindowNewMouseTool( self, getNewMouseToolFunction, mouseTool ):
		"Set the getNewMouseTool function and the update function."
		mouseTool.getNewMouseToolFunction = getNewMouseToolFunction
		mouseTool.setUpdateFunction( self.activateMouseModeTool )
 
	def setWindowToDisplaySavePhoenixUpdate( self, event = None ):
		"Set the setting values to the display, save the new values, then call the update function."
		self.repository.setToDisplaySave()
		self.phoenixUpdate()
 
	def setWindowToDisplaySaveUpdate( self, event = None ):
		"Set the setting values to the display, save the new values, then call the update function."
		self.repository.setToDisplaySave()
		self.update()
 
	def shiftButtonRelease1( self, event ):
		"The button was released while the shift key was pressed."
		self.mouseTool.buttonRelease1( event, True )
 
	def shiftMotion( self, event ):
		"The mouse moved."
		self.mouseTool.motion( event, True )
 
	def soar( self ):
		"Soar, go up periodically."
		oldSoarButtonText = self.soarButton[ 'text' ]
		self.cancelTimerResetButtons()
		if oldSoarButtonText == 'stop':
			return
		self.soarCycle()
 
	def soarCycle( self ):
		"Start the soar cycle."
		self.cancelTimer()
		self.repository.layer.value += 1
		self.update()
		if self.repository.layer.value > len( self.skeinPanes ) - 2:
			self.resetPeriodicButtonsText()
			return
		self.setButtonImageText( self.soarButton, 'stop' )
		self.timerID = self.canvas.after( self.getSlideShowDelay(), self.soarCycle )
 
	def updateDeiconify( self, center = complex( 0.5, 0.5 ) ):
		"Update and deiconify the window."
		self.addSettingsMenuSetWindowGeometry( center )
		self.update()
		self.root.deiconify()
 
	def updateMouseToolIfSelection( self ):
		"Update the mouse tool if it is a selection tool."
		if self.mouseTool == None:
			return
		if self.mouseTool.isSelectionTool():
			self.mouseTool.update()
 
	def updateNewDestroyOld( self, scrollPaneCenter ):
		"Update and deiconify a window and destroy the old."
		self.getCopyWithNewSkein().updateDeiconify( scrollPaneCenter )
		self.root.after( 1, self.root.destroy ) # to get around 'Font Helvetica -12 still in cache.' segmentation bug, instead of simply calling self.root.destroy()