class gtoRemote(QObject):
    def __init__(self, gtomain, parent=None):
        try:
            super(gtoRemote, self).__init__(parent)
            self.gtomain = gtomain
            self.iface = gtomain.iface
            self.debug = gtomain.debug
            self.info = gtoInfo(self)
            self.fs_watcher = None

            if 'remote_watch_file' in gtomain.settings:
                remote_watch_path = gtomain.settings.get(
                    'remote_watch_file', None)
                if remote_watch_path is not None and remote_watch_path != "":
                    remote_watch_path = self.gtomain.helper.getFilePath(
                        remote_watch_path)
                    self.remote_watch_file = os.path.basename(
                        remote_watch_path)
                    self.remote_watch_dir = os.path.dirname(remote_watch_path)

                    if self.debug:
                        self.info.log("Watching:", self.remote_watch_dir)
                    if not os.path.exists(self.remote_watch_dir):
                        os.makedirs(self.remote_watch_dir)
                        if self.debug:
                            self.info.log("Created:", self.remote_watch_dir)

                    self.paths = [self.remote_watch_dir]
                    self.fs_watcher = QFileSystemWatcher(self.paths)
                    #if file already exists
                    self.directory_changed(self.paths[0])
                    self.fs_watcher.directoryChanged.connect(
                        self.directory_changed)
                    self.fs_watcher.fileChanged.connect(self.file_changed)
        except Exception as e:
            self.info.err(e)

    def unload(self):
        try:
            if self.fs_watcher is not None:
                self.fs_watcher.directoryChanged.disconnect()
                self.fs_watcher.fileChanged.disconnect()
                self.fs_watcher = None
        except Exception as e:
            self.info.err(e)

    def directory_changed(self, path):
        try:
            if self.debug: self.info.log('Directory Changed: %s' % path)
            for f in os.listdir(path):
                #self.info.log(os.listdir(path))
                #if (self.debug and f.lower().endswith(".json")) or f.lower() == self.remote_watch_file.lower():
                if f.lower() == self.remote_watch_file.lower():
                    if self.debug: self.info.log("Execute", f.lower())
                    self.fs_watcher.blockSignals(True)
                    self.excuteCommand(path, f)
                    self.fs_watcher.blockSignals(False)
        except Exception as e:
            self.info.err(e)

    def file_changed(self, path):
        try:
            if self.debug: self.info.log('File Changed: %s' % path)
        except Exception as e:
            self.info.err(e)

    def excuteCommand(self, path, f):
        try:
            if self.debug: self.info.log('excute command')
            filename = path + '/' + f
            #time.sleep(0.5)
            f = io.open(filename, encoding='utf-8')
            jdata = json.load(f)
            f.close()

            res = True
            for cmd in jdata['commands']:
                if self.debug: self.info.log(cmd)
                method = getattr(self, cmd['ecommand'])
                res = res and (method(self.gtomain, self.debug, **
                                      cmd['config']))
            if self.debug: self.info.log('result:', res)
            if res:
                os.remove(filename)
        except Exception as e:
            self.info.err(e)

    def writeRemoteFile(self, jdata, prefix=''):
        try:
            if self.debug: self.info.log('writeRemoteFile:', 'data:', jdata)
            remotefile = self.gtomain.settings['remote_file']
            remotefile = self.gtomain.helper.getFilePath(remotefile, True)
            remotefile = '%s%s' % (remotefile, prefix)
            if self.debug: self.info.log("remotefile", remotefile)
            # write the file
            # from io import StringIO
            # io = StringIO()
            # json.dump(jdata,io,ensure_ascii=False, sort_keys=True,indent=4)
            # io.close()
            with open(remotefile, 'w', encoding='utf8') as outfile:
                sort = True
                #simplejson.dump(jdata, outfile,ensure_ascii=False, sort_keys=sort,indent=4)#,encoding='utf8')#.encode('utf8')
                json.dump(jdata,
                          outfile,
                          ensure_ascii=False,
                          sort_keys=sort,
                          indent=4)  # ,encoding='utf8')#.encode('utf8')
            # import pickle
            # with open(remotefile, 'wb') as f:
            #     pickle.dump(jdata,f)

            #activate/start remote app
            if self.debug:
                self.info.log('writeRemoteFile:', 'settings:',
                              self.gtomain.settings)
            remote_app_file = self.gtomain.settings['remote_app_file']
            remote_app_title = self.gtomain.settings['remote_app_title']
            if os.name == 'nt':
                try:
                    from .gto_windows import ActivateApp
                    ActivateApp(self.gtomain, self.debug, remote_app_title,
                                remote_app_file)
                except Exception as e:
                    self.info.err(e)
            else:
                os.startfile(remote_app_file)

        except Exception as e:
            self.info.err(e)

    def getLayerByName(self, layername):
        try:
            layers = QgsProject.instance().mapLayersByName(layername)
            if layers:
                return layers[0]  # duplicte names => take the first
            else:
                return None
        except Exception as e:
            self.info.err(e)

    def getFeatures(self, gtoobj, debug, **kwargs):
        try:
            if self.debug: self.info.log('getFeatures:', 'parameters:', kwargs)
            layername = kwargs['objectclass']
            layer = self.getLayerByName(layername)
            request = QgsFeatureRequest()
            if 'whereclause' in kwargs:
                whereclause = kwargs['whereclause']
                request.setFilterExpression(whereclause)
            elif 'attribute' in kwargs:
                attribute = kwargs['attribute']
                values = kwargs['data']
                expr_in = ''
                for v in values:
                    expr_in = expr_in + '%s ,' % str(v)
                expr_in = expr_in[:-1]
                expr = '"' + attribute + '" IN (%s)' % expr_in
                if self.debug: self.info.log("expr: %s" % expr)
                request.setFilterExpression(expr)
            features = layer.getFeatures(request)
            ids = [f.id() for f in features]
            if self.debug: self.info.log("res from request ids:", layer, ids)
            return layer, ids
        except Exception as e:
            gtoobj.info.err(e)

    def ZOOMTOFEATURESET(self, gtoobj, debug, **kwargs):
        try:
            scale = kwargs.get('scale', 0)

            iface = gtoobj.iface
            layer, ids = self.getFeatures(gtoobj, debug, **kwargs)

            iface.setActiveLayer(layer)
            prj = QgsProject.instance()
            prj.layerTreeRoot().findLayer(
                layer.id()).setItemVisibilityCheckedParentRecursive(True)
            #legend.setCurrentLayer(layer)
            #legend.setLayerVisible(layer, True)

            layer.selectByIds(ids)
            iface.mapCanvas().zoomToSelected()
            if scale > 0:
                iface.mapCanvas().zoomScale(scale)
            return True
        except Exception as e:
            gtoobj.info.err(e)

    def SETSELECTSET(self, gtoobj, debug, **kwargs):
        try:
            iface = gtoobj.iface
            layer, ids = self.getFeatures(gtoobj, debug, **kwargs)
            layer.removeSelection()
            layer.selectByIds(ids)
            self.iface.mapCanvas().refresh()
            return True
        except Exception as e:
            gtoobj.info.err(e)

    def GETCOORDINATE(self, gtoobj, debug, **kwargs):
        try:
            objclass = kwargs['objectclass']
            id = kwargs['id']
            esubcommand = kwargs['esubcommand']
            iface = gtoobj.iface
            from qgis.gui import QgsMapToolEmitPoint

            # create tool
            prevTool = iface.mapCanvas().mapTool()
            curTool = QgsMapToolEmitPoint(iface.mapCanvas())

            def on_click(coordinate, clickedMouseButton):
                if debug: gtoobj.info.log("Coordinate:", coordinate)
                if clickedMouseButton == Qt.LeftButton:
                    if esubcommand == 'GETCOORDINATE_ID':
                        #jdata = {"commands": [{"ecommand": "SETCOORDINATE", "config": {"esubcommand": "SETCOORDINATE_ID","objectclass": objclass.encode('utf8'),"id":id, "x": round( coordinate.x(),3),"y":round(coordinate.y(),3)}}]}
                        jdata = {
                            "commands": [{
                                "ecommand": "SETCOORDINATE",
                                "config": {
                                    "esubcommand": "SETCOORDINATE_ID",
                                    "objectclass": objclass,
                                    "id": id,
                                    "x": round(coordinate.x(), 3),
                                    "y": round(coordinate.y(), 3)
                                }
                            }]
                        }
                        self.writeRemoteFile(jdata)
                        if debug:
                            self.info.log("Set prev tool:",
                                          prevTool.toolName())
                        if prevTool is curTool:
                            iface.mapCanvas().setMapTool(None)
                        else:
                            iface.mapCanvas().setMapTool(prevTool)
                    else:
                        if debug:
                            self.info.log('Unknown esubcommand:', esubcommand)

            def tool_changed(tool):  # another tool was activated
                iface.mapCanvas().mapToolSet.disconnect(tool_changed)
                #curTool.deleteLater()

            curTool.canvasClicked.connect(on_click)
            iface.mapCanvas().setMapTool(curTool)
            iface.mapCanvas().mapToolSet.connect(tool_changed)
            return True
        except Exception as e:
            gtoobj.info.err(e)

    def getSelectedFeatures(self, gtoobj, debug, layer, attribute):
        try:
            data = []
            for f in layer.selectedFeatures():
                val = f[attribute]
                try:
                    if int(val) == val: val = int(val)
                except:
                    pass
                data.append("%s" % str(val))
            return data
        except Exception as e:
            gtoobj.info.err(e)
