#!/usr/bin/env python ################################################################################ # # This file is part of Gato (Graph Animation Toolbox) # # file: Gato.py # author: Alexander Schliep (schliep@molgen.mpg.de) # # Copyright (C) 1998-2005, Alexander Schliep, Winfried Hochstaettler and # Copyright 1998-2001 ZAIK/ZPR, Universitaet zu Koeln # # Contact: schliep@molgen.mpg.de, wh@zpr.uni-koeln.de # # Information: http://gato.sf.net # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Library General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library 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 # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public # License along with this library; if not, write to the Free # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # # # This file is version $Revision: 1.53 $ # from $Date: 2006/09/04 11:55:37 $ # last change by $Author: schliep $. # ################################################################################ import sys import tempfile import traceback import os import bdb import random import re import string import StringIO import tokenize import tkFont import copy import webbrowser from Gato import Gred from Tkinter import * from tkFileDialog import askopenfilename, asksaveasfilename from tkMessageBox import askokcancel, showerror, askyesno from ScrolledText import ScrolledText from Gato.GatoConfiguration import GatoConfiguration from Gato.Graph import Graph from Gato.GraphUtil import * from Gato.GraphDisplay import GraphDisplayToplevel from Gato.GatoUtil import * from Gato.GatoGlobals import * from Gato.GatoDialogs import AboutBox, SplashScreen, HTMLViewer from Gato import GatoIcons from Gato import GatoSystemConfiguration from Gato.AnimationHistory import AnimationHistory # put someplace else def WMExtrasGeometry(window): """ Returns (top,else) where - top is the amount of extra pixels the WM puts on top of the window - else is the amount of extra pixels the WM puts everywhere else around the window NOTE: Does not work with tk8.0 style menus, since those are handled by WM (according to Tk8.1 docs) NOTE: Some window managers return bad geometry definition Handle in caller """ try: window.geometry() # XXX Sometimes first produced wrong results ... g = string.split(window.geometry(),"+") except TclError: # bad geometry specifier: e.g. ... "-1949x260+1871+1" return (32,32) trueRootx = string.atoi(g[1]) trueRooty = string.atoi(g[2]) rootx = window.winfo_rootx() # top left of our window rooty = window.winfo_rooty() # *WITHOUT* WM extras topWMExtra = abs(rooty - trueRooty) # WM adds that on top WMExtra = abs(rootx - trueRootx) # and that on all other sides # XXX KLUDGE topWMExtra,WMExtra should always be in 0...32 pixels, or? topWMExtra = min(32,topWMExtra) WMExtra = min(32, WMExtra) return (topWMExtra,WMExtra) ################################################################################ # # # Public Methods of class AlgoWin # # ShowActive(lineNo) Display line lineNo as activated # # ShowBreakpoint(lineNo) Show breakpoint at line lineNo # # HideBreakpoint(lineNo) Hide breakpoint at line lineNo # # WaitNextEvent() Wait for some GUI event # # WaitTime(delay) Wait for delay (in ms) # class AlgoWin(Frame): """ Provide GUI with main menubar for displaying and controlling algorithms and the algorithm text widget """ def __init__(self, parent=None): Frame.__init__(self,parent) #XXX import tkoptions #tkoptions.tkoptions(self) Splash = SplashScreen(self.master) # Need to change things a bit for Tk running on MacOS X # using the native drawing environment (TkAqua) self.windowingsystem = self.tk.call("tk", "windowingsystem") self.algoFont = "Courier" self.algoFontSize = 10 self.keywordsList = [ "del", "from", "lambda", "return", "and", "elif", "global", "not", "try", "break", "else", "if", "or", "while", "class", "except", "import", "pass", "continue", "finally", "in", "print", "def", "for", "is", "raise"] GatoIcons.Init() self.config = GatoConfiguration(self) self.gatoInstaller=GatoSystemConfiguration.GatoInstaller() # Create widgets self.pack() self.pack(expand=1,fill=BOTH) # Makes menuBar and toolBar sizeable self.makeMenuBar() self.makeAlgoTextWidget() self.makeToolBar() self.master.title("Gato 0.99 - Algorithm") self.master.iconname("Gato 0.99") self.algorithm = Algorithm() self.algorithm.SetGUI(self) # So that algorithm can call us self.graphDisplay = GraphDisplayToplevel() self.secondaryGraphDisplay = None self.AboutAlgorithmDialog = None self.AboutGraphDialog = None self.lastActiveLine = 0 self.algorithmIsRunning = 0 # state self.commandAfterStop = None # command to call after forced Stop self.goOn = IntVar() # lock variable to avoid busy idling self.master.protocol('WM_DELETE_WINDOW',self.Quit) # Handle WM Kills Splash.Destroy() # Fix focus and stacking if os.name == 'nt' or os.name == 'dos': self.graphDisplay.tkraise() self.master.tkraise() self.master.focus_force() else: self.tkraise() # Make AlgoWins requested size its minimal size to keep # toolbar from vanishing when changing window size # Packer has been running due to splash screen wmExtras = WMExtrasGeometry(self.graphDisplay) width = self.master.winfo_reqwidth() height = self.master.winfo_reqheight() # XXX Some WM + packer combinatios ocassionally produce absurd requested sizes log.debug(os.name + str(wmExtras) + " width = %f height = %f " % (width, height)) width = min(600, self.master.winfo_reqwidth()) height = min(750, self.master.winfo_reqheight()) if os.name == 'nt' or os.name == 'dos': self.master.minsize(width, height + wmExtras[1]) else: # Unix & Mac self.master.minsize(width, height + wmExtras[0] + wmExtras[1]) self.BindKeys(self.master) self.BindKeys(self.graphDisplay) self.SetFromConfig() # Set values read in config ############################################################ # # Create GUI # def makeMenuBar(self): """ *Internal* """ self.menubar = Menu(self, tearoff=0) # Cross-plattform accelerators if self.windowingsystem == "aqua": accMod = "command" else: accMod = "Ctrl" # --- FILE menu ---------------------------------------- self.fileMenu = Menu(self.menubar, tearoff=0) self.fileMenu.add_command(label='Open Algorithm...', command=self.OpenAlgorithm) self.fileMenu.add_command(label='Open Graph...', command=self.OpenGraph) if self.windowingsystem != 'aqua': self.fileMenu.add_command(label='New Graph...', command=self.NewGraph) # Obsolete. Only used for TRIAL-SOLUTION Gato version #self.fileMenu.add_command(label='Open GatoFile...', # command=self.OpenGatoFile) #self.fileMenu.add_command(label='Save GatoFile...', # command=self.SaveGatoFile) self.fileMenu.add_command(label='Reload Algorithm & Graph', command=self.ReloadAlgorithmGraph) self.fileMenu.add_command(label='Export Graph as EPS...', command=self.ExportEPSF) if self.windowingsystem != 'aqua': self.fileMenu.add_separator() self.fileMenu.add_command(label='Preferences...', command=self.Preferences, accelerator='%s-,' % accMod) #self.gatoInstaller.addMenuEntry(self.fileMenu) self.fileMenu.add_separator() self.fileMenu.add_command(label='Quit', command=self.Quit, accelerator='%s-Q' % accMod) self.menubar.add_cascade(label="File", menu=self.fileMenu, underline=0) # --- WINDOW menu ---------------------------------------- self.windowMenu=Menu(self.menubar, tearoff=0) self.windowMenu.add_command(label='One graph window', accelerator='%s-1' % accMod, command=self.OneGraphWindow) self.windowMenu.add_command(label='Two graph windows', accelerator='%s-2' % accMod, command=self.TwoGraphWindow) self.menubar.add_cascade(label="Window Layout", menu=self.windowMenu, underline=0) # --- HELP menu ---------------------------------------- self.helpMenu=Menu(self.menubar, tearoff=0, name='help') if self.windowingsystem != 'aqua': self.helpMenu.add_command(label='About Gato', command=self.AboutBox) self.helpMenu.add_command(label='Help', accelerator='%s-?' % accMod, command=self.HelpBox) self.helpMenu.add_separator() self.helpMenu.add_command(label='Go to Gato website', command=self.GoToGatoWebsite) self.helpMenu.add_command(label='Go to CATBox website', command=self.GoToCATBoxWebsite) self.helpMenu.add_separator() self.helpMenu.add_command(label='About Algorithm', command=self.AboutAlgorithm) self.helpMenu.add_command(label='About Graph', command=self.AboutGraph) self.menubar.add_cascade(label="Help", menu=self.helpMenu, underline=0) # --- MacOS X application menu -------------------------- # On a Mac we put our about box under the Apple menu ... if self.windowingsystem == 'aqua': self.apple=Menu(self.menubar, tearoff=0, name='apple') self.apple.add_command(label='About Gato', command=self.AboutBox) self.apple.add_separator() self.apple.add_command(label='Preferences...', accelerator='command-,', command=self.Preferences) self.menubar.add_cascade(menu=self.apple) self.master.configure(menu=self.menubar) def makeToolBar(self): """ *Internal* Creates Start/Stop/COntinue ... toolbar """ toolbar = Frame(self, cursor='hand2', relief=FLAT) toolbar.pack(side=BOTTOM, fill=X) # Allows horizontal growth toolbar.columnconfigure(5,weight=1) if os.name == 'nt' or os.name == 'dos': px = 0 py = 0 else: # Unix px = 0 py = 3 if self.windowingsystem == 'aqua': bWidth = 10 else: bWidth = 8 self.buttonStart = Button(toolbar, width=bWidth, padx=px, pady=py, text='Start', command=self.CmdStart, highlightbackground='#DDDDDD') self.buttonStep = Button(toolbar, width=bWidth, padx=px, pady=py, text='Step', command=self.CmdStep, highlightbackground='#DDDDDD') self.buttonTrace = Button(toolbar, width=bWidth, padx=px, pady=py, text='Trace', command=self.CmdTrace, highlightbackground='#DDDDDD') self.buttonContinue = Button(toolbar, width=bWidth, padx=px, pady=py, text='Continue', command=self.CmdContinue, highlightbackground='#DDDDDD') self.buttonStop = Button(toolbar, width=bWidth, padx=px, pady=py, text='Stop', command=self.CmdStop, highlightbackground='#DDDDDD') self.buttonStart.grid(row=0, column=0, padx=2, pady=2) self.buttonStep.grid(row=0, column=1, padx=2, pady=2) self.buttonTrace.grid(row=0, column=2, padx=2, pady=2) self.buttonContinue.grid(row=0, column=3, padx=2, pady=2) self.buttonStop.grid(row=0, column=4, padx=2, pady=2) self.buttonStart['state'] = DISABLED self.buttonStep['state'] = DISABLED self.buttonTrace['state'] = DISABLED self.buttonContinue['state'] = DISABLED self.buttonStop['state'] = DISABLED if self.windowingsystem == 'aqua': dummy = Frame(toolbar, relief=FLAT, bd=2) dummy.grid(row=0, column=5, padx=6, pady=2) def makeAlgoTextWidget(self): """ *Internal* Here we also define appearance of - interactive lines - breakpoints - the active line """ if self.windowingsystem == 'aqua': borderFrame = Frame(self, relief=FLAT, bd=1, background='#666666') # Extra Frame else: borderFrame = Frame(self, relief=SUNKEN, bd=2) # Extra Frame # around widget needed for more Windows-like appearance self.algoText = ScrolledText(borderFrame, relief=FLAT, padx=3, pady=3, background="white", wrap='none', width=43, height=30, ) self.SetAlgorithmFont(self.algoFont, self.algoFontSize) self.algoText.pack(expand=1, fill=BOTH) borderFrame.pack(side=TOP, expand=1, fill=BOTH) # GUI-related tags self.algoText.tag_config('Interactive', foreground='#009900',background="#E5E5E5") self.algoText.tag_config('Break', foreground='#ff0000',background="#E5E5E5") self.algoText.tag_config('Active', background='#bbbbff') self.algoText.bind("", self.handleMouse) self.algoText['state'] = DISABLED def SetAlgorithmFont(self, font, size): self.algoFont = font self.algoFontSize = size f = tkFont.Font(self, (font, size, tkFont.NORMAL)) bf = tkFont.Font(self, (font, size, tkFont.BOLD)) itf = tkFont.Font(self, (font, size, tkFont.ITALIC)) self.algoText.config(font=f) # syntax highlighting tags self.algoText.tag_config('keyword', font=bf) self.algoText.tag_config('string', font=itf) self.algoText.tag_config('comment', font=itf) self.algoText.tag_config('identifier', font=bf) def SetFromConfig(self): c = self.config.get # Shortcut to accessor self.SetAlgorithmFont(c('algofont'), int(c('algofontsize'))) self.algoText.config(fg=c('algofg'), bg=c('algobg')) self.algoText.tag_config('Interactive', foreground=c('interactivefg'), background=c('interactivebg')) self.algoText.tag_config('Break', foreground=c('breakpointfg'), background=c('breakpointbg')) self.algoText.tag_config('Active', foreground=c('activefg'), background=c('activebg')) globals()['gBlinkRate'] = int(c('blinkrate')) globals()['gBlinkRepeat'] = int(c('blinkrepeat')) def OpenSecondaryGraphDisplay(self): """ Pops up a second graph window """ if self.secondaryGraphDisplay == None: self.secondaryGraphDisplay = GraphDisplayToplevel() self.BindKeys(self.secondaryGraphDisplay) else: self.secondaryGraphDisplay.Show() def WithdrawSecondaryGraphDisplay(self): """ Hide window containing second graph """ if self.secondaryGraphDisplay != None: self.secondaryGraphDisplay.Withdraw() ############################################################ # # GUI Helpers # # Lock def touchLock(self): """ *Internal* The lock (self.goOn) is a variable which is used to control the flow of the programm and to allow GUI interactions without busy idling. The following methods wait for the lock to be touched: - WaitNextEvent - WaitTime The following methods touch it: - CmdStop - CmdStep - CmdContinue """ self.goOn.set(self.goOn.get() + 1) #XXX possible overflow def activateMenu(self): """ Make the menu active (i.e., after stopping an algo) """ self.menubar.entryconfigure(0, state = NORMAL) def deactivateMenu(self): """ Make the menu inactive (i.e., before running an algo) """ self.menubar.entryconfigure(0, state = DISABLED) def tagLine(self, lineNo, tag): """ Add tag 'tag' to line lineNo """ self.algoText.tag_add(tag,'%d.0' % lineNo,'%d.0' % (lineNo + 1)) def unTagLine(self, lineNo, tag): """ Remove tag 'tag' from line lineNo """ self.algoText.tag_remove(tag,'%d.0' % lineNo,'%d.0' % (lineNo + 1)) def tagLines(self, lines, tag): """ Tag every line in list lines with specified tag """ for l in lines: self.tagLine(l, tag) def tokenEater(self, type, token, (srow, scol), (erow, ecol), line): #log.debug("%d,%d-%d,%d:\t%s\t%s" % \ # (srow, scol, erow, ecol, type, repr(token))) if type == 1: # Name if token in self.keywordsList: self.algoText.tag_add('keyword','%d.%d' % (srow, scol), '%d.%d' % (erow, ecol)) elif type == 3: # String self.algoText.tag_add('string','%d.%d' % (srow, scol), '%d.%d' % (erow, ecol)) elif type == 39: # Comment self.algoText.tag_add('comment','%d.%d' % (srow, scol), '%d.%d' % (erow, ecol)) ############################################################ # # Menu Commands # # The menu commands are passed as call back parameters to # the menu items. # def OpenAlgorithm(self,file=""): """ GUI to allow selection of algorithm to open file parameter for testing purposes """ if self.algorithmIsRunning: self.CmdStop() self.commandAfterStop = self.OpenAlgorithm return if file == "": # caller did not specify file file = askopenfilename(title="Open Algorithm", defaultextension=".py", filetypes = [ ("Gato Algorithm", ".alg") ,("Python Code", ".py") ] ) if file is not "" and file is not (): try: self.algorithm.Open(file) except (EOFError, IOError): self.HandleFileIOError("Algorithm",file) return self.algoText['state'] = NORMAL self.algoText.delete('0.0', END) self.algoText.insert('0.0', self.algorithm.GetSource()) self.algoText['state'] = DISABLED self.tagLines(self.algorithm.GetInteractiveLines(), 'Interactive') self.tagLines(self.algorithm.GetBreakpointLines(), 'Break') # Syntax highlighting tokenize.tokenize(StringIO.StringIO(self.algorithm.GetSource()).readline, self.tokenEater) if self.algorithm.ReadyToStart(): self.buttonStart['state'] = NORMAL self.master.title("Gato 0.99 - " + stripPath(file)) if self.AboutAlgorithmDialog: self.AboutAlgorithmDialog.Update(self.algorithm.About(),"About Algorithm") def NewGraph(self): Gred.Start() def OpenGraph(self,file=""): """ GUI to allow selection of graph to open file parameter for testing purposes """ if self.algorithmIsRunning: self.CmdStop() self.commandAfterStop = self.OpenGraph return if file == "": # caller did not specify file file = askopenfilename(title="Open Graph", defaultextension=".gato", filetypes = [ ("Gred", ".cat") #,("Gato Plus", ".cat") #,("LEDA", ".gph") #,("Graphlet", ".let") #,("Gato",".gato") ] ) if file is not "" and file is not (): try: self.algorithm.OpenGraph(file) except (EOFError, IOError): self.HandleFileIOError("Graph",file) return if self.algorithm.ReadyToStart(): self.buttonStart['state'] = NORMAL if self.AboutGraphDialog: self.AboutGraphDialog.Update(self.graphDisplay.About(), "About Graph") def SaveGatoFile(self,filename=""): """ under Construction... """ import GatoFile # ToDo if not askyesno("Ooops...", "...this feature is under developement.\nDo you want to proceed?"): return if self.algorithmIsRunning: # variable file is lost here! self.CmdStop() self.commandAfterStop = self.SaveGatoFile return if filename == "": # caller did not specify file filename = asksaveasfilename(title="Save Graph and Algorithm", defaultextension=".gato", filetypes = [ ("Gato",".gato") #,("xml",".xml") ] ) def OpenGatoFile(self,filename=""): """ menu command """ import GatoFile if self.algorithmIsRunning: # variable file is lost here! self.CmdStop() self.commandAfterStop = self.OpenGatoFile return if filename == "": # caller did not specify file filename = askopenfilename(title="Open Graph and Algorithm", defaultextension=".gato", filetypes = [ ("Gato",".gato") #,("xml",".xml") ] ) if filename is not "": select={} try: # open xml file f=GatoFile.GatoFile(filename) select=f.getDefaultSelection() if not select: # select the graph select=f.displaySelectionDialog(self) except GatoFile.FileException, e: self.HandleFileIOError("GatoFile: %s"%e.reason,filename) return # nothing selected if select is None: return # a graph is selected if select.get("graph"): try: # open graph graphStream=select["graph"].getGraphAsStringIO() self.algorithm.OpenGraph(graphStream, fileName="%s::%s"%(filename, select["graph"].getName())) except (EOFError, IOError): self.HandleFileIOError("Gato",filename) return if self.algorithm.ReadyToStart(): self.buttonStart['state'] = NORMAL if self.AboutGraphDialog: self.AboutGraphDialog.Update(self.graphDisplay.About(), "About Graph") # great shit! create files to get old gato running if select.get("algorithm"): xmlAlgorithm=select.get("algorithm") # save last algorithm tmp_name lastAlgoFileName=None if hasattr(self,"tmpAlgoFileName"): lastAlgoFileName=self.tmpAlgoFileName lastAlgoDispalyName=None if hasattr(self,"algoDisplayFileName"): lastAlgoDispalyName=self.algoDisplayFileName # provide a temporary files for algortihm and prologue tmpFileName=tempfile.mktemp() self.tmpAlgoFileName="%s.alg"%tmpFileName self.algoDisplayFileName="%s::%s"%(filename,xmlAlgorithm.getName()) tmp=file(self.tmpAlgoFileName,"w") tmp.write(xmlAlgorithm.getText()) tmp.close() proFileName="%s.pro"%tmpFileName tmp=file(proFileName,"w") tmp.write(xmlAlgorithm.getProlog()) tmp.close() # open it! # text copied from AlgoWin.OpenAlgorithm try: self.algorithm.Open(self.tmpAlgoFileName) except (EOFError, IOError): os.remove(self.tmpAlgoFileName) os.remove(proFileName) self.HandleFileIOError("Algorithm",self.tmpAlgoFileName) self.algoDisplayFileName=lastAlgoDispalyName self.tmpAlgoFileName=lastAlgoFileName return # handle old tempfile if lastAlgoFileName: os.remove(lastAlgoFileName) os.remove(lastAlgoFileName[:-3]+'pro') # prepare algorithm text widget self.algoText['state'] = NORMAL self.algoText.delete('0.0', END) self.algoText.insert('0.0', self.algorithm.GetSource()) self.algoText['state'] = DISABLED self.tagLines(self.algorithm.GetInteractiveLines(), 'Interactive') self.tagLines(self.algorithm.GetBreakpointLines(), 'Break') # Syntax highlighting tokenize.tokenize(StringIO.StringIO(self.algorithm.GetSource()).readline, self.tokenEater) # set the state if self.algorithm.ReadyToStart(): self.buttonStart['state'] = NORMAL self.master.title("Gato 0.99 - " + stripPath(self.algoDisplayFileName)) if self.AboutAlgorithmDialog: # to do ... alright for xml about ?! self.AboutAlgorithmDialog.Update(self.algorithm.About(), "About Algorithm") def CleanUp(self): """ removes the temporary files... """ if hasattr(self,"tmpAlgoFileName") and self.tmpAlgoFileName: os.remove(self.tmpAlgoFileName) os.remove(self.tmpAlgoFileName[:-3]+'pro') def ReloadAlgorithmGraph(self): if self.algorithmIsRunning: self.CmdStop() self.commandAfterStop = self.ReloadAlgorithmGraph return if self.algorithm.algoFileName is not "": self.OpenAlgorithm(self.algorithm.algoFileName) if self.algorithm.graphFileName is not "": self.OpenGraph(self.algorithm.graphFileName) def Preferences(self,event=None): """ Handle editing preferences """ self.config.edit() def ExportEPSF(self): """ GUI to control export of EPSF file """ file = asksaveasfilename(title="Export EPSF", defaultextension=".eps", filetypes = [ ("Encapsulated PS", ".eps") ,("Postscript", ".ps") ] ) if file is not "": self.graphDisplay.PrintToPSFile(file) def Quit(self,event=None): if self.algorithmIsRunning: self.commandAfterStop = self.Quit self.CmdStop() return if askokcancel("Quit","Do you really want to quit?"): Frame.quit(self) self.CleanUp() def OneGraphWindow(self,event=None): """ Align windows nicely for one graph window """ self.WithdrawSecondaryGraphDisplay() self.master.update() if self.windowingsystem == 'aqua': screenTop = 22 # Take care of menubar else: screenTop = 0 # Keep the AlgoWin fixed in size but move it to 0,0 (topWMExtra,WMExtra) = WMExtrasGeometry(self.graphDisplay) pad = 1 # Some optional extra space trueWidth = self.master.winfo_width() + 2 * WMExtra + pad # Move AlgoWin so that the WM extras will be at 0,0 # Silly enough one hast to specify the true coordinate at which # the window will appear try: self.master.geometry("+%d+%d" % (pad, screenTop + pad)) except TclError: log.debug("OneGraphWindow: self.master.geometry failed for +%d+%d" % (pad, screenTop + pad)) log.debug("OneGraphWindow: screen= (%d * %d), extras = (%d %d)" % ( self.master.winfo_screenwidth(), self.master.winfo_screenheight(), WMExtra, topWMExtra) ) # Move graph win to take up the rest of the screen screenwidth = self.master.winfo_screenwidth() screenheight = self.master.winfo_screenheight() - screenTop self.graphDisplay.geometry("%dx%d+%d+%d" % ( screenwidth - trueWidth - 2 * WMExtra - pad - 1,# see 1 below screenheight - WMExtra - topWMExtra - pad, trueWidth + 1 + pad, screenTop + pad)) self.graphDisplay.update() self.master.update() def TwoGraphWindow(self,event=None): """ Align windows nicely for two graph windows """ self.OpenSecondaryGraphDisplay() self.master.update() if self.windowingsystem == 'aqua': screenTop = 22 # Take care of menubar else: screenTop = 0 # Keep the AlgoWin fixed in size but move it to 0,0 (topWMExtra,WMExtra) = WMExtrasGeometry(self.graphDisplay) pad = 1 # Some optional extra space trueWidth = self.master.winfo_width() + 2 * WMExtra + pad # Move AlgoWin so that the WM extras will be at 0,0 # Silly enough one hast to specify the true coordinate at which # the window will appear self.master.geometry("+%d+%d" % (pad, screenTop + pad)) # Move GraphWins so that the are stacked dividing vertical # space evenly and taking up as much as possible horizontally screenwidth = self.master.winfo_screenwidth() screenheight = self.master.winfo_screenheight() - screenTop reqGDWidth = screenwidth - trueWidth - 2 * WMExtra - pad - 1 reqGDHeight = screenheight/2 - WMExtra - topWMExtra - pad self.graphDisplay.geometry("%dx%d+%d+%d" % ( reqGDWidth, reqGDHeight, trueWidth + 1 + pad, screenTop + pad)) self.secondaryGraphDisplay.geometry("%dx%d+%d+%d" % ( reqGDWidth, reqGDHeight, trueWidth + 1 + pad, screenTop + reqGDHeight + WMExtra + topWMExtra + 2 * pad)) self.master.update() def AboutBox(self): d = AboutBox(self.master) def HelpBox(self,event=None): d = HTMLViewer(gGatoHelp, "Help", self.master) def GoToGatoWebsite(self): webbrowser.open('http://gato.sf.net', new=1, autoraise=1) def GoToCATBoxWebsite(self): webbrowser.open('http://algorithmics.molgen.mpg.de/CATBox', new=1, autoraise=1) def AboutAlgorithm(self): d = HTMLViewer(self.algorithm.About(), "About Algorithm", self.master) self.AboutAlgorithmDialog = d def AboutGraph(self): d = HTMLViewer(self.graphDisplay.About(), "About Graph", self.master) self.AboutGraphDialog = d ############################################################ # # Tool bar Commands # # The tool bar commands are passed as call back parameters to # the tool bar buttons. # def CmdStart(self): """ Command linked to toolbar 'Start' """ # self.deactivateMenu() self.buttonStart['state'] = DISABLED self.buttonStep['state'] = NORMAL self.buttonTrace['state'] = NORMAL self.buttonContinue['state'] = NORMAL self.buttonStop['state'] = NORMAL self.algorithmIsRunning = 1 self.algorithm.Start() def CmdStop(self): """ Command linked to toolbar 'Stop' """ self.algorithm.Stop() self.clickResult = ('abort',None) # for aborting interactive # selection of vertices/edges self.touchLock() def CommitStop(self): """ Commit a stop for the GUI """ self.buttonStart['state'] = NORMAL self.buttonStep['state'] = DISABLED self.buttonTrace['state'] = DISABLED self.buttonContinue['state'] = DISABLED self.buttonStop['state'] = DISABLED # Un-activate last line if self.lastActiveLine != 0: self.unTagLine(self.lastActiveLine,'Active') self.update() # Forcing redraw self.algorithmIsRunning = 0 if self.commandAfterStop != None: self.commandAfterStop() self.commandAfterStop = None # self.activateMenu() def CmdStep(self): """ Command linked to toolbar 'Step' """ self.algorithm.Step() self.clickResult = ('auto',None) # for stepping over interactive # selection of vertices/edges self.touchLock() def CmdContinue(self): """ Command linked to toolbar 'Continue' """ # Should we disable continue buton here ? self.algorithm.Continue() self.clickResult = ('auto',None) # for stepping over interactive # selection of vertices/edges self.touchLock() def CmdTrace(self): """ Command linked to toolbar 'Trace' """ self.algorithm.Trace() self.touchLock() ############################################################ # # Key commands for Tool bar Commands # def BindKeys(self, widget): #widget.bind('',self.OnQuitMenu) # self.master.bind_all screws up EPSF save dialog widget.bind('s', self.KeyStart) widget.bind('x', self.KeyStop) widget.bind('', self.KeyStep) widget.bind('c', self.KeyContinue) widget.bind('t', self.KeyTrace) widget.bind('b', self.KeyBreak) widget.bind('r', self.KeyReplay) widget.bind('u', self.KeyUndo) widget.bind('d', self.KeyDo) # Cross-plattform accelerators if self.windowingsystem == 'aqua': accMod = "Command" else: accMod = "Control" widget.bind('<%s-q>' % accMod, self.Quit) widget.bind('<%s-comma>' % accMod, self.Preferences) widget.bind('<%s-KeyPress-1>' % accMod, self.OneGraphWindow) widget.bind('<%s-KeyPress-2>' % accMod, self.TwoGraphWindow) widget.bind('<%s-question>' % accMod, self.HelpBox) def KeyStart(self, event): """ Command linked to toolbar 'Start' """ if self.buttonStart['state'] != DISABLED: self.CmdStart() def KeyStop(self, event): if self.buttonStop['state'] != DISABLED: self.CmdStop() def KeyStep(self, event): """ Command linked to toolbar 'Step' """ if self.buttonStep['state'] != DISABLED: self.CmdStep() else: self.KeyStart(event) def KeyContinue(self, event): """ Command linked to toolbar 'Continue' """ if self.buttonContinue['state'] != DISABLED: self.CmdContinue() def KeyTrace(self, event): """ Command linked to toolbar 'Trace' """ if self.buttonTrace['state'] != DISABLED: self.CmdTrace() def KeyBreak(self, event): """ Command for toggling breakpoints """ self.algorithm.ToggleBreakpoint() def KeyReplay(self, event): """ Command for Replaying last animation """ self.algorithm.Replay() def KeyUndo(self, event): """ Command for Replaying last animation """ self.algorithm.Undo() def KeyDo(self, event): """ Command for Replaying last animation """ self.algorithm.Do() ############################################################ # # Mouse Commands # # # handleMouse def handleMouse(self, event): """ Callback for canvas to allow toggeling of breakpoints """ currLine = string.splitfields(self.algoText.index(CURRENT),'.')[0] self.algorithm.ToggleBreakpoint(string.atoi(currLine)) ############################################################ # # Public methods (for callbacks from algorithm) # def ShowActive(self, lineNo): """ Show lineNo as active line """ if self.lastActiveLine != 0: self.unTagLine(self.lastActiveLine,'Active') self.lastActiveLine = lineNo self.tagLine(lineNo,'Active') self.algoText.yview_pickplace('%d.0' % lineNo) self.update() # Forcing redraw def ShowBreakpoint(self, lineNo): """ Show lineNo as breakpoint """ self.tagLine(lineNo,'Break') def HideBreakpoint(self, lineNo): """ Show lineNo w/o breakpoint """ self.unTagLine(lineNo,'Break') def WaitNextEvent(self): """ Stop Execution until user does something. This avoids busy idling. See touchLock() """ self.wait_variable(self.goOn) def WaitTime(self, delay): """ Stop Execution until delay is passed. This avoids busy idling. See touchLock() """ self.after(delay,self.touchLock) self.wait_variable(self.goOn) def ClickHandler(self,type,t): """ *Internal* Callback for GraphDisplay """ self.clickResult = (type,t) self.touchLock() def PickInteractive(self, type, filterChoice=None, default=None): """ Pick a vertex or an edge (specified by 'type') interactively GUI blocks until - a fitting object is clicked - the algorithm is stopped - 'Step' is clicked which will randomly select a vertex or an edge filterChoice is an optional method (only argument: the vertex or edge). It returns true if the choice is acceptable NOTE: To avoid fatal blocks randomly selected objects are not subjected to filterChoice """ self.graphDisplay.RegisterClickhandler(self.ClickHandler) if default == "None": self.graphDisplay.UpdateInfo("Select a " + type + " or click 'Step' or 'Continue' for no selection") elif default == None: self.graphDisplay.UpdateInfo("Select a " + type + " or click 'Step' or 'Continue' for random selection") else: self.graphDisplay.UpdateInfo("Select a " + type + " or click 'Step' or 'Continue' for default selection") self.clickResult = (None,None) goOn = 1 while goOn == 1: self.wait_variable(self.goOn) if self.clickResult[0] == type: if filterChoice != None: if filterChoice(self.clickResult[1]): goOn = 0 else: goOn = 0 if self.clickResult[0] in ['abort','auto']: goOn = 0 self.graphDisplay.UnregisterClickhandler() self.graphDisplay.DefaultInfo() if self.clickResult[0] == 'auto': return None else: return self.clickResult[1] def HandleFileIOError(self, fileDescription, fileName): log.error("%s file named %s produced an error" % (fileDescription, fileName)) # Endof: AlgoWin --------------------------------------------------------------- class AlgorithmDebugger(bdb.Bdb): """*Internal* Bdb subclass to allow debugging of algorithms REALLY UGLY CODE: Written before I understood the Debugger. Probably should use sys.settrace() directly with the different modes of debugging encoded in appropriate methods""" def __init__(self,dbgGUI): """ *Internal* dbgGUI is the GUI for the debugger """ self.GUI = dbgGUI bdb.Bdb.__init__(self) self.doTrace = 0 self.lastLine = -1 def dispatch_line(self, frame): """ *Internal* Only dispatch if we are in the algorithm file """ fn = frame.f_code.co_filename if fn != self.GUI.algoFileName: return None line = self.currentLine(frame) if line == self.lastLine: return self.trace_dispatch self.lastLine = line self.user_line(frame) if self.quitting: raise bdb.BdbQuit return self.trace_dispatch def dispatch_call(self, frame, arg): fn = frame.f_code.co_filename line = self.currentLine(frame) doTrace = self.doTrace # value of self.doTrace might change # No tracing of functions defined outside of our algorithmfile if fn != self.GUI.algoFileName: return None #import inspect #log.debug("dispatch_call %s %s %s %s %s %s" % (fn, line, frame, self.stop_here(frame), self.break_anywhere(frame), self.break_here(frame))) #log.debug("%s" % inspect.getframeinfo(frame)) frame.f_locals['__args__'] = arg if self.botframe is None: # First call of dispatch since reset() self.botframe = frame return self.trace_dispatch #if self.stop_here(frame) or self.break_anywhere(frame): # return self.trace_dispatch self.user_call(frame, arg) if self.quitting: raise bdb.BdbQuit if doTrace == 1: self.doTrace = 0 return self.trace_dispatch if self.break_anywhere(frame): self.doTrace = 0 return self.trace_nofeedback_dispatch return None def trace_nofeedback_dispatch(self, frame, event, arg): if self.quitting: return # None if event == 'line': line = self.currentLine(frame) if line in self.GUI.breakpoints: self.GUI.mode = 2 return self.dispatch_line(frame) else: return None if event == 'call': return self.dispatch_call(frame, arg) if event == 'return': return self.dispatch_return(frame, arg) if event == 'exception': return self.dispatch_exception(frame, arg) log.debug("bdb.Bdb.dispatch: unknown debugging event: %s" % event) def reset(self): """ *Internal* Put debugger into initial state, calls forget() """ bdb.Bdb.reset(self) self.forget() def forget(self): self.lineno = None self.stack = [] self.curindex = 0 self.curframe = None def setup(self, f, t): #self.forget() self.stack, self.curindex = self.get_stack(f, t) self.curframe = self.stack[self.curindex][0] def user_call(self, frame, argument_list): """ *Internal* This function is called when we stop or break at this line """ line = self.currentLine(frame) # log.debug("*user_call* %s %s" % (line, argument_list)) if self.doTrace == 1: line = self.currentLine(frame) if line in self.GUI.breakpoints: self.GUI.mode = 2 self.GUI.GUI.ShowActive(line) # TO Avoid multiple steps in def line of called fun #self.interaction(frame, None) self.doTrace = 0 else: pass def user_line(self, frame): """ *Internal* This function is called when we stop or break at this line """ self.doTrace = 0 # XXX line = self.currentLine(frame) # log.debug("*user_line* %s" % line) if line in self.GUI.breakpoints: self.GUI.mode = 2 self.GUI.GUI.ShowActive(line) self.interaction(frame, None) def user_return(self, frame, return_value): """ *Internal* This function is called when a return trap is set here """ frame.f_locals['__return__'] = return_value #log.debug('--Return--') #self.doTrace = 0 #YYY # TO Avoid multiple steps in return line of called fun #self.interaction(frame, None) def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): """ *Internal* This function is called if an exception occurs, but only if we are to stop at or just below this level """ frame.f_locals['__exception__'] = exc_type, exc_value if type(exc_type) == type(''): exc_type_name = exc_type else: exc_type_name = exc_type.__name__ #log.debug("exc_type_name: %s" repr.repr(exc_value)) self.interaction(frame, exc_traceback) def interaction(self, frame, traceback): """ *Internal* This function does all the interaction with the user depending on self.GUI.mode - Step (self.GUI.mode == 2) - Quit (self.GUI.mode == 0) - Auto-run w/timer (self.GUI.mode == 1)""" self.setup(frame, traceback) # #line = self.currentLine(frame) if self.GUI.mode == 2: old = self.GUI.mode self.GUI.GUI.WaitNextEvent() # user event -- might change self.GUI.mode #log.debug("self.GUI.mode: %s -> %s " % (old, self.GUI.mode)) #if self.GUI.mode == 2: #self.do_next() if self.GUI.mode == 0: self.do_quit() return # Changed if self.GUI.mode == 1: self.GUI.GUI.WaitTime(10) # timer event was 100 #self.do_next() self.forget() def do_next(self): self.set_next(self.curframe) def do_quit(self): self.set_quit() def currentLine(self, frame): """ *Internal* returns the current line number """ return frame.f_lineno # Endof: AlgorithmDebugger ---------------------------------------------------- class Algorithm: """ Provides all services necessary to load an algorithm, run it and provide facilities for visualization """ def __init__(self): self.DB = AlgorithmDebugger(self) self.source = "" # Source as a big string self.interactive = [] self.breakpoints = [] # Doesnt debugger take care of it ? self.algoFileName = "" self.graphFileName = "" self.mode = 0 # mode = 0 Stop # mode = 1 Running # mode = 2 Stepping self.graph = None # graph for the algorithm self.cleanGraphCopy = None # this is the backup of the graph self.graphIsDirty = 0 # If graph was changed by running self.algoGlobals = {} # Sandbox for Algorithm self.logAnimator = 1 self.about = None self.commentPattern = re.compile('[ \t]*#') self.blankLinePattern = re.compile('[ \t]*\n') def SetGUI(self, itsGUI): """ Set the connection to its GUI """ self.GUI = itsGUI def Open(self,file): """ Read in an algorithm from file. """ self.ClearBreakpoints() self.algoFileName = file input=open(file, 'r') self.source = input.read() input.close() # Now read in the prolog as a module to get access to the following data # Maybe should obfuscate the names ala xxx_, have one dict ? try: input = open(os.path.splitext(self.algoFileName)[0] + ".pro", 'r') options = self.ReadPrologOptions(input) input.close() except EOFError, IOError: self.GUI.HandleFileIOError("Prolog",file) return try: self.breakpoints = options['breakpoints'] except: self.breakpoints = [] try: self.interactive = options['interactive'] except: self.interactive = [] try: self.graphDisplays = options['graphDisplays'] except: self.graphDisplays = None try: self.about = options['about'] except: self.about = None if self.graphDisplays != None: if self.graphDisplays == 1 and hasattr(self,"GUI"): self.GUI.WithdrawSecondaryGraphDisplay() def ReadPrologOptions(self, file): """ Prolog files should contain the following variables: - breakpoints = [] a list of line numbers which are choosen as default breakpoints - interactive = [] a list of line numbers which contain interactive commands (e.g., PickVertex) - graphDisplays = 1 | 2 the number of graphDisplays needed by the algorithm - about = \"\"\"\"\"\" information about the algorithm Parameter: filelike object """ import re import sys text = file.read() options = {} optionPattern = {'breakpoints':'breakpoints[ \t]*=[ \t]*(\[[^\]]+\])', 'interactive':'interactive[ \t]*=[ \t]*(\[[^\]]+\])', 'graphDisplays':'graphDisplays[ \t]*=[ \t]*([1-2])'} # about is more complicated for patternName in optionPattern.keys(): compPattern = re.compile(optionPattern[patternName]) match = compPattern.search(text) if match != None: options[patternName] = eval(match.group(1)) # Special case with about (XXX: assuming about = """ ... """) try: aboutStartPat = re.compile('about[ \t]*=[ \t]*"""') aboutEndPat = re.compile('"""') left = aboutStartPat.search(text).end() right = aboutEndPat.search(text, left).start() options['about'] = text[left:right] except: pass return options def About(self): """ Return a HTML-page giving information about the algorithm """ if self.about != None: return self.about else: return "

