#
# Modification History:
#   180803 David Shifflett
#     Added graph_list_column and trace_navigation classes
#

import os
import webbrowser
from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtWidgets import QStyle # for PM_ScrollBarExtent
from PyQt5.QtWidgets import qApp
from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject
from PyQt5.QtCore import pyqtSlot
from version_file import VERSION
from main_splitter import MainSplitter
from mp_code_column import MPCodeColumn
from graph_list_column import GraphListColumn
from trace_navigation import TraceNavigation
from graph_main_widget import GraphMainWidget
from graph_list_widget import GraphListWidget
from graph_list_width_manager import GraphListWidthManager
from scope_spinner import ScopeSpinner
from graph_status_label import GraphStatusLabel
from graphs_manager import GraphsManager
from mp_code_editor import MPCodeEditor
from logger import Logger
import mp_code_io_manager
from mp_code_syntax_checker import parse_error_line_number, MPCodeSyntaxChecker
from trace_generator_manager import TraceGeneratorManager
import export_trace_manager
from settings_manager import SettingsManager, SETTINGS_THEMES
from settings_dialog_wrapper import SettingsDialogWrapper
from keyboard_dialog_wrapper import KeyboardDialogWrapper
from about_mp_dialog_wrapper import AboutMPDialogWrapper
from event_menu import EventMenu
from path_constants import MP_CODE_EXAMPLES