Exemple #2
0
class Editor(CodeEditor, ComponentMixin):

    name = 'Code Editor'

    # This signal is emitted whenever the currently-open file changes and
    # autoreload is enabled.
    triggerRerender = pyqtSignal(bool)
    sigFilenameChanged = pyqtSignal(str)

    preferences = Parameter.create(name='Preferences',
                                   children=[{
                                       'name': 'Font size',
                                       'type': 'int',
                                       'value': 12
                                   }, {
                                       'name': 'Autoreload',
                                       'type': 'bool',
                                       'value': False
                                   }, {
                                       'name': 'Autoreload delay',
                                       'type': 'int',
                                       'value': 50
                                   }, {
                                       'name':
                                       'Autoreload: watch imported modules',
                                       'type': 'bool',
                                       'value': False
                                   }, {
                                       'name': 'Line wrap',
                                       'type': 'bool',
                                       'value': False
                                   }, {
                                       'name':
                                       'Color scheme',
                                       'type':
                                       'list',
                                       'values':
                                       ['Spyder', 'Monokai', 'Zenburn'],
                                       'value':
                                       'Spyder'
                                   }])

    EXTENSIONS = 'py'

    def __init__(self, parent=None):

        self._watched_file = None

        super(Editor, self).__init__(parent)
        ComponentMixin.__init__(self)

        self.setup_editor(linenumbers=True,
                          markers=True,
                          edge_line=False,
                          tab_mode=False,
                          show_blanks=True,
                          font=QFontDatabase.systemFont(
                              QFontDatabase.FixedFont),
                          language='Python',
                          filename='')

        self._actions =  \
                {'File' : [QAction(icon('new'),
                                  'New',
                                  self,
                                  shortcut='ctrl+N',
                                  triggered=self.new),
                          QAction(icon('open'),
                                  'Open',
                                  self,
                                  shortcut='ctrl+O',
                                  triggered=self.open),
                          QAction(icon('save'),
                                  'Save',
                                  self,
                                  shortcut='ctrl+S',
                                  triggered=self.save),
                          QAction(icon('save_as'),
                                  'Save as',
                                  self,
                                  shortcut='ctrl+shift+S',
                                  triggered=self.save_as),
                          QAction(icon('autoreload'),
                                  'Automatic reload and preview',
                                  self,triggered=self.autoreload,
                                  checkable=True,
                                  checked=False,
                                  objectName='autoreload'),
                          ]}

        for a in self._actions.values():
            self.addActions(a)

        self._fixContextMenu()

        # autoreload support
        self._file_watcher = QFileSystemWatcher(self)
        # we wait for 50ms after a file change for the file to be written completely
        self._file_watch_timer = QTimer(self)
        self._file_watch_timer.setInterval(
            self.preferences['Autoreload delay'])
        self._file_watch_timer.setSingleShot(True)
        self._file_watcher.fileChanged.connect(
            lambda val: self._file_watch_timer.start())
        self._file_watch_timer.timeout.connect(self._file_changed)

        self.updatePreferences()

    def _fixContextMenu(self):

        menu = self.menu

        menu.removeAction(self.run_cell_action)
        menu.removeAction(self.run_cell_and_advance_action)
        menu.removeAction(self.run_selection_action)
        menu.removeAction(self.re_run_last_cell_action)

    def updatePreferences(self, *args):

        self.set_color_scheme(self.preferences['Color scheme'])

        font = self.font()
        font.setPointSize(self.preferences['Font size'])
        self.set_font(font)

        self.findChild(QAction, 'autoreload') \
            .setChecked(self.preferences['Autoreload'])

        self._file_watch_timer.setInterval(
            self.preferences['Autoreload delay'])

        self.toggle_wrap_mode(self.preferences['Line wrap'])

        self._clear_watched_paths()
        self._watch_paths()

    def confirm_discard(self):

        if self.modified:
            rv = confirm(
                self, 'Please confirm',
                'Current document is not saved - do you want to continue?')
        else:
            rv = True

        return rv

    def new(self):

        if not self.confirm_discard(): return

        self.set_text('')
        self.filename = ''
        self.reset_modified()

    def open(self):

        if not self.confirm_discard(): return

        curr_dir = Path(self.filename).abspath().dirname()
        fname = get_open_filename(self.EXTENSIONS, curr_dir)
        if fname != '':
            self.load_from_file(fname)

    def load_from_file(self, fname):

        self.set_text_from_file(fname)
        self.filename = fname
        self.reset_modified()

    def save(self):

        if self._filename != '':

            if self.preferences['Autoreload']:
                self._file_watcher.blockSignals(True)
                self._file_watch_timer.stop()

            with open(self._filename, 'w') as f:
                f.write(self.toPlainText())

            if self.preferences['Autoreload']:
                self._file_watcher.blockSignals(False)
                self.triggerRerender.emit(True)

            self.reset_modified()

        else:
            self.save_as()

    def save_as(self):

        fname = get_save_filename(self.EXTENSIONS)
        if fname != '':
            with open(fname, 'w') as f:
                f.write(self.toPlainText())
                self.filename = fname

            self.reset_modified()

    def _update_filewatcher(self):
        if self._watched_file and (self._watched_file != self.filename
                                   or not self.preferences['Autoreload']):
            self._clear_watched_paths()
            self._watched_file = None
        if self.preferences[
                'Autoreload'] and self.filename and self.filename != self._watched_file:
            self._watched_file = self._filename
            self._watch_paths()

    @property
    def filename(self):
        return self._filename

    @filename.setter
    def filename(self, fname):
        self._filename = fname
        self._update_filewatcher()
        self.sigFilenameChanged.emit(fname)

    def _clear_watched_paths(self):
        paths = self._file_watcher.files()
        if paths:
            self._file_watcher.removePaths(paths)

    def _watch_paths(self):
        if Path(self._filename).exists():
            self._file_watcher.addPath(self._filename)
            if self.preferences['Autoreload: watch imported modules']:
                module_paths = self.get_imported_module_paths(self._filename)
                if module_paths:
                    self._file_watcher.addPaths(module_paths)

    # callback triggered by QFileSystemWatcher
    def _file_changed(self):
        # neovim writes a file by removing it first so must re-add each time
        self._watch_paths()
        self.set_text_from_file(self._filename)
        self.triggerRerender.emit(True)

    # Turn autoreload on/off.
    def autoreload(self, enabled):
        self.preferences['Autoreload'] = enabled
        self._update_filewatcher()

    def reset_modified(self):

        self.document().setModified(False)

    @property
    def modified(self):

        return self.document().isModified()

    def saveComponentState(self, store):

        if self.filename != '':
            store.setValue(self.name + '/state', self.filename)

    def restoreComponentState(self, store):

        filename = store.value(self.name + '/state')

        if filename and self.filename == '':
            try:
                self.load_from_file(filename)
            except IOError:
                self._logger.warning(f'could not open {filename}')

    def get_imported_module_paths(self, module_path):

        finder = ModuleFinder([os.path.dirname(module_path)])
        imported_modules = []

        try:
            finder.run_script(module_path)
        except SyntaxError as err:
            self._logger.warning(f'Syntax error in {module_path}: {err}')
        except Exception as err:
            self._logger.warning(
                f'Cannot determine imported modules in {module_path}: {type(err).__name__} {err}'
            )
        else:
            for module_name, module in finder.modules.items():
                if module_name != '__main__':
                    path = getattr(module, '__file__', None)
                    if path is not None and os.path.isfile(path):
                        imported_modules.append(path)

        return imported_modules
