#!/usr/bin/env python
# -*- coding: utf-8 -*-

# dialogs.dvha_app.py
"""
Dialogs used in the main view of DVHA (e.g., query design, importing, etc.)
"""
# Copyright (c) 2016-2019 Dan Cutright
# This file is part of DVH Analytics, released under a BSD license.
#    See the file LICENSE included with this distribution, also
#    available at https://github.com/cutright/DVH-Analytics

import wx
import wx.adv
from dateutil.parser import parse as parse_date
import matplotlib.colors as plot_colors
import numpy as np
import time
from os.path import isdir
from dvha.tools.utilities import get_selected_listctrl_items, MessageDialog, get_window_size,\
    get_installed_python_libraries, set_msw_background_color, set_frame_icon
from dvha.db import sql_columns
from dvha.db.sql_connector import DVH_SQL
from dvha.models.data_table import DataTable
from dvha.paths import LICENSE_PATH
from dvha.options import DefaultOptions


class DatePicker(wx.Dialog):
    """
    Pop-up window to select a date to ensure proper formatting (over typing in a date into a text_ctrl directly)
    """
    def __init__(self, title='', initial_date=None, action=None, sql_date_format=False):
        """
        :param title: optional title for the wx.Dialog
        :type title: str
        :param initial_date: optional initial date
        :type initial_date: str
        :param action: pointer to function to be executed on wx.ID_OK or
        :param sql_date_format: Set to True for SQL compatible format
        :type sql_date_format: bool
        """
        wx.Dialog.__init__(self, None, title=title)

        self.sql_date_format = sql_date_format

        self.calendar_ctrl = wx.adv.CalendarCtrl(self, wx.ID_ANY,
                                                 style=wx.adv.CAL_SHOW_HOLIDAYS | wx.adv.CAL_SHOW_SURROUNDING_WEEKS)
        if initial_date and initial_date.lower() != 'none':
            self.calendar_ctrl.SetDate(parse_date(initial_date))

        self.button = {'ok': wx.Button(self, wx.ID_OK, "OK"),
                       'delete': wx.Button(self, wx.ID_ANY, "Delete"),
                       'cancel': wx.Button(self, wx.ID_CANCEL, "Cancel")}

        self.button['delete'].Enable(not sql_date_format)

        self.none = False  # If True after close of this dialog, user deleted the value

        self.action = action

        self.__do_layout()
        self.__do_bind()

        self.run()

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_main = wx.BoxSizer(wx.VERTICAL)
        sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_main.Add(self.calendar_ctrl, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        for button in self.button.values():
            sizer_buttons.Add(button, 0, wx.ALL, 5)
        sizer_main.Add(sizer_buttons, 1, wx.ALIGN_CENTER | wx.BOTTOM | wx.TOP, 10)
        sizer_wrapper.Add(sizer_main, 1, wx.ALL | wx.EXPAND, 10)
        self.SetSizer(sizer_wrapper)
        sizer_wrapper.Fit(self)
        self.Layout()
        self.Center()

    def __do_bind(self):
        self.Bind(wx.EVT_BUTTON, self.on_delete, id=self.button['delete'].GetId())

    @property
    def date(self):
        if self.none:
            return ''
        date = self.calendar_ctrl.GetDate()
        date = "%s/%s/%s" % (date.month+1, date.day, date.year)
        if self.sql_date_format:
            return str(parse_date(date).date())
        return date

    def on_delete(self, evt):
        self.none = True
        self.Close()  # resolve dialog with wx.ID_CANCEL

    def run(self):
        res = self.ShowModal()
        if res == wx.ID_OK or (res == wx.ID_CANCEL and self.none):
            if self.action is not None:
                self.action(self.date)
        self.Destroy()


class AddEndpointDialog(wx.Dialog):
    """
    Add a new column to the endpoint table of the Endpoint tab
    """
    def __init__(self):
        wx.Dialog.__init__(self, None, title='Add Endpoint')

        self.combo_box_output = wx.ComboBox(self, wx.ID_ANY,
                                            choices=["Dose (Gy)", "Dose(%)", "Volume (cc)", "Volume (%)"],
                                            style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.text_input = wx.TextCtrl(self, wx.ID_ANY, "")
        self.radio_box_units = wx.RadioBox(self, wx.ID_ANY, "", choices=["cc ", "% "], majorDimension=1,
                                           style=wx.RA_SPECIFY_ROWS)
        self.button_ok = wx.Button(self, wx.ID_OK, "OK")
        self.button_ok.Disable()
        self.button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")

        self.__do_bind()
        self.__set_properties()
        self.__do_layout()

    def __set_properties(self):
        self.radio_box_units.SetSelection(0)
        self.combo_box_output.SetValue('Dose (Gy)')

    def __do_bind(self):
        self.Bind(wx.EVT_COMBOBOX, self.combo_box_ticker, id=self.combo_box_output.GetId())
        self.Bind(wx.EVT_TEXT, self.text_input_ticker, id=self.text_input.GetId())
        self.Bind(wx.EVT_RADIOBOX, self.radio_box_ticker, id=self.radio_box_units.GetId())

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_buttons_wrapper = wx.BoxSizer(wx.HORIZONTAL)
        sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_input = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, ""), wx.HORIZONTAL)
        sizer_input_units = wx.BoxSizer(wx.VERTICAL)
        sizer_input_value = wx.BoxSizer(wx.VERTICAL)
        sizer_output = wx.BoxSizer(wx.VERTICAL)

        label_ouput = wx.StaticText(self, wx.ID_ANY, "Output:")
        sizer_output.Add(label_ouput, 0, wx.BOTTOM | wx.EXPAND, 8)
        sizer_output.Add(self.combo_box_output, 0, wx.EXPAND, 0)
        sizer_input.Add(sizer_output, 1, wx.ALL | wx.EXPAND, 5)

        self.label_input_value = wx.StaticText(self, wx.ID_ANY, "Input Volume (cc):")
        sizer_input_value.Add(self.label_input_value, 0, wx.BOTTOM | wx.EXPAND, 8)
        sizer_input_value.Add(self.text_input, 0, wx.EXPAND | wx.LEFT, 5)
        sizer_input.Add(sizer_input_value, 1, wx.ALL | wx.EXPAND, 5)

        label_input_units = wx.StaticText(self, wx.ID_ANY, "Input Units:")
        sizer_input_units.Add(label_input_units, 0, wx.BOTTOM | wx.EXPAND, 3)
        sizer_input_units.Add(self.radio_box_units, 0, wx.EXPAND, 0)
        sizer_input.Add(sizer_input_units, 1, wx.ALL | wx.EXPAND, 5)
        sizer_wrapper.Add(sizer_input, 0, wx.ALL | wx.EXPAND, 10)

        self.text_short_hand = wx.StaticText(self, wx.ID_ANY, "Short-hand: ")
        sizer_wrapper.Add(self.text_short_hand, 0, wx.ALL, 5)
        sizer_buttons.Add(self.button_ok, 0, wx.ALL, 5)
        sizer_buttons.Add(self.button_cancel, 0, wx.ALL | wx.EXPAND, 5)
        sizer_buttons_wrapper.Add(sizer_buttons, 0, wx.ALL | wx.EXPAND, 5)
        sizer_wrapper.Add(sizer_buttons_wrapper, 0, wx.ALIGN_CENTER | wx.ALL, 5)

        self.SetSizer(sizer_wrapper)
        sizer_wrapper.Fit(self)
        self.Layout()
        self.Center()

    def combo_box_ticker(self, evt):
        self.update_radio_box_choices()
        self.update_label_input()
        self.update_short_hand()

    def text_input_ticker(self, evt):
        self.update_short_hand()

    def radio_box_ticker(self, evt):
        self.update_label_input()
        self.update_short_hand()

    def update_label_input(self):
        new_label = "%s (%s):" % (['Input Dose', 'Input Volume']['Dose' in self.combo_box_output.GetValue()],
                                  self.radio_box_units.GetItemLabel(self.radio_box_units.GetSelection()).strip())
        self.label_input_value.SetLabelText(new_label)

    def update_radio_box_choices(self):
        choice_1 = ['Gy', 'cc']['Dose' in self.combo_box_output.GetValue()]
        self.radio_box_units.SetItemLabel(0, choice_1)

    def update_short_hand(self):
        short_hand = 'Short-hand: '
        value = self.text_input.GetValue()
        if value:
            prepend = ['V_', 'D_']['Dose' in self.combo_box_output.GetValue()]
            units = self.radio_box_units.GetItemLabel(self.radio_box_units.GetSelection()).strip()
            short_hand = short_hand + prepend + value + units
        self.text_short_hand.SetLabelText(short_hand)
        self.set_button_ok_enable(value)

    def set_button_ok_enable(self, value):
        try:
            if value.isdigit():
                value = int(value)
            else:
                value = float(value)
        except ValueError:
            self.text_short_hand.SetLabelText('Short-hand: ')
        self.button_ok.Enable(type(value) in [int, float])

    @property
    def is_endpoint_valid(self):
        return bool(len(self.short_hand_label))

    @property
    def short_hand_label(self):
        return self.text_short_hand.GetLabel().replace('Short-hand: ', '').strip()

    @property
    def output_type(self):
        return ['absolute', 'relative']['%' in self.combo_box_output.GetValue()]

    @property
    def input_type(self):
        return ['absolute', 'relative'][self.radio_box_units.GetSelection()]

    @property
    def units_in(self):
        return self.radio_box_units.GetItemLabel(self.radio_box_units.GetSelection()).replace('%', '').strip()

    @property
    def units_out(self):
        return self.combo_box_output.GetValue().split('(')[1][:-1].replace('%', '').strip()

    @property
    def input_value(self):
        try:
            return float(self.text_input.GetValue())
        except ValueError:
            return 0.

    @property
    def endpoint_row(self):
        return [self.short_hand_label,
                self.output_type,
                self.input_type,
                self.input_value,
                self.units_in,
                self.units_out]


