#------------------------------------------------------------------------------- # Name: DuchampParameterGUI_mainInterfaceClass # Purpose: # # Author: Kelvin # # Created: 4/12/2013 #------------------------------------------------------------------------------- # Python 2 packages try: from Tkinter import * import tkFileDialog # Python 3 packages except ImportError: from tkinter import * import tkinter.filedialog as tkFileDialog from DuchampParameterGUI_fileIO import * from DuchampParameterGUI_listPrinting import * # GUI - Tkinter section class mainInterface: def __init__(self, master): # -------------------------------------------------- # Initialise the GUI window. self.frame = Frame(master) self.frame.grid() # -------------------------------------------------- # -------------------------------------------------- # Menu self.menubar = Menu(master) # File Menu self.filemenu = Menu(self.menubar, tearoff = 0) self.filemenu.add_command(label = "Open", command = self.createInputInterface) self.filemenu.add_command(label = "Save specification", command = self.createSpecifiedOutputInterface) self.filemenu.add_command(label = "Save all specification", command = self.createEffectiveOutputInterface) self.filemenu.add_separator() self.filemenu.add_command(label = "Exit", command = self.frame.quit) self.menubar.add_cascade(label = "File", menu = self.filemenu) # Options Menu self.editmenu = Menu(self.menubar, tearoff = 0) self.editmenu.add_command(label = "Cut", command = self.notImplementedYet) self.editmenu.add_command(label = "Copy", command = self.notImplementedYet) self.editmenu.add_command(label = "Paste", command = self.notImplementedYet) self.menubar.add_cascade(label = "Options", menu = self.editmenu) # Help Menu self.helpmenu = Menu(self.menubar, tearoff = 0) self.helpmenu.add_command(label = "About", command = self.notImplementedYet) self.menubar.add_cascade(label = "Help", menu = self.helpmenu) # -------------------------------------------------- # Row 1-10 reserved for future extensions # -------------------------------------------------- # Parameter name and value entry self.parameterLabel = Label(self.frame, text = "Parameter: ", foreground = "Blue") self.parameterLabel.grid(row = 11, column = 1, columnspan = 2) self.valueLabel = Label(self.frame, text = "Value: ", foreground = "Forest Green") self.valueLabel.grid(row = 11, column = 3, columnspan = 2) self.parameterEntry = Entry(self.frame, width = 25) self.parameterEntry.bind("", self.checkParameterEvent) self.parameterEntry.bind("", self.previousDescription) self.parameterEntry.bind("", self.nextDescription) self.parameterEntry.grid(row = 12, column = 1, padx = 10, columnspan = 2) self.valueEntry = Entry(self.frame, width = 25) self.valueEntry.bind("", self.updateParameterEvent) self.valueEntry.grid(row = 12, column = 3, padx = 10, columnspan = 2) # -------------------------------------------------- # -------------------------------------------------- # Check, update, and describe parameter buttons self.checkButton = Button(self.frame, text = "Check", command = self.checkParameterWithDescription) self.checkButton.grid(row = 13, column = 1, padx = 10, pady = 10) self.updateButton = Button(self.frame, text = "Update", command = self.updateParameter) self.updateButton.grid(row = 13, column = 2, padx = 10, pady = 10) self.revertDefaultButton = Button(self.frame, text = "Revert to default", command = self.revertDefault) self.revertDefaultButton.grid(row = 13, column = 4, padx = 10, pady = 10, columnspan = 2) # -------------------------------------------------- # Row 14-15 reserved for displaying the information queried from above # -------------------------------------------------- # List parameter files buttons (default and specified) self.listDefault = Button(self.frame, text = "List Default Parameters", command = lambda: self.listParameters("Default")) self.listDefault.grid(row = 16, column = 1, padx = 10, pady = 10, columnspan = 2) self.listSpecified = Button(self.frame, text = "List User Specified Parameters", command = lambda: self.listParameters("Specified")) self.listSpecified.grid(row = 17, column = 1, padx = 10, pady = 10, columnspan = 2) self.listEffective = Button(self.frame, text = "List Effective Parameters", command = lambda: self.listParameters("Effective")) self.listEffective.grid(row = 18, column = 1, padx = 10, pady = 10, columnspan = 2) # -------------------------------------------------- # -------------------------------------------------- # Radio Buttons # Obtain (parameter: class) dictionary, and a list of the names of classes in order (self.classOfParameter, self.classNames, self.description, self.paramType, self.paramFormat) = readParamByClass(defaultParamFilePath) # Initialise the variable containing the name of the class to be displayed self.whichClass = StringVar() # Index the buttons i = 0 # Create each radio button in order for name in self.classNames: b = Radiobutton(self.frame, text = name, variable = self.whichClass, value = name, indicatoron = 0, command = lambda: self.listParameters("Class"), width = 30) # Place them at appropriate positions r = 19 + int(i/2) if (i % 2) == 0: c = 1 else: c = 3 # Make them stick to the left b.grid(row = r, column = c, columnspan = 2) i = i + 1 # -------------------------------------------------- # -------------------------------------------------- # Variable definitions throughout the window # Firstly, determine the default list of parameters (self.defaultParameters, self.orderedParameters) = readParamFile(defaultParamFilePath) # Then, determine the user specified list of parameters self.userSpecifiedParameters = {} self.orderedSpecifiedParameters = [] # This is the effective parameters self.effectiveParameters = dict(self.defaultParameters) # Define the string variables in the parameter name and value entry section self.resultTitle = StringVar() self.result = StringVar() self.notesTitle = StringVar() self.notes = StringVar() self.color = StringVar() # Define the string variables for entering file directories, names, and paths self.userSpecifiedFileDirectory = StringVar() self.userSpecifiedFileName = StringVar() self.userSpecifiedFilePath = StringVar() # -------------------------------------------------- # Give a default name for the user specified file name self.userSpecifiedFileName = 'duchampHIPASS.txt' self.frame.bind("", self.precaution) self.frame.bind("", self.previousDescription) self.frame.bind("", self.nextDescription) # Define a notice box (one button only) def createNoticeBox1(self, title, message, command, buttonText = "Dismiss"): self.noticeBox = Toplevel(width = 200, borderwidth = 20) self.noticeBox.title(title) self.noticeMsg = Message(self.noticeBox, text = message) self.noticeMsg.grid(row = 1, column = 1, columnspan = 2) self.noticeBoxButton = Button(self.noticeBox, text = buttonText, command = command) self.noticeBoxButton.grid(row = 2, column = 1, pady = 5, columnspan = 2) # Define a notice box (two buttons) def createNoticeBox2(self, title, message, command1, command2, buttonText1 = "Yes", buttonText2 = "No"): self.noticeBox = Toplevel(width = 200, borderwidth = 20) self.noticeBox.title(title) self.noticeMsg = Message(self.noticeBox, text = message) self.noticeMsg.grid(row = 1, column = 1, columnspan = 2) self.noticeBoxButton1 = Button(self.noticeBox, text = buttonText1, command = command1) self.noticeBoxButton1.grid(row = 2, column = 1, pady = 5) self.noticeBoxButton2 = Button(self.noticeBox, text = buttonText2, command = command2) self.noticeBoxButton2.grid(row = 2, column = 2, pady = 5) # This destroys notice box def dismissNotice(self): self.noticeBox.destroy() # This checks if file is missing or corrupted or now def precaution(self, event): d = [len(self.defaultParameters), len(self.description), len(self.paramType), len(self.paramFormat)] s = ["Default Parameter", "Description", "Type", "Format"] # Firstly, if the default parameter file was not loaded, notify the user if min(d) == 0: which = s[d.index(min(d))] self.createNoticeBox1(title = "Notice", message = "Input data file missing or corrupted. " + which + " data missing. Close the window and restart.", command = self.frame.quit, buttonText = "Okay") self.frame.bind("", self.quitNow) self.frame.bind("", self.quitNow) # Also, check if all of the loaded dictionaries have the same number of elements if max(d) != min(d): if d.count(min(d)) == 3: print(1) which = s[d.index(max(d))] elif d.count(max(d)) == 3: print(2) which = s[d.index(min(d))] else: which = "Multiple" self.createNoticeBox1(title = "Notice", message = "Input data file corrupted. " + which + " data corrupted. Close the window and restart.", command = self.frame.quit, buttonText = "Okay") self.frame.bind("", self.quitNow) self.frame.bind("", self.quitNow) # This quits the frame by events def quitNow(self, event): self.frame.quit() # A notice for functions being not implemented yet def notImplementedYet(self): self.createNoticeBox1(title = "Be Patient!", message = "Wait! This button is still under developement!", command = self.dismissNotice, buttonText = "Alright!") # Some commands want both checking and describing parameters to be done def checkParameterWithDescription(self): self.checkParameter() self.showDescription() # Allow checking and describing parameters to be driven by events def checkParameterEvent(self, event): self.checkParameterWithDescription() # Allow updating parameters to be driven by events def updateParameterEvent(self, event): self.updateParameter() # This checks the value of the parameter entered in the parameter entry box def checkParameter(self): # Determine which parameter the user wants to check par = self.parameterEntry.get() # Find which list it is in and determine its value # Note that specified list takes priority if par.lower() in lower(self.orderedSpecifiedParameters): index = lower(self.orderedSpecifiedParameters).index(par.lower()) parameter2check = self.orderedSpecifiedParameters[index] value = self.userSpecifiedParameters[parameter2check] paramInfo = par + ': ' + value self.color.set("Forest Green") defaultYN = "Specified Value" elif par.lower() in lower(self.orderedParameters): index = lower(self.orderedParameters).index(par.lower()) parameter2check = self.orderedParameters[index] value = self.defaultParameters[parameter2check] paramInfo = parameter2check + ': ' + value self.color.set("Forest Green") defaultYN = "Default Value" else: value = "N/A" paramInfo = '"' + par + '" does not exist' self.color.set("Red") defaultYN = "N/A" # Store the results from the above self.resultTitle.set("Parameter Information") self.result.set(paramInfo) self.notesTitle.set("Default/Specified") self.notes.set(defaultYN) self.valueEntry.delete(0, END) self.valueEntry.insert(0, value) # -------------------------------------------------- # Windows that correspond to the variables above self.resultLabel = Label(self.frame, textvariable = self.resultTitle) self.resultLabel.grid(row = 14, column = 1, pady = 0, columnspan = 2) self.resultValue = Label(self.frame, textvariable = self.result, foreground = self.color.get()) self.resultValue.grid(row = 15, column = 1, pady = 10, columnspan = 2) self.notesLabel = Label(self.frame, textvariable = self.notesTitle) self.notesLabel.grid(row = 14, column = 3, pady = 0, columnspan = 2) self.notesValue = Label(self.frame, textvariable = self.notes) self.notesValue.grid(row = 15, column = 3, pady = 10, columnspan = 2) # -------------------------------------------------- # This updates the value of the parameter entered in the parameter entry box def updateParameter(self): # Determine which parameter the user wants to update par = self.parameterEntry.get() # Update the value and determine if the parameter is being changed or added into the list if par.lower() in lower(self.orderedSpecifiedParameters): value2write = self.valueEntry.get() index = lower(self.orderedSpecifiedParameters).index(par.lower()) parameter2update = self.orderedSpecifiedParameters[index] self.userSpecifiedParameters[parameter2update] = value2write paramInfo = parameter2update + ': ' + value2write newParamYN = "Updated specified parameter list" self.color.set("Blue") elif par.lower() in lower(self.orderedParameters): index = lower(self.orderedParameters).index(par.lower()) parameter2update = self.orderedParameters[index] value2write = self.valueEntry.get() self.userSpecifiedParameters[parameter2update] = value2write paramInfo = parameter2update + ': ' + value2write newParamYN = "Added in specified parameter list" self.color.set("Blue") self.orderedSpecifiedParameters = self.orderedSpecifiedParameters + [parameter2update] else: paramInfo = '"' + par + '" does not exist' newParamYN = "-" self.color.set("Red") # Store the results from above self.resultTitle.set("Parameter Information") self.result.set(paramInfo) self.notesTitle.set("Notes") self.notes.set(newParamYN) # -------------------------------------------------- # Windows that correspond to the variables above self.resultLabel = Label(self.frame, textvariable = self.resultTitle) self.resultLabel.grid(row = 14, column = 1, pady = 0, columnspan = 2) self.resultValue = Label(self.frame, textvariable = self.result, foreground = self.color.get()) self.resultValue.grid(row = 15, column = 1, pady = 10, columnspan = 2) self.notesLabel = Label(self.frame, textvariable = self.notesTitle) self.notesLabel.grid(row = 14, column = 3, pady = 0, columnspan = 2) self.notesValue = Label(self.frame, textvariable = self.notes) self.notesValue.grid(row = 15, column = 3, pady = 10, columnspan = 2) # -------------------------------------------------- # Update the effective parameters self.effectiveParameters.update(self.defaultParameters) # In case default wasn't loaded before self.effectiveParameters.update(self.userSpecifiedParameters) # List specified parameters self.listParameters("Specified") # This displays a description of the parameter entered in the parameter entry box def showDescription(self): # Determine which parameter to describe par = self.parameterEntry.get() # Determine if the parameter is within the description file if par.lower() in lower(self.orderedParameters): index = lower(self.orderedParameters).index(par.lower()) parameter2describe = self.orderedParameters[index] description = self.description[parameter2describe] else: parameter2describe = par description = "No description available" # Create the description self.descriptionList = Listbox(self.frame, width = longestLineLength(self.defaultParameters) + 5, font = ('courier', (10))) self.descriptionList.grid(row = 30, column = 1, columnspan = 4) try: self.descriptionList.insert(END, "Description of '" + parameter2describe + "'") self.descriptionList.insert(END, "Class: " + self.classOfParameter[parameter2describe]) self.descriptionList.insert(END, "Type: " + self.paramType[parameter2describe]) self.descriptionList.insert(END, "Format: " + self.paramFormat[parameter2describe]) self.descriptionList.insert(END, " ") except KeyError: self.descriptionList.delete(0, END) self.descriptionList.insert(END, "Please Enter a parameter") return descriptionWords = description.split() largestWordLength = 0 for word in descriptionWords: if len(word) > largestWordLength: largestWordLength = len(word) b = 0 for i in range(len(descriptionWords)): phrase = ' '.join(descriptionWords[b:i]) if len(phrase) > longestLineLength(self.defaultParameters) - largestWordLength: line = phrase self.descriptionList.insert(END, line) b = i self.descriptionList.insert(END, ' '.join(descriptionWords[b:])) self.descriptionList.bind("", self.previousDescription) self.descriptionList.bind("", self.nextDescription) def nextDescription(self, event): par = self.parameterEntry.get() try: nextPar = self.orderedParameters[self.orderedParameters.index(par) + 1] except IndexError: nextPar = self.orderedParameters[0] except ValueError: return self.parameterEntry.delete(0, END) self.parameterEntry.insert(0, nextPar) self.checkParameterWithDescription() def previousDescription(self, event): par = self.parameterEntry.get() try: nextPar = self.orderedParameters[self.orderedParameters.index(par) - 1] except ValueError: return self.parameterEntry.delete(0, END) self.parameterEntry.insert(0, nextPar) self.checkParameterWithDescription() # This allows the user to revert a parameter to its default value def revertDefault(self): # Determine which parameter the user wants to revert parameter2revert = self.parameterEntry.get() finalValue = self.defaultParameters.get(parameter2revert, "N/A") paramInfo = parameter2revert + ': ' + finalValue # Revert the value and determine if the parameter was already default if parameter2revert in self.userSpecifiedParameters: del(self.userSpecifiedParameters[parameter2revert]) self.orderedSpecifiedParameters.remove(parameter2revert) alreadyDefaultYN = "Reverted back to default" self.color.set("Sky Blue") elif parameter2revert in self.defaultParameters: alreadyDefaultYN = "Already default" self.color.set("Sky Blue") else: paramInfo = '"' + parameter2revert + '" does not exist' alreadyDefaultYN = "-" self.color.set("Red") # Store the results from above self.resultTitle.set("Parameter Information") self.result.set(paramInfo) self.notesTitle.set("Notes") self.notes.set(alreadyDefaultYN) self.valueEntry.delete(0, END) self.valueEntry.insert(0, finalValue) # -------------------------------------------------- # Windows that correspond to the variables above self.resultLabel = Label(self.frame, textvariable = self.resultTitle) self.resultLabel.grid(row = 14, column = 1, pady = 0, columnspan = 2) self.resultValue = Label(self.frame, textvariable = self.result, foreground = self.color.get()) self.resultValue.grid(row = 15, column = 1, pady = 10, columnspan = 2) self.notesLabel = Label(self.frame, textvariable = self.notesTitle) self.notesLabel.grid(row = 14, column = 3, pady = 0, columnspan = 2) self.notesValue = Label(self.frame, textvariable = self.notes) self.notesValue.grid(row = 15, column = 3, pady = 10, columnspan = 2) # -------------------------------------------------- # Update the effective parameters self.effectiveParameters.update(self.defaultParameters) # In case default wasn't loaded before self.effectiveParameters.update(self.userSpecifiedParameters) # List the specified parameter values self.listParameters("Specified") # This displays a list of parameters sets (depending on user command) def listParameters(self, parameterSet): paramOrder = self.orderedParameters if parameterSet == "Default": self.parameters = self.defaultParameters elif parameterSet == "Specified": self.parameters = self.userSpecifiedParameters paramOrder = self.orderedSpecifiedParameters elif parameterSet == "Effective": self.parameters = self.effectiveParameters elif parameterSet == "Class": self.effectiveParameters.update(self.defaultParameters) # In case default wasn't loaded before self.effectiveParameters.update(self.userSpecifiedParameters) self.parameters = self.effectiveParameters className = self.whichClass.get() else: print("Coding Error: Used wrong parameter set.") return # If the list is empty, it means we haven't loaded it yet if self.parameters == {}: self.createNoticeBox1(title = "Notice", message = "No " + parameterSet + " Parameter File Loaded", command = self.dismissNotice, buttonText = "Okay") # Otherwise we can print it using the specified format else: self.paramListScrollBar = Scrollbar(self.frame) self.paramListScrollBar.grid(row = 30, column = 5, ipady = 65) self.parameterList = Listbox(self.frame, yscrollcommand = self.paramListScrollBar.set, width = longestLineLength(self.defaultParameters) + 5, font = ('courier', (10))) self.parameterList.grid(row = 30, column = 1, columnspan = 4) if parameterSet == "Class": self.parameterList.insert(END, className) else: self.parameterList.insert(END, parameterSet + " set of Parameters") self.parameterList.insert(END, " ") star = 0 for par in paramOrder: if parameterSet == "Effective": if par in self.userSpecifiedParameters: star = 1 else: star = 0 if parameterSet == "Class": if self.classOfParameter[par] == className: if par in self.userSpecifiedParameters: star = 1 else: star = 0 tabbedPar = tabbedDictionaryLine(self.parameters, par, star) self.parameterList.insert(END, tabbedPar + self.parameters[par]) else: tabbedPar = tabbedDictionaryLine(self.parameters, par, star) self.parameterList.insert(END, tabbedPar + self.parameters[par]) self.paramListScrollBar.config(command = self.parameterList.yview) self.list2read = self.parameterList self.parameterList.bind("", self.checkItem) self.parameterList.bind("", self.checkItemDescript) self.parameterList.bind("", self.checkItemDescript) # When description is not needed def checkItem(self, event): self.doCheckItem(0) # When description is needed def checkItemDescript(self, event): self.doCheckItem(1) # When buttons are pressed on the lists, the items will be checked or described. def doCheckItem(self, describeFlag): try: if int(self.list2read.curselection()[0]) != 0: line = self.list2read.get(self.list2read.curselection()) linebits = line.split() parameter = linebits[0] self.parameterEntry.delete(0, END) self.parameterEntry.insert(0, parameter) if describeFlag == 0: self.checkParameter() elif describeFlag == 1: self.checkParameterWithDescription() else: print("Coding Error: Describe flag not binary") except IndexError: pass # This handles the input interface (loading a file) def createInputInterface(self): self.file_opt = options = {} options['defaultextension'] = '.txt' options['filetypes'] = [('text files', '.txt'), ('all files', '.*')] options['initialdir'] = os.getcwd options['initialfile'] = 'duchampHIPASS.txt' options['parent'] = self.frame options['title'] = 'Load Parameter File' self.userSpecifiedFilePath = tkFileDialog.askopenfilename(**self.file_opt) self.userSpecifiedFileName = self.userSpecifiedFilePath.split('/')[-1] self.loadInputFile() # This handles the output interface for specified parameters (saving a file) def createSpecifiedOutputInterface(self): self.file_opt = options = {} options['defaultextension'] = '.txt' options['filetypes'] = [('text files', '.txt'), ('all files', '.*')] options['initialdir'] = os.getcwd options['initialfile'] = self.userSpecifiedFileName.split('.')[0] + 'Edited.' + self.userSpecifiedFileName.split('.')[1] options['parent'] = self.frame options['title'] = 'Save User Specified Parameters' self.newFilePath = tkFileDialog.asksaveasfilename(**self.file_opt) writeParamFile(self.newFilePath, self.userSpecifiedParameters, self.orderedSpecifiedParameters, 1) # This handles the output interface for effective parameters (saving a file) def createEffectiveOutputInterface(self): self.file_opt = options = {} options['defaultextension'] = '.txt' options['filetypes'] = [('text files', '.txt'), ('all files', '.*')] options['initialdir'] = os.getcwd options['initialfile'] = self.userSpecifiedFileName.split('.')[0] + 'AllEdited.' + self.userSpecifiedFileName.split('.')[1] options['parent'] = self.frame options['title'] = 'Save List of Effective Parameters' self.newFilePath = tkFileDialog.asksaveasfilename(**self.file_opt) writeParamFile(self.newFilePath, self.effectiveParameters, self.orderedParameters, 1) # This decides whether to load or update the input files for specified parameters def loadInputFile(self): try: fin = open(self.userSpecifiedFilePath, 'r') fin.close() # If the list is empty, that means we are loading the file for the first time if self.userSpecifiedParameters == {}: self.createNoticeBox1(title = "Notice", message = "Input Files Loaded", command = self.load, buttonText = "Okay") # If the list is non-empty, this means we risk rewritting any edits we have made else: msg = "Are you sure? Any edits you've made in the previous session will be lost and replaced with the contents of the new files." self.createNoticeBox2(title = "Notice", message = msg, command1 = self.load, command2 = self.dismissNotice, buttonText1 = "Yes", buttonText2 = "No") except IOError: pass except TypeError: pass print("TypeError") # This loads or updates the input files for specified parameters def load(self): # The notice box can go away self.noticeBox.destroy() # Then we can determine the list of specified and effective parameters (self.userSpecifiedParameters, self.orderedSpecifiedParameters) = readParamFile(self.userSpecifiedFilePath) self.effectiveParameters.update(self.userSpecifiedParameters) messages = [] # If the file is empty, let the user know. if self.userSpecifiedParameters == {}: messages += ["The file you loaded is empty. Parameter file discarded."] # Otherwise, determine if some parameters are not part of the default set of parameters. else: self.listParameters("Specified") for par in self.orderedSpecifiedParameters: if par not in self.defaultParameters: messages += ["Some parameters loaded are not part of the default set of parameters."] break fin = open(self.userSpecifiedFilePath) # Also, determine if the format was not correct. for line in fin: if line[0] != '#' and len(line.split()) > 1: if len(line.split()) != 2: messages += ["File loaded has the wrong format. Loaded inputs may be incorrect."] break fin.close() # Let the user know of the above detections for msg in messages: self.createNoticeBox1(title = "Notice", message = msg, command = self.dismissNotice, buttonText = "Dismiss")