No information available

" def OpenGraph(self,file,fileName=None): """ Read in a graph from file and open the display """ if type(file) in types.StringTypes: self.graphFileName = file elif type(file)==types.FileType or issubclass(file.__class__,StringIO.StringIO): self.graphFileName = fileName else: raise Exception("wrong types in argument list: expected string or file like object") self.cleanGraphCopy = OpenCATBoxGraph(file) self.restoreGraph() self.GUI.graphDisplay.Show() # In case we are hidden self.GUI.graphDisplay.ShowGraph(self.graph, stripPath(self.graphFileName)) self.GUI.graphDisplay.RegisterGraphInformer(WeightedGraphInformer(self.graph)) def restoreGraph(self): self.graph=copy.deepcopy(self.cleanGraphCopy) self.graphIsDirty = 0 def OpenSecondaryGraph(self,G,title,informer=None): """ Read in graph from file and open the the second display """ self.GUI.OpenSecondaryGraphDisplay() self.GUI.secondaryGraphDisplay.ShowGraph(G, title) if informer != None: self.GUI.secondaryGraphDisplay.RegisterGraphInformer(informer) def ReadyToStart(self): """ Return 1 if we are ready to run. That is when we user has opened both an algorithm and a graph. """ if self.graphFileName != "" and self.algoFileName != "": return 1 else: return 0 def Start(self): """ Start an loaded algorithm. It firsts execs the prolog and then starts the algorithm in the debugger. The algorithms globals (i.e., the top-level locals are in a dict we supply and for which we preload the packages we want to make available)""" if self.graphIsDirty == 1: self.restoreGraph() # Does show self.GUI.graphDisplay.Show() # In case we are hidden self.GUI.graphDisplay.ShowGraph(self.graph, stripPath(self.graphFileName)) self.GUI.graphDisplay.RegisterGraphInformer(WeightedGraphInformer(self.graph)) else: self.GUI.graphDisplay.Show() # In case we are hidden self.graphIsDirty = 1 self.mode = 1 # Set global vars ... self.algoGlobals = {} self.algoGlobals['self'] = self self.algoGlobals['G'] = self.graph self.animation_history = None if self.logAnimator: self.animation_history = AnimationHistory(self.GUI.graphDisplay) self.algoGlobals['A'] = self.animation_history else: self.algoGlobals['A'] = self.GUI.graphDisplay # XXX # explictely loading packages we want to make available to the algorithm modules = ['Gato.DataStructures', 'Gato.AnimatedDataStructures', 'Gato.AnimatedAlgorithms', 'Gato.GraphUtil', 'Gato.GatoUtil'] for m in modules: exec("from %s import *" % m, self.algoGlobals, self.algoGlobals) # transfer required globals self.algoGlobals['gInteractive'] = globals()['gInteractive'] # Read in prolog and execute it try: execfile(os.path.splitext(self.algoFileName)[0] + ".pro", self.algoGlobals, self.algoGlobals) except: log.exception("Bug in %s.pro" % os.path.splitext(self.algoFileName)[0]) #traceback.print_exc() # Read in algo and execute it in the debugger file = self.algoFileName # Filename must be handed over in a very safe way # because of \ and ~1 under windows self.algoGlobals['_tmp_file']=self.algoFileName # Switch on all shown breakpoints for line in self.breakpoints: self.DB.set_break(self.algoFileName,line) try: command = "execfile(_tmp_file)" self.DB.run(command, self.algoGlobals, self.algoGlobals) except: log.exception("Bug in %s" % self.algoFileName) #traceback.print_exc() self.GUI.CommitStop() def Stop(self): self.mode = 0 def Step(self): if self.animation_history is not None: self.animation_history.DoAll() self.DB.doTrace = 0 self.mode = 2 def Continue(self): if self.animation_history is not None: self.animation_history.DoAll() self.DB.doTrace = 0 self.mode = 1 def Trace(self): if self.animation_history is not None: self.animation_history.DoAll() self.mode = 2 self.DB.doTrace = 1 def Replay(self): #self.GUI.CmdStep() if self.animation_history is not None: self.animation_history.DoAll() self.animation_history.Replay() def Undo(self): #self.GUI.CmdStep() if self.animation_history is not None: self.animation_history.Undo() def Do(self): #self.GUI.CmdStep() if self.animation_history is not None: self.animation_history.Do() def ClearBreakpoints(self): """ Clear all breakpoints """ for line in self.breakpoints: self.GUI.HideBreakpoint(line) self.DB.clear_break(self.algoFileName,line) self.breakpoints = [] def SetBreakpoints(self, list): """ SetBreakpoints is depreciated NOTE: Use 'breakpoint' var in prolog instead. Set all breakpoints in list: So an algorithm prolog can set a bunch of pre-assigned breakpoints at once """ log.info("SetBreakpoints() is depreciated. Use 'breakpoint' var in prolog instead. ") for line in list: self.GUI.ShowBreakpoint(line) self.breakpoints.append(line) self.DB.set_break(self.algoFileName,line) def ToggleBreakpoint(self,line = None): """ If we have a breakpoint on line, delete it, else add it. If no line is passed we ask the DB for it""" if line == None: line = self.DB.lastLine if line in self.breakpoints: self.GUI.HideBreakpoint(line) self.breakpoints.remove(line) self.DB.clear_break(self.algoFileName,line) else: # New Breakpoint # check for not breaking in comments nor on empty lines. import linecache codeline = linecache.getline(self.algoFileName,line) if codeline != '' and self.commentPattern.match(codeline) == None and self.blankLinePattern.match(codeline) == None: self.GUI.ShowBreakpoint(line) self.breakpoints.append(line) self.DB.set_break(self.algoFileName,line) def GetInteractiveLines(self): """ Return lines on which user interaction (e.g., choosing a vertex occurrs. """ return self.interactive def GetBreakpointLines(self): """ Return lines on which user interaction (e.g., choosing a vertex occurrs. """ return self.breakpoints def GetSource(self): """ Return the algorithms source """ return self.source def NeededProperties(self, propertyValueDict): """ Check that graph has that value for each property specified in the dictionary 'propertyValueDict' If check fails algorithm is stopped Proper names for properties are defined in gProperty """ for property in propertyValueDict.keys(): value = self.graph.Property(property) if value != propertyValueDict[property]: r = askokcancel("Gato - Error", "The algorithm you started requires that the graph " + "it works on has certain properties. The graph does " + "not have the correct value " + "for the property '" + property + "'.\n" + "Do you still want to proceed ?") if not r: self.GUI.CmdStop() def PickVertex(self, default=None, filter=None, visual=None): """ Pick a vertex interactively. - default: specifies the vertex returned when user does not want to select one. If default==None, a random vertex not subject to filter will be returned. - filter: a function which should return a non-None value if the passed vertex is acceptable - visual is a function which takes the vertex as its only argument and cause e.g. some visual feedback """ v = None #log.debug("pickVertex %s" %s globals()['gInteractive']) if globals()['gInteractive'] == 1: v = self.GUI.PickInteractive('vertex', filter, default) if v == None: if default == None: v = random.choice(self.graph.vertices) else: v = default if visual is not None: visual(v) return v def PickEdge(self, default=None, filter=None, visual=None): """ Pick an edge interactively - default: specifies the edge returned when user does not want to select one. If default==None, a random edge not subject to filter will be returned - filter: a function which should return a non-None value if the passed edge is acceptable - visual is a function which takes the edge as its only argument and cause e.g. some visual feedback """ e = None if globals()['gInteractive'] == 1: e = self.GUI.PickInteractive('edge', filter, default) if e == None: if default == None: e = random.choice(self.graph.Edges()) else: e = default if visual is not None: visual(e) return e ################################################################################ def usage(): print "Usage: Gato.py" print " Gato.py -v algorithm.alg graph.cat | gato-file" print " -v or --verbose switches on the debugging/logging information" if __name__ == '__main__': import getopt try: opts, args = getopt.getopt(sys.argv[1:], "v", ["verbose"]) except getopt.GetoptError: usage() sys.exit(2) if (len(args) < 3): import logging log = logging.getLogger("Gato.py") for o, a in opts: if o in ("-v", "--verbose"): logging.verbose = 1 tk = Tk() # Prevent the Tcl console from popping up in standalone apps on MacOS X # Checking for hasattr(sys,'frozen') does not work for bundelbuilder try: tk.tk.call('console','hide') except tkinter.TclError: pass #tk.option_add('*ActiveBackground','#EEEEEE') tk.option_add('*background','#DDDDDD') #XXX Buttons look ugly with white backgrounds on MacOS X, added directly to Button(...) # The option not working is might be a known bug # http://aspn.activestate.com/ASPN/Mail/Message/Tcl-bugs/2131881 # Still present in the 8.4.7 that comes with 10.4 tk.option_add('*Highlightbackground','#DDDDDD') tk.option_add('*Button.highlightbackground','#DDDDDD') tk.option_add('*Button.background','#DDDDDD') tk.option_add('Tk*Scrollbar.troughColor','#CACACA') app = AlgoWin(tk) # On MacOS X the Quit menu entry otherwise bypasses our Quit Handler # According to # http://mail.python.org/pipermail/pythonmac-sig/2006-May/017432.html # this should work, Maybr if app.windowingsystem == 'aqua': tk.tk.createcommand("::tk::mac::Quit",app.Quit) #====================================================================== # Gato.py if len(args) == 2: algorithm = args[0] graph = args[1] app.OpenAlgorithm(algorithm) app.update_idletasks() app.update() app.OpenGraph(graph) app.update_idletasks() app.update() app.after_idle(app.CmdContinue) # after idle needed since CmdStart app.CmdStart() app.update_idletasks() elif len(args)==1: # expect gato file name or url fileName=args[0] app.OpenGatoFile(fileName) app.update_idletasks() app.update() app.mainloop() else: usage() sys.exit(2)