class SelectFromListDialog(wx.Dialog):
    """
    Select an item in a list
    """
    def __init__(self, title, column, choices, exclude=None, size=None, column_width=200, selections=None):
        wx.Dialog.__init__(self, None, title=title)

        self.column = column
        self.choices = choices
        self.exclude = [exclude, []][exclude is None]
        self.size = size
        self.column_width = column_width

        self.list_ctrl = wx.ListCtrl(self, wx.ID_ANY, style=wx.LC_REPORT)
        self.button_select_all = wx.Button(self, wx.ID_ANY, "Select All")
        self.button_deselect_all = wx.Button(self, wx.ID_ANY, "Deselect All")
        self.button_ok = wx.Button(self, wx.ID_OK, "OK")
        self.button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")

        self.Bind(wx.EVT_BUTTON, self.select_all, id=self.button_select_all.GetId())
        self.Bind(wx.EVT_BUTTON, self.deselect_all, id=self.button_deselect_all.GetId())

        self.__set_properties()
        self.__do_layout()

        if selections:
            self.set_selection(selections)

    def __set_properties(self):
        self.list_ctrl.AppendColumn(self.column, format=wx.LIST_FORMAT_LEFT, width=self.column_width)

        for choice in self.choices:
            if choice not in self.exclude:
                self.list_ctrl.InsertItem(50000, choice)

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_ok_cancel = wx.BoxSizer(wx.HORIZONTAL)
        sizer_select = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, ""), wx.VERTICAL)
        sizer_select_buttons = wx.BoxSizer(wx.HORIZONTAL)

        sizer_select.Add(self.list_ctrl, 1, wx.ALL | wx.EXPAND, 5)
        sizer_select_buttons.Add(self.button_select_all, 0, wx.ALL, 5)
        sizer_select_buttons.Add(self.button_deselect_all, 0, wx.ALL, 5)
        sizer_select.Add(sizer_select_buttons, 0, wx.ALIGN_CENTER | wx.ALL, 0)
        sizer_wrapper.Add(sizer_select, 1, wx.ALL | wx.EXPAND, 5)

        sizer_ok_cancel.Add(self.button_ok, 0, wx.ALL, 5)
        sizer_ok_cancel.Add(self.button_cancel, 0, wx.ALL, 5)
        sizer_wrapper.Add(sizer_ok_cancel, 0, wx.ALIGN_CENTER | wx.ALL, 5)

        self.SetSizer(sizer_wrapper)
        if self.size:
            self.SetSize(self.size)
        else:
            sizer_wrapper.Fit(self)
        self.Layout()
        self.Center()

    @property
    def selected_indices(self):
        return get_selected_listctrl_items(self.list_ctrl)

    @property
    def selected_values(self):
        return [self.list_ctrl.GetItem(i, 0).GetText() for i in self.selected_indices]

    @property
    def item_count(self):
        return len(self.choices) - len(self.exclude)

    def select_all(self, evt):
        self.apply_global_selection()

    def deselect_all(self, evt):
        self.apply_global_selection(on=0)

    def apply_global_selection(self, on=1):
        for i in range(self.item_count):
            self.list_ctrl.Select(i, on=on)

    def set_selection(self, selections):
        for selection in selections:
            if selection in self.choices:
                index = self.choices.index(selection)
                self.list_ctrl.Select(index, on=True)


