<reponame>jmarrec/IDFVersionUpdater2
import os
import subprocess

import wx

from EnergyPlusPath import EnergyPlusPath
from TransitionRunThread import TransitionRunThread
from International import translate as _, Languages, set_language
from Settings import Keys, load_settings, save_settings

__program_name__ = "IDFVersionUpdater (v2.0)"

_doing_restart = False


def doing_restart():
    """
    This function exposes the private, global, variable to determine if the program is going through an automated restart
    :return: True if the program should be restarted after reloaded settings, or False to just let the program be done
    """
    return _doing_restart


class VersionUpdaterWindow(wx.Frame):
    """ The main window, or wx.Frame, for the IDFVersionUpdater program.
    This initializer function creates instance variables, sets up threading, and builds the GUI"""

    def __init__(self):

        # initialize base class
        wx.Frame.__init__(self, None, title=__program_name__, size=(600, 183))
        self.SetMinSize((400, 175))

        # load the settings here very early; the tilde is cross platform thanks to Python
        self.settings_file_name = os.path.join(os.path.expanduser("~"), ".idfversionupdater.json")
        self.settings = load_settings(self.settings_file_name)

        # initialize some class-level "constants"
        self.box_spacing = 4

        # reset the restart flag
        global _doing_restart
        _doing_restart = False

        # initialize instance variables to be set later
        self.btn_select_idf = None
        self.btn_about = None
        self.lbl_path = None
        self.lbl_old_version = None
        self.chk_create_inter_versions = None
        self.btn_update_file = None
        self.btn_open_run_dir = None
        self.btn_cancel = None
        self.btn_exit = None
        self.status_bar = None
        self.idf_version = None
        self.running_transition_thread = None

        # try to load the settings very early since it includes initialization
        set_language(self.settings[Keys.language])

        # connect signals for the GUI
        self.Bind(wx.EVT_CLOSE, self.on_closing_form)

        # build up the GUI itself
        self.build_gui()

        # update the list of E+ versions
        self.ep_run_folder = EnergyPlusPath()
        title = TransitionRunThread.get_ep_version(os.path.join(self.ep_run_folder.installation_path, 'EnergyPlus'))
        self.SetTitle(__program_name__ + " -- " + title)
        self.status_bar.SetStatusText(_("Program Initialized"))

        # check the validity of the idf versions once at load time to initialize the action availability
        self.on_update_for_new_file(None)

    # GUI Worker Functions

    def build_gui(self):
        """
        This function manages the window construction, including position, title, and presentation
        """
        self.status_bar = self.CreateStatusBar()  # A StatusBar in the bottom of the window

        # highest level layout control is the main panel
        panel = wx.Panel(self, wx.ID_ANY)

        # this is then broken into rows with one vertical sizer
        top_sizer = wx.BoxSizer(wx.VERTICAL)

        # each row then has their own horizontal sizers on which controls are placed
        choose_about_sizer = wx.BoxSizer(wx.HORIZONTAL)
        path_version_sizer = wx.BoxSizer(wx.HORIZONTAL)
        checkbox_sizer = wx.BoxSizer(wx.HORIZONTAL)
        update_audit_close_sizer = wx.BoxSizer(wx.HORIZONTAL)

        # let's create a bunch of controls
        self.btn_select_idf = wx.Button(panel, wx.ID_ANY, _('Choose File to Update...'))
        self.btn_select_idf.Bind(wx.EVT_BUTTON, self.on_choose_idf)
        self.btn_about = wx.Button(panel, wx.ID_ANY, _('About...'))
        self.btn_about.Bind(wx.EVT_BUTTON, self.on_about)
        self.lbl_path = wx.TextCtrl(panel, wx.ID_ANY, _('File Path'), style=wx.BORDER_NONE)
        self.lbl_path.Bind(wx.EVT_TEXT, self.on_update_for_new_file)
        if self.settings[Keys.last_idf] is not None:
            self.lbl_path.SetValue(self.settings[Keys.last_idf])
        self.lbl_old_version = wx.StaticText(panel, wx.ID_ANY, _('Old Version'))
        self.chk_create_inter_versions = wx.CheckBox(panel, wx.ID_ANY, _('Keep Intermediate Versions of Files?'))
        self.chk_create_inter_versions.SetValue(True)
        self.btn_update_file = wx.Button(panel, wx.ID_ANY, _('Update File'))
        self.btn_update_file.Bind(wx.EVT_BUTTON, self.on_update_idf)
        self.btn_cancel = wx.Button(panel, wx.ID_ANY, _('Cancel Run'))
        self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel)
        self.btn_cancel.Disable()
        self.btn_open_run_dir = wx.Button(panel, wx.ID_ANY, _('Open Run Directory'))
        self.btn_open_run_dir.Bind(wx.EVT_BUTTON, self.on_open_run_dir)
        self.btn_exit = wx.Button(panel, wx.ID_ANY, _('Close'))
        self.btn_exit.Bind(wx.EVT_BUTTON, self.on_close)

        # now let's add the controls to each sizer
        choose_about_sizer.Add(self.btn_select_idf, 1, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        choose_about_sizer.Add(wx.StaticText(panel, -1, ''), 100, wx.EXPAND, border=self.box_spacing)
        choose_about_sizer.Add(self.btn_about, 1, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=self.box_spacing)
        path_version_sizer.Add(self.lbl_path, 4, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        path_version_sizer.Add(wx.StaticText(panel, -1, ''), 1, wx.EXPAND, border=self.box_spacing)
        path_version_sizer.Add(self.lbl_old_version, 1, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=self.box_spacing)
        checkbox_sizer.Add(self.chk_create_inter_versions, 1, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        update_audit_close_sizer.Add(self.btn_update_file, 1, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        update_audit_close_sizer.Add(self.btn_cancel, 1, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        update_audit_close_sizer.Add(wx.StaticText(panel, -1, ''), 7, wx.EXPAND, border=self.box_spacing)
        update_audit_close_sizer.Add(self.btn_open_run_dir, 1, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=self.box_spacing)
        update_audit_close_sizer.Add(self.btn_exit, 1, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=self.box_spacing)

        # then we'll add all the horizontal sizers into the main vertical sizer
        top_sizer.Add(choose_about_sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(wx.StaticLine(panel, ), proportion=0, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(path_version_sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(wx.StaticLine(panel, ), proportion=0, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(checkbox_sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(wx.StaticLine(panel, ), proportion=0, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(update_audit_close_sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)

        # and now tell the panel we are using this topSizer
        panel.SetSizer(top_sizer)

        # also build out the menu bar
        menu_bar = wx.MenuBar()
        menu1 = wx.Menu()
        # create the actual actionable items under the language menu
        menu1.Append(Languages.English, "English", "Change language to English")
        self.Bind(wx.EVT_MENU, self.on_switch_language, id=Languages.English)
        menu1.Append(Languages.Spanish, "Spanish", "Change language to Spanish")
        self.Bind(wx.EVT_MENU, self.on_switch_language, id=Languages.Spanish)
        menu1.Append(Languages.French, "French", "Change language to French")
        self.Bind(wx.EVT_MENU, self.on_switch_language, id=Languages.French)
        menu1.AppendSeparator()
        menu1.Append(106, "&Close\tCtrl+W", "Closes the window")
        self.Bind(wx.EVT_MENU, self.on_close, id=106)
        menu_bar.Append(menu1, "&File")
        self.SetMenuBar(menu_bar)  # Adding the MenuBar to the Frame content.

        # finally show the window in the center of the (primary? current?) screen
        self.Show(True)
        self.CenterOnScreen()

    def set_buttons_for_running(self, enabled):
        """
        This function sets the state of different buttons on the main window while a background task is running
        The list of controls to be enabled/disabled is hardcoded in an array in this function
        :param enabled: True if the controls are to be enabled, for example when the process is complete, False to disable.
        :return:
        """
        buttons = [self.btn_update_file, self.btn_select_idf, self.btn_exit]
        if enabled:
            [x.Enable() for x in buttons]
        else:
            [x.Disable() for x in buttons]
        buttons_to_invert = [self.btn_cancel]
        if enabled:
            [x.Disable() for x in buttons_to_invert]
        else:
            [x.Enable() for x in buttons_to_invert]

    # Event Handlers

    def on_switch_language(self, event):
        """
        This function handles the request to change languages, where the language identifier is passed in with the event
        :param event: The event information generated by the caller, for this function, the event ID should be set to
        an item in the :py:class:`Languages <International.Languages>` enumeration class
        """
        global _doing_restart
        this_id = event.GetId()
        language = this_id
        self.settings[Keys.language] = language
        message = wx.MessageDialog(
            parent=self,
            message=_(
                "You must restart the app to make the language change take effect.  Would you like to restart now?"),
            caption=__program_name__,
            style=wx.YES_NO | wx.CENTRE | wx.ICON_QUESTION)
        resp = message.ShowModal()
        if resp == wx.ID_YES:
            _doing_restart = True
        message.Destroy()
        if doing_restart():
            self.Close(False)

    def on_update_for_new_file(self, event):
        """
        This function handles the request to update for a new file, including updating program settings,
        gui button state, and updating the file version label if the file exists
        :param event: The event information generated by the caller, which is typically a text ctrl text change event
        """
        if self.lbl_path is None or self.btn_update_file is None:
            return
        idf = self.lbl_path.GetValue()
        self.settings[Keys.last_idf] = idf
        if os.path.exists(idf):
            self.on_msg(_("IDF File exists, ready to go"))
            self.idf_version = self.get_idf_version(idf)
            self.lbl_old_version.SetLabel("%s: %s" % (_('Old Version'), self.idf_version))
            self.btn_update_file.Enable()
        else:
            self.on_msg(_("IDF File doesn't exist at path given; cannot transition"))
            self.btn_update_file.Disable()

    def on_open_run_dir(self, event):
        """
        This function handles the request to open the current run directory in the default application (Finder...)
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        try:
            cur_platform = EnergyPlusPath.get_platform()
            open_cmd = ""
            if cur_platform == "linux":
                open_cmd = "xdg-open"
            elif cur_platform == "mac":
                open_cmd = "open"
            elif cur_platform == "windows":
                open_cmd = "explorer"
            subprocess.Popen([open_cmd, self.ep_run_folder.transition_directory], shell=False)
        except Exception:
            message = wx.MessageDialog(
                parent=self,
                message=_("Could not open run directory"),
                caption=__program_name__,
                style=wx.OK | wx.CENTRE | wx.ICON_WARNING)
            message.ShowModal()
            message.Destroy()

    def on_closing_form(self, event):
        """
        This function handles the request to close the form, first trying to save program settings
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        try:
            save_settings(self.settings, self.settings_file_name)
        except Exception:
            pass
        self.Destroy()

    def on_choose_idf(self, event):
        """
        This function handles the request to choose a new idf, opening a dialog, and updating settings if applicable
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        cur_folder = ""
        if self.settings[Keys.last_idf_folder] is not None:
            cur_folder = self.settings[Keys.last_idf_folder]
        cur_idf = ""
        if self.settings[Keys.last_idf] is not None:
            cur_idf = self.settings[Keys.last_idf]
        open_file_dialog = wx.FileDialog(self, _("Open File for Transition"), cur_folder, cur_idf,
                                         "EnergyPlus Input Files (*.idf;*.imf)|*.idf;*.imf",
                                         wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
        result = open_file_dialog.ShowModal()
        if result == wx.ID_OK:
            cur_idf = open_file_dialog.GetPath()
            self.settings[Keys.last_idf] = cur_idf
            self.settings[Keys.last_idf_folder] = os.path.dirname(cur_idf)
            self.lbl_path.SetValue(cur_idf)
        open_file_dialog.Destroy()

    def on_about(self, event):
        """
        This function handles the request to show the "About..." modal dialog window
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        message = wx.MessageDialog(parent=self,
                                   message=_("ABOUT_DIALOG"),
                                   caption=__program_name__,
                                   style=wx.OK | wx.CENTRE | wx.ICON_INFORMATION)
        message.ShowModal()
        message.Destroy()

    def on_update_idf(self, event):
        """
        This function handles the request to run Transition itself, building up the list of transitions,
        creating a new thread instance, prepping the gui, and running it
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        if self.idf_version not in [tr.source_version for tr in self.ep_run_folder.transitions_available]:
            self.on_msg(_("Cannot find a matching transition tool for this idf version"))
        # we need to build up the list of transition steps to perform
        transitions_to_run = []
        for tr in self.ep_run_folder.transitions_available:
            if tr.source_version < self.idf_version:
                continue  # skip this older version
            transitions_to_run.append(tr)
        self.running_transition_thread = TransitionRunThread(
            transitions_to_run,
            self.ep_run_folder.transition_directory,
            self.settings[Keys.last_idf],
            self.chk_create_inter_versions.GetValue(),
            self.callback_on_msg,
            self.callback_on_done
        )
        self.running_transition_thread.start()
        self.set_buttons_for_running(enabled=False)

    def on_cancel(self, event):
        self.btn_cancel.Disable()
        self.running_transition_thread.stop()

    def on_close(self, event):
        """
        This function handles the request to close the form, simply calling Close
        Note this does not destroy the form, allowing the owning code to still access the form settings
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        self.Close(False)

    # Callback functions and delegates to be called on MainLoop thread

    def callback_on_msg(self, message):
        wx.CallAfter(self.on_msg, message)

    def on_msg(self, message):
        self.status_bar.SetStatusText(message)

    def callback_on_done(self, message):
        wx.CallAfter(self.on_done, message)

    def on_done(self, message):
        self.status_bar.SetStatusText(message)
        self.set_buttons_for_running(enabled=True)

    # Utilities

    @staticmethod
    def get_idf_version(path_to_idf):
        """
        This function returns the current version of a given input file.
        The function uses a simplified parsing approach so it only works for valid syntax files, and provides no specialized error handling

        :param path_to_idf: Absolute path to a EnergyPlus input file
        :rtype: A floating point version number for the input file, for example 8.5 for an 8.5.0 input file
        """
        # phase 1: read in lines of file
        with open(path_to_idf, "r") as fo:
            lines = fo.readlines()
        # phases 2: remove comments and blank lines
        lines_a = []
        for line in lines:
            line_text = line.strip()
            this_line = ""
            if len(line_text) > 0:
                exclamation = line_text.find("!")
                if exclamation == -1:
                    this_line = line_text
                elif exclamation == 0:
                    this_line = ""
                elif exclamation > 0:
                    this_line = line_text[:exclamation]
                if not this_line == "":
                    lines_a.append(this_line)
        # phase 3: join entire array and re-split by semicolon
        idf_data_joined = ''.join(lines_a)
        idf_object_strings = idf_data_joined.split(";")
        # phase 4: break each object into an array of object name and field values
        for this_object in idf_object_strings:
            tokens = this_object.split(',')
            if tokens[0].upper() == "VERSION":
                version_string = tokens[1]
                version_string_tokens = version_string.split('.')  # might be 2 or 3...
                version_number = float("%s.%s" % (version_string_tokens[0], version_string_tokens[1]))
                return version_number

import os
import subprocess

import wx

from EnergyPlusPath import EnergyPlusPath
from TransitionRunThread import TransitionRunThread
from International import translate as _, Languages, set_language
from Settings import Keys, load_settings, save_settings

__program_name__ = "IDFVersionUpdater (v2.0)"

_doing_restart = False


def doing_restart():
    """
    This function exposes the private, global, variable to determine if the program is going through an automated restart
    :return: True if the program should be restarted after reloaded settings, or False to just let the program be done
    """
    return _doing_restart


class VersionUpdaterWindow(wx.Frame):
    """ The main window, or wx.Frame, for the IDFVersionUpdater program.
    This initializer function creates instance variables, sets up threading, and builds the GUI"""

    def __init__(self):

        # initialize base class
        wx.Frame.__init__(self, None, title=__program_name__, size=(600, 183))
        self.SetMinSize((400, 175))

        # load the settings here very early; the tilde is cross platform thanks to Python
        self.settings_file_name = os.path.join(os.path.expanduser("~"), ".idfversionupdater.json")
        self.settings = load_settings(self.settings_file_name)

        # initialize some class-level "constants"
        self.box_spacing = 4

        # reset the restart flag
        global _doing_restart
        _doing_restart = False

        # initialize instance variables to be set later
        self.btn_select_idf = None
        self.btn_about = None
        self.lbl_path = None
        self.lbl_old_version = None
        self.chk_create_inter_versions = None
        self.btn_update_file = None
        self.btn_open_run_dir = None
        self.btn_cancel = None
        self.btn_exit = None
        self.status_bar = None
        self.idf_version = None
        self.running_transition_thread = None

        # try to load the settings very early since it includes initialization
        set_language(self.settings[Keys.language])

        # connect signals for the GUI
        self.Bind(wx.EVT_CLOSE, self.on_closing_form)

        # build up the GUI itself
        self.build_gui()

        # update the list of E+ versions
        self.ep_run_folder = EnergyPlusPath()
        title = TransitionRunThread.get_ep_version(os.path.join(self.ep_run_folder.installation_path, 'EnergyPlus'))
        self.SetTitle(__program_name__ + " -- " + title)
        self.status_bar.SetStatusText(_("Program Initialized"))

        # check the validity of the idf versions once at load time to initialize the action availability
        self.on_update_for_new_file(None)

    # GUI Worker Functions

    def build_gui(self):
        """
        This function manages the window construction, including position, title, and presentation
        """
        self.status_bar = self.CreateStatusBar()  # A StatusBar in the bottom of the window

        # highest level layout control is the main panel
        panel = wx.Panel(self, wx.ID_ANY)

        # this is then broken into rows with one vertical sizer
        top_sizer = wx.BoxSizer(wx.VERTICAL)

        # each row then has their own horizontal sizers on which controls are placed
        choose_about_sizer = wx.BoxSizer(wx.HORIZONTAL)
        path_version_sizer = wx.BoxSizer(wx.HORIZONTAL)
        checkbox_sizer = wx.BoxSizer(wx.HORIZONTAL)
        update_audit_close_sizer = wx.BoxSizer(wx.HORIZONTAL)

        # let's create a bunch of controls
        self.btn_select_idf = wx.Button(panel, wx.ID_ANY, _('Choose File to Update...'))
        self.btn_select_idf.Bind(wx.EVT_BUTTON, self.on_choose_idf)
        self.btn_about = wx.Button(panel, wx.ID_ANY, _('About...'))
        self.btn_about.Bind(wx.EVT_BUTTON, self.on_about)
        self.lbl_path = wx.TextCtrl(panel, wx.ID_ANY, _('File Path'), style=wx.BORDER_NONE)
        self.lbl_path.Bind(wx.EVT_TEXT, self.on_update_for_new_file)
        if self.settings[Keys.last_idf] is not None:
            self.lbl_path.SetValue(self.settings[Keys.last_idf])
        self.lbl_old_version = wx.StaticText(panel, wx.ID_ANY, _('Old Version'))
        self.chk_create_inter_versions = wx.CheckBox(panel, wx.ID_ANY, _('Keep Intermediate Versions of Files?'))
        self.chk_create_inter_versions.SetValue(True)
        self.btn_update_file = wx.Button(panel, wx.ID_ANY, _('Update File'))
        self.btn_update_file.Bind(wx.EVT_BUTTON, self.on_update_idf)
        self.btn_cancel = wx.Button(panel, wx.ID_ANY, _('Cancel Run'))
        self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel)
        self.btn_cancel.Disable()
        self.btn_open_run_dir = wx.Button(panel, wx.ID_ANY, _('Open Run Directory'))
        self.btn_open_run_dir.Bind(wx.EVT_BUTTON, self.on_open_run_dir)
        self.btn_exit = wx.Button(panel, wx.ID_ANY, _('Close'))
        self.btn_exit.Bind(wx.EVT_BUTTON, self.on_close)

        # now let's add the controls to each sizer
        choose_about_sizer.Add(self.btn_select_idf, 1, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        choose_about_sizer.Add(wx.StaticText(panel, -1, ''), 100, wx.EXPAND, border=self.box_spacing)
        choose_about_sizer.Add(self.btn_about, 1, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=self.box_spacing)
        path_version_sizer.Add(self.lbl_path, 4, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        path_version_sizer.Add(wx.StaticText(panel, -1, ''), 1, wx.EXPAND, border=self.box_spacing)
        path_version_sizer.Add(self.lbl_old_version, 1, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=self.box_spacing)
        checkbox_sizer.Add(self.chk_create_inter_versions, 1, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        update_audit_close_sizer.Add(self.btn_update_file, 1, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        update_audit_close_sizer.Add(self.btn_cancel, 1, flag=wx.ALIGN_LEFT | wx.LEFT, border=self.box_spacing)
        update_audit_close_sizer.Add(wx.StaticText(panel, -1, ''), 7, wx.EXPAND, border=self.box_spacing)
        update_audit_close_sizer.Add(self.btn_open_run_dir, 1, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=self.box_spacing)
        update_audit_close_sizer.Add(self.btn_exit, 1, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=self.box_spacing)

        # then we'll add all the horizontal sizers into the main vertical sizer
        top_sizer.Add(choose_about_sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(wx.StaticLine(panel, ), proportion=0, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(path_version_sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(wx.StaticLine(panel, ), proportion=0, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(checkbox_sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(wx.StaticLine(panel, ), proportion=0, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)
        top_sizer.Add(update_audit_close_sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=self.box_spacing)

        # and now tell the panel we are using this topSizer
        panel.SetSizer(top_sizer)

        # also build out the menu bar
        menu_bar = wx.MenuBar()
        menu1 = wx.Menu()
        # create the actual actionable items under the language menu
        menu1.Append(Languages.English, "English", "Change language to English")
        self.Bind(wx.EVT_MENU, self.on_switch_language, id=Languages.English)
        menu1.Append(Languages.Spanish, "Spanish", "Change language to Spanish")
        self.Bind(wx.EVT_MENU, self.on_switch_language, id=Languages.Spanish)
        menu1.Append(Languages.French, "French", "Change language to French")
        self.Bind(wx.EVT_MENU, self.on_switch_language, id=Languages.French)
        menu1.AppendSeparator()
        menu1.Append(106, "&Close\tCtrl+W", "Closes the window")
        self.Bind(wx.EVT_MENU, self.on_close, id=106)
        menu_bar.Append(menu1, "&File")
        self.SetMenuBar(menu_bar)  # Adding the MenuBar to the Frame content.

        # finally show the window in the center of the (primary? current?) screen
        self.Show(True)
        self.CenterOnScreen()

    def set_buttons_for_running(self, enabled):
        """
        This function sets the state of different buttons on the main window while a background task is running
        The list of controls to be enabled/disabled is hardcoded in an array in this function
        :param enabled: True if the controls are to be enabled, for example when the process is complete, False to disable.
        :return:
        """
        buttons = [self.btn_update_file, self.btn_select_idf, self.btn_exit]
        if enabled:
            [x.Enable() for x in buttons]
        else:
            [x.Disable() for x in buttons]
        buttons_to_invert = [self.btn_cancel]
        if enabled:
            [x.Disable() for x in buttons_to_invert]
        else:
            [x.Enable() for x in buttons_to_invert]

    # Event Handlers

    def on_switch_language(self, event):
        """
        This function handles the request to change languages, where the language identifier is passed in with the event
        :param event: The event information generated by the caller, for this function, the event ID should be set to
        an item in the :py:class:`Languages <International.Languages>` enumeration class
        """
        global _doing_restart
        this_id = event.GetId()
        language = this_id
        self.settings[Keys.language] = language
        message = wx.MessageDialog(
            parent=self,
            message=_(
                "You must restart the app to make the language change take effect.  Would you like to restart now?"),
            caption=__program_name__,
            style=wx.YES_NO | wx.CENTRE | wx.ICON_QUESTION)
        resp = message.ShowModal()
        if resp == wx.ID_YES:
            _doing_restart = True
        message.Destroy()
        if doing_restart():
            self.Close(False)

    def on_update_for_new_file(self, event):
        """
        This function handles the request to update for a new file, including updating program settings,
        gui button state, and updating the file version label if the file exists
        :param event: The event information generated by the caller, which is typically a text ctrl text change event
        """
        if self.lbl_path is None or self.btn_update_file is None:
            return
        idf = self.lbl_path.GetValue()
        self.settings[Keys.last_idf] = idf
        if os.path.exists(idf):
            self.on_msg(_("IDF File exists, ready to go"))
            self.idf_version = self.get_idf_version(idf)
            self.lbl_old_version.SetLabel("%s: %s" % (_('Old Version'), self.idf_version))
            self.btn_update_file.Enable()
        else:
            self.on_msg(_("IDF File doesn't exist at path given; cannot transition"))
            self.btn_update_file.Disable()

    def on_open_run_dir(self, event):
        """
        This function handles the request to open the current run directory in the default application (Finder...)
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        try:
            cur_platform = EnergyPlusPath.get_platform()
            open_cmd = ""
            if cur_platform == "linux":
                open_cmd = "xdg-open"
            elif cur_platform == "mac":
                open_cmd = "open"
            elif cur_platform == "windows":
                open_cmd = "explorer"
            subprocess.Popen([open_cmd, self.ep_run_folder.transition_directory], shell=False)
        except Exception:
            message = wx.MessageDialog(
                parent=self,
                message=_("Could not open run directory"),
                caption=__program_name__,
                style=wx.OK | wx.CENTRE | wx.ICON_WARNING)
            message.ShowModal()
            message.Destroy()

    def on_closing_form(self, event):
        """
        This function handles the request to close the form, first trying to save program settings
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        try:
            save_settings(self.settings, self.settings_file_name)
        except Exception:
            pass
        self.Destroy()

    def on_choose_idf(self, event):
        """
        This function handles the request to choose a new idf, opening a dialog, and updating settings if applicable
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        cur_folder = ""
        if self.settings[Keys.last_idf_folder] is not None:
            cur_folder = self.settings[Keys.last_idf_folder]
        cur_idf = ""
        if self.settings[Keys.last_idf] is not None:
            cur_idf = self.settings[Keys.last_idf]
        open_file_dialog = wx.FileDialog(self, _("Open File for Transition"), cur_folder, cur_idf,
                                         "EnergyPlus Input Files (*.idf;*.imf)|*.idf;*.imf",
                                         wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
        result = open_file_dialog.ShowModal()
        if result == wx.ID_OK:
            cur_idf = open_file_dialog.GetPath()
            self.settings[Keys.last_idf] = cur_idf
            self.settings[Keys.last_idf_folder] = os.path.dirname(cur_idf)
            self.lbl_path.SetValue(cur_idf)
        open_file_dialog.Destroy()

    def on_about(self, event):
        """
        This function handles the request to show the "About..." modal dialog window
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        message = wx.MessageDialog(parent=self,
                                   message=_("ABOUT_DIALOG"),
                                   caption=__program_name__,
                                   style=wx.OK | wx.CENTRE | wx.ICON_INFORMATION)
        message.ShowModal()
        message.Destroy()

    def on_update_idf(self, event):
        """
        This function handles the request to run Transition itself, building up the list of transitions,
        creating a new thread instance, prepping the gui, and running it
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        if self.idf_version not in [tr.source_version for tr in self.ep_run_folder.transitions_available]:
            self.on_msg(_("Cannot find a matching transition tool for this idf version"))
        # we need to build up the list of transition steps to perform
        transitions_to_run = []
        for tr in self.ep_run_folder.transitions_available:
            if tr.source_version < self.idf_version:
                continue  # skip this older version
            transitions_to_run.append(tr)
        self.running_transition_thread = TransitionRunThread(
            transitions_to_run,
            self.ep_run_folder.transition_directory,
            self.settings[Keys.last_idf],
            self.chk_create_inter_versions.GetValue(),
            self.callback_on_msg,
            self.callback_on_done
        )
        self.running_transition_thread.start()
        self.set_buttons_for_running(enabled=False)

    def on_cancel(self, event):
        self.btn_cancel.Disable()
        self.running_transition_thread.stop()

    def on_close(self, event):
        """
        This function handles the request to close the form, simply calling Close
        Note this does not destroy the form, allowing the owning code to still access the form settings
        :param event: The event information generated by the caller, which in this case is a wx Button
        """
        self.Close(False)

    # Callback functions and delegates to be called on MainLoop thread

    def callback_on_msg(self, message):
        wx.CallAfter(self.on_msg, message)

    def on_msg(self, message):
        self.status_bar.SetStatusText(message)

    def callback_on_done(self, message):
        wx.CallAfter(self.on_done, message)

    def on_done(self, message):
        self.status_bar.SetStatusText(message)
        self.set_buttons_for_running(enabled=True)

    # Utilities

    @staticmethod
    def get_idf_version(path_to_idf):
        """
        This function returns the current version of a given input file.
        The function uses a simplified parsing approach so it only works for valid syntax files, and provides no specialized error handling

        :param path_to_idf: Absolute path to a EnergyPlus input file
        :rtype: A floating point version number for the input file, for example 8.5 for an 8.5.0 input file
        """
        # phase 1: read in lines of file
        with open(path_to_idf, "r") as fo:
            lines = fo.readlines()
        # phases 2: remove comments and blank lines
        lines_a = []
        for line in lines:
            line_text = line.strip()
            this_line = ""
            if len(line_text) > 0:
                exclamation = line_text.find("!")
                if exclamation == -1:
                    this_line = line_text
                elif exclamation == 0:
                    this_line = ""
                elif exclamation > 0:
                    this_line = line_text[:exclamation]
                if not this_line == "":
                    lines_a.append(this_line)
        # phase 3: join entire array and re-split by semicolon
        idf_data_joined = ''.join(lines_a)
        idf_object_strings = idf_data_joined.split(";")
        # phase 4: break each object into an array of object name and field values
        for this_object in idf_object_strings:
            tokens = this_object.split(',')
            if tokens[0].upper() == "VERSION":
                version_string = tokens[1]
                version_string_tokens = version_string.split('.')  # might be 2 or 3...
                version_number = float("%s.%s" % (version_string_tokens[0], version_string_tokens[1]))
                return version_number
This function exposes the private, global, variable to determine if the program is going through an automated restart     :return: True if the program should be restarted after reloaded settings, or False to just let the program be done The main window, or wx.Frame, for the IDFVersionUpdater program.     This initializer function creates instance variables, sets up threading, and builds the GUI # initialize base class # load the settings here very early; the tilde is cross platform thanks to Python # initialize some class-level "constants" # reset the restart flag # initialize instance variables to be set later # try to load the settings very early since it includes initialization # connect signals for the GUI # build up the GUI itself # update the list of E+ versions # check the validity of the idf versions once at load time to initialize the action availability # GUI Worker Functions This function manages the window construction, including position, title, and presentation # A StatusBar in the bottom of the window # highest level layout control is the main panel # this is then broken into rows with one vertical sizer # each row then has their own horizontal sizers on which controls are placed # let's create a bunch of controls # now let's add the controls to each sizer # then we'll add all the horizontal sizers into the main vertical sizer # and now tell the panel we are using this topSizer # also build out the menu bar # create the actual actionable items under the language menu # Adding the MenuBar to the Frame content. # finally show the window in the center of the (primary? current?) screen This function sets the state of different buttons on the main window while a background task is running         The list of controls to be enabled/disabled is hardcoded in an array in this function         :param enabled: True if the controls are to be enabled, for example when the process is complete, False to disable.         :return: # Event Handlers This function handles the request to change languages, where the language identifier is passed in with the event         :param event: The event information generated by the caller, for this function, the event ID should be set to         an item in the :py:class:`Languages <International.Languages>` enumeration class This function handles the request to update for a new file, including updating program settings,         gui button state, and updating the file version label if the file exists         :param event: The event information generated by the caller, which is typically a text ctrl text change event This function handles the request to open the current run directory in the default application (Finder...)         :param event: The event information generated by the caller, which in this case is a wx Button This function handles the request to close the form, first trying to save program settings         :param event: The event information generated by the caller, which in this case is a wx Button This function handles the request to choose a new idf, opening a dialog, and updating settings if applicable         :param event: The event information generated by the caller, which in this case is a wx Button This function handles the request to show the "About..." modal dialog window         :param event: The event information generated by the caller, which in this case is a wx Button This function handles the request to run Transition itself, building up the list of transitions,         creating a new thread instance, prepping the gui, and running it         :param event: The event information generated by the caller, which in this case is a wx Button # we need to build up the list of transition steps to perform # skip this older version This function handles the request to close the form, simply calling Close         Note this does not destroy the form, allowing the owning code to still access the form settings         :param event: The event information generated by the caller, which in this case is a wx Button # Callback functions and delegates to be called on MainLoop thread # Utilities This function returns the current version of a given input file.         The function uses a simplified parsing approach so it only works for valid syntax files, and provides no specialized error handling          :param path_to_idf: Absolute path to a EnergyPlus input file         :rtype: A floating point version number for the input file, for example 8.5 for an 8.5.0 input file # phase 1: read in lines of file # phases 2: remove comments and blank lines # phase 3: join entire array and re-split by semicolon # phase 4: break each object into an array of object name and field values # might be 2 or 3...