579 lines
24 KiB
Python
579 lines
24 KiB
Python
import wx
|
|
import wx.grid
|
|
from mt5_correlation.correlation import Correlation
|
|
from mt5_correlation.config import Config
|
|
from datetime import datetime, timedelta
|
|
import pytz
|
|
import pandas as pd
|
|
import logging
|
|
import logging.config
|
|
|
|
|
|
class MonitorFrame(wx.Frame):
|
|
|
|
cor = None
|
|
rows = 0 # Need to track as we need to notify grid if row count changes.
|
|
opened_filename = None # So we can save to same file as we opened
|
|
config = None # The applications config
|
|
|
|
COLUMN_INDEX = 0
|
|
COLUMN_SYMBOL1 = 1
|
|
COLUMN_SYMBOL2 = 2
|
|
COLUMN_BASE_COEFFICIENT = 3
|
|
COLUMN_DATE_FROM = 4
|
|
COLUMN_DATE_TO = 5
|
|
COLUMN_TIMEFRAME = 6
|
|
COLUMN_LAST_CHECK = 7
|
|
COLUMN_LAST_COEFFICIENT = 8
|
|
|
|
def __init__(self):
|
|
# Super
|
|
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, title="Divergence Monitor")
|
|
|
|
# Create logger and get config
|
|
self.log = logging.getLogger(__name__)
|
|
self.config = Config()
|
|
|
|
# Create correlation instance to maintain state of calculated coefficients. Set min coefficient from config
|
|
self.cor = Correlation()
|
|
self.cor.monitoring_threshold = self.config.get("monitor.monitoring_threshold")
|
|
|
|
# Status bar
|
|
self.statusbar = self.CreateStatusBar(1)
|
|
|
|
# Menu Bar and file menu
|
|
self.menubar = wx.MenuBar()
|
|
file_menu = wx.Menu()
|
|
|
|
# File menu items
|
|
menu_item_open = file_menu.Append(wx.ID_ANY, "Open", "Open correlations file.")
|
|
menu_item_save = file_menu.Append(wx.ID_ANY, "Save", "Save correlations file.")
|
|
menu_item_saveas = file_menu.Append(wx.ID_ANY, "Save As", "Save correlations file.")
|
|
file_menu.AppendSeparator()
|
|
menu_item_calculate = file_menu.Append(wx.ID_ANY, "Calculate", "Calculate base coefficients.")
|
|
file_menu.AppendSeparator()
|
|
menu_item_settings = file_menu.Append(wx.ID_ANY, "Settings", "Change application settings.")
|
|
file_menu.AppendSeparator()
|
|
menu_item_exit = file_menu.Append(wx.ID_ANY, "Exit", "Close the application")
|
|
|
|
# Add file menu and set menu bar
|
|
self.menubar.Append(file_menu, "File")
|
|
self.SetMenuBar(self.menubar)
|
|
|
|
# Main window. We want 2 horizontal sections, the grid showing correlations and a graph. In the correlations
|
|
# section, we want 2 vertical sections, the monitor toggle and the correlations grid. For the toggle we want 2
|
|
# sections, a label and a toggle.
|
|
# ---------------------------------------------------------------
|
|
# |label | toggle | |
|
|
# |-----------------------| |
|
|
# | | |
|
|
# |Correlations Grid | Graphs |
|
|
# | | |
|
|
# | | |
|
|
# | | |
|
|
# | | |
|
|
# ----------------------------------------------------------------
|
|
panel = wx.Panel(self, wx.ID_ANY)
|
|
toggle_sizer = wx.BoxSizer(wx.HORIZONTAL) # Label and toggle
|
|
correlations_sizer = wx.BoxSizer(wx.VERTICAL) # Toggle sizer and correlations grid
|
|
main_sizer = wx.BoxSizer(wx.HORIZONTAL) # Correlations sizer and graphs panel
|
|
panel.SetSizer(main_sizer)
|
|
|
|
# Create the label and toggle, populate the toggle sizer and add the toggle sizer to the correlations sizer
|
|
monitor_toggle_label = wx.StaticText(panel, id=wx.ID_ANY, label="Monitoring")
|
|
toggle_sizer.Add(monitor_toggle_label, 0, wx.ALL, 1)
|
|
self.monitor_toggle = wx.ToggleButton(panel, wx.ID_ANY, label="Off")
|
|
self.monitor_toggle.SetBackgroundColour(wx.RED)
|
|
toggle_sizer.Add(self.monitor_toggle, 0, wx.ALL, 1)
|
|
correlations_sizer.Add(toggle_sizer, 0, wx.ALL, 1)
|
|
|
|
# Create the correlations grid. This is a data table using pandas dataframe for underlying data. Add the
|
|
# correlations_grid to the correlations sizer.
|
|
self.table = DataTable(self.cor.filtered_coefficient_data)
|
|
self.grid_correlations = wx.grid.Grid(panel, wx.ID_ANY)
|
|
self.grid_correlations.SetTable(self.table, takeOwnership=True)
|
|
self.grid_correlations.EnableEditing(False)
|
|
self.grid_correlations.EnableDragRowSize(False)
|
|
self.grid_correlations.EnableDragColSize(False)
|
|
self.grid_correlations.EnableDragGridSize(False)
|
|
self.grid_correlations.SetSelectionMode(wx.grid.Grid.SelectRows)
|
|
self.grid_correlations.SetRowLabelSize(0)
|
|
self.grid_correlations.SetColSize(self.COLUMN_INDEX, 0) # Index. Hide
|
|
self.grid_correlations.SetColSize(self.COLUMN_SYMBOL1, 100) # Symbol 1
|
|
self.grid_correlations.SetColSize(self.COLUMN_SYMBOL2, 100) # Symbol 2
|
|
self.grid_correlations.SetColSize(self.COLUMN_BASE_COEFFICIENT, 100) # Base Coefficient
|
|
self.grid_correlations.SetColSize(self.COLUMN_DATE_FROM, 0) # UTC Date From. Hide
|
|
self.grid_correlations.SetColSize(self.COLUMN_DATE_TO, 0) # UTC Date To. Hide
|
|
self.grid_correlations.SetColSize(self.COLUMN_TIMEFRAME, 0) # Timeframe. Hide.
|
|
self.grid_correlations.SetColSize(self.COLUMN_LAST_CHECK, 100) # Last Check
|
|
self.grid_correlations.SetColSize(self.COLUMN_LAST_COEFFICIENT, 100) # Last Coefficient
|
|
self.grid_correlations.SetMinSize((520, 500))
|
|
self.grid_correlations.SetMaxSize((520, -1))
|
|
correlations_sizer.Add(self.grid_correlations, 1, wx.ALL | wx.EXPAND, 1)
|
|
|
|
# Create the charts
|
|
charts = wx.StaticText(panel, wx.ID_ANY, "Charts Go Here", style=wx.ALIGN_CENTER_HORIZONTAL)
|
|
|
|
# Add the correlations sizer and the charts to the main sizer.
|
|
main_sizer.Add(correlations_sizer, 1, wx.ALL | wx.EXPAND, 1)
|
|
main_sizer.Add(charts, 1, wx.ALL | wx.EXPAND, 1)
|
|
|
|
# Size the window.
|
|
self.SetSize((800, 500))
|
|
self.Layout()
|
|
|
|
# Set up timer to refresh grid
|
|
self.timer = wx.Timer(self)
|
|
|
|
# Bind monitor button
|
|
self.monitor_toggle.Bind(wx.EVT_TOGGLEBUTTON, self.monitor)
|
|
|
|
# Bind timer
|
|
self.Bind(wx.EVT_TIMER, self.refresh_grid, self.timer)
|
|
|
|
# Bind menu items
|
|
self.Bind(wx.EVT_MENU, self.open_file, menu_item_open)
|
|
self.Bind(wx.EVT_MENU, self.save_file, menu_item_save)
|
|
self.Bind(wx.EVT_MENU, self.save_file_as, menu_item_saveas)
|
|
self.Bind(wx.EVT_MENU, self.calculate_coefficients, menu_item_calculate)
|
|
self.Bind(wx.EVT_MENU, self.open_settings, menu_item_settings)
|
|
self.Bind(wx.EVT_MENU, self.quit, menu_item_exit)
|
|
|
|
# Bind window close event
|
|
self.Bind(wx.EVT_CLOSE, self.on_close, self)
|
|
|
|
def open_file(self, event):
|
|
with wx.FileDialog(self, "Open Coefficients file", wildcard="CSV (*.csv)|*.csv",
|
|
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
|
|
|
|
if fileDialog.ShowModal() == wx.ID_CANCEL:
|
|
return # the user changed their mind
|
|
|
|
# Load the file chosen by the user
|
|
self.opened_filename = fileDialog.GetPath()
|
|
self.cor.load(self.opened_filename)
|
|
|
|
# Refresh data in grid
|
|
self.refresh_grid(event)
|
|
|
|
self.SetStatusText(f"File {self.opened_filename} loaded.")
|
|
|
|
def save_file(self, event):
|
|
self.cor.save(self.opened_filename)
|
|
self.SetStatusText(f"File saved as {self.opened_filename}")
|
|
|
|
def save_file_as(self, event):
|
|
with wx.FileDialog(self, "Save Coefficients file", wildcard="CSV (*.csv)|*.csv",
|
|
style=wx.FD_SAVE) as fileDialog:
|
|
if fileDialog.ShowModal() == wx.ID_CANCEL:
|
|
return # the user changed their mind
|
|
|
|
# Save the file, changing opened filename so next save writes to new file
|
|
self.opened_filename = fileDialog.GetPath()
|
|
self.cor.save(self.opened_filename)
|
|
|
|
self.SetStatusText(f"File saved as {self.opened_filename}")
|
|
|
|
def calculate_coefficients(self, event):
|
|
# set time zone to UTC to avoid local offset issues, and get from and to dates (a week ago to today)
|
|
timezone = pytz.timezone("Etc/UTC")
|
|
utc_to = datetime.now(tz=timezone)
|
|
utc_from = utc_to - timedelta(days=self.config.get('calculate.from.days'))
|
|
|
|
# Set timeframe
|
|
timeframe = self.config.get('calculate.timeframe')
|
|
|
|
# Calculate
|
|
self.SetStatusText("Calculating coefficients.")
|
|
self.cor.calculate(date_from=utc_from, date_to=utc_to,
|
|
timeframe=self.config.get('calculate.timeframe'),
|
|
min_prices=self.config.get('calculate.min_prices'),
|
|
max_set_size_diff_pct=self.config.get('calculate.max_set_size_diff_pct'),
|
|
overlap_pct=self.config.get('calculate.overlap_pct'),
|
|
max_p_value=self.config.get('calculate.max_p_value'))
|
|
self.SetStatusText("")
|
|
|
|
# Show calculated data
|
|
self.refresh_grid(event)
|
|
|
|
def quit(self, event):
|
|
self.Close()
|
|
|
|
def refresh_grid(self, event):
|
|
"""
|
|
Refreshes grid. Notifies if rows have been added or deleted.
|
|
:return:
|
|
"""
|
|
self.log.debug(f"Refreshing grid. Timer running: {self.timer.IsRunning()}")
|
|
|
|
# Update data
|
|
self.table.data = self.cor.coefficient_data.copy()
|
|
|
|
# Format
|
|
self.table.data['Base Coefficient'] = self.table.data['Base Coefficient'].map('{:.5f}'.format)
|
|
self.table.data['Last Check'] = pd.to_datetime(self.table.data['Last Check'], utc=True)
|
|
self.table.data['Last Check'] = self.table.data['Last Check'].dt.strftime('%d-%m-%y %H:%M:%S')
|
|
self.table.data['Last Coefficient'] = self.table.data['Last Coefficient'].map('{:.5f}'.format)
|
|
|
|
# Remove nans. The ones from the float column wil be str nan as they have been formatted
|
|
self.table.data = self.table.data.fillna('')
|
|
self.table.data['Last Coefficient'] = self.table.data['Last Coefficient'].replace('nan', '')
|
|
|
|
# Start refresh
|
|
self.grid_correlations.BeginBatch()
|
|
|
|
# Check if num rows in dataframe has changed, and send appropriate APPEND or DELETE messages
|
|
cur_rows = len(self.cor.filtered_coefficient_data.index)
|
|
if cur_rows < self.rows:
|
|
# Data has been deleted. Send message
|
|
msg = wx.grid.GridTableMessage(self.table, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED,
|
|
self.rows - cur_rows, self.rows - cur_rows)
|
|
self.grid_correlations.ProcessTableMessage(msg)
|
|
elif cur_rows > self.rows:
|
|
# Data has been added. Send message
|
|
msg = wx.grid.GridTableMessage(self.table, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED,
|
|
cur_rows - self.rows) # how many
|
|
self.grid_correlations.ProcessTableMessage(msg)
|
|
|
|
self.grid_correlations.EndBatch()
|
|
|
|
# Send updated message
|
|
msg = wx.grid.GridTableMessage(self.table, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
|
|
self.grid_correlations.ProcessTableMessage(msg)
|
|
|
|
# Update row count
|
|
self.rows = cur_rows
|
|
|
|
def monitor(self, event):
|
|
# Check state of toggle button. If on, then start monitoring, else stop
|
|
if self.monitor_toggle.GetValue():
|
|
self.log.debug("Starting monitoring.")
|
|
self.monitor_toggle.SetBackgroundColour(wx.GREEN)
|
|
self.monitor_toggle.SetLabelText("On")
|
|
self.SetStatusText("Monitoring for changes to coefficients.")
|
|
|
|
# Calculate correlations fro last 10 mins
|
|
timezone = pytz.timezone("Etc/UTC")
|
|
utc_to = datetime.now(tz=timezone)
|
|
utc_from = utc_to - timedelta(minutes=self.config.get('monitor.from.minutes'))
|
|
|
|
self.timer.Start(10000)
|
|
self.cor.start_monitor(interval=self.config.get('monitor.interval'), date_from=utc_from, date_to=utc_to,
|
|
min_prices=self.config.get('monitor.min_prices'),
|
|
max_set_size_diff_pct=self.config.get('monitor.max_set_size_diff_pct'),
|
|
overlap_pct=self.config.get('monitor.overlap_pct'),
|
|
max_p_value=self.config.get('monitor.max_p_value'))
|
|
else:
|
|
self.log.debug("Stopping monitoring.")
|
|
self.monitor_toggle.SetBackgroundColour(wx.RED)
|
|
self.monitor_toggle.SetLabelText("Off")
|
|
self.SetStatusText("Monitoring stopped.")
|
|
self.timer.Stop()
|
|
self.cor.stop_monitor()
|
|
|
|
def open_settings(self, event):
|
|
"""
|
|
Opens the settings dialog
|
|
:return:
|
|
"""
|
|
settings_dialog = SettingsDialog(self)
|
|
res = settings_dialog.ShowModal()
|
|
if res == wx.ID_OK:
|
|
# Reload relevant parts of app
|
|
# TODO: Reload relevant parts of app once settings have changed. Stop and restart monitoring,
|
|
# reload logger, filter data, refresh data window.
|
|
self.log.debug("Settings updated. Reloading logger.")
|
|
log_config = Config().get('logging')
|
|
logging.config.dictConfig(log_config)
|
|
settings_dialog.Destroy()
|
|
|
|
def on_close(self, event):
|
|
"""
|
|
Window closing. Save coefficients and stop monitoring.
|
|
:param event:
|
|
:return:
|
|
"""
|
|
if self.opened_filename is not None:
|
|
self.cor.save(self.opened_filename)
|
|
|
|
self.cor.stop_monitor()
|
|
|
|
event.Skip()
|
|
|
|
|
|
class DataTable(wx.grid.GridTableBase):
|
|
"""
|
|
A data table that holds data in a pandas dataframe
|
|
"""
|
|
def __init__(self, data=None):
|
|
wx.grid.GridTableBase.__init__(self)
|
|
self.headerRows = 1
|
|
if data is None:
|
|
data = pd.DataFrame()
|
|
self.data = data
|
|
|
|
# Get divergence threshold from app config
|
|
self.divergence_threshold = Config().get('monitor.divergence_threshold')
|
|
|
|
def GetNumberRows(self):
|
|
return len(self.data)
|
|
|
|
def GetNumberCols(self):
|
|
return len(self.data.columns) + 1
|
|
|
|
def GetValue(self, row, col):
|
|
if col == 0:
|
|
return self.data.index[row]
|
|
return self.data.iloc[row, col - 1]
|
|
|
|
def SetValue(self, row, col, value):
|
|
self.data.iloc[row, col - 1] = value
|
|
|
|
def GetColLabelValue(self, col):
|
|
if col == 0:
|
|
if self.data.index.name is None:
|
|
return 'Index'
|
|
else:
|
|
return self.data.index.name
|
|
return str(self.data.columns[col - 1])
|
|
|
|
def GetTypeName(self, row, col):
|
|
return wx.grid.GRID_VALUE_STRING
|
|
|
|
def GetAttr(self, row, col, prop):
|
|
attr = wx.grid.GridCellAttr()
|
|
|
|
# If column is last coefficient, get value and check against threshold. Highlight if diverged.
|
|
threshold = Config().get('monitor.divergence_threshold')
|
|
if col == MonitorFrame.COLUMN_LAST_COEFFICIENT:
|
|
value = self.GetValue(row, col)
|
|
if value != "":
|
|
value = float(value)
|
|
if value <= threshold:
|
|
attr.SetBackgroundColour(wx.YELLOW)
|
|
else:
|
|
attr.SetBackgroundColour(wx.WHITE)
|
|
|
|
return attr
|
|
|
|
|
|
class SettingsDialog(wx.Dialog):
|
|
|
|
# Store any settings that have changed
|
|
changed_settings = {}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
# Super Constructor
|
|
wx.Dialog.__init__(self, *args, **kwargs)
|
|
self.SetTitle("Settings")
|
|
|
|
# Create logger and get config
|
|
self.log = logging.getLogger(__name__)
|
|
|
|
# Get settings
|
|
self.__settings = Config()
|
|
|
|
# Dict of changes. Will commit only on ok
|
|
self.__changes = {}
|
|
|
|
# We want 2 vertical sections, the tabbed notebook and the buttons. The buttons sizer will have 2 horizontal
|
|
# sections, one for each button.
|
|
# -------------------------
|
|
# |Tabbed notebook |
|
|
# | |
|
|
# | |
|
|
# | |
|
|
# | |
|
|
# |-----------------------|
|
|
# |ok | cancel |
|
|
# -------------------------
|
|
main_sizer = wx.BoxSizer(wx.VERTICAL) # Notebook panel
|
|
button_sizer = wx.BoxSizer(wx.HORIZONTAL) # Button sizer
|
|
|
|
# Notebook
|
|
self.__notebook = wx.Notebook(self, wx.ID_ANY) # The notebook
|
|
|
|
# A tab for each root node in config. We will store the tabs components in lists which can be accessed by the
|
|
# index returned from notebook.GetSelectedItem()
|
|
root_nodes = self.__settings.get_root_nodes()
|
|
self.__tabs = []
|
|
self.__trees = []
|
|
self.__tab_sizers = []
|
|
self.__value_sizers = []
|
|
self.__value_boxes = [] # We need to store these as they will all be bound to a change event
|
|
# self.roots = []
|
|
for node in root_nodes:
|
|
# Create new tab
|
|
self.__tabs.append(wx.Panel(self.__notebook, wx.ID_ANY))
|
|
self.__tab_sizers.append(wx.BoxSizer(wx.HORIZONTAL))
|
|
self.__tabs[-1].SetSizer(self.__tab_sizers[-1])
|
|
|
|
# Get settings items for tab / node
|
|
node_settings = self.__settings.get(node)
|
|
|
|
# Create tree control
|
|
self.__trees.append(wx.TreeCtrl(self.__tabs[-1], wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize))
|
|
|
|
# Add root to tree and use item data to store settings path
|
|
root = self.__trees[-1].AddRoot(node)
|
|
self.__trees[-1].SetItemData(root, node)
|
|
|
|
# Add items to root
|
|
self.__trees[-1] = self.__build_tree(self.__trees[-1], root, node_settings)
|
|
|
|
# expand tree and add it to the sizer
|
|
self.__trees[-1].Expand(root)
|
|
self.__tab_sizers[-1].Add(self.__trees[-1], 1, wx.ALL | wx.EXPAND, 1)
|
|
|
|
# Add the value sizer for settings values. This is only used for spacing, as it will be overwritten when
|
|
# tree items are selected.
|
|
self.__value_sizers.append(wx.FlexGridSizer(rows=1, cols=2, vgap=2, hgap=2))
|
|
self.__tab_sizers[-1].Add(self.__value_sizers[-1], 1, wx.ALL | wx.EXPAND, 1)
|
|
label = wx.StaticText(self.__tabs[-1], wx.ID_ANY, " ".ljust(50), style=wx.ALIGN_LEFT)
|
|
self.__value_sizers[-1].Add(label)
|
|
|
|
# Add tab to notebook
|
|
self.__notebook.AddPage(self.__tabs[-1], node)
|
|
|
|
# Buttons
|
|
button_ok = wx.Button(self, label="Update")
|
|
button_cancel = wx.Button(self, label="Cancel")
|
|
button_sizer.Add(button_ok, 0, wx.ALL, 1)
|
|
button_sizer.Add(button_cancel, 0, wx.ALL, 1)
|
|
|
|
# Add notebook and button sizer to main sizer and set main sizer for window
|
|
main_sizer.Add(self.__notebook, 1, wx.ALL | wx.EXPAND, 5)
|
|
main_sizer.Add(button_sizer)
|
|
self.SetSizer(main_sizer)
|
|
|
|
# Bind buttons, notebook page select and tree control select item
|
|
button_ok.Bind(wx.EVT_BUTTON, self.__on_ok)
|
|
button_cancel.Bind(wx.EVT_BUTTON, self.__on_cancel)
|
|
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.__on_page_select)
|
|
self.Bind(wx.EVT_TREE_SEL_CHANGED, self.__on_tree_select)
|
|
|
|
# Call on_page_select to select the first page
|
|
self.__on_page_select(event=None)
|
|
|
|
def __build_tree(self, tree, node, settings):
|
|
"""
|
|
Recursive function to build the tree from the node, using the settings
|
|
:param tree: The tree to build
|
|
:param node: The tree view node
|
|
:param settings: The settings dict for the node.
|
|
:return: The built tree
|
|
"""
|
|
for setting in settings:
|
|
# Get value. If dict, add the node and recursively call this function again.
|
|
value = settings[setting]
|
|
if type(value) is dict:
|
|
# Add the node and set its settings path
|
|
node_id = tree.AppendItem(node, setting)
|
|
current_settings_path = tree.GetItemData(node)
|
|
tree.SetItemData(node_id, f"{current_settings_path}.{setting}")
|
|
|
|
# Recurse
|
|
tree = self.__build_tree(tree, node_id, value)
|
|
|
|
return tree
|
|
|
|
def __populate_settings_values(self, setting_path, index):
|
|
"""
|
|
Populates the settings in the value sizer for a settings path.
|
|
|
|
:param setting_path:
|
|
:param index: The tab index containing the value_sizer to populate
|
|
|
|
:return:
|
|
"""
|
|
# Get the setting values
|
|
settings = self.__settings.get(setting_path)
|
|
|
|
# Get the value sizer, clear it and set its rows
|
|
value_sizer = self.__value_sizers[index]
|
|
value_sizer.Clear(True)
|
|
value_sizer.SetRows(len(settings))
|
|
|
|
# Display every value that is a leaf (not dict)
|
|
for setting in settings:
|
|
value = settings[setting]
|
|
if type(value) is not dict:
|
|
# Add a label and value text box
|
|
label = wx.StaticText(self.__tabs[index], wx.ID_ANY, setting, style=wx.ALIGN_LEFT)
|
|
self.__value_boxes.append(wx.TextCtrl(self.__tabs[index], wx.ID_ANY, f"{value}", style=wx.ALIGN_LEFT))
|
|
value_sizer.AddMany([(label, 0, wx.EXPAND), (self.__value_boxes[-1], 0, wx.EXPAND)])
|
|
|
|
# Bind to text change. We need to generate a handler as this will have a parameter.
|
|
self.__value_boxes[-1].Bind(wx.EVT_TEXT,
|
|
self.__get_on_change_evt_handler(setting_path=f'{setting_path}.{setting}'))
|
|
|
|
value_sizer.Layout()
|
|
|
|
def __on_page_select(self, event):
|
|
# Populate value sizer for the selected item, If no item is selected, populate for root.
|
|
index = self.__notebook.GetSelection()
|
|
selected_item = self.__trees[index].GetSelection()
|
|
if selected_item.ID is None:
|
|
root_node = self.__trees[index].GetRootItem()
|
|
setting_path = self.__trees[index].GetItemData(root_node)
|
|
else:
|
|
setting_path = self.__trees[index].GetItemData(selected_item)
|
|
|
|
self.__populate_settings_values(setting_path, index)
|
|
|
|
def __on_tree_select(self, event):
|
|
# Get Selected item and check that it is a tree item
|
|
tree_item = event.GetItem()
|
|
if not tree_item.IsOk():
|
|
return
|
|
|
|
# Get the index of the current tab
|
|
index = self.__notebook.GetSelection()
|
|
|
|
# Get the setting path from item data
|
|
setting_path = self.__trees[index].GetItemData(tree_item)
|
|
|
|
# Populate value_sizer
|
|
self.__populate_settings_values(setting_path, index)
|
|
|
|
def __on_cancel(self, e):
|
|
# Clear changed settings and close
|
|
self.changed_settings = {}
|
|
self.EndModal(wx.ID_CANCEL)
|
|
self.Destroy()
|
|
|
|
def __on_ok(self, e):
|
|
# Update settings and save
|
|
delkeys = []
|
|
for setting in self.changed_settings:
|
|
# Get the current and new setting
|
|
orig_value = self.__settings.get(setting)
|
|
new_value = self.changed_settings[setting]
|
|
|
|
# If they are the same, discard from changes. We will use a list of items to delete (delkeys) as we cant
|
|
# delete whilst iterating. If they are different, update settings.
|
|
if orig_value == new_value:
|
|
delkeys.append(setting)
|
|
else:
|
|
# We need to retain data type. New values will all be string as they were retrieved from textctl.
|
|
# Get the data type of the original and cast new to it.
|
|
new_value = type(orig_value)(new_value)
|
|
self.__settings.set(setting, new_value)
|
|
|
|
# Now delete the items that were the same from changed_settings. changed_settings may be used by settings
|
|
# dialog caller.
|
|
for key in delkeys:
|
|
del(self.changed_settings[key])
|
|
|
|
# Save the settings and close dialog
|
|
self.__settings.save()
|
|
self.EndModal(wx.ID_OK)
|
|
self.Destroy()
|
|
|
|
def __get_on_change_evt_handler(self, setting_path):
|
|
def on_value_changed(event):
|
|
self.changed_settings[setting_path] = event.String
|
|
self.log.debug(f"Value changed for {setting_path}.")
|
|
|
|
return on_value_changed
|