class DelEndpointDialog(SelectFromListDialog):
    def __init__(self, endpoints):
        SelectFromListDialog.__init__(self, "Delete Endpoint", "Endpoints", endpoints,
                                      exclude={'MRN', 'Tx Site', 'ROI Name', 'Volume (cc)'})


class SelectRegressionVariablesDialog(SelectFromListDialog):
    def __init__(self, dependent_variable, independent_variable_choices, selections=None):
        self.dependent_variable = dependent_variable
        self.independent_variable_choices = independent_variable_choices
        size = get_window_size(1, 0.8)
        SelectFromListDialog.__init__(self, "Select Variables for %s" % dependent_variable,
                                      "Independent Variables", independent_variable_choices,
                                      size=(350, size[1]), column_width=300, selections=selections)


def query_dlg(parent, query_type, set_values=False):
    """
    Function to create either QueryCategoryDialog or QueryNumericalDialog
    :param parent: pointer to app object
    :type parent: DVHAMainFrame
    :param query_type: either 'categorical' or 'numerical'
    :type query_type: str
    :param set_values: If True, pre-populate dialog values with the currently selected row (i.e., edit a row)
    :type set_values: bool
    """
    dlg = {'categorical': QueryCategoryDialog,
           'numerical': QueryNumericalDialog}[query_type]()
    data_table = {'categorical': parent.data_table_categorical,
                  'numerical': parent.data_table_numerical}[query_type]
    selected_index = {'categorical': parent.selected_index_categorical,
                      'numerical': parent.selected_index_numerical}[query_type]
    if set_values:
        dlg.set_values(data_table.get_row(selected_index))

    res = dlg.ShowModal()
    if res == wx.ID_OK:
        row = dlg.get_values()
        if set_values:
            data_table.edit_row(row, selected_index)
        else:
            data_table.append_row(row)
        parent.update_all_query_buttons()
    dlg.Destroy()