Exemple #3
0
class MainWindow(QMainWindow):
    OnlineHelpUrl = QUrl("https://eth-cscs.github.io/serialbox2/sdb.html")

    def __init__(self):
        super().__init__()
        Logger.info("Setup main window")

        self.__input_serializer_data = SerializerData("Input Serializer")
        self.__input_stencil_data = StencilData(self.__input_serializer_data)

        self.__reference_serializer_data = SerializerData(
            "Reference Serializer")
        self.__reference_stencil_data = StencilData(
            self.__reference_serializer_data)

        self.__stencil_field_mapper = StencilFieldMapper(
            self.__input_stencil_data, self.__reference_stencil_data,
            GlobalConfig()["async"])

        self.__file_system_watcher = QFileSystemWatcher()
        self.__file_system_watcher.directoryChanged[str].connect(
            self.popup_reload_box)
        self.__file_system_watcher_last_modify = time()

        # Load from session?
        self.__session_manager = SessionManager()

        if GlobalConfig()["default_session"]:
            self.__session_manager.load_from_file()

        self.__session_manager.set_serializer_data(
            self.__input_serializer_data)
        self.__session_manager.set_serializer_data(
            self.__reference_serializer_data)

        # Setup GUI
        self.setWindowTitle('sdb - stencil debugger (%s)' %
                            Version().sdb_version())
        self.resize(1200, 600)

        if GlobalConfig()["center_window"]:
            self.center()

        if GlobalConfig()["move_window"]:
            self.move(GlobalConfig()["move_window"])

        self.setWindowIcon(Icon("logo-small.png"))
        self.init_menu_tool_bar()

        # Tabs
        self.__tab_highest_valid_state = TabState.Setup
        self.__widget_tab = QTabWidget(self)

        # Setup tab
        self.__widget_tab.addTab(
            SetupWindow(self, self.__input_serializer_data,
                        self.__reference_serializer_data), "Setup")

        # Stencil tab
        self.__widget_tab.addTab(
            StencilWindow(self, self.__stencil_field_mapper,
                          self.__input_stencil_data,
                          self.__reference_stencil_data), "Stencil")

        # Result tab
        self.__widget_tab.addTab(
            ResultWindow(self,
                         self.__widget_tab.widget(TabState.Stencil.value),
                         self.__stencil_field_mapper), "Result")

        # Error tab
        self.__widget_tab.addTab(ErrorWindow(self), "Error")

        self.__widget_tab.currentChanged.connect(self.switch_to_tab)

        self.__widget_tab.setTabEnabled(TabState.Setup.value, True)
        self.__widget_tab.setTabEnabled(TabState.Stencil.value, False)
        self.__widget_tab.setTabEnabled(TabState.Result.value, False)
        self.__widget_tab.setTabEnabled(TabState.Error.value, False)

        self.__widget_tab.setTabToolTip(TabState.Setup.value,
                                        "Setup Input and Refrence Serializer")
        self.__widget_tab.setTabToolTip(
            TabState.Stencil.value,
            "Set the stencil to compare and define the mapping of the fields")
        self.__widget_tab.setTabToolTip(TabState.Result.value,
                                        "View to comparison result")
        self.__widget_tab.setTabToolTip(
            TabState.Error.value,
            "Detailed error desscription of the current field")

        self.__tab_current_state = TabState.Setup
        self.set_tab_highest_valid_state(TabState.Setup)
        self.switch_to_tab(TabState.Setup)

        self.setCentralWidget(self.__widget_tab)

        # If the MainWindow is closed, kill all popup windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        Logger.info("Starting main loop")
        self.show()

    def init_menu_tool_bar(self):
        Logger.info("Setup menu toolbar")

        action_exit = QAction("Exit", self)
        action_exit.setShortcut("Ctrl+Q")
        action_exit.setStatusTip("Exit the application")
        action_exit.triggered.connect(self.close)

        action_about = QAction("&About", self)
        action_about.setStatusTip("Show the application's About box")
        action_about.triggered.connect(self.popup_about_box)

        action_save_session = QAction(Icon("filesave.png"), "&Save", self)
        action_save_session.setStatusTip("Save current session")
        action_save_session.setShortcut("Ctrl+S")
        action_save_session.triggered.connect(self.save_session)

        action_open_session = QAction(Icon("fileopen.png"), "&Open", self)
        action_open_session.setShortcut("Ctrl+O")
        action_open_session.setStatusTip("Open session")
        action_open_session.triggered.connect(self.open_session)

        action_help = QAction(Icon("help.png"), "&Online Help", self)
        action_help.setStatusTip("Online Help")
        action_help.setToolTip("Online Help")
        action_help.triggered.connect(self.go_to_online_help)

        self.__action_continue = QAction(Icon("next_cursor.png"), "Continue",
                                         self)
        self.__action_continue.setStatusTip("Continue to next tab")
        self.__action_continue.triggered.connect(self.switch_to_next_tab)
        self.__action_continue.setEnabled(True)

        self.__action_back = QAction(Icon("prev_cursor.png"), "Back", self)
        self.__action_back.setStatusTip("Back to previous tab")
        self.__action_back.triggered.connect(self.switch_to_previous_tab)
        self.__action_back.setEnabled(False)

        self.__action_reload = QAction(Icon("step_in.png"), "Reload", self)
        self.__action_reload.setStatusTip(
            "Reload Input and Reference Serializer")
        self.__action_reload.setShortcut("Ctrl+R")
        self.__action_reload.triggered.connect(self.reload_serializer)
        self.__action_reload.setEnabled(False)

        self.__action_try_switch_to_error_tab = QAction(
            Icon("visualize.png"), "Detailed error description", self)
        self.__action_try_switch_to_error_tab.setStatusTip(
            "Detailed error desscription of the current field")
        self.__action_try_switch_to_error_tab.triggered.connect(
            self.try_switch_to_error_tab)
        self.__action_try_switch_to_error_tab.setEnabled(False)

        menubar = self.menuBar()
        menubar.setNativeMenuBar(False)
        self.statusBar()

        file_menu = menubar.addMenu('&File')
        file_menu.addAction(action_open_session)
        file_menu.addAction(action_save_session)
        file_menu.addAction(action_exit)

        edit_menu = menubar.addMenu('&Edit')
        edit_menu.addAction(self.__action_back)
        edit_menu.addAction(self.__action_continue)
        edit_menu.addAction(self.__action_reload)

        help_menu = menubar.addMenu('&Help')
        help_menu.addAction(action_about)
        help_menu.addAction(action_help)

        toolbar = self.addToolBar("Toolbar")
        toolbar.addAction(action_help)
        toolbar.addAction(action_open_session)
        toolbar.addAction(action_save_session)
        toolbar.addAction(self.__action_back)
        toolbar.addAction(self.__action_continue)
        toolbar.addAction(self.__action_reload)
        toolbar.addAction(self.__action_try_switch_to_error_tab)

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def closeEvent(self, event):
        self.__session_manager.update_serializer_data(
            self.__input_serializer_data)
        self.__session_manager.update_serializer_data(
            self.__reference_serializer_data)

        if GlobalConfig()["default_session"]:
            self.__session_manager.store_to_file()

    # ===----------------------------------------------------------------------------------------===
    #   TabWidgets
    # ==-----------------------------------------------------------------------------------------===

    def tab_widget(self, idx):
        return self.__widget_tab.widget(
            idx if not isinstance(idx, TabState) else idx.value)

    def switch_to_tab(self, tab):
        idx = tab.value if isinstance(tab, TabState) else tab
        if self.__tab_current_state == TabState(idx):
            return

        Logger.info("Switching to %s tab" % TabState(idx).name)
        self.__tab_current_state = TabState(idx)

        self.__widget_tab.setCurrentIndex(idx)
        self.tab_widget(idx).make_update()

        self.__action_try_switch_to_error_tab.setEnabled(
            TabState(idx) == TabState.Result)

        # Error tab is always disabled if not in "Error"
        self.__widget_tab.setTabEnabled(TabState.Error.value,
                                        TabState(idx) == TabState.Error)

        # First tab
        if idx == 0:
            self.__action_continue.setEnabled(True)
            self.__action_back.setEnabled(False)
        # Last tab
        elif idx == self.__widget_tab.count() - 1:
            self.__action_continue.setEnabled(False)
            self.__action_back.setEnabled(True)
        # Middle tab
        else:
            self.__action_continue.setEnabled(True)
            self.__action_back.setEnabled(True)

    def set_tab_highest_valid_state(self, state):
        """Set the state at which the data is valid i.e everything <= self.valid_tab_state is valid
        """
        self.__tab_highest_valid_state = state
        self.enable_tabs_according_to_tab_highest_valid_state()

    def enable_tabs_according_to_tab_highest_valid_state(self):
        """Enable/Disable tabs according to self.__tab_highest_valid_state
        """
        if self.__tab_highest_valid_state == TabState.Setup:
            self.__widget_tab.setTabEnabled(TabState.Setup.value, True)
            self.__widget_tab.setTabEnabled(TabState.Stencil.value, False)
            self.__widget_tab.setTabEnabled(TabState.Result.value, False)
            self.__widget_tab.setTabEnabled(TabState.Error.value, False)

            self.__action_try_switch_to_error_tab.setEnabled(False)

            watched_directories = self.__file_system_watcher.directories()
            if watched_directories:
                self.__file_system_watcher.removePaths(
                    self.__file_system_watcher.directories())

        elif self.__tab_highest_valid_state == TabState.Stencil:

            self.__file_system_watcher.addPath(
                self.__input_serializer_data.serializer.directory)
            self.__file_system_watcher.addPath(
                self.__reference_stencil_data.serializer.directory)

            self.__widget_tab.setTabEnabled(TabState.Setup.value, True)
            self.__widget_tab.setTabEnabled(TabState.Stencil.value, True)
            self.__widget_tab.setTabEnabled(TabState.Result.value, False)
            self.__widget_tab.setTabEnabled(TabState.Error.value, False)

            self.__widget_tab.widget(
                TabState.Stencil.value).initial_field_match()

            self.__action_reload.setEnabled(True)
            self.__action_try_switch_to_error_tab.setEnabled(False)

        elif self.__tab_highest_valid_state == TabState.Result:
            self.__widget_tab.setTabEnabled(TabState.Setup.value, True)
            self.__widget_tab.setTabEnabled(TabState.Stencil.value, True)
            self.__widget_tab.setTabEnabled(TabState.Result.value, True)
            self.__widget_tab.setTabEnabled(TabState.Error.value, True)

            self.__action_try_switch_to_error_tab.setEnabled(True)

        elif self.__tab_highest_valid_state == TabState.Error:
            self.__widget_tab.setTabEnabled(TabState.Setup.value, True)
            self.__widget_tab.setTabEnabled(TabState.Stencil.value, True)
            self.__widget_tab.setTabEnabled(TabState.Result.value, True)
            self.__action_try_switch_to_error_tab.setEnabled(False)

    def switch_to_next_tab(self):
        self.__widget_tab.currentWidget().make_continue()

    def switch_to_previous_tab(self):
        self.__widget_tab.currentWidget().make_back()

    def try_switch_to_error_tab(self):
        if self.__widget_tab.widget(
                TabState.Result.value).try_switch_to_error_tab():
            self.__widget_tab.setTabEnabled(TabState.Error.value, True)

    def error_window_set_result_data(self, result_data):
        self.__widget_tab.widget(
            TabState.Error.value).set_result_data(result_data)

    # ===----------------------------------------------------------------------------------------===
    #   PopupWidgets
    # ==-----------------------------------------------------------------------------------------===

    def popup_about_box(self):
        self.__about_widget = PopupAboutWidget(self)

    def popup_error_box(self, msg):
        Logger.error(
            msg.replace("<b>",
                        "").replace("</b>",
                                    "").replace("<br />",
                                                ":").replace("<br/>", ":"))

        msg_box = QMessageBox()
        msg_box.setWindowTitle("Error")
        msg_box.setIcon(QMessageBox.Critical)
        msg_box.setText(msg)
        msg_box.setStandardButtons(QMessageBox.Ok)
        reply = msg_box.exec_()  # Blocking

    def popup_reload_box(self, path):
        self.__file_system_watcher.blockSignals(True)
        reply = QMessageBox.question(
            self, "Reload serializer?",
            "The path \"%s\" has changed.\nDo want to reload the serializers?"
            % path, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if reply == QMessageBox.Yes:
            self.reload_serializer()

        self.__file_system_watcher.blockSignals(False)

    # ===----------------------------------------------------------------------------------------===
    #   Session manager
    # ==-----------------------------------------------------------------------------------------===

    def save_session(self):
        Logger.info("Try saving current session")

        dialog = QFileDialog(self, "Save current session")
        dialog.setAcceptMode(QFileDialog.AcceptSave)
        dialog.setDefaultSuffix("json")
        dialog.setDirectory(getcwd())

        if not dialog.exec_():
            Logger.info("Abort saving current session")
            return

        filename = dialog.selectedFiles()
        self.__session_manager.update_serializer_data(
            self.__input_serializer_data)
        self.__session_manager.update_serializer_data(
            self.__reference_serializer_data)

        ret, msglist = self.__session_manager.store_to_file(filename[0])
        if not ret:
            self.popup_error_box("Failed to save configuration file: %s\n%s " %
                                 (filename[0], msglist[0]))

    def open_session(self):
        Logger.info("Try opening session")
        filename = QFileDialog.getOpenFileName(
            self, "Open Session", getcwd(), "JSON configuration (*.json)")[0]

        if filename is None or filename is "":
            Logger.info("Abort opening session")
            return

        ret, msglist = self.__session_manager.load_from_file(filename)
        if not ret:
            self.popup_error_box("Failed to load configuration file: %s\n%s " %
                                 (filename, msglist[0]))
        else:
            Logger.info("Successfully opened session")
            self.__session_manager.set_serializer_data(
                self.__input_serializer_data)
            self.__session_manager.set_serializer_data(
                self.__reference_serializer_data)
            self.switch_to_tab(TabState.Setup)

    @property
    def session_manager(self):
        return self.__session_manager

    # ===----------------------------------------------------------------------------------------===
    #   Reload Serializer
    # ==-----------------------------------------------------------------------------------------===

    def reload_serializer(self):
        Logger.info("Reloading serializers")
        try:
            self.__input_serializer_data.reload()
            self.__reference_serializer_data.reload()

            if self.__widget_tab.currentIndex() == TabState.Error.value:
                self.switch_to_tab(TabState.Result)

            self.__widget_tab.currentWidget().make_update()

        except RuntimeError as e:
            self.popup_error_box(str(e))
            self.set_tab_highest_valid_state(TabState.Setup)
            self.switch_to_tab(TabState.Setup)
            self.__widget_tab.currentWidget().make_update()

    # ===----------------------------------------------------------------------------------------===
    #   Online help
    # ==-----------------------------------------------------------------------------------------===

    def go_to_online_help(self):
        Logger.info("Opening online help")
        QDesktopServices.openUrl(MainWindow.OnlineHelpUrl)