class GUIManager(QObject):

    """MP main window containing menu, toolbar, statusbar, and central widget.
    Central widget contains split pane areas, ref. http://firebird.nps.edu/:
      code_area
      console_area
      graph_area
      graph_list_area
    """

    def __init__(self, main_window):
        super(GUIManager, self).__init__()

        # user's preferred MP Code path
        self.preferred_mp_code_dir = os.path.expanduser("~")

        # user's preferred JSON Graph Gryphon file path
        self.preferred_gry_file_dir = os.path.expanduser("~")

        # user's preferred JPG trace path
        self.preferred_trace_dir = os.path.expanduser("~")

        # the settings manager
        self.settings_manager = SettingsManager()

        # the graphs manager
        self.graphs_manager = GraphsManager(self.settings_manager)

        # the graph list width manager
        self.graph_list_width_manager = GraphListWidthManager(
                  main_window.style().pixelMetric(QStyle.PM_ScrollBarExtent))

        # state
        self.w = main_window

        # main window decoration
        self.w.setGeometry(0, 0, 850, 800)
        self.w.setWindowTitle("Monterey Phoenix v4 - Gryphon GUI %s"%VERSION)
        self.w.setWindowIcon(QIcon('icons/MP-logo-small-blue.png'))

        # the main splitter which emits size_changed
        main_splitter = MainSplitter(self.graph_list_width_manager)

        # the scope spinner containing spinner=QSpinBox
        self.scope_spinner = ScopeSpinner()

        # the graph list widget which responds to events
        self.graph_list_widget = GraphListWidget(main_splitter,
                                                 self.graphs_manager,
                                                 self.graph_list_width_manager)

        # the graph main widget
        self.graph_main_widget = GraphMainWidget(self.graphs_manager,
                                                 self.graph_list_widget)
        # the graph list widget repaints when a node coordinate in the
        # main widget changes
        self.graph_main_widget.signal_graph_item_view_changed.connect(
                           self.graph_list_widget.graph_item_view_changed)

        # the event menu
        self.event_menu = EventMenu(self.graphs_manager,
                                    self.graph_list_widget,
                                    self.settings_manager)

        # the graph status widget
        self.graph_status_label = GraphStatusLabel(self.graphs_manager)

        # the statusbar
        self.statusbar = self.w.statusBar()
        self.statusbar.addPermanentWidget(self.graph_status_label.status_text)
        self.statusbar.showMessage("Open, Import, or Compose MP Code to begin.")

        # the logger containing log_pane=QPlainTextEdit
        self.logger = Logger(self.statusbar)

        # the trace generator manager which runs the compiler asynchronously
        self.trace_generator_manager = TraceGeneratorManager()
        self.trace_generator_manager.signal_compile_response.connect(
                                        self.response_compile_mp_code)

        # the MP Code editor
        self.mp_code_editor = MPCodeEditor(self.settings_manager,
                                                         self.statusbar)

        # the syntax checker
        self.mp_code_syntax_checker = MPCodeSyntaxChecker(self.mp_code_editor,
                                        self.trace_generator_manager)
        self.mp_code_syntax_checker.signal_syntax_report.connect(
                                        self.mp_code_editor.set_syntax_status)

        # setup
        self.define_actions()
        self.define_menus()
        self.define_toolbar()

        # trace navigation widget
        self.trace_navigation = TraceNavigation(self.graphs_manager,
                                        self.graph_list_widget)
        self.trace_navigation.setDisabled(True)

        # the central widget containing the main split pane
        self.mp_code_column = MPCodeColumn(self)
        self.mp_code_column.set_sizes([600, 200])
        self.graph_list_column = GraphListColumn(self)
        self.graph_list_column.set_sizes([50, 600])
        main_splitter.addWidget(self.mp_code_column)
        main_splitter.addWidget(self.graph_main_widget.view)
        main_splitter.addWidget(self.graph_list_column)
        main_splitter.setSizes([250, 500, 100])
        self.w.setCentralWidget(main_splitter)

        # clean shutdown
        qApp.aboutToQuit.connect(self.trace_generator_manager.mp_clean_shutdown)

        self.w.show()

    # actions
    def define_actions(self):
        # action exit
        self.action_exit = QAction(QIcon("icons/application-exit.png"),
                                   "&Exit", self.w)
        self.action_exit.setShortcut('Ctrl+Q')
        self.action_exit.setStatusTip("Exit MP")
        self.action_exit.triggered.connect(self.w.close)

        # action help
        self.action_help = QAction(QIcon("icons/help-contents-5.png"),
                                   "&Help", self.w)
        self.action_help.setShortcut('Ctrl+H')
        self.action_help.setStatusTip("Help using MP")
        self.action_help.triggered.connect(self.help_mp)

        # action keyboard_shortcuts
        self.action_keyboard_shortcuts = QAction("&Keyboard Shortcuts", self.w)
        self.action_keyboard_shortcuts.setStatusTip(
                                        "Keyboard shorcuts for MP controls")
        self.action_keyboard_shortcuts.triggered.connect(
                                        self.keyboard_shortcuts)

        # action about
        self.action_about = QAction(QIcon("icons/document-properties.png"),
                                        "&About MP", self)
        self.action_about.setStatusTip("about MP")
        self.action_about.triggered.connect(self.about_mp)

        # action run
        self.action_run = QAction(QIcon("icons/run-build-install.png"),
                                        "&Run", self)
        self.action_run.setShortcut('Ctrl+R')
        self.action_run.setStatusTip("Generate traces from MP Code")
        self.action_run.triggered.connect(self.request_compile_mp_code)

        # action cancel
        self.action_cancel = QAction(QIcon("icons/dialog-cancel-3.png"),
                                        "&Stop", self)
        self.action_cancel.setStatusTip("Cancel trace generation process")
        self.action_cancel.triggered.connect(self.cancel_compile_mp_code)
        self.action_cancel.setDisabled(True)

        # action clear log
        self.action_clear_log = QAction(QIcon("icons/edit-clear-list.png"),
                                        "&Clear MP Code Log", self)
        self.action_clear_log.setShortcut('Ctrl+L')
        self.action_clear_log.setStatusTip("Clear MP Code log")
        self.action_clear_log.triggered.connect(self.logger.clear_log)

        # action open MP Code file
        self.action_open_mp_code_file = QAction(QIcon(
                       "icons/document-open-2.png"), "&Open...", self)
        self.action_open_mp_code_file.setStatusTip("Open MP Code File")
        self.action_open_mp_code_file.triggered.connect(
                                           self.select_and_open_mp_code_file)

        # action close MP Code file
        self.action_close_mp_code_file = QAction(QIcon(
                       "icons/document-close.png"), "Close", self)
        self.action_close_mp_code_file.setStatusTip("Close MP Code File")
        self.action_close_mp_code_file.triggered.connect(self.close_mp_code)

        # action save MP Code file
        self.action_save_mp_code_file = QAction(QIcon(
                       "icons/document-save-2.png"), "&Save...", self)
        self.action_save_mp_code_file.setStatusTip("Save MP Code file")
        self.action_save_mp_code_file.triggered.connect(
                                           self.save_mp_code_file)

        # action import JSON Graph Format .gry file
        self.action_import_gry_file = QAction(QIcon(
                   "icons/document-import-2.png"), "&Import...", self)
        self.action_import_gry_file.setStatusTip("Import Gryphon Graph file")
        self.action_import_gry_file.triggered.connect(
                                     self.select_and_import_gry_file)

        # action export JSON Graph Format .gry file
        self.action_export_gry_file = QAction(QIcon(
                   "icons/document-export-4.png"), "&Export...", self)
        self.action_export_gry_file.setStatusTip("Export Gryphon Graph file")
        self.action_export_gry_file.triggered.connect(
                                     self.select_and_export_gry_file)

        # action export trace as image file
        self.action_export_trace = QAction("&Export Trace...", self)
        self.action_export_trace.setStatusTip("Export trace as image file")
        self.action_export_trace.triggered.connect(
                                     self.select_and_export_trace)

        # action export all traces as image files
        self.action_export_all_traces = QAction("&Export All Traces...", self)
        self.action_export_all_traces.setStatusTip(
                                           "Export all traces as image files")
        self.action_export_all_traces.triggered.connect(
                                     self.select_and_export_all_traces)

        # action settings custom...
        self.action_settings_custom = QAction("&Custom...", self)
        self.action_settings_custom.setStatusTip(
                                     "Draw using custom settings")
        self.action_settings_custom.triggered.connect(
                                     self.set_settings_custom)

        # events action button which opens the event menu
        self.action_event_menu_button = QAction(QIcon("icons/Events_icon.png"),
                                              "Events", self)
        self.action_event_menu_button.setStatusTip("Manage event groups")
        self.action_event_menu_button.setMenu(self.event_menu.event_menu)

    # menus
    def define_menus(self):
        menubar = self.w.menuBar()
        menubar.setNativeMenuBar(False)

        # menu | file
        file_menu = menubar.addMenu('&File')

        # menu | file | open
        file_menu.addAction(self.action_open_mp_code_file)

        # menu | file | close
        file_menu.addAction(self.action_close_mp_code_file)

        # menu | file | open examples
        self.open_examples_menu = file_menu.addMenu("Open Example")
        self.open_examples_menu.setStatusTip("Open MP Code Example")
        for filename in sorted(os.listdir(MP_CODE_EXAMPLES)):
            if filename.endswith(".mp"):
                # add action with dedicated lambda function containing filename
                action = QAction(filename, self)
                action.triggered.connect(lambda checked, filename=
                            os.path.join(MP_CODE_EXAMPLES, filename):
                                    self.open_mp_code(filename))
                self.open_examples_menu.addAction(action)

        # menu | file | save MP Code
        file_menu.addAction(self.action_save_mp_code_file)

        # menu | file | separator
        file_menu.addSeparator()

        # menu | file | import Gryphon Graph file
        file_menu.addAction(self.action_import_gry_file)

        # menu | file | export Gryphon Graph file
        file_menu.addAction(self.action_export_gry_file)

        # menu | file | separator
        file_menu.addSeparator()

        # menu | file | export trace
        file_menu.addAction(self.action_export_trace)

        # menu | file | export all traces
        file_menu.addAction(self.action_export_all_traces)

        # menu | file | separator
        file_menu.addSeparator()

        # menu | file | exit
        file_menu.addAction(self.action_exit)

        # menu | actions
        actions_menu = menubar.addMenu('&Actions')

        # menu | actions | run
        actions_menu.addAction(self.action_run)

        # menu | actions | cancel
        actions_menu.addAction(self.action_cancel)

        # menu | actions | separator
        file_menu.addSeparator()

        # menu | actions | clear log
        actions_menu.addAction(self.action_clear_log)

        # menu | preferences
        preferences_menu = menubar.addMenu('&Preferences')

        # menu | preferences | settings
        settings_menu = preferences_menu.addMenu("Settings")

        # menu | preferences | settings options
        for theme_name, theme in SETTINGS_THEMES:

            # add action with dedicated lambda function containing filename
            action = QAction(theme_name, self)
            action.triggered.connect(lambda checked, theme=theme:
                                     self.settings_manager.change(theme))
            settings_menu.addAction(action)

        settings_menu.addSeparator()
        settings_menu.addAction(self.action_settings_custom)