class QueryCategoryDialog(wx.Dialog):
    """
    Add/Edit query parameters for categorical data
    """
    def __init__(self):
        wx.Dialog.__init__(self, None, title='Query by Categorical Data')

        self.selector_categories = sql_columns.categorical

        selector_options = list(self.selector_categories)
        selector_options.sort()

        self.combo_box_1 = wx.ComboBox(self, wx.ID_ANY, choices=selector_options, style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_2 = wx.ComboBox(self, wx.ID_ANY, choices=[], style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.checkbox_1 = wx.CheckBox(self, wx.ID_ANY, "Exclude")
        self.button_OK = wx.Button(self, wx.ID_OK, "OK")
        self.button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")

        self.__do_layout()

        self.combo_box_1.SetValue('ROI Institutional Category')
        self.update_category_2(None)
        self.Bind(wx.EVT_COMBOBOX, self.update_category_2, id=self.combo_box_1.GetId())

        self.Fit()
        self.Center()

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_vbox = wx.BoxSizer(wx.VERTICAL)
        sizer_ok_cancel = wx.BoxSizer(wx.HORIZONTAL)
        sizer_widgets = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, ""), wx.HORIZONTAL)
        sizer_category_2 = wx.BoxSizer(wx.VERTICAL)
        sizer_category_1 = wx.BoxSizer(wx.VERTICAL)
        label_category_1 = wx.StaticText(self, wx.ID_ANY, "Category 1:")
        sizer_category_1.Add(label_category_1, 0, wx.ALL | wx.EXPAND, 5)
        sizer_category_1.Add(self.combo_box_1, 0, wx.ALL, 5)
        sizer_widgets.Add(sizer_category_1, 1, wx.EXPAND, 0)
        label_category_2 = wx.StaticText(self, wx.ID_ANY, "Category 2:")
        sizer_category_2.Add(label_category_2, 0, wx.ALL | wx.EXPAND, 5)
        sizer_category_2.Add(self.combo_box_2, 0, wx.EXPAND | wx.ALL, 5)
        sizer_widgets.Add(sizer_category_2, 1, wx.EXPAND, 0)
        sizer_widgets.Add(self.checkbox_1, 0, wx.ALL | wx.EXPAND, 5)
        sizer_vbox.Add(sizer_widgets, 0, wx.ALL | wx.EXPAND, 5)
        sizer_ok_cancel.Add(self.button_OK, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
        sizer_ok_cancel.Add(self.button_cancel, 0, wx.LEFT | wx.RIGHT, 5)
        sizer_vbox.Add(sizer_ok_cancel, 0, wx.ALIGN_RIGHT | wx.ALL, 5)
        sizer_wrapper.Add(sizer_vbox, 0, wx.ALL | wx.EXPAND, 5)
        self.SetSizer(sizer_wrapper)

    def update_category_2(self, evt):
        key = self.combo_box_1.GetValue()
        table = self.selector_categories[key]['table']
        col = self.selector_categories[key]['var_name']
        with DVH_SQL() as cnx:
            options = cnx.get_unique_values(table, col)
        self.combo_box_2.Clear()
        self.combo_box_2.Append(options)
        if options:
            self.combo_box_2.SetValue(options[0])

    def set_category_1(self, value):
        self.combo_box_1.SetValue(value)
        self.update_category_2(None)

    def set_category_2(self, value):
        self.combo_box_2.SetValue(value)

    def set_check_box_not(self, value):
        self.checkbox_1.SetValue(value)

    def set_values(self, values):
        self.set_category_1(values[0])
        self.set_category_2(values[1])
        self.set_check_box_not({'Include': False, 'Exclude': True}[values[2]])

    def get_values(self):
        return [self.combo_box_1.GetValue(),
                self.combo_box_2.GetValue(),
                ['Include', 'Exclude'][self.checkbox_1.GetValue()]]


class QueryNumericalDialog(wx.Dialog):
    """
    Add/Edit query parameters for numerical data
    """
    def __init__(self):
        wx.Dialog.__init__(self, None, title='Query by Numerical Data')

        self.numerical_categories = sql_columns.numerical

        numerical_options = list(self.numerical_categories)
        numerical_options.sort()

        self.combo_box_1 = wx.ComboBox(self, wx.ID_ANY, choices=numerical_options, style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.text_ctrl_min = wx.TextCtrl(self, wx.ID_ANY, "")
        self.text_ctrl_max = wx.TextCtrl(self, wx.ID_ANY, "")
        self.checkbox_1 = wx.CheckBox(self, wx.ID_ANY, "Exclude")
        self.button_OK = wx.Button(self, wx.ID_OK, "OK")
        self.button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
        self.button_date_picker = wx.Button(self, wx.ID_ANY, "Edit Dates")

        self.__do_layout()
        self.__do_bind()

        self.combo_box_1.SetValue("ROI Max Dose")
        self.update_range(None)

    def __do_bind(self):
        self.Bind(wx.EVT_COMBOBOX, self.update_range, id=self.combo_box_1.GetId())
        self.Bind(wx.EVT_BUTTON, self.on_date_picker, id=self.button_date_picker.GetId())

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_vbox = wx.BoxSizer(wx.VERTICAL)
        sizer_ok_cancel = wx.BoxSizer(wx.HORIZONTAL)
        sizer_widgets = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, ""), wx.HORIZONTAL)
        sizer_max = wx.BoxSizer(wx.VERTICAL)
        sizer_min = wx.BoxSizer(wx.VERTICAL)
        sizer_category_1 = wx.BoxSizer(wx.VERTICAL)
        label_category = wx.StaticText(self, wx.ID_ANY, "Category:")
        sizer_category_1.Add(label_category, 0, wx.ALL | wx.EXPAND, 5)
        sizer_category_1.Add(self.combo_box_1, 0, wx.ALL, 5)
        sizer_widgets.Add(sizer_category_1, 1, wx.EXPAND, 0)
        self.label_min = wx.StaticText(self, wx.ID_ANY, "Min:")
        sizer_min.Add(self.label_min, 0, wx.ALL | wx.EXPAND, 5)
        sizer_min.Add(self.text_ctrl_min, 0, wx.ALL, 5)
        sizer_widgets.Add(sizer_min, 0, wx.EXPAND, 0)
        self.label_max = wx.StaticText(self, wx.ID_ANY, "Max:")
        sizer_max.Add(self.label_max, 0, wx.ALL | wx.EXPAND, 5)
        sizer_max.Add(self.text_ctrl_max, 0, wx.ALL, 5)
        sizer_widgets.Add(sizer_max, 0, wx.EXPAND, 0)
        sizer_widgets.Add(self.checkbox_1, 0, wx.ALL | wx.EXPAND, 5)
        sizer_vbox.Add(sizer_widgets, 0, wx.ALL | wx.EXPAND, 5)
        sizer_ok_cancel.Add(self.button_date_picker, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
        sizer_ok_cancel.Add(self.button_OK, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
        sizer_ok_cancel.Add(self.button_cancel, 0, wx.LEFT | wx.RIGHT, 5)
        sizer_vbox.Add(sizer_ok_cancel, 0, wx.ALIGN_RIGHT | wx.ALL, 5)
        sizer_wrapper.Add(sizer_vbox, 0, wx.ALL | wx.EXPAND, 5)
        self.SetSizer(sizer_wrapper)

        self.Fit()
        self.Center()

    def update_range(self, evt):
        key = self.combo_box_1.GetValue()
        table = self.numerical_categories[key]['table']
        col = self.numerical_categories[key]['var_name']
        units = self.numerical_categories[key]['units']
        with DVH_SQL() as cnx:
            min_value = cnx.get_min_value(table, col)
            max_value = cnx.get_max_value(table, col)

        self.button_date_picker.Enable('date' in key.lower())
        self.text_ctrl_min.Enable('date' not in key.lower())
        self.text_ctrl_max.Enable('date' not in key.lower())

        self.set_min_value(min_value)
        self.set_max_value(max_value)

        if 'date' in key.lower():
            self.update_min_max_text('Start:', 'End:')
            self.on_date_picker()
        elif units:
            self.update_min_max_text('Min (%s):' % units, 'Max (%s):' % units)
        else:
            self.update_min_max_text('Min:', 'Max:')

        self.clean_numeric_input()

    def update_min_max_text(self, min_text, max_text):
        self.label_min.SetLabelText(min_text)
        self.label_max.SetLabelText(max_text)

    def set_category(self, value):
        self.combo_box_1.SetValue(value)
        self.update_range(None)

    def set_min_value(self, value):
        self.text_ctrl_min.SetValue(str(value))

    def set_max_value(self, value):
        self.text_ctrl_max.SetValue(str(value))

    def set_check_box_not(self, value):
        self.checkbox_1.SetValue(value)

    def set_values(self, values):
        self.set_category(values[0])
        self.set_min_value(str(values[1]))
        self.set_max_value(str(values[2]))
        self.set_check_box_not({'Include': False, 'Exclude': True}[values[3]])

    def get_values(self):
        return [self.combo_box_1.GetValue(),
                self.text_ctrl_min.GetValue(),
                self.text_ctrl_max.GetValue(),
                ['Include', 'Exclude'][self.checkbox_1.GetValue()]]

    def validated_text(self, input_type):
        old_value = {'min': self.text_ctrl_min.GetValue(), 'max': self.text_ctrl_max.GetValue()}[input_type]

        try:
            new_value = float(old_value)
        except ValueError:
            key = self.combo_box_1.GetValue()
            table = self.numerical_categories[key]['table']
            col = self.numerical_categories[key]['var_name']
            with DVH_SQL() as cnx:
                if input_type == 'min':
                    new_value = cnx.get_min_value(table, col)
                else:
                    new_value = cnx.get_max_value(table, col)
        return new_value

    def on_date_picker(self, *evt):
        DatePicker(initial_date=self.text_ctrl_min.GetValue(), title='Start Date',
                   action=self.set_min_value, sql_date_format=True)
        time.sleep(0.3)  # Immediately loading the next DatePicker looks like a glitch
        DatePicker(initial_date=self.text_ctrl_max.GetValue(), title='End Date',
                   action=self.set_max_value, sql_date_format=True)

    def clean_numeric_input(self):
        for text_ctrl in [self.text_ctrl_min, self.text_ctrl_max]:
            try:
                value = float(text_ctrl.GetValue())
                func = [np.floor, np.ceil][text_ctrl == self.text_ctrl_max]
                text_ctrl.SetValue("%0.2f" % func(value))
            except ValueError:
                pass


class UserSettings(wx.Frame):
    """
    Customize directories and visual settings for DVHA
    """
    def __init__(self, parent):
        """
        :param parent: main application frame
        """
        wx.Frame.__init__(self, None, title="User Settings")

        self.parent = parent
        self.options = parent.options

        colors = list(plot_colors.cnames)
        colors.sort()

        color_variables = self.get_option_choices('COLOR')
        size_variables = self.get_option_choices('SIZE')
        width_variables = self.get_option_choices('LINE_WIDTH')
        line_dash_variables = self.get_option_choices('LINE_DASH')
        alpha_variables = self.get_option_choices('ALPHA')

        line_style_options = ['solid', 'dashed', 'dotted', 'dotdash', 'dashdot']

        # self.SetSize((500, 580))
        self.text_ctrl_inbox = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_DONTWRAP)
        self.button_inbox = wx.Button(self, wx.ID_ANY, u"…")
        self.text_ctrl_imported = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_DONTWRAP)
        self.button_imported = wx.Button(self, wx.ID_ANY, u"…")

        self.checkbox_dicom_dvh = wx.CheckBox(self, wx.ID_ANY, "Import DICOM DVH if available")
        self.dvh_bin_width_input = wx.TextCtrl(self, wx.ID_ANY, str(self.options.dvh_bin_width))
        self.combo_box_colors_category = wx.ComboBox(self, wx.ID_ANY, choices=color_variables,
                                                     style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_colors_selection = wx.ComboBox(self, wx.ID_ANY, choices=colors,
                                                      style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_sizes_category = wx.ComboBox(self, wx.ID_ANY, choices=size_variables,
                                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.spin_ctrl_sizes_input = wx.SpinCtrl(self, wx.ID_ANY, "0", min=6, max=20, style=wx.SP_ARROW_KEYS)
        self.combo_box_line_widths_category = wx.ComboBox(self, wx.ID_ANY, choices=width_variables,
                                                          style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.spin_ctrl_line_widths_input = wx.SpinCtrl(self, wx.ID_ANY, "0", min=1, max=5, style=wx.SP_ARROW_KEYS)
        self.combo_box_line_styles_category = wx.ComboBox(self, wx.ID_ANY, choices=line_dash_variables,
                                                          style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_line_styles_selection = wx.ComboBox(self, wx.ID_ANY, choices=line_style_options,
                                                           style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_alpha_category = wx.ComboBox(self, wx.ID_ANY, choices=alpha_variables,
                                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.spin_ctrl_alpha_input = wx.SpinCtrlDouble(self, wx.ID_ANY, "0", min=0, max=1.0, style=wx.SP_ARROW_KEYS)
        self.button_restore_defaults = wx.Button(self, wx.ID_ANY, "Restore Defaults")
        self.button_ok = wx.Button(self, wx.ID_OK, "OK")
        self.button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
        self.button_apply = wx.Button(self, wx.ID_ANY, 'Apply')

        self.__set_properties()
        self.__do_layout()
        self.__do_bind()

        self.refresh_options()
        self.load_paths()

        self.is_edited = False

        set_msw_background_color(self)
        set_frame_icon(self)

    def __set_properties(self):
        self.text_ctrl_inbox.SetToolTip("Default directory for batch processing of incoming DICOM files")
        self.text_ctrl_inbox.SetMinSize((100, 21))
        self.button_inbox.SetMinSize((40, 21))
        self.text_ctrl_imported.SetToolTip("Directory for post-processed DICOM files")
        self.text_ctrl_imported.SetMinSize((100, 21))
        self.button_imported.SetMinSize((40, 21))
        self.checkbox_dicom_dvh.SetValue(self.options.USE_DICOM_DVH)
        self.checkbox_dicom_dvh.SetToolTip("If a DICOM RT-Dose file has a DVH Sequence, use this DVH instead of "
                                           "recalculating during import.")
        self.dvh_bin_width_input.SetToolTip("Value must be an integer.")
        self.dvh_bin_width_input.SetMinSize((50, 21))
        self.combo_box_colors_category.SetMinSize((250, self.combo_box_colors_category.GetSize()[1]))
        self.combo_box_colors_selection.SetMinSize((145, self.combo_box_colors_selection.GetSize()[1]))
        self.combo_box_sizes_category.SetMinSize((250, self.combo_box_sizes_category.GetSize()[1]))
        self.spin_ctrl_sizes_input.SetMinSize((50, 22))
        self.combo_box_line_widths_category.SetMinSize((250, self.combo_box_line_widths_category.GetSize()[1]))
        self.spin_ctrl_line_widths_input.SetMinSize((50, 22))
        self.combo_box_line_styles_category.SetMinSize((250, self.combo_box_line_styles_category.GetSize()[1]))
        self.combo_box_line_styles_selection.SetMinSize((145, self.combo_box_line_styles_selection.GetSize()[1]))
        self.combo_box_alpha_category.SetMinSize((250, self.combo_box_alpha_category.GetSize()[1]))
        self.spin_ctrl_alpha_input.SetMinSize((70, 22))

        self.spin_ctrl_alpha_input.SetIncrement(0.1)

        # Windows needs this done explicitly or the value will be an empty string
        self.combo_box_alpha_category.SetSelection(7)  # IQR Alpha
        self.combo_box_colors_category.SetSelection(14)  # Plot Color
        self.combo_box_line_styles_category.SetSelection(4)  # DVH Line Dash
        self.combo_box_line_widths_category.SetSelection(4)  # DVH Line Width
        self.combo_box_sizes_category.SetSelection(6)  # Plot Axis Label Font Size

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_ok_cancel = wx.BoxSizer(wx.HORIZONTAL)
        sizer_plot_options = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "Plot Options"), wx.VERTICAL)
        sizer_dvh_options = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "DVH Options"), wx.VERTICAL)
        sizer_dvh_bin_width = wx.BoxSizer(wx.HORIZONTAL)
        sizer_alpha = wx.BoxSizer(wx.VERTICAL)
        sizer_alpha_input = wx.BoxSizer(wx.HORIZONTAL)
        sizer_line_styles = wx.BoxSizer(wx.VERTICAL)
        sizer_line_styles_input = wx.BoxSizer(wx.HORIZONTAL)
        sizer_line_widths = wx.BoxSizer(wx.VERTICAL)
        sizer_line_widths_input = wx.BoxSizer(wx.HORIZONTAL)
        sizer_sizes = wx.BoxSizer(wx.VERTICAL)
        sizer_sizes_input = wx.BoxSizer(wx.HORIZONTAL)
        sizer_colors = wx.BoxSizer(wx.VERTICAL)
        sizer_colors_input = wx.BoxSizer(wx.HORIZONTAL)
        sizer_dicom_directories = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "DICOM Directories"), wx.VERTICAL)
        sizer_imported_wrapper = wx.BoxSizer(wx.HORIZONTAL)
        sizer_imported = wx.BoxSizer(wx.VERTICAL)
        sizer_imported_input = wx.BoxSizer(wx.HORIZONTAL)
        sizer_inbox_wrapper = wx.BoxSizer(wx.HORIZONTAL)
        sizer_inbox = wx.BoxSizer(wx.VERTICAL)
        sizer_inbox_input = wx.BoxSizer(wx.HORIZONTAL)

        label_inbox = wx.StaticText(self, wx.ID_ANY, "Inbox:")
        label_inbox.SetToolTip("Default directory for batch processing of incoming DICOM files")
        sizer_inbox.Add(label_inbox, 0, 0, 5)
        sizer_inbox_input.Add(self.text_ctrl_inbox, 1, wx.ALL, 5)
        sizer_inbox_input.Add(self.button_inbox, 0, wx.ALL, 5)
        sizer_inbox.Add(sizer_inbox_input, 1, wx.EXPAND, 0)
        sizer_inbox_wrapper.Add(sizer_inbox, 1, wx.EXPAND, 0)
        sizer_dicom_directories.Add(sizer_inbox_wrapper, 1, wx.EXPAND, 0)

        label_imported = wx.StaticText(self, wx.ID_ANY, "Imported:")
        label_imported.SetToolTip("Directory for post-processed DICOM files")
        sizer_imported.Add(label_imported, 0, 0, 5)
        sizer_imported_input.Add(self.text_ctrl_imported, 1, wx.ALL, 5)
        sizer_imported_input.Add(self.button_imported, 0, wx.ALL, 5)
        sizer_imported.Add(sizer_imported_input, 1, wx.EXPAND, 0)
        sizer_imported_wrapper.Add(sizer_imported, 1, wx.EXPAND, 0)
        sizer_dicom_directories.Add(sizer_imported_wrapper, 1, wx.EXPAND, 0)
        sizer_wrapper.Add(sizer_dicom_directories, 0, wx.ALL | wx.EXPAND, 10)

        sizer_dvh_options.Add(self.checkbox_dicom_dvh, 0, wx.ALL, 5)
        sizer_wrapper.Add(sizer_dvh_options, 0, wx.ALL | wx.EXPAND, 10)

        label_dvh_bin_width = wx.StaticText(self, wx.ID_ANY, "DVH Bin Width (cGy):")
        label_dvh_bin_width.SetToolTip("Value must be an integer")
        sizer_dvh_bin_width.Add(label_dvh_bin_width, 0, wx.EXPAND | wx.RIGHT, 10)
        sizer_dvh_bin_width.Add(self.dvh_bin_width_input, 1, wx.ALL, 5)
        sizer_plot_options.Add(sizer_dvh_bin_width, 0, wx.BOTTOM, 10)

        label_colors = wx.StaticText(self, wx.ID_ANY, "Colors:")
        sizer_colors.Add(label_colors, 0, 0, 0)
        sizer_colors_input.Add(self.combo_box_colors_category, 0, 0, 0)
        sizer_colors_input.Add((20, 20), 0, 0, 0)
        sizer_colors_input.Add(self.combo_box_colors_selection, 0, 0, 0)
        sizer_colors.Add(sizer_colors_input, 1, wx.EXPAND, 0)
        sizer_plot_options.Add(sizer_colors, 1, wx.EXPAND, 0)

        label_sizes = wx.StaticText(self, wx.ID_ANY, "Sizes:")
        sizer_sizes.Add(label_sizes, 0, 0, 0)
        sizer_sizes_input.Add(self.combo_box_sizes_category, 0, 0, 0)
        sizer_sizes_input.Add((20, 20), 0, 0, 0)
        sizer_sizes_input.Add(self.spin_ctrl_sizes_input, 0, 0, 0)
        sizer_sizes.Add(sizer_sizes_input, 1, wx.EXPAND, 0)
        sizer_plot_options.Add(sizer_sizes, 1, wx.EXPAND, 0)

        label_line_widths = wx.StaticText(self, wx.ID_ANY, "Line Widths:")
        sizer_line_widths.Add(label_line_widths, 0, 0, 0)
        sizer_line_widths_input.Add(self.combo_box_line_widths_category, 0, 0, 0)
        sizer_line_widths_input.Add((20, 20), 0, 0, 0)
        sizer_line_widths_input.Add(self.spin_ctrl_line_widths_input, 0, 0, 0)
        sizer_line_widths.Add(sizer_line_widths_input, 1, wx.EXPAND, 0)
        sizer_plot_options.Add(sizer_line_widths, 1, wx.EXPAND, 0)

        label_line_styles = wx.StaticText(self, wx.ID_ANY, "Line Styles:")
        sizer_line_styles.Add(label_line_styles, 0, 0, 0)
        sizer_line_styles_input.Add(self.combo_box_line_styles_category, 0, 0, 0)
        sizer_line_styles_input.Add((20, 20), 0, 0, 0)
        sizer_line_styles_input.Add(self.combo_box_line_styles_selection, 0, 0, 0)
        sizer_line_styles.Add(sizer_line_styles_input, 1, wx.EXPAND, 0)
        sizer_plot_options.Add(sizer_line_styles, 1, wx.EXPAND, 0)

        label_alpha = wx.StaticText(self, wx.ID_ANY, "Alpha:")
        sizer_alpha.Add(label_alpha, 0, 0, 0)
        sizer_alpha_input.Add(self.combo_box_alpha_category, 0, 0, 0)
        sizer_alpha_input.Add((20, 20), 0, 0, 0)
        sizer_alpha_input.Add(self.spin_ctrl_alpha_input, 0, 0, 0)
        sizer_alpha.Add(sizer_alpha_input, 1, wx.EXPAND, 0)
        sizer_plot_options.Add(sizer_alpha, 1, wx.EXPAND, 0)
        sizer_wrapper.Add(sizer_plot_options, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)

        sizer_ok_cancel.Add(self.button_restore_defaults, 0, wx.RIGHT, 20)
        sizer_ok_cancel.Add(self.button_apply, 0, wx.LEFT | wx.RIGHT, 5)
        sizer_ok_cancel.Add(self.button_ok, 0, wx.LEFT | wx.RIGHT, 5)
        sizer_ok_cancel.Add(self.button_cancel, 0, wx.LEFT | wx.RIGHT, 5)
        sizer_wrapper.Add(sizer_ok_cancel, 0, wx.ALIGN_RIGHT | wx.ALL, 10)

        self.SetSizer(sizer_wrapper)
        self.Layout()
        self.Fit()
        self.Center()

    def __do_bind(self):
        self.Bind(wx.EVT_BUTTON, self.inbox_dir_dlg, id=self.button_inbox.GetId())
        self.Bind(wx.EVT_BUTTON, self.imported_dir_dlg, id=self.button_imported.GetId())

        self.Bind(wx.EVT_CHECKBOX, self.on_use_dicom_dvh, id=self.checkbox_dicom_dvh.GetId())

        self.Bind(wx.EVT_TEXT, self.update_dvh_bin_width_val, id=self.dvh_bin_width_input.GetId())
        self.Bind(wx.EVT_COMBOBOX, self.update_input_colors_var, id=self.combo_box_colors_category.GetId())
        self.Bind(wx.EVT_COMBOBOX, self.update_size_var, id=self.combo_box_sizes_category.GetId())
        self.Bind(wx.EVT_COMBOBOX, self.update_line_width_var, id=self.combo_box_line_widths_category.GetId())
        self.Bind(wx.EVT_COMBOBOX, self.update_line_style_var, id=self.combo_box_line_styles_category.GetId())
        self.Bind(wx.EVT_COMBOBOX, self.update_alpha_var, id=self.combo_box_alpha_category.GetId())

        self.Bind(wx.EVT_COMBOBOX, self.update_input_colors_val, id=self.combo_box_colors_selection.GetId())
        self.Bind(wx.EVT_TEXT, self.update_size_val, id=self.spin_ctrl_sizes_input.GetId())
        self.Bind(wx.EVT_TEXT, self.update_line_width_val, id=self.spin_ctrl_line_widths_input.GetId())
        self.Bind(wx.EVT_COMBOBOX, self.update_line_style_val, id=self.combo_box_line_styles_selection.GetId())
        self.Bind(wx.EVT_TEXT, self.update_alpha_val, id=self.spin_ctrl_alpha_input.GetId())

        self.Bind(wx.EVT_BUTTON, self.restore_defaults, id=self.button_restore_defaults.GetId())
        self.Bind(wx.EVT_BUTTON, self.on_apply, id=self.button_apply.GetId())
        self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)
        self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_CANCEL)

        self.Bind(wx.EVT_CLOSE, self.close)

    def on_ok(self, *evt):
        self.options.save()
        if self.is_edited:
            self.apply_and_redraw_plots()
        self.close()

    def on_cancel(self, *evt):
        self.options.load()
        if self.is_edited:
            self.apply_and_redraw_plots()
        self.close()

    def close(self, *evt):
        self.parent.user_settings = None
        self.Destroy()

    def inbox_dir_dlg(self, evt):
        self.dir_dlg('inbox', self.text_ctrl_inbox)

    def imported_dir_dlg(self, evt):
        self.dir_dlg('imported', self.text_ctrl_imported)

    def dir_dlg(self, dir_type, text_ctrl):
        """
        Create a DirDialog and edit the associated TextCtrl
        :param dir_type: the directory type to be displayed in DirDialog title
        :type dir_type: str
        :param text_ctrl: the associated TextCtrl from the user settings dialog
        :type text_ctrl: TextCtrl
        """
        starting_dir = text_ctrl.GetValue()
        if not isdir(starting_dir):
            starting_dir = ""
        dlg = wx.DirDialog(self, "Select %s directory" % dir_type, starting_dir,
                           wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST)
        if dlg.ShowModal() == wx.ID_OK:
            text_ctrl.SetValue(dlg.GetPath())
            option_attr = 'INBOX_DIR' if dir_type == 'inbox' else 'IMPORTED_DIR'
            self.options.set_option(option_attr, dlg.GetPath())
        dlg.Destroy()

    def get_option_choices(self, category):
        """
        Lookup properties in Options.option_attr that fit the specified category
        :param category: COLOR, SIZE, ALPHA, LINE_WIDTH, LINE_DASH
        :type category: str
        :return: all options with the category in their name
        :rtype: list
        """
        choices = [self.clean_option_variable(c) for c in self.options.option_attr if c.find(category) > -1]
        choices.sort()
        return choices

    @staticmethod
    def clean_option_variable(option_variable, inverse=False):
        """
        Convert option variable between UI and python format
        :param option_variable: option available for edit user settings UI
        :type option_variable: str
        :param inverse: True to return python format, False to return UI format
        :type inverse: bool
        :return: formatted option variable
        :rtype: str
        """
        if inverse:
            return option_variable.upper().replace(' ', '_')
        else:
            return option_variable.replace('_', ' ').title().replace('Dvh', 'DVH').replace('Iqr', 'IQR')

    def update_input_colors_var(self, *args):
        var = self.clean_option_variable(self.combo_box_colors_category.GetValue(), inverse=True)
        val = getattr(self.options, var)
        self.combo_box_colors_selection.SetValue(val)

    def update_input_colors_val(self, *args):
        var = self.clean_option_variable(self.combo_box_colors_category.GetValue(), inverse=True)
        val = self.combo_box_colors_selection.GetValue()
        self.options.set_option(var, val)

    def update_size_var(self, *args):
        var = self.clean_option_variable(self.combo_box_sizes_category.GetValue(), inverse=True)
        try:
            val = getattr(self.options, var).replace('pt', '')
        except AttributeError:
            val = str(getattr(self.options, var))
        try:
            val = int(float(val))
        except ValueError:
            pass
        self.spin_ctrl_sizes_input.SetValue(val)

    def update_size_val(self, *args):
        new = self.spin_ctrl_sizes_input.GetValue()
        if 'Font' in self.combo_box_sizes_category.GetValue():
            try:
                val = str(int(new)) + 'pt'
            except ValueError:
                val = '10pt'
        else:
            try:
                val = float(new)
            except ValueError:
                val = 1.

        var = self.clean_option_variable(self.combo_box_sizes_category.GetValue(), inverse=True)
        self.options.set_option(var, val)

    def update_line_width_var(self, *args):
        var = self.clean_option_variable(self.combo_box_line_widths_category.GetValue(), inverse=True)
        val = str(getattr(self.options, var))
        try:
            val = int(val)
        except ValueError:
            pass
        self.spin_ctrl_line_widths_input.SetValue(val)

    def update_line_width_val(self, *args):
        new = self.spin_ctrl_line_widths_input.GetValue()
        try:
            val = float(new)
        except ValueError:
            val = 1.
        var = self.clean_option_variable(self.combo_box_line_widths_category.GetValue(), inverse=True)
        self.options.set_option(var, val)

    def update_line_style_var(self, *args):
        var = self.clean_option_variable(self.combo_box_line_styles_category.GetValue(), inverse=True)
        self.combo_box_line_styles_selection.SetValue(getattr(self.options, var))

    def update_line_style_val(self, *args):
        var = self.clean_option_variable(self.combo_box_line_styles_category.GetValue(), inverse=True)
        val = self.combo_box_line_styles_selection.GetValue()
        self.options.set_option(var, val)

    def update_alpha_var(self, *args):
        var = self.clean_option_variable(self.combo_box_alpha_category.GetValue(), inverse=True)
        self.spin_ctrl_alpha_input.SetValue(str(getattr(self.options, var)))

    def update_alpha_val(self, *args):
        new = self.spin_ctrl_alpha_input.GetValue()
        try:
            val = float(new)
        except ValueError:
            val = 1.
        var = self.clean_option_variable(self.combo_box_alpha_category.GetValue(), inverse=True)
        self.options.set_option(var, val)

    def update_dvh_bin_width_val(self, *args):
        new = self.dvh_bin_width_input.GetValue()
        try:
            val = abs(int(new))
            self.options.set_option('dvh_bin_width', val)
        except ValueError:
            if new != '':
                self.dvh_bin_width_input.SetValue(str(self.options.dvh_bin_width))

    def update_dvh_bin_width_var(self, *args):
        self.dvh_bin_width_input.SetValue(str(self.options.dvh_bin_width))

    def refresh_options(self):
        self.update_dvh_bin_width_var()
        self.update_alpha_var()
        self.update_input_colors_var()
        self.update_line_style_var()
        self.update_line_width_var()
        self.update_size_var()

    def load_paths(self):
        self.text_ctrl_inbox.SetValue(self.options.INBOX_DIR)
        self.text_ctrl_imported.SetValue(self.options.IMPORTED_DIR)

    def restore_defaults(self, *args):
        MessageDialog(self, "Restore default preferences?", action_yes_func=self.options.restore_defaults)
        self.update_size_val()
        self.refresh_options()

    def on_use_dicom_dvh(self, *evt):
        self.options.set_option('USE_DICOM_DVH', self.checkbox_dicom_dvh.GetValue())

    def on_apply(self, *evt):
        self.apply_and_redraw_plots()
        self.is_edited = True

    def apply_and_redraw_plots(self):
        self.parent.apply_plot_options()
        self.parent.redraw_plots()


