#############################
# Created July 21, 2017
#
# @author Collin Pampalone
#
#############################
import tkMessageBox
import tkFileDialog
from Tkinter import Toplevel, Entry, Button, BOTH, Frame, SUNKEN, Label, LEFT, BOTTOM, TOP, X, \
Checkbutton, StringVar, BooleanVar, W, Grid, NORMAL
from bokeh.colors import white
from Tkconstants import END
from constants import CONF
from log.log import logger
from os.path import dirname
[docs]class SettingsDialog(Toplevel):
"""
Dialog Window that allows the user to manually change the settings in config.json
"""
# This dialog should be a singleton, so the caller will ensure no other
# windows are open by checking this variable
singleton = False
def __init__(self, root, master):
SettingsDialog.singleton = True # Creation of a pseudo singleton
# Set up the window as transient, i.e. it is a subset the main window
logger.info('Instantiating SettingsDialog')
Toplevel.__init__(self, root)
self.transient(root)
self.protocol('WM_DELETE_WINDOW', self.free) # Custom exit protocol
self.__root = root # Declare the inputs, root is the Tk root, master is Calipso
self.__master = master
self.__var_dict = None # A formatted copy of config.json to hold changes
self.__settings_entries = list() # The text boxes for files settings, holds Entry objects
self.__browse_buttons = list() # Holds the browse buttons
self.__bool_buttons = list() # Holds the boolean setting checkboxes
self.__lock_setting_buttons = list() # Holds the lock setting checkboxes
self.__top_frame = None
self.__bottom_frame = None
self.__container = Frame(self) # Main frame to hold top and bottom
self.__container.pack(side=TOP, fill=BOTH, expand=True) # Place the frame
self.get_variable_dict() # Grab config file settings
self.create_top_frame() # Make the top and bottom frames
self.create_bottom_frame()
[docs] def create_top_frame(self):
""" The top frame holds all of the widgets for displaying and changing settings """
self.__top_frame = Frame(self.__container) # Create and place the frame
self.__top_frame.pack(side=TOP, fill=BOTH, expand=False)
# Create the title bar to explain the contents of each column
title_bar = []
title_bar_text = ['Config Setting', 'Value', '', 'Lock']
n = 0
for text in title_bar_text:
# Iterate through the column titles, create labels, and place them on the grid
title_bar.append(Label(self.__top_frame, text=text))
title_bar[n].grid(row=0, column=n, padx=5, pady=5, sticky=W)
n += 1
# Create a row for each setting with the necessary widgets for changing it
label_settings = []
n = 0
for key, value in self.__var_dict.iteritems():
# By iterating through, we can change the number and types of settings in config.py and
# we don't have to make any changes to the settings dialog as long as it is a bool or
# file directory
var_text = StringVar() # Temporary holds a string of the current directory
var_text.set(str(value['value']))
label_name = key + ':' # Temporarily holds the label of the setting
lock_val = BooleanVar() # Temporarily holds the value of lock_setting
bool_value = BooleanVar() # Temporarily holds the value of a bool setting
# Create the setting label
label_settings.append(Label(self.__top_frame, text=label_name))
# Figure out if we are making a file-type or bool-type setting
setting_type = self.__var_dict[key]['type']
if setting_type == 'file':
# Create a readonly entry to display the string of the directory path
self.__settings_entries.append(Entry(self.__top_frame, state='readonly', width=30,
justify=LEFT, textvariable=var_text,
readonlybackground=white, relief=SUNKEN))
# Create a browse button to change the path
dialog_box = self.__settings_entries[-1]
self.__browse_buttons.append(
Button(self.__top_frame, text='Change', width=10, command=lambda key=key, dialog_box=dialog_box:
self.change_dir_setting(key, dialog_box)))
elif setting_type == 'bool':
# Create a checkbutton to switch bool values
self.__bool_buttons.append(
Checkbutton(self.__top_frame, variable=bool_value, onvalue=True, offvalue=False,
command=lambda key=key, bool_value=bool_value:
self.change_bool_setting(key, bool_value)))
# Set the checkbox to show the current config value
bool_value.set(value['value'])
# Create checkbutton to switch the value between
# locked (true, set to manual) or not (false, auto)
self.__lock_setting_buttons.append(
Checkbutton(self.__top_frame, variable=lock_val, onvalue=True, offvalue=False,
command=lambda key=key, check_val=lock_val:
self.change_lock_setting(key, check_val)))
# Set the checkbox to show the current config value
lock_val.set(value['lock_setting'])
# Place all of the widgets on the grid
label_settings[n].grid(row=(1 + n), column=0, padx=5, pady=5, sticky=W)
# Make sure we only place the correct widget
if setting_type == 'file':
self.__settings_entries[-1].grid(row=(1 + n), column=1, padx=5, pady=5)
self.__browse_buttons[-1].grid(row=(1 + n), column=2, pady=5, padx=5)
elif setting_type == 'bool':
self.__bool_buttons[-1].grid(row=(1 + n), column=1, padx=5, pady=5)
self.__lock_setting_buttons[n].grid(row=(1 + n), column=3, padx=5, pady=5)
n += 1
# Give the entry boxes, bool checkboxes in column 1 the ability to move with the window
Grid.columnconfigure(self.__top_frame, 1, weight=1)
[docs] def create_bottom_frame(self):
""" The bottom frame which holds the save and revert buttons """
# TODO add a default button to reset config.json to defaults
self.__bottom_frame = Frame(self.__container)
self.__bottom_frame.pack(side=BOTTOM, fill=X, expand=True)
# Create a save and close button to save config
save_button = Button(self.__bottom_frame, text='Save Settings', command=lambda: self.save())
# Create a revert button to scrap the changes and go back to the initial settings
revert_button = Button(self.__bottom_frame, text='Revert Settings',
command=lambda: self.revert())
# Place all of the buttons and ability to move
save_button.grid(row=0, column=0, pady=5)
revert_button.grid(row=0, column=1, pady=5)
Grid.columnconfigure(self.__bottom_frame, 0, weight=1)
Grid.columnconfigure(self.__bottom_frame, 1, weight=1)
[docs] def change_lock_setting(self, key, new_val):
""" Change the manual/auto lock setting """
self.__var_dict[key]['lock_setting'] = new_val.get()
logger.info(key + ' manual/auto lock changed to ' + str(new_val.get()))
[docs] def change_bool_setting(self, key, new_val):
""" Change a boolean config setting """
self.__var_dict[key]['value'] = new_val.get()
logger.info(key + ' bool setting changed to ' + str(new_val.get()))
[docs] def change_dir_setting(self, key, dialog_box):
""" Change a directory config setting """
options = dict()
if 'database' in key.lower():
options['filetypes'] = [('CALIPSO Database', '*.db'), ('All files', '*')]
elif 'hdf' in key.lower():
options['filetypes'] = [('CALIPSO Data File', '*.hdf'), ('All files', '*')]
options['initialdir'] = dirname(dialog_box.get())
directory = tkFileDialog.askopenfilename(**options)
if directory != '': # if dir is not empty
dialog_box.config(state=NORMAL)
dialog_box.delete(0, END)
dialog_box.insert(END, directory)
self.__var_dict[key]['value'] = directory
logger.info(key + ' file setting changed to ' + directory)
[docs] def get_variable_dict(self):
""" Load the dictionary of variables from config.py through CONF in constants """
# The dict must be copied this way so that we can make changes without referencing the
# actual variables named in the CONF dictionary
var_dict = CONF.get_variable_dict()
new_dict = dict()
for key, variable in var_dict.iteritems(): # Iterate through the dict to copy it
key = key.replace('_', ' ')
key = key.title()
new_dict[key] = {
'value': variable.value(),
'lock_setting': variable.manual_setting(),
'type': variable.get_type()
}
self.__var_dict = new_dict
[docs] def free(self):
""" Destroy the window and ensure the session is closed correctly """
SettingsDialog.singleton = False # Singleton no longer exists, so set it to false
logger.info('Closing SettingsDialog')
self.destroy()
[docs] def save(self):
""" Write all of the settings from the self.__var_dict to config.json and close"""
writing_dict = CONF.get_variable_dict() # get the variable dict
for key, value in self.__var_dict.iteritems(): # Iterate through and write changes
proceed = True
key = key.replace(' ', '_')
key = key.lower()
# We want to warn users if they change the default db
if key == 'default_database' and writing_dict[key].value() != value['value']:
print writing_dict[key].value()
print value['value']
message = 'You are attempting to change the default database, are you sure sure ' \
'you would like to change it to %s?\n\nSelecting cancel will skip this ' \
'change and finish the save.' %value['value']
proceed = tkMessageBox.askokcancel('Proceed?', message)
if not proceed:
continue
# Locked values that are changed in the diaglog will still be written
writing_dict[key].force_change(value['value'])
writing_dict[key].change_manual(value['lock_setting'])
logger.info('Settings saved')
# Close the window
self.free()
[docs] def revert(self):
""" Reverts any unsaved changes back to the initial values """
self.get_variable_dict()
self.__top_frame.destroy()
self.__settings_entries = list()
self.__browse_buttons = list()
self.__lock_setting_buttons = list()
self.__top_frame = Frame(self)
self.__top_frame.pack(side=TOP, fill=BOTH, expand=True)
self.create_top_frame()