#        # menu | preferences | separator
#        preferences_menu.addSeparator()

        # menu | help
        help_menu = menubar.addMenu('&Help')
        help_menu.addAction(self.action_help)
        help_menu.addAction(self.action_keyboard_shortcuts)
        help_menu.addAction(self.action_about)

    # toolbar items
    def define_toolbar(self):
        toolbar = self.w.addToolBar("MP_Py Toolbar")
        toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        toolbar.addAction(self.action_open_mp_code_file)
        toolbar.addWidget(self.scope_spinner.spinner)
        toolbar.addWidget(QLabel("Scope"))
        toolbar.addAction(self.action_run)
        toolbar.addAction(self.action_cancel)
        toolbar.addAction(self.action_event_menu_button)

    ############################################################
    # action handlers
    ############################################################

    # help...
    @pyqtSlot()
    def help_mp(self):
        path = os.path.abspath("../doc/mp_py_um/mp_py_um.pdf")
        if not os.path.exists(path):
            path = os.path.abspath("pdf/mp_py_um.pdf")
        if os.path.exists(path):
            webbrowser.open("file://%s"%path)
        else:
            self.logger.log("Error: Missing help file.")

    # Keyboard Shortcuts...
    @pyqtSlot()
    def keyboard_shortcuts(self):
        wrapper = KeyboardDialogWrapper(self.w)
        wrapper.show()

    # About MP...
    @pyqtSlot()
    def about_mp(self):
        wrapper = AboutMPDialogWrapper(self.w)
        wrapper.show()

    # Open MP Code file
    @pyqtSlot()
    def select_and_open_mp_code_file(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        filename, _ = QFileDialog.getOpenFileName(self.w,
        "Open MP Code file",
        self.preferred_mp_code_dir,
        "MP Code files (*.mp);;All Files (*)", options=options)

        if filename:
            # remember the preferred path
            head, _tail = os.path.split(filename)
            self.preferred_mp_code_dir = head

            # open the file
            self.open_mp_code(filename)

    # Save MP Code file
    @pyqtSlot()
    def save_mp_code_file(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog

        # suggested filename
        schema_name = self.mp_code_editor.schema_name()
        if not schema_name:
            schema_name = "unnamed"
        suggested_filename = "%s.mp" % schema_name

        mp_code_filename, _ = QFileDialog.getSaveFileName(self.w,
        "Save MP Code file",
        os.path.join(self.preferred_mp_code_dir, suggested_filename),
        "MP Code files (*.mp);;All Files (*)", options=options)

        if mp_code_filename:
            # remember the preferred path
            head, _tail = os.path.split(mp_code_filename)
            self.preferred_mp_code_dir = head

            # save the file
            status = mp_code_io_manager.save_mp_code_file(
                              self.mp_code_editor.text(), mp_code_filename)
            if status:
                # log failure
                self.logger.log(status)
            else:
                # great, exported.
                self.logger.log("Saved to file %s" % mp_code_filename)

    # import Gryphon Graph file
    @pyqtSlot()
    def select_and_import_gry_file(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        filename, _ = QFileDialog.getOpenFileName(self.w,
        "Import Gryphon Graph file",
        self.preferred_gry_file_dir,
        "Gryphon graph files (*.gry);;All Files (*)", options=options)

        if filename:
            # remember the preferred path
            head, _tail = os.path.split(filename)
            self.preferred_gry_file_dir = head

            # import the file
            self.import_gry_file(filename)

    # export Gryphon Graph file
    @pyqtSlot()
    def select_and_export_gry_file(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog

        # suggested filename
        schema_name = self.mp_code_editor.schema_name()
        if not schema_name:
            schema_name = "unnamed"
        suggested_filename = "%s_scope_%d.gry" % (schema_name,
                                              self.scope_spinner.scope())

        filename, _ = QFileDialog.getSaveFileName(self.w,
        "Export Gryphon Graph file",
        os.path.join(self.preferred_gry_file_dir, suggested_filename),
        "Gryphon graph files (*.gry);;All Files (*)", options=options)

        if filename:
            # remember the preferred path
            head, _tail = os.path.split(filename)
            self.preferred_gry_file_dir = head

            # export the file
            self.export_gry_file(filename)

    # export trace as image file
    @pyqtSlot()
    def select_and_export_trace(self):

        graph = self.graph_list_widget.selected_graph()
        if not graph:
            self.logger.log("Error exporting trace: There are no traces")
            return

        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog

        # suggested filename
        schema_name = self.mp_code_editor.schema_name()
        if not schema_name:
            schema_name = "unnamed"
        suggested_filename = "%s_%03d.png" % (schema_name, graph.index)

        filename, _ = QFileDialog.getSaveFileName(self.w,
        "Export trace image file",
        os.path.join(self.preferred_trace_dir, suggested_filename),
        "MP trace image files (*.png);;All Files (*)", options=options)

        if filename:
            # remember the preferred path
            head, _tail = os.path.split(filename)
            self.preferred_trace_dir = head

            # export the trace as image file
            self.export_trace(filename)

    # export traces as image files under path
    @pyqtSlot()
    def select_and_export_all_traces(self):

        if not self.graphs_manager.graphs:
            self.logger.log("Error exporting trace: There are no traces")
            return

        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog

        # suggested filename
        schema_name = self.mp_code_editor.schema_name()
        if not schema_name:
            schema_name = "unnamed"

        filename_prefix, _ = QFileDialog.getSaveFileName(self.w,
          "Export serialized <File name>_nnn.png trace image files",
          os.path.join(self.preferred_trace_dir, schema_name),
          "MP trace image files (*.png);;All Files (*)", options=options)

        if filename_prefix:
            # remember the preferred path
            head, _tail = os.path.split(filename_prefix)
            self.preferred_trace_dir = head

            # export the file
            # export the traces as serialized image files
            self.export_serialized_traces(filename_prefix)

    @pyqtSlot()
    def set_settings_custom(self):
        wrapper = SettingsDialogWrapper(self.w, self.settings_manager)
        wrapper.exec_()

    ############################################################
    # primary interfaces
    ############################################################
    # open MP Code file
    def open_mp_code(self, mp_code_filename):
        status, mp_code_text = mp_code_io_manager.read_mp_code_file(
                                                         mp_code_filename)

        if status:
            # log failure
            self.logger.log(status)
        else:
            # accept request
            self.logger.clear_log()
            self.logger.log("Opened MP Code file %s" % mp_code_filename)
            self.mp_code_editor.set_text(mp_code_text)
            self.graphs_manager.clear_graphs()
            self.trace_navigation.clear()
            self.graph_main_widget.reset_view_orientation()

            # set visual state
            self.set_is_compiling(False)

            # no trace nav until after Run
            self.trace_navigation.setDisabled(True)

    # Close MP Code file
    @pyqtSlot()
    def close_mp_code(self):

        # accept request
        self.logger.clear_log()
        self.mp_code_editor.set_text("")
        self.graphs_manager.clear_graphs()
        self.trace_navigation.clear()
        self.graph_main_widget.reset_view_orientation()

        # no trace nav until after Open and Run
        self.trace_navigation.setDisabled(True)

    # compile MP Code
    @pyqtSlot()
    def request_compile_mp_code(self):
        # compile
        scope = self.scope_spinner.scope()
        schema_name = self.mp_code_editor.schema_name()
        mp_code_text = self.mp_code_editor.text()

        # set visual state
        self.set_is_compiling(True)

        self.logger.log("Compiling %s..." % schema_name)
        self.trace_generator_manager.mp_compile(
                                         schema_name, scope, mp_code_text)

    # receive response from trace generator manager request to compile MP Code
    @pyqtSlot(str, str, str)
    def response_compile_mp_code(self, status, generated_json_text, log):

        # log the compilation log
        self.logger.log(log)

        if status:
            # log failure
            self.logger.log(status)

            # set line number
            line_number = parse_error_line_number(status)
            self.mp_code_editor.set_error_line_number(line_number)

        else:
            # accept request
            status, global_view_specification, graphs = \
                    mp_code_io_manager.read_generated_json(generated_json_text)

            if status:
                # log failure
                self.logger.log(status)

                # clear graphs
                self.graphs_manager.clear_graphs()
                self.trace_navigation.clear()

                # no trace nav until after Open and Run
                self.trace_navigation.setDisabled(True)

            else:
                # accept graphs
                scope = self.scope_spinner.scope()
                schema_name = self.mp_code_editor.schema_name()
                self.graphs_manager.set_graphs(schema_name, scope, graphs)
                self.logger.log("Compiled %s" % schema_name)

                # select graph at first row
                # ref. https://stackoverflow.com/questions/6925951/how-to-select-a-row-in-a-qlistview
                if graphs:
                    self.graph_list_widget.select_graph(graphs[0])

        # set visual state
        self.set_is_compiling(False)

    # cancel compile MP Code
    @pyqtSlot()
    def cancel_compile_mp_code(self):
        # cancel
        schema_name = self.mp_code_editor.schema_name()
        self.logger.log("Canceling compiling %s" % schema_name)
        self.trace_generator_manager.mp_cancel_compile()

    # import Gryphon graph file
    def import_gry_file(self, gry_file_filename):
        self.logger.log("Importing Gryphon Graph file %s..." %
                                                          gry_file_filename)
        status, mp_code_text, scope, selected_index, \
                scale, x_slider, y_slider, graphs = \
                mp_code_io_manager.import_gry_file(gry_file_filename)

        if status:
            # log failure
            self.logger.log(status)

        else:
            # accept import request
            self.logger.clear_log()

            # set view to match JSON graph specifications
            self.mp_code_editor.set_text(mp_code_text)
            schema_name = self.mp_code_editor.schema_name()
            self.graphs_manager.set_graphs(schema_name, scope, graphs)
            self.graph_list_widget.select_graph(self.graphs_manager.find_graph(
                                                             selected_index))
            self.graph_main_widget.set_view_orientation(scale,
                                                       x_slider, y_slider)
            self.logger.log("Imported Gryphon Graph file %s" %
                                                       gry_file_filename)

            # set visual state
            self.set_is_compiling(False)

            # enable trace nav
            self.trace_navigation.setDisabled(False)

    # export Gryphon Graph file
    def export_gry_file(self, gry_file_filename):
        scale, x_slider, y_slider = \
                            self.graph_main_widget.get_view_orientation()
        if self.graph_list_widget.selected_graph():
            index = self.graph_list_widget.selected_graph().index
        else:
            index = 0
        status = mp_code_io_manager.export_gry_file(
                    gry_file_filename,
                    self.mp_code_editor.text(),
                    self.scope_spinner.scope(),
                    index,
                    scale, x_slider, y_slider,
                    self.graphs_manager.graphs)

        if status:
            # log failure
            self.logger.log(status)

        else:
            # great, exported.
            self.logger.log("Exported Gryphon Graph file %s" %
                                                        gry_file_filename)

    # export trace as image file
    def export_trace(self, trace_filename):
        status = export_trace_manager.export_trace(
                    trace_filename,
                    self.graph_list_widget.selected_graph())

        if status:
            # log failure
            self.logger.log(status)

        else:
            # great, exported.
            self.logger.log("Exported trace image file %s" % trace_filename)

    # export all traces as image files
    def export_serialized_traces(self, trace_filename_prefix):
        for graph in self.graphs_manager.graphs:
            trace_filename = "%s_%03d.png" % (trace_filename_prefix,
                                              graph.index)

            status = export_trace_manager.export_trace(
                        trace_filename, graph)

            if status:
                # log failure
                self.logger.log("%s.  Aborting." % status)
                break

            else:
                # great, exported.
                self.logger.log("Exported trace image file %s" % trace_filename)

    # run and cancel button state
    def set_is_compiling(self, is_compiling):

        # run and cancel
        self.action_run.setDisabled(is_compiling)
        self.action_cancel.setDisabled(not is_compiling)

        # trace navigation widgets
        self.trace_navigation.setDisabled(is_compiling)

        # other actions and menus
        self.action_open_mp_code_file.setDisabled(is_compiling)
        self.action_close_mp_code_file.setDisabled(is_compiling)
        self.open_examples_menu.setDisabled(is_compiling)
        self.action_import_gry_file.setDisabled(is_compiling)
        self.action_export_gry_file.setDisabled(is_compiling)
        self.action_export_trace.setDisabled(is_compiling)
        self.action_export_all_traces.setDisabled(is_compiling)

        # note: these two are disabled when compiling because the
        # local trace generator changes the local directory, which
        # makes these fail.
        self.action_help.setDisabled(is_compiling)
        self.action_about.setDisabled(is_compiling)

        # disable the code editor so it does not fire a contentsChanged event
        # which would trigger the syntax checker, which is not assynchronous.
        self.mp_code_editor.code_editor.setDisabled(is_compiling)

    # send a line of text to the log pane
    def write_log(self, log_line):
        self.logger.log(log_line)