class About(wx.Dialog):
    """
    Simple dialog to display the LICENSE file and a brief text header in a scrollable window
    """
    def __init__(self, *evt):
        wx.Dialog.__init__(self, None, title='About DVH Analytics')

        scrolled_window = wx.ScrolledWindow(self, wx.ID_ANY)

        with open(LICENSE_PATH, 'r', encoding="utf8") as license_file:
            license_text = ''.join([line for line in license_file])

        license_text = "DVH Analytics v%s\ndvhanalytics.com\n\n%s" % (DefaultOptions().VERSION, license_text)

        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_text = wx.BoxSizer(wx.VERTICAL)

        scrolled_window.SetScrollRate(20, 20)

        license_text = wx.StaticText(scrolled_window, wx.ID_ANY, license_text)
        sizer_text.Add(license_text, 0, wx.EXPAND | wx.ALL, 5)
        scrolled_window.SetSizer(sizer_text)
        sizer_wrapper.Add(scrolled_window, 1, wx.EXPAND, 0)

        self.SetBackgroundColour(wx.WHITE)

        self.SetSizer(sizer_wrapper)
        self.SetSize((750, 900))
        self.Center()

        self.ShowModal()
        self.Destroy()


class PythonLibraries(wx.Dialog):
    """Simple dialog to display the installed python libraries"""
    def __init__(self, *evt):
        wx.Dialog.__init__(self, None, title='Installed Python Libraries')

        self.list_ctrl = wx.ListCtrl(self, wx.ID_ANY,
                                     style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
        self.data_table = DataTable(self.list_ctrl, widths=[200, 150])

        self.__set_data()
        self.__do_layout()

        self.run()

    def __set_data(self):
        columns = ['Library', 'Version']
        try:
            libraries = get_installed_python_libraries()
        except Exception:
            libraries = {'Library': ['pip list failed'], 'Version': [' ']}
        self.data_table.set_data(libraries, columns)

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_main = wx.BoxSizer(wx.VERTICAL)

        note = wx.StaticText(self, wx.ID_ANY, "NOTE: If running from source, this might reflect global packages.\n"
                                              "If running from an executable, this should be accurate.")
        note.Wrap(500)

        sizer_main.Add(note, 0, wx.EXPAND | wx.ALL, 5)
        sizer_main.Add(self.list_ctrl, 1, wx.EXPAND, 0)
        sizer_wrapper.Add(sizer_main, 1, wx.EXPAND | wx.ALL, 10)

        self.SetBackgroundColour(wx.WHITE)
        self.SetSizer(sizer_wrapper)
        self.SetSize(get_window_size(0.3, 0.8))
        self.Center()

    def run(self):
        self.ShowModal()
        self.Destroy()
