class FileSystemWatcher(QObject):
    fileChanged = pyqtSignal(str)
    directoryChanged = pyqtSignal(str)

    def __init__(self, parent = None):
        super().__init__(parent)

        self.mWatchCount = QMap()
        self.mWatcher = QFileSystemWatcher(self)
        self.mWatcher.fileChanged.connect(self.onFileChanged)
        self.mWatcher.directoryChanged.connect(self.onDirectoryChanged)

    def addPath(self, path):
        # Just silently ignore the request when the file doesn't exist
        if (not QFile.exists(path)):
            return
        entry = self.mWatchCount.find(path)
        if not entry:
            self.mWatcher.addPath(path)
            self.mWatchCount.insert(path, 1)
        else:
            # Path is already being watched, increment watch count
            self.mWatchCount[path] += 1

    def removePath(self, path):
        entry = self.mWatchCount.find(path)
        if (entry == self.mWatchCount.end()):
            if (QFile.exists(path)):
                qWarning("FileSystemWatcher: Path was never added:\n"+path)
            return

        # Decrement watch count
        entry -= 1
        self.mWatchCount[path] = entry
        if (entry == 0):
            self.mWatchCount.erase(path)
            self.mWatcher.removePath(path)

    def onFileChanged(self, path):
        # If the file was replaced, the watcher is automatically removed and needs
        # to be re-added to keep watching it for changes. This happens commonly
        # with applications that do atomic saving.
        if (not self.mWatcher.files().__contains__(path)):
            if (QFile.exists(path)):
                self.mWatcher.addPath(path)
        self.fileChanged.emit(path)

    def onDirectoryChanged(self, path):
        self.directoryChanged.emit(path)
Exemple #2
0
class WatchFileWidget(QWidget):
    display_signal = pyqtSignal(str)

    def __init__(self, fnam='.log'):
        super(WatchFileWidget, self).__init__()

        self.fnam = fnam
        self.last_read = None
        self.min_time = 1e-1

    def start(self):
        # if the file does not exist
        # create it then watch it
        if not os.path.exists(self.fnam):
            with open(self.fnam, 'w') as f:
                pass

        # watch file
        self.w = QFileSystemWatcher([self.fnam])
        self.w.fileChanged.connect(self.fileChanged)

    def fileChanged(self):
        # if the file was read less than self.min_time ago, then skip
        if (self.last_read is not None) and (time.time() -
                                             self.last_read) < self.min_time:
            return

        # read the file and print output
        with open(self.fnam) as f:
            lines = f.readlines()
            last_line = lines[-1]
            print('last_line:', last_line)
            self.last_read = time.time()
            self.display_signal.emit(last_line.split('display:')[1].strip())

    def stop(self):
        self.w.removePath(self.fnam)
Exemple #3
0
class PugdebugDocuments(QObject):

    watcher = None

    open_documents = {}

    document_changed = pyqtSignal(object)

    document_removed = pyqtSignal(object)

    def __init__(self):
        super(PugdebugDocuments, self).__init__()

        self.watcher = QFileSystemWatcher()
        self.watcher.fileChanged.connect(self.handle_file_changed)

    def open_document(self, path):
        path_key = self.get_path_key(path)

        document = PugdebugDocument(path)
        self.open_documents[path_key] = document

        self.watcher.addPath(path)

        return document

    def close_document(self, path):
        path_key = self.get_path_key(path)
        self.open_documents.pop(path_key, None)

        self.watcher.removePath(path)

    def refresh_document(self, path):
        """Refresh a document

        Gets called when the file system watcher notices
        a change to an open document.
        """
        path_key = self.get_path_key(path)
        document = self.open_documents[path_key]

        document.read_file(path)

        self.document_changed.emit(document)

    def is_document_open(self, path):
        path_key = self.get_path_key(path)
        return path_key in self.open_documents

    def handle_file_changed(self, path):
        """Handle when a watched file gets changed

        Crazy stuff ahead.

        If a file is modified, some editors (systems?) will first remove
        the file and then write it back to the disk. And for that split
        second, the watcher will drop the file from being watched.

        But then again, maybe that file really got deleted? Who knows?!

        Anyway, when a file gets modified, we sleep a short while to
        see if that file will "get back" and if so, add it back to the
        watcher. If not, we'll assume the file got deleted.
        """
        if not self.__is_path_watched(path):
            fileinfo = QFileInfo(path)

            total_slept = 0
            file_exists = fileinfo.exists()

            while not file_exists:
                sleep_for = 0.1
                total_slept += sleep_for

                if total_slept > 1:
                    break

                time.sleep(sleep_for)
                file_exists = fileinfo.exists()

            if file_exists:
                self.watcher.addPath(path)

                self.refresh_document(path)
            else:
                # file got deleted?
                path_key = self.get_path_key(path)
                document = self.open_documents[path_key]
                self.document_removed.emit(document)
        elif self.__is_path_watched(path):
            self.refresh_document(path)

    def get_path_key(self, path):
        path_key = hashlib.md5(path.encode('utf-8'))
        return path_key.hexdigest()

    def __is_path_watched(self, path):
        return path in self.watcher.files()
Exemple #4
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, ):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.fig_dict = {}

        self.tabWidget.setCurrentIndex(0)

        self.plotAreaVerticalLayout = QtWidgets.QVBoxLayout()
        self.plotsFrame.setLayout(self.plotAreaVerticalLayout)

        #initialize Table
        self.headerV = self.tableWidget.verticalHeader()
        self.headerV.show()

        # add a widget for previewing plots, they can then be added to the actual plot
        self.plotPreview = PlotWidget()
        self.plotAreaVerticalLayout.addWidget(self.plotPreview)

        # Create tree model to store sim data items and connect it to views
        self.simDataItemTreeModel = SimDataItemTreeModel()
        self.bdTableModel = BdTableModel()
        self.bdUserGeneratedTableModel = BdUserGeneratedCurvesTableModel()
        self.simDataItemTreeView.setModel(self.simDataItemTreeModel)
        self.plotPreview.tableView.setModel(self.bdTableModel)

        # connect a double clicked section of the bd table to a change of the anchor
        self.plotPreview.tableView.horizontalHeader(
        ).sectionDoubleClicked.connect(self.update_bd_table)
        self.plotPreview.tableView.verticalHeader(
        ).sectionDoubleClicked.connect(
            self.update_bd_user_generated_curves_table)

        # Set custom selection model, so that sub items are automatically
        # selected if parent is selected
        self._selection_model = QRecursiveSelectionModel(
            self.simDataItemTreeView.model())
        self.simDataItemTreeView.setSelectionModel(self._selection_model)

        # Connect list view with model for the selected values of tree view
        self.selectedSimulationDataItemListModel = OrderedDictModel()
        self.simDataItemListView.setModel(
            self.selectedSimulationDataItemListModel)
        self._selection_model.selectionChanged.connect(self.change_list)

        # set up signals and slots
        self.selectedSimulationDataItemListModel.items_changed.connect(
            self.update_variable_tree)

        # Connect signals of menus
        self.actionOpen_File.triggered.connect(
            self.simDataItemTreeView.add_file)
        self.actionOpen_Directory.triggered.connect(
            self.simDataItemTreeView.add_folder)
        self.actionOpen_Directory_List.triggered.connect(
            self.simDataItemTreeView.add_folder_list)
        self.actionHide_PlotSettings.triggered.connect(
            self.set_plot_settings_visibility)
        self.actionHide_Sequence.triggered.connect(
            self.set_sequence_widget_visibility)
        self.actionHide_Status.triggered.connect(
            self.set_status_widget_visibility)

        self.actionSave_Table.triggered.connect(self.save_bd_table)

        self.actionExport_Figure_as_Tikzpicture.triggered.connect(
            self.plotPreview.export_plot_tikz)

        self.actionExport_TableWidget.triggered.connect(
            self.export_table_to_csv)

        self.actionSave_Data.triggered.connect(self.save_current_selection)

        self.action_About.triggered.connect(self.open_about_page)

        self.variableTreeModel = VariableTreeModel()
        self.variableTreeView.setModel(self.variableTreeModel)
        self.plotsettings.visibilityChanged.connect(
            self.plot_settings_visibility_changed)
        self.sequenceWidget.visibilityChanged.connect(
            self.sequence_widget_visibility_changed)
        self.statusWidget.visibilityChanged.connect(
            self.status_widget_visibility_changed)

        # Set recursive selection model for variable view
        self._variable_tree_selection_model = QRecursiveSelectionModel(
            self.variableTreeView.model())
        self.variableTreeView.setSelectionModel(
            self._variable_tree_selection_model)

        # set up combo boxes for rate/psnr and interpolation options
        self.combo_interp.addItems(["pchip", "pol"])
        self.combo_rate_psnr.addItems(["drate", "dsnr"])
        self.combo_interp.currentIndexChanged.connect(self.on_combo_box)
        self.combo_rate_psnr.currentIndexChanged.connect(self.on_combo_box)

        # set up bd plot checkbox
        self.checkBox_bdplot.stateChanged.connect(self.update_bd_plot)

        self.curveWidget.hide()
        self.curveListModel = OrderedDictModel()
        self.curveListView.setModel(self.curveListModel)
        self.curveListSelectionModel = QItemSelectionModel(self.curveListModel)
        self.curveListView.setSelectionModel(self.curveListSelectionModel)
        self.curveListView.setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection)
        self.curveWidget.visibilityChanged.connect(
            self.curve_widget_visibility_changed)

        self.actionGenerate_curve.triggered.connect(self.generate_new_curve)
        self.actionRemove_items.triggered.connect(self.remove)
        self.actionReload_files.triggered.connect(self.reload_files)

        self.settings = QSettings()
        self.get_recent_files()
        self.simDataItemTreeView.itemsOpened.connect(self.add_recent_files)

        self.watcher = QFileSystemWatcher(self)
        self.watcher.fileChanged.connect(self.warning_file_change)
        self.watcher.directoryChanged.connect(self.warning_file_change)
        self.simDataItemTreeView.parserThread.newParsedData.connect(
            self.add_files_to_watcher)
        self.show_file_changed_message = True
        self.reset_timer = QTimer(self)
        self.reset_timer.setSingleShot(True)
        self.reset_timer.setInterval(15000)
        self.reset_timer.timeout.connect(self._reset_file_changed_message)

        self.simDataItemTreeView.customContextMenuRequested.connect(
            self.show_sequences_context_menu)
        # self.curveListView.actionCalculateBD.triggered.connect(self.bd_user_generated_curves)

    # sets Visibility for the Plotsettings Widget
    def set_plot_settings_visibility(self):
        self.plotsettings.visibilityChanged.disconnect(
            self.plot_settings_visibility_changed)
        if self.plotsettings.isHidden():
            self.plotsettings.setVisible(True)
        else:
            self.plotsettings.setHidden(True)
        self.plotsettings.visibilityChanged.connect(
            self.plot_settings_visibility_changed)

    # updates the QAction if Visibility is changed
    def plot_settings_visibility_changed(self):
        if self.plotsettings.isHidden():
            self.actionHide_PlotSettings.setChecked(True)
        else:
            self.actionHide_PlotSettings.setChecked(False)
        self._variable_tree_selection_model.selectionChanged.connect(
            self.update_plot)
        self.curveListSelectionModel.selectionChanged.connect(self.update_plot)
        self.simDataItemTreeView.deleteKey.connect(self.remove)

    # sets Visibility for the Sequence Widget
    def set_sequence_widget_visibility(self):
        self.sequenceWidget.visibilityChanged.disconnect(
            self.sequence_widget_visibility_changed)
        if self.sequenceWidget.isHidden():
            self.sequenceWidget.setVisible(True)
        else:
            self.sequenceWidget.setHidden(True)
        self.sequenceWidget.visibilityChanged.connect(
            self.sequence_widget_visibility_changed)

    def sequence_widget_visibility_changed(self):
        if self.sequenceWidget.isHidden():
            self.actionHide_Sequence.setChecked(True)
        else:
            self.actionHide_Sequence.setChecked(False)

    # Sets Visibility for the Status Widget
    def set_status_widget_visibility(self):
        self.statusWidget.visibilityChanged.disconnect(
            self.status_widget_visibility_changed)
        if self.statusWidget.isHidden():
            self.statusWidget.setVisible(True)
        else:
            self.statusWidget.setHidden(True)
        self.statusWidget.visibilityChanged.connect(
            self.status_widget_visibility_changed)

    def status_widget_visibility_changed(self):
        if self.statusWidget.isHidden():
            self.actionHide_Status.setChecked(True)
        else:
            self.actionHide_Status.setChecked(False)

    def curve_widget_visibility_changed(self):
        if self.curveWidget.isHidden():
            self.curveListView.delete_key.disconnect()
        else:
            self.curveListView.delete_key.connect(self.remove_curves)

    def remove(self):
        values = self.selectedSimulationDataItemListModel.values()

        for value in values:
            self.watcher.removePath(value.path)
        # List call necessary to avoid runtime error because of elements changing
        # during iteration
        self._variable_tree_selection_model.selectionChanged.disconnect()
        # disconnect slot to avoid multiple function triggers by selectionChanged signal
        # not disconnecting slows program down significantly
        self._selection_model.selectionChanged.disconnect(self.change_list)
        self.simDataItemTreeModel.remove(list(values))
        self.change_list(QItemSelection(), QItemSelection())
        self._selection_model.selectionChanged.connect(self.change_list)
        self._variable_tree_selection_model.selectionChanged.connect(
            self.update_plot)
        if len(self.selectedSimulationDataItemListModel.values()) == 0:
            self.update_plot()

    def change_list(self, q_selected, q_deselected):
        """Extend superclass behavior by automatically adding the values of
           all selected items in :param: `q_selected` to value list model. """

        selected_q_indexes = q_deselected.indexes()

        q_reselect_indexes = []
        for q_index in self.simDataItemTreeView.selectedIndexes():
            if q_index not in selected_q_indexes:
                q_reselect_indexes.append(q_index)

        # Find all all values that are contained by selected tree items
        tuples = []
        for q_index in q_selected.indexes() + q_reselect_indexes:
            # Add values, ie. sim data items stored at the item, to the list
            # model.
            sim_data_items = q_index.internalPointer().values
            tuples.extend((e.path, e) for e in sim_data_items)

        # Overwrite all elements in dictionary by selected values
        # Note, that overwriting only issues one `updated` signal, and thus,
        # only rerenders the plots one time. Therefore, simply overwriting
        # is much more efficient, despite it would seem, that selectively
        # overwriting keys is.
        self.selectedSimulationDataItemListModel.clear_and_update_from_tuples(
            tuples)

    def get_selected_simulation_data_items(self):
        return [
            self.selectedSimulationDataItemListModel[key]
            for key in self.selectedSimulationDataItemListModel
        ]

    def get_plot_data_collection_from_selected_variables(self):
        """Get a :class: `dict` with y-variable as key and values as items
        from the current selection of the variable tree.

        :rtype: :class: `dict` of :class: `string` and :class: `list`
        """

        plot_data_collection = []
        for q_index in self.variableTreeView.selectedIndexes():
            item = q_index.internalPointer()
            if len(item.values) > 0:
                plot_data_collection.extend(item.values)

        return plot_data_collection

    def update_variable_tree(self):
        """Collect all SimDataItems currently selected, and create variable
        tree and corresponding data from it. Additionaly reselect all previously
        selected variables.
        """

        # Create a list of all currently selected paths
        # Note, that *q_index* can not be used directly, because it might
        # change if the variable tree is recreated
        selected_path_collection = []
        for q_index in self.variableTreeView.selectedIndexes():
            selected_path_collection.append(
                # Create list of identifiers from path
                # Note, that the root node is excluded from the path
                [
                    item.identifier
                    for item in q_index.internalPointer().path[1:]
                ])

        # Join the data of all currently selected items to a dictionary
        # tree
        sim_data_items = self.get_selected_simulation_data_items()
        dict_tree = dict_tree_from_sim_data_items(sim_data_items)
        # check if qp values are the same
        # self.check_qp(sim_data_items)

        # Reset variable tree and update it with *dict_tree*
        self.variableTreeModel.clear_and_update_from_dict_tree(dict_tree)

        # Auto expand variable tree
        self.variableTreeView.expandToDepth(1)

        # Reselect all variables, which also exist on the new tree
        for path in selected_path_collection:
            # Try to reselect, and do nothing, if path does not exist anymore
            try:
                # Reselect new item corresponding to the previously selected
                # path
                item = self.variableTreeModel.get_item_from_path(*path)
                self.variableTreeView.selectionModel().select(
                    self.variableTreeModel._get_index_from_item(item),
                    QItemSelectionModel.Select,
                )
            except KeyError:
                pass

    # TODO: it might be that some log files do not have a QP value, therefore the check_qp method must be
    #       implemented in a way that these files are not affected
    # def check_qp(self, sim_data_items): # check if qp values are the same for each sequence
    #     if len(sim_data_items) < 2: return
    #     sim_data_items.sort(key=lambda item: (item.sequence))
    #
    #     qp_list,list = [],[]
    #     seq = sim_data_items[0].sequence
    #     config = sim_data_items[0].config
    #
    #     for item in sim_data_items:
    #         if ((seq == item.sequence) & (config == item.config)):
    #             list.append(item.qp)
    #         elif (seq == item.sequence): #same sequence different config
    #             config = item.config
    #             qp_list.append(list)
    #             list = []
    #             list.append(item.qp)
    #         else:  # different sequence
    #             seq = item.sequence
    #             config = item.config
    #             qp_list.append(list)
    #             if not(all(list == qp_list[0] for list in qp_list)):
    #                 QtWidgets.QMessageBox.warning(self, "Warning",
    #                                               "Be careful! You chose a sequence with different QP.")
    #                 return
    #             list, qp_list = [], []
    #             list.append(item.qp)
    #     qp_list.append(list)
    #
    #     if not(all(list == qp_list[0] for list in qp_list )):
    #         QtWidgets.QMessageBox.warning(self, "Warning",
    #                                         "Be careful! You chose a sequence with different QP.")

    def check_labels(self):
        selectionmodel = self.variableTreeView.selectionModel()
        selected = self.variableTreeView.selectedIndexes()
        # return if no comparison needed
        if len(selected) < 2:
            return
        labelx = []
        labely = []
        for index in selected:
            x = index.internalPointer()
            if len(x.values) > 0:
                labelx.append(x.values[0].label[0])
                labely.append(x.values[0].label[1])

        if all(x == labelx[0] for x in labelx) and all(x == labely[0]
                                                       for x in labely):
            return

        else:
            QtWidgets.QMessageBox.information(
                self, "Error!",
                "You should not choose curves with different units.")
            selectionmodel.clearSelection()

    # updates the plot if the plot variable is changed
    def update_plot(self):
        # user-generated curves and curves loaded from files are not supposed to be mixed
        user_generated_curves = False
        if self.sender() == self._variable_tree_selection_model or self.sender(
        ) == self.curveListSelectionModel:
            self.check_labels()
            data_collection = self.get_plot_data_collection_from_selected_variables(
            )
            data_collection_user_generated = []
            for index in self.curveListView.selectedIndexes():
                data_collection_user_generated.append(
                    self.curveListModel[index.data()])
        else:
            return

        plot_data_collection = data_collection + data_collection_user_generated
        if len(data_collection_user_generated):
            self.plotPreview.tableView.setModel(self.bdUserGeneratedTableModel)
            self.plotPreview.change_plot(plot_data_collection, True)
        else:
            self.plotPreview.tableView.setModel(self.bdTableModel)
            self.update_table(data_collection)
            self.plotPreview.change_plot(plot_data_collection, False)
        if len(data_collection) and len(data_collection_user_generated):
            # don't mix user-generated and normal curves
            self.plotPreview.tableView.hide()
            self.plotPreview.label_warning.show()
            return

        self.plotPreview.tableView.show()
        self.plotPreview.label_warning.hide()
        self.plotPreview.tableView.model().update(
            plot_data_collection, self.combo_rate_psnr.currentText(),
            self.combo_interp.currentText(),
            not (self.checkBox_bdplot.isChecked()))

    def get_table_header(self, plot_data_collection):
        tmp_legend = []
        tmp_config = []

        # make legend
        for plot_data in plot_data_collection:
            tmp = []
            for identifiers in plot_data.identifiers[1:]:
                tmp += identifiers.split(sep)
            tmp2 = tmp + plot_data.path
            tmp_legend.append(tmp2)
            tmp_config.append(tmp)

        legend = []
        config = []
        for c in tmp_legend:
            result = list(
                filter(lambda x: all(x in l for l in tmp_legend) == False, c))
            if result == []: result = [plot_data.path[-1]]
            legend.append(" ".join(result))
        if len(tmp_legend) == 1:
            legend = [plot_data.path[-1]]

        #make config
        for c in tmp_config:
            result = list(
                filter(lambda x: all(x in l for l in tmp_config) == False, c))
            if ((set([" ".join(result)]) - set(config) != set()) &
                (result != [])):
                config.append(" ".join(result))

        result = (legend, config)
        return result

    #updates the table
    def update_table(self, plot_data_collection):

        self.tableWidget.clear()
        self.tableWidget.setColumnCount(0)
        self.tableWidget.setRowCount(0)

        if plot_data_collection != []:
            if 'Temporal' in plot_data_collection[0].path:
                self.change_table_temporal(plot_data_collection)
            else:
                self.change_table_summary(plot_data_collection)

        self.tableWidget.resizeColumnsToContents()

    def change_table_temporal(self, plot_data_collect):

        plot_data_collection = plot_data_collect
        self.tableWidget.setRowCount(len(plot_data_collection))
        plot_count = data_count = 0
        data_names = []
        plot_data_collection.sort(
            key=lambda plot_data: (plot_data.identifiers))
        legend = self.get_table_header(plot_data_collection)
        header = legend[0]

        for plot_data in plot_data_collection:
            values = ((float(x), float(y)) for (x, y) in plot_data.values)

            sorted_value_pairs = sorted(values, key=lambda pair: pair[0])
            [xs, ys] = list(zip(*sorted_value_pairs))

            # make header
            if plot_data.identifiers[0] not in data_names:
                self.tableWidget.insertRow(plot_count)
                v_item = QtWidgets.QTableWidgetItem(
                    str(plot_data.identifiers[0]))
                font = self.tableWidget.font()
                v_item.setData(
                    6, QtGui.QFont(self.tableWidget.font().setBold(True)))
                v_item.setData(6, QtGui.QFont("Ubuntu", 11, QtGui.QFont.Bold))
                self.tableWidget.setVerticalHeaderItem(plot_count, v_item)
                header_count = plot_count
                data_names.append(plot_data.identifiers[0])
                plot_count += 1

            # round data
            if plot_data.label[1] == 'dB':
                ys = tuple(map(lambda i: round(i, 1), ys))

            self.tableWidget.horizontalHeader().setVisible(False)
            # fill up column per column
            for column_count in range(0, len(xs)):

                self.tableWidget.setCurrentCell(plot_count, column_count)
                if column_count > self.tableWidget.currentColumn():
                    self.tableWidget.insertColumn(column_count)
                self.tableWidget.setItem(
                    plot_count, column_count,
                    QtWidgets.QTableWidgetItem(str(ys[column_count])))
                self.tableWidget.setVerticalHeaderItem(
                    plot_count,
                    QtWidgets.QTableWidgetItem(
                        str(header[data_count]) + " [" +
                        str(plot_data.label[1]) + "] "))
                new_item = QtWidgets.QTableWidgetItem(str(xs[column_count]))
                new_item.setData(6, QtGui.QFont("Ubuntu", 11,
                                                QtGui.QFont.Bold))
                self.tableWidget.setItem(header_count, column_count, new_item)

                column_count += 1

            plot_count += 1
            data_count += 1

    def change_table_summary(self, plot_data_collect):

        plot_data_collection = plot_data_collect
        header_count = plot_count = data_count = config_count = column_saver = 0
        data_names = []
        plot_data_collection.sort(key=lambda plot_data: plot_data.path[-1])
        plot_data_collection.sort(
            key=lambda plot_data: plot_data.identifiers[0])
        legend = self.get_table_header(plot_data_collection)
        header = legend[0]
        config = legend[1]

        if ((config == []) | (len(config) == 1)):
            self.change_table_temporal(plot_data_collection)
            return

        self.tableWidget.setRowCount(len(plot_data_collection) / len(config))

        for plot_data in plot_data_collection:

            values = ((float(x), float(y)) for (x, y) in plot_data.values)

            sorted_value_pairs = sorted(values, key=lambda pair: pair[0])
            [xs, ys] = list(zip(*sorted_value_pairs))

            # make header, important if more than one plot
            if plot_data.identifiers[0] not in data_names:
                self.tableWidget.insertRow(plot_count)
                v_item = QtWidgets.QTableWidgetItem(
                    str(plot_data.identifiers[0]))
                v_item.setData(6, QtGui.QFont("Ubuntu", 11, QtGui.QFont.Bold))
                self.tableWidget.setVerticalHeaderItem(plot_count, v_item)
                header_count = plot_count
                data_names.append(plot_data.identifiers[0])
                plot_count += 1

            # round data
            if plot_data.label[1] == 'dB':
                ys = tuple(map(lambda i: round(i, 1), ys))

            #horizontal header if more than one config
            if len(config) > 1:
                self.tableWidget.horizontalHeader().setVisible(True)
            else:
                self.tableWidget.horizontalHeader().setVisible(False)

            for column_count in range(0, len(xs)):

                columns = column_saver + column_count
                if (((column_saver + column_count) >=
                     self.tableWidget.columnCount()) |
                    (self.tableWidget.columnCount() == 0)):
                    self.tableWidget.insertColumn(column_saver + column_count)
                if plot_count >= self.tableWidget.rowCount():
                    self.tableWidget.insertRow(plot_count)
                # units in first row of table
                new_item = QtWidgets.QTableWidgetItem(plot_data.label[0] +
                                                      ' | ' +
                                                      plot_data.label[1])
                new_item.setData(6, QtGui.QFont("Ubuntu", 11,
                                                QtGui.QFont.Bold))
                self.tableWidget.setItem(header_count,
                                         column_saver + column_count, new_item)
                #self.tableWidget.setItem(header_count, column_saver + column_count, QtWidgets.QTableWidgetItem(plot_data.label[0] + ' | ' + plot_data.label[1]))
                # x and y-value in one cell
                self.tableWidget.setItem(
                    plot_count, columns,
                    QtWidgets.QTableWidgetItem(
                        str(xs[column_count]) + ' | ' + str(ys[column_count])))
                # header
                self.tableWidget.setHorizontalHeaderItem(
                    column_saver + column_count,
                    QtWidgets.QTableWidgetItem(str(config[config_count])))
                column_count += 1

            if config[config_count] == header[data_count]:
                header[data_count] = header[data_count].replace(
                    config[config_count], plot_data.path[-1])
            elif config[config_count] in header[data_count]:
                header[data_count] = header[data_count].replace(
                    config[config_count], '')

            self.tableWidget.setVerticalHeaderItem(
                plot_count,
                QtWidgets.QTableWidgetItem(str(header[data_count])))
            column_saver = column_saver + column_count
            config_count += 1

            if config_count == len(config):
                plot_count += 1
                column_saver = config_count = 0
            data_count += 1

    def update_bd_table(self, index):
        # update bd table, the index determines the anchor,
        # if it is non integer per default the first config is regarded as
        # anchor

        self.bdTableModel.update_table(self.combo_rate_psnr.currentText(),
                                       self.combo_interp.currentText(), index,
                                       not (self.checkBox_bdplot.isChecked()))

    def update_bd_user_generated_curves_table(self, index):
        clicked_text = self.bdUserGeneratedTableModel.headerData(
            index, Qt.Vertical, Qt.DisplayRole)
        self.bdUserGeneratedTableModel.update(
            None, self.combo_rate_psnr.currentText(),
            self.combo_interp.currentText(),
            not (self.checkBox_bdplot.isChecked()), clicked_text)

    def update_bd_plot(self):
        data_collection = self.get_plot_data_collection_from_selected_variables(
        )
        data_collection_user_generated = []
        for index in self.curveListSelectionModel.selectedIndexes():
            data_collection_user_generated.append(
                self.curveListModel[index.data()])

        if len(data_collection):
            self.bdTableModel.update(data_collection,
                                     self.combo_rate_psnr.currentText(),
                                     self.combo_interp.currentText(),
                                     not (self.checkBox_bdplot.isChecked()))
        elif len(data_collection_user_generated):
            self.bdTableModel.update(data_collection_user_generated,
                                     self.combo_rate_psnr.currentText(),
                                     self.combo_interp.currentText(),
                                     not (self.checkBox_bdplot.isChecked()))

    def export_table_to_csv(self):
        # remember that the decimal mark is '.'
        if self.tableWidget.rowCount() > 0:
            path, extension = QtWidgets.QFileDialog.getSaveFileName(
                self, 'Save Table View as', '.', 'CSV (*.csv)')
            if path != '':
                if '.csv' not in path:
                    path += '.csv'
                with open(str(path), 'w', newline='') as stream:
                    writer = csv.writer(stream)
                    for row in range(self.tableWidget.rowCount()):
                        rowdata = []
                        rowdata.append(
                            str(
                                self.tableWidget.verticalHeaderItem(row).data(
                                    0)))  #data(0) = data(Qt.displayRole)
                        for column in range(self.tableWidget.columnCount()):
                            item = self.tableWidget.item(row, column)
                            if item is not None:
                                rowdata.append(str(item.text()))
                            else:
                                rowdata.append('')
                        writer.writerow(rowdata)

    def save_bd_table(self):
        if self.bdTableModel.rowCount(self) == 0:
            return
        filename, extension = QtWidgets.QFileDialog.getSaveFileName(
            self, 'Save Table as', '.', 'Latex (*.tex)')
        if filename != '':
            if '.tex' not in filename:
                filename += '.tex'
            self.bdTableModel.export_to_latex(filename)

    def on_combo_box(self):
        # just update the bd table but do not change the anchor
        self.update_bd_table(-1)

    def save_current_selection(self):
        """Saves the current selected sim data item collection"""
        if not self.get_selected_simulation_data_items():
            msg = QtWidgets.QMessageBox(self)  # use self as parent here
            msg.setIcon(QtWidgets.QMessageBox.Information)
            msg.setText(
                "You did not select any simulation data item to store\n"
                "Please make a selection and try again.")
            msg.setWindowTitle("Info")
            msg.show()
            return
        filename, extension = QtWidgets.QFileDialog.getSaveFileName(
            self, 'Save RD data as', '.', 'RDPlot (*.rd)')
        if filename != '':
            if '.rd' not in filename:
                filename += '.rd'
            f = open(filename, 'w')
            f.write(
                jsonpickle.encode(self.get_selected_simulation_data_items()))
            f.close()

    def process_cmd_line_args(self, args):
        """Processes cmd line arguments. Those are only pathes or files."""
        for path in args[1:]:
            if not isdir(path) and not isfile(path):
                continue
            if path.endswith('.rd'):
                f = open(path, 'r')
                json_str = f.read()
                sim_data_items = jsonpickle.decode(json_str)
                self.simDataItemTreeModel.update(sim_data_items, False)
                f.close()
                continue

            self.simDataItemTreeView.msg.show()
            self.simDataItemTreeView.parserThread.add_path(path)
            self.simDataItemTreeView.parserThread.start()

    def open_about_page(self):
        """Opens and displays an Html About file"""
        try:
            html_path = path.abspath(here + '/docs/about.html')
            html_file = open(html_path, 'r', encoding='utf-8', errors='ignore')
            source_code = html_file.read()

            try:
                f = open(here + '/version.txt', 'r')
                app_version = f.readline()
            except:
                app_version = 'could not detect version'

            source_code = source_code.replace("##VERSION##", app_version)
            source_code = source_code.replace("##here##", here)
            about_dialog = QtWidgets.QDialog(self)
            about_dialog.setWindowTitle("About RDPlot")
            about_dialog.setMaximumSize(950, 800)
            about_text = QtWidgets.QTextBrowser(about_dialog)
            about_text.setMinimumWidth(950)
            about_text.setMinimumHeight(800)
            about_text.setHtml(source_code)
            about_text.setOpenExternalLinks(True)
            about_text.show()
            about_dialog.exec_()
            about_dialog.close()
            about_text.close()
        except IOError:
            html_error = QtWidgets.QMessageBox()
            html_error.setIcon(QtWidgets.QMessageBox.Critical)
            html_error.setText("Error opening about or help")
            html_error.setInformativeText(
                "The html file from the resource could not be loaded.")
            html_error.exec_()

    def generate_new_curve(self):
        plot_data_collection = self.get_plot_data_collection_from_selected_variables(
        )
        if plot_data_collection:
            new_plot_values = []
            for _plot_data in plot_data_collection:
                new_plot_values.extend(_plot_data.values)
            if len(new_plot_values) < 4:
                QtWidgets.QMessageBox.warning(
                    self, "Warning!", "You didn't select at least 4 points.")
            else:
                curve_name, ok = QtWidgets.QInputDialog.getText(
                    self, "New curve",
                    "Please enter a name for the new curve.\n"
                    "If you enter an already existing name,\nits data will be overwritten."
                )
                curve_name = curve_name.strip()
                if curve_name is not '':
                    new_plot_data = PlotData([curve_name], new_plot_values, [],
                                             plot_data_collection[0].label)
                    self.add_curve(curve_name, new_plot_data)
                else:
                    QtWidgets.QMessageBox.warning(
                        self, "Warning!", "Please enter a valid name.")
        else:
            QtWidgets.QMessageBox.warning(
                self, "Warning!", "You didn't select at least 4 points.")

    def add_curve(self, name, data):
        if self.curveWidget.isHidden():
            self.curveWidget.show()
        data_tuple = (name, data)
        self.curveListModel.update_from_tuples((data_tuple, ))
        # all_indexes = QItemSelection(self.curveListModel.index(0),
        #                             self.curveListModel.index(self.curveListModel.rowCount(QModelIndex())))
        # self.curveListSelectionModel.select(all_indexes, QItemSelectionModel.Clear)
        # self.curveListSelectionModel.select(self.curveListModel.index(self.curveListModel.rowCount(QModelIndex())-1),
        #                                    QItemSelectionModel.Select)
        # self.curveListView.setFocus()

    def remove_curves(self):
        # todo integrate bjontegaard for generated curves(should be fully functional already)
        curves_to_remove = []
        for index in self.curveListSelectionModel.selectedIndexes():
            curves_to_remove.append(index.data())
        self.curveListModel.remove_keys(curves_to_remove)
        if len(self.curveListModel) > 0:
            self.curveListSelectionModel.select(self.curveListModel.index(0),
                                                QItemSelectionModel.Select)
        else:
            self.curveWidget.hide()
            self.update_plot()

    def get_recent_files(self):
        recent_files = self.settings.value('recentFiles')
        if recent_files is not None:
            for recent_file in recent_files:
                if path.exists(recent_file):
                    action = self.menuRecent_files.addAction(recent_file)
                    action.triggered.connect(self.open_recent_file)

    def open_recent_file(self):
        path_recent = self.sender().text()
        if path.isdir(path_recent):
            self.simDataItemTreeView.add_folder(path_recent)
        else:
            self.simDataItemTreeView.add_file(path_recent)

    def add_recent_files(self, files, reload):
        # files doesn't necessarily have to just be a list of files
        # it can also be a directory
        if not reload:
            recent_files = self.settings.value('recentFiles')
            if recent_files is None:
                recent_files = []
            for file in files:

                if file in recent_files:
                    # put our file on top of the list
                    recent_files.remove(file)
                recent_files.insert(0, file)
            while len(recent_files) > 5:
                del recent_files[-1]
            self.settings.setValue('recentFiles', recent_files)

            self.menuRecent_files.clear()
            for recent_file in recent_files:
                if path.exists(recent_file):
                    action = self.menuRecent_files.addAction(recent_file)
                    action.triggered.connect(self.open_recent_file)

    def add_files_to_watcher(self, items):
        for item in items:
            if isfile(item.path):
                self.watcher.addPath(item.path)

    def warning_file_change(self, path_item):
        # inform user about the fact that one of the loaded files has been changed since the application has started
        # timer is used to avoid spamming the user when multiple files are deleted in a row
        # retrieve affected notes and parent nodes
        # change their style in the tree view to indicate which files are affected
        if self.show_file_changed_message:
            self.show_file_changed_message = False
            self.reset_timer.start()
            QtWidgets.QMessageBox.warning(
                self, 'File change',
                'One or more of your loaded files have been changed.\n'
                'You can choose to reload them.\n'
                'Hint: Changed files are greyed out in the sequences widget.')
        else:
            self.reset_timer.stop()
            self.reset_timer.start()

        affected_notes = []
        for leaf in self.simDataItemTreeModel.root.leafs:
            for value in leaf.values:
                if value.path == path_item:
                    affected_notes.append(leaf)
        for node in affected_notes:
            node_index = self.simDataItemTreeModel._get_index_from_item(node)
            node.setProperty('needs_reload', 'True')
            parent = self.simDataItemTreeModel.parent(node_index)
            level = 0
            while parent.isValid() and level < 2:  #MAX_LEVEL
                parent.internalPointer().setProperty('needs_reload', 'True')
                parent = self.simDataItemTreeModel.parent(parent)
                level += 1

    def _reset_file_changed_message(self):
        self.show_file_changed_message = True

    def reload_files(self):
        # remove all selected files first
        # reload available files
        # could possibly limit this to only files that we know have been changed
        def check_children(parent):
            if len(parent.children) > 0:
                for child in parent.children:
                    if not check_children(child):
                        return False
                parent.setProperty('needs_reload', 'False')
                return True
            else:
                if parent.property('needs_reload') == 'True':
                    return False
                return True

        values = self.selectedSimulationDataItemListModel.values()
        if len(values) == 0:
            for index in self.simDataItemTreeModel.root.leafs:
                for sim_data_item in index.values:
                    values.append(sim_data_item)
        items_to_be_reloaded = []
        for value in values:
            if path.exists(value.path):
                # reload file
                items_to_be_reloaded.append(value)

        self._variable_tree_selection_model.selectionChanged.disconnect()
        self._selection_model.selectionChanged.disconnect(self.change_list)
        self.simDataItemTreeModel.remove(values)

        self.simDataItemTreeView.msg.show()
        for item in items_to_be_reloaded:
            self.simDataItemTreeView.add_file(item.path, reload=True)

        self.change_list(QItemSelection(), QItemSelection())
        self._selection_model.selectionChanged.connect(self.change_list)
        self._variable_tree_selection_model.selectionChanged.connect(
            self.update_plot)

        for node in self.simDataItemTreeModel.root.children:
            # remove grey font color if all changed files have been reloaded
            # have to check every single item because possible deletion of older nodes makes things very difficult
            check_children(node)

    def show_sequences_context_menu(self, position):
        self.menuEdit.exec(self.simDataItemTreeView.mapToGlobal(position))
Exemple #5
0
class NFile(QObject):
    """
    SIGNALS:
    @askForSaveFileClosing(QString)
    @fileClosing(QString)
    @fileChanged()
    @willDelete(PyQt_PyObject, PyQt_PyObject)
    @willOverWrite(PyQt_PyObject, QString, QString)
    @willMove(Qt_PyQtObject, QString, QString)
    @willSave(QString, QString)
    @savedAsNewFile(PyQt_PyObject, QString, QString)
    @gotAPath(PyQt_PyObject)
    @willAttachToExistingFile(PyQt_PyObject, QString)
    """
    fileChanged = pyqtSignal()
    fileRemoved = pyqtSignal()
    fileReaded = pyqtSignal()
    willAttachToExistingFile = pyqtSignal('PyQt_PyObject', 'QString')
    gotAPath = pyqtSignal('PyQt_PyObject')
    willSave = pyqtSignal('QString', 'QString')
    willMove = pyqtSignal('PyQt_PyObject', 'QString', 'QString')
    willOverWrite = pyqtSignal('PyQt_PyObject', 'QString', 'QString')
    willCopyTo = pyqtSignal('PyQt_PyObject', 'QString', 'QString')
    willDelete = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject')
    fileClosing = pyqtSignal('QString', bool)

    def __init__(self, path=None):
        """
        """
        self._file_path = path
        self.__created = False
        self.__watcher = None
        self.__mtime = None
        super(NFile, self).__init__()
        if not self._exists():
            self.__created = True

    @property
    def file_name(self):
        """"Returns filename of nfile"""
        file_name = None
        if self._file_path is None:
            file_name = translations.TR_NEW_DOCUMENT
        else:
            file_name = get_basename(self._file_path)
        return file_name

    @property
    def display_name(self):
        """Returns a pretty name to be displayed by tabs"""
        display_name = self.file_name
        if self._file_path is not None and not self.has_write_permission():
            display_name += translations.TR_READ_ONLY
        return display_name

    @property
    def is_new_file(self):
        return self.__created

    def file_ext(self):
        """"Returns extension of nfile"""
        if self._file_path is None:
            return ''
        return get_file_extension(self._file_path)

    @property
    def file_path(self):
        """"Returns file path of nfile"""
        return self._file_path

    def start_watching(self):
        """Create a file system watcher and connect its fileChanged
        SIGNAL to our _file_changed SLOT"""
        if self.__watcher is None:
            self.__watcher = QFileSystemWatcher(self)
            self.__watcher.fileChanged['const QString&'].connect(
                self._file_changed)
        if self._file_path is not None:
            self.__mtime = os.path.getmtime(self._file_path)
            self.__watcher.addPath(self._file_path)

    def _file_changed(self, path):
        if self._exists():
            current_mtime = os.path.getmtime(self._file_path)
            if current_mtime != self.__mtime:
                self.__mtime = current_mtime
                self.fileChanged.emit()
        # FIXME: for swap file
        # else:
        #     self.fileRemoved.emit()

    def has_write_permission(self):
        if not self._exists():
            return True
        return os.access(self._file_path, os.W_OK)

    def _exists(self):
        """
        Check if we have been created with a path and if such path exists
        In case there is no path, we are most likely a new file.
        """
        file_exists = False
        if self._file_path and os.path.exists(self._file_path):
            file_exists = True
        return file_exists

    def attach_to_path(self, new_path):
        if os.path.exists(new_path):
            signal_handler = SignalFlowControl()
            self.willAttachToExistingFile.emit(signal_handler, new_path)
            if signal_handler.stopped():
                    return
        self._file_path = new_path
        self.gotAPath.emit(self)
        return self._file_path

    def create(self):
        if self.__created:
            self.save("")
        self.__created = False

    def save(self, content, path=None):
        """
        Write a temporary file with .tnj extension and copy it over the
        original one.
        .nsf = Ninja Swap File
        # FIXME: Where to locate addExtension, does not fit here
        """
        new_path = False
        if path:
            self.attach_to_path(path)
            new_path = True

        save_path = self._file_path

        if not save_path:
            raise NinjaNoFileNameException("I am asked to write a "
                                           "file but no one told me where")
        swap_save_path = "%s.nsp" % save_path

        # If we have a file system watcher, remove the file path
        # from its watch list until we are done making changes.
        if self.__watcher is not None:
            self.__watcher.removePath(save_path)

        flags = QIODevice.WriteOnly | QIODevice.Truncate
        f = QFile(swap_save_path)
        if settings.use_platform_specific_eol():
            flags |= QIODevice.Text

        if not f.open(flags):
            raise NinjaIOException(f.errorString())

        stream = QTextStream(f)
        encoding = get_file_encoding(content)
        if encoding:
            stream.setCodec(encoding)

        encoded_stream = stream.codec().fromUnicode(content)
        f.write(encoded_stream)
        f.flush()
        f.close()
        # SIGNAL: Will save (temp, definitive) to warn folder to do something
        self.willSave.emit(swap_save_path, save_path)
        self.__mtime = os.path.getmtime(swap_save_path)
        shutil.move(swap_save_path, save_path)
        self.reset_state()

        # If we have a file system watcher, add the saved path back
        # to its watch list, otherwise create a watcher and start
        # watching
        if self.__watcher is not None:
            if new_path:
                # self.__watcher.removePath(self.__watcher.files()[0])
                self.__watcher.addPath(self._file_path)
            else:
                self.__watcher.addPath(save_path)
        else:
            self.start_watching()
        return self

    def reset_state(self):
        """
        #FIXE: to have a ref to changed I need to have the doc here
        """
        self.__created = False

    def read(self, path=None):
        """
        Read the file or fail
        """
        open_path = path and path or self._file_path
        self._file_path = open_path
        if not self._file_path:
            raise NinjaNoFileNameException("I am asked to read a file "
                                           "but no one told me from where")
        try:
            with open(open_path, 'r') as f:
                content = f.read()
        except (IOError, UnicodeDecodeError) as reason:
            raise NinjaIOException(reason)
        self.fileReaded.emit()
        return content

    def move(self, new_path):
        """
        Phisically move the file
        """
        if self._exists():
            signal_handler = SignalFlowControl()
            # SIGNALL: WILL MOVE TO, to warn folder to exist
            self.willMove.emit(signal_handler,
                               self._file_path,
                               new_path)
            if signal_handler.stopped():
                return
            if os.path.exists(new_path):
                signal_handler = SignalFlowControl()
                self.willOverWrite.emit(signal_handler,
                                        self._file_path,
                                        new_path)
                if signal_handler.stopped():
                    return
            if self.__watcher is not None:
                self.__watcher.removePath(self._file_path)
            shutil.move(self._file_path, new_path)
            if self.__watcher:
                self.__watcher.addPath(new_path)
        self._file_path = new_path

    def copy(self, new_path):
        """
        Copy the file to a new path
        """
        if self._exists():
            signal_handler = SignalFlowControl()
            # SIGNALL: WILL COPY TO, to warn folder to exist
            self.willCopyTo.emit(signal_handler,
                                 self._file_path,
                                 new_path)
            if signal_handler.stopped():
                return
            if os.path.exists(new_path):
                signal_handler = SignalFlowControl()
                self.willOverWrite.emit(signal_handler,
                                        self._file_path,
                                        new_path)
                if signal_handler.stopped():
                    return

            shutil.copy(self._file_path, new_path)

    def delete(self, force=False):
        """
        This deletes the object and closes the file.
        """
        # if created but exists this file migth to someone else
        self.close()
        if ((not self.__created) or force) and self._exists():
            DEBUG("Deleting our own NFile %s" % self._file_path)
            signal_handler = SignalFlowControl()
            self.willDelete.emit(signal_handler, self)
            if not signal_handler.stopped():
                if self.__watcher is not None:
                    self.__watcher.removePath(self._file_path)
                os.remove(self._file_path)

    def close(self, force_close=False):
        """
        Lets let people know we are going down so they can act upon
        As you can see close does nothing but let everyone know that we are
        not saved yet
        """
        DEBUG("About to close NFile")
        self.fileClosing.emit(self._file_path, force_close)

    def remove_watcher(self):
        if self.__watcher is not None:
            self.__watcher.removePath(self._file_path)
Exemple #6
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.initConfig()
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2)
		if not screenRect.contains(self.geometry()):
			self.showMaximized()
		if globalSettings.iconTheme:
			QIcon.setThemeName(globalSettings.iconTheme)
		if QFile.exists(icon_path+'retext.png'):
			self.setWindowIcon(QIcon(icon_path+'retext.png'))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.editBoxes = []
		self.previewBoxes = []
		self.highlighters = []
		self.markups = []
		self.fileNames = []
		self.actionPreviewChecked = []
		self.actionLivePreviewChecked = []
		self.tabWidget = QTabWidget(self)
		self.initTabWidget()
		self.setCentralWidget(self.tabWidget)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh', trig=self.openFileMain)
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeFont = self.act(self.tr('Change default font'), trig=self.changeFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find)
		self.actionSearch.setCheckable(True)
		self.actionSearch.triggered[bool].connect(self.searchBar.setVisible)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png'))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.editBoxes[self.ind].enableTableMode(x))
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.editBoxes[self.ind].undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.editBoxes[self.ind].redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.editBoxes[self.ind].copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.editBoxes[self.ind].cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.editBoxes[self.ind].paste(), shct=QKeySequence.Paste)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant_available:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About %s', 'Example of final string: About ReText')
		self.aboutWindowTitle = self.aboutWindowTitle % app_name
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		self.defaultMarkup = availableMarkups[0] if availableMarkups else None
		if globalSettings.defaultMarkup:
			mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup)
			if mc and mc.available():
				self.defaultMarkup = mc
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup == self.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertChars('**'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertChars('*'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertTag('u'))
		self.usefulTags = ('a', 'big', 'center', 'img', 's', 'small', 'span',
			'table', 'td', 'tr', 'u')
		self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.tagsBox = QComboBox(self.editBar)
		self.tagsBox.addItem(self.tr('Tags'))
		self.tagsBox.addItems(self.usefulTags)
		self.tagsBox.activated.connect(self.insertTag)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = QMenuBar(self)
		menubar.setGeometry(QRect(0, 0, 800, 25))
		self.setMenuBar(menubar)
		menuFile = menubar.addMenu(self.tr('File'))
		menuEdit = menubar.addMenu(self.tr('Edit'))
		menuHelp = menubar.addMenu(self.tr('Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addMenu(self.menuRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addSeparator()
		if enchant_available:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionChangeFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		menuEdit.addAction(self.actionWebKit)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionLivePreview)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionTableMode)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		menubar.addMenu(menuFile)
		menubar.addMenu(menuEdit)
		menubar.addMenu(menuHelp)
		toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		toolBar.addAction(self.actionNew)
		toolBar.addSeparator()
		toolBar.addAction(self.actionOpen)
		toolBar.addAction(self.actionSave)
		toolBar.addAction(self.actionPrint)
		toolBar.addSeparator()
		toolBar.addAction(self.actionPreview)
		toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.tagsBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant_available:
			self.sl = globalSettings.spellCheckLocale
			if self.sl:
				try:
					enchant.Dict(self.sl)
				except Exception as e:
					print(e, file=sys.stderr)
					self.sl = None
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
				self.enableSpellCheck(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def initConfig(self):
		self.font = None
		if globalSettings.font:
			self.font = QFont(globalSettings.font)
		if self.font and globalSettings.fontSize:
			self.font.setPointSize(globalSettings.fontSize)

	def updateStyleSheet(self):
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()
		else:
			self.ss = ''

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(icon_path+name+'.png'))

	def printError(self):
		import traceback
		print('Exception occured while parsing document:', file=sys.stderr)
		traceback.print_exc()

	def getSplitter(self, index):
		splitter = QSplitter(Qt.Horizontal)
		# Give both boxes a minimum size so the minimumSizeHint will be
		# ignored when splitter.setSizes is called below
		for widget in self.editBoxes[index], self.previewBoxes[index]:
			widget.setMinimumWidth(125)
			splitter.addWidget(widget)
		splitter.setSizes((50, 50))
		splitter.setChildrenCollapsible(False)
		return splitter

	def getWebView(self):
		webView = QWebView()
		if not globalSettings.handleWebLinks:
			webView.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks)
			webView.page().linkClicked.connect(QDesktopServices.openUrl)
		webView.settings().setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False)
		return webView

	def createTab(self, fileName):
		self.previewBlocked = False
		self.editBoxes.append(ReTextEdit(self))
		self.highlighters.append(ReTextHighlighter(self.editBoxes[-1].document()))
		if enchant_available and self.actionEnableSC.isChecked():
			self.highlighters[-1].dictionary = \
			enchant.Dict(self.sl) if self.sl else enchant.Dict()
			self.highlighters[-1].rehighlight()
		if globalSettings.useWebKit:
			self.previewBoxes.append(self.getWebView())
		else:
			self.previewBoxes.append(QTextBrowser())
			self.previewBoxes[-1].setOpenExternalLinks(True)
		self.previewBoxes[-1].setVisible(False)
		self.fileNames.append(fileName)
		markupClass = self.getMarkupClass(fileName)
		self.markups.append(self.getMarkup(fileName))
		self.highlighters[-1].docType = (markupClass.name if markupClass else '')
		liveMode = globalSettings.restorePreviewState and globalSettings.previewState
		self.actionPreviewChecked.append(liveMode)
		self.actionLivePreviewChecked.append(liveMode)
		metrics = QFontMetrics(self.editBoxes[-1].font())
		self.editBoxes[-1].setTabStopWidth(globalSettings.tabWidth * metrics.width(' '))
		self.editBoxes[-1].textChanged.connect(self.updateLivePreviewBox)
		self.editBoxes[-1].undoAvailable.connect(self.actionUndo.setEnabled)
		self.editBoxes[-1].redoAvailable.connect(self.actionRedo.setEnabled)
		self.editBoxes[-1].copyAvailable.connect(self.enableCopy)
		self.editBoxes[-1].document().modificationChanged.connect(self.modificationChanged)
		if globalSettings.useFakeVim:
			self.installFakeVimHandler(self.editBoxes[-1])
		return self.getSplitter(-1)

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.tabWidget.addTab(self.createTab(""), self.tr("New document"))
			if self.fileNames[ind]:
				self.fileSystemWatcher.removePath(self.fileNames[ind])
			del self.editBoxes[ind]
			del self.previewBoxes[ind]
			del self.highlighters[ind]
			del self.markups[ind]
			del self.fileNames[ind]
			del self.actionPreviewChecked[ind]
			del self.actionLivePreviewChecked[ind]
			self.tabWidget.removeTab(ind)

	def getMarkupClass(self, fileName=None):
		if fileName is None:
			fileName = self.fileNames[self.ind]
		if fileName:
			markupClass = markups.get_markup_for_file_name(
				fileName, return_class=True)
			if markupClass:
				return markupClass
		return self.defaultMarkup

	def getMarkup(self, fileName=None):
		if fileName is None:
			fileName = self.fileNames[self.ind]
		markupClass = self.getMarkupClass(fileName=fileName)
		if markupClass and markupClass.available():
			return markupClass(filename=fileName)

	def docTypeChanged(self):
		oldType = self.highlighters[self.ind].docType
		markupClass = self.getMarkupClass()
		newType = markupClass.name if markupClass else ''
		if oldType != newType:
			self.markups[self.ind] = self.getMarkup()
			self.updatePreviewBox()
			self.highlighters[self.ind].docType = newType
			self.highlighters[self.ind].rehighlight()
		dtMarkdown = (newType == DOCTYPE_MARKDOWN)
		dtMkdOrReST = (newType in (DOCTYPE_MARKDOWN, DOCTYPE_REST))
		self.tagsBox.setEnabled(dtMarkdown)
		self.symbolBox.setEnabled(dtMarkdown)
		self.actionUnderline.setEnabled(dtMarkdown)
		self.actionBold.setEnabled(dtMkdOrReST)
		self.actionItalic.setEnabled(dtMkdOrReST)
		canReload = bool(self.fileNames[self.ind]) and not self.autoSaveActive()
		self.actionSetEncoding.setEnabled(canReload)
		self.actionReload.setEnabled(canReload)

	def changeIndex(self, ind):
		if ind > -1:
			self.actionUndo.setEnabled(self.editBoxes[ind].document().isUndoAvailable())
			self.actionRedo.setEnabled(self.editBoxes[ind].document().isRedoAvailable())
			self.actionCopy.setEnabled(self.editBoxes[ind].textCursor().hasSelection())
			self.actionCut.setEnabled(self.editBoxes[ind].textCursor().hasSelection())
			self.actionPreview.setChecked(self.actionPreviewChecked[ind])
			self.actionLivePreview.setChecked(self.actionLivePreviewChecked[ind])
			self.actionTableMode.setChecked(self.editBoxes[ind].tableModeEnabled)
			self.editBar.setDisabled(self.actionPreviewChecked[ind])
		self.ind = ind
		if self.fileNames[ind]:
			self.setCurrentFile()
		else:
			self.setWindowTitle(self.tr('New document') + '[*]')
			self.docTypeChanged()
		self.modificationChanged(self.editBoxes[ind].document().isModified())
		if globalSettings.restorePreviewState:
			globalSettings.previewState = self.actionLivePreviewChecked[ind]
		if self.actionLivePreviewChecked[ind]:
			self.enableLivePreview(True)
		self.editBoxes[self.ind].setFocus(Qt.OtherFocusReason)

	def changeFont(self):
		if not self.font:
			self.font = QFont()
		fd = QFontDialog.getFont(self.font, self)
		if fd[1]:
			self.font = QFont()
			self.font.setFamily(fd[0].family())
			settings.setValue('font', fd[0].family())
			self.font.setPointSize(fd[0].pointSize())
			settings.setValue('fontSize', fd[0].pointSize())
			self.updatePreviewBox()

	def preview(self, viewmode):
		self.actionPreviewChecked[self.ind] = viewmode
		if self.actionLivePreview.isChecked():
			self.actionLivePreview.setChecked(False)
			return self.enableLivePreview(False)
		self.editBar.setDisabled(viewmode)
		self.editBoxes[self.ind].setVisible(not viewmode)
		self.previewBoxes[self.ind].setVisible(viewmode)
		if viewmode:
			self.updatePreviewBox()

	def enableLivePreview(self, livemode):
		if globalSettings.restorePreviewState:
			globalSettings.previewState = livemode
		self.actionLivePreviewChecked[self.ind] = livemode
		self.actionPreviewChecked[self.ind] = livemode
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.previewBoxes[self.ind].setVisible(livemode)
		self.editBoxes[self.ind].setVisible(True)
		if livemode:
			self.updatePreviewBox()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		oldind = self.ind
		self.tabWidget.clear()
		for self.ind in range(len(self.editBoxes)):
			if enable:
				self.previewBoxes[self.ind] = self.getWebView()
			else:
				self.previewBoxes[self.ind] = QTextBrowser()
				self.previewBoxes[self.ind].setOpenExternalLinks(True)
			splitter = self.getSplitter(self.ind)
			self.tabWidget.addTab(splitter, self.getDocumentTitle(baseName=True))
			self.updatePreviewBox()
			self.previewBoxes[self.ind].setVisible(self.actionPreviewChecked[self.ind])
		self.ind = oldind
		self.tabWidget.setCurrentIndex(self.ind)

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def installFakeVimHandler(self, editor):
		if ReTextFakeVimHandler:
			fakeVimEditor = ReTextFakeVimHandler(editor, self)
			fakeVimEditor.setSaveAction(self.actionSave)
			fakeVimEditor.setQuitAction(self.actionQuit)
			self.actionFakeVimMode.triggered.connect(fakeVimEditor.remove)

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for editor in self.editBoxes:
				self.installFakeVimHandler(editor)
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		if yes:
			if self.sl:
				self.setAllDictionaries(enchant.Dict(self.sl))
			else:
				self.setAllDictionaries(enchant.Dict())
		else:
			self.setAllDictionaries(None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for hl in self.highlighters:
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		if self.sl:
			localedlg = LocaleDialog(self, defaultText=self.sl)
		else:
			localedlg = LocaleDialog(self)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		setdefault = localedlg.checkBox.isChecked()
		if sl:
			try:
				sl = str(sl)
				enchant.Dict(sl)
			except Exception as e:
				QMessageBox.warning(self, '', str(e))
			else:
				self.sl = sl
				self.enableSpellCheck(self.actionEnableSC.isChecked())
		else:
			self.sl = None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
		if setdefault:
			globalSettings.spellCheckLocale = sl

	def searchBarVisibilityChanged(self, visible):
		self.actionSearch.setChecked(visible)
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		editBox = self.editBoxes[self.ind]
		cursor = editBox.textCursor()
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		cursor.movePosition(QTextCursor.End if back else QTextCursor.Start)
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		self.setSearchEditColor(False)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def getHtml(self, includeStyleSheet=True, includeTitle=True,
	            includeMeta=False, styleForWebKit=False, webenv=False):
		if self.markups[self.ind] is None:
			markupClass = self.getMarkupClass()
			errMsg = self.tr('Could not parse file contents, check if '
			'you have the <a href="%s">necessary module</a> installed!')
			try:
				errMsg %= markupClass.attributes[MODULE_HOME_PAGE]
			except (AttributeError, KeyError):
				# Remove the link if markupClass doesn't have the needed attribute
				errMsg = errMsg.replace('<a href="%s">', '')
				errMsg = errMsg.replace('</a>', '')
			return '<p style="color: red">%s</p>' % errMsg
		text = self.editBoxes[self.ind].toPlainText()
		headers = ''
		if includeStyleSheet:
			fontline = ''
			if styleForWebKit:
				fontname = self.font.family() if self.font else 'Sans'
				fontsize = (self.font if self.font else QFont()).pointSize()
				fontline = 'body { font-family: %s; font-size: %spt }\n' % \
					(fontname, fontsize)
			headers += '<style type="text/css">\n' + fontline + self.ss + '</style>\n'
		cssFileName = self.getDocumentTitle(baseName=True)+'.css'
		if QFile(cssFileName).exists():
			headers += '<link rel="stylesheet" type="text/css" href="%s">\n' \
			% cssFileName
		if includeMeta:
			headers += '<meta name="generator" content="%s %s">\n' % \
			(app_name, app_version)
		fallbackTitle = self.getDocumentTitle() if includeTitle else ''
		return self.markups[self.ind].get_whole_html(text,
			custom_headers=headers, include_stylesheet=includeStyleSheet,
			fallback_title=fallbackTitle, webenv=webenv)

	def updatePreviewBox(self):
		self.previewBlocked = False
		pb = self.previewBoxes[self.ind]
		textedit = isinstance(pb, QTextEdit)
		if textedit:
			scrollbar = pb.verticalScrollBar()
			disttobottom = scrollbar.maximum() - scrollbar.value()
		else:
			frame = pb.page().mainFrame()
			scrollpos = frame.scrollPosition()
		try:
			html = self.getHtml(styleForWebKit=(not textedit))
		except Exception:
			return self.printError()
		if textedit:
			pb.setHtml(html)
		else:
			pb.setHtml(html, QUrl.fromLocalFile(self.fileNames[self.ind]))
		if self.font and textedit:
			pb.document().setDefaultFont(self.font)
		if textedit:
			scrollbar.setValue(scrollbar.maximum() - disttobottom)
		else:
			frame.setScrollPosition(scrollpos)

	def updateLivePreviewBox(self):
		if self.actionLivePreview.isChecked() and self.previewBlocked == False:
			self.previewBlocked = True
			QTimer.singleShot(1000, self.updatePreviewBox)

	def showInDir(self):
		if self.fileNames[self.ind]:
			path = QFileInfo(self.fileNames[self.ind]).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def setCurrentFile(self):
		self.setWindowTitle("")
		self.tabWidget.setTabText(self.ind, self.getDocumentTitle(baseName=True))
		self.setWindowFilePath(self.fileNames[self.ind])
		files = readListFromSettings("recentFileList")
		while self.fileNames[self.ind] in files:
			files.remove(self.fileNames[self.ind])
		files.insert(0, self.fileNames[self.ind])
		if len(files) > 10:
			del files[10:]
		writeListToSettings("recentFileList", files)
		QDir.setCurrent(QFileInfo(self.fileNames[self.ind]).dir().path())
		self.docTypeChanged()

	def createNew(self, text=None):
		self.tabWidget.addTab(self.createTab(""), self.tr("New document"))
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.editBoxes[self.ind].textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFuntion(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFuntion(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.getMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype == None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), "",
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i in range(self.tabWidget.count()):
			if self.fileNames[i] == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.fileNames[self.ind] or
				self.editBoxes[self.ind].toPlainText() or
				self.editBoxes[self.ind].document().isModified()
			)
			if noEmptyTab:
				self.tabWidget.addTab(self.createTab(fileName), "")
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.fileNames[self.ind] = fileName
			self.openFileMain()

	def openFileMain(self, encoding=None):
		openfile = QFile(self.fileNames[self.ind])
		openfile.open(QIODevice.ReadOnly)
		stream = QTextStream(openfile)
		if encoding:
			stream.setCodec(encoding)
		elif globalSettings.defaultCodec:
			stream.setCodec(globalSettings.defaultCodec)
		text = stream.readAll()
		openfile.close()
		markupClass = markups.get_markup_for_file_name(
			self.fileNames[self.ind], return_class=True)
		self.highlighters[self.ind].docType = (markupClass.name if markupClass else '')
		self.markups[self.ind] = self.getMarkup()
		if self.defaultMarkup:
			self.highlighters[self.ind].docType = self.defaultMarkup.name
		editBox = self.editBoxes[self.ind]
		modified = bool(encoding) and (editBox.toPlainText() != text)
		editBox.setPlainText(text)
		self.setCurrentFile()
		editBox.document().setModified(modified)
		self.setWindowModified(modified)

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in QTextCodec.availableCodecs()],
			0, False)
		if ok:
			self.openFileMain(encoding)

	def saveFile(self):
		self.saveFileMain(dlg=False)

	def saveFileAs(self):
		self.saveFileMain(dlg=True)

	def saveAll(self):
		oldind = self.ind
		for self.ind in range(self.tabWidget.count()):
			if self.fileNames[self.ind] and QFileInfo(self.fileNames[self.ind]).isWritable():
				self.saveFileCore(self.fileNames[self.ind])
				self.editBoxes[self.ind].document().setModified(False)
		self.ind = oldind

	def saveFileMain(self, dlg):
		if (not self.fileNames[self.ind]) or dlg:
			markupClass = self.getMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			newFileName = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), "", defaultExt)[0]
			if newFileName:
				if not QFileInfo(newFileName).suffix():
					newFileName += ext
				if self.fileNames[self.ind]:
					self.fileSystemWatcher.removePath(self.fileNames[self.ind])
				self.fileNames[self.ind] = newFileName
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if self.fileNames[self.ind]:
			result = self.saveFileCore(self.fileNames[self.ind])
			if result:
				self.setCurrentFile()
				self.editBoxes[self.ind].document().setModified(False)
				self.setWindowModified(False)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveFileCore(self, fn):
		self.fileSystemWatcher.removePath(fn)
		savefile = QFile(fn)
		result = savefile.open(QIODevice.WriteOnly)
		if result:
			savestream = QTextStream(savefile)
			if globalSettings.defaultCodec:
				savestream.setCodec(globalSettings.defaultCodec)
			savestream << self.editBoxes[self.ind].toPlainText()
			savefile.close()
		self.fileSystemWatcher.addPath(fn)
		return result

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			htmltext = self.getHtml(includeStyleSheet=False, includeMeta=True,
			webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		htmlFile.open(QIODevice.WriteOnly)
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle, self.getDocumentTitle())
		if self.ss:
			td.setDefaultStyleSheet(self.ss)
		td.setHtml(self.getHtml())
		if self.font:
			td.setDefaultFont(self.font)
		return td

	def saveOdf(self):
		try:
			document = self.textDocument()
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), "",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat("odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), "",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self):
		if globalSettings.useWebKit:
			return self.previewBoxes[self.ind]
		try:
			return self.textDocument()
		except Exception:
			self.printError()

	def standardPrinter(self):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(self.getDocumentTitle())
		printer.setCreator(app_name+" "+app_version)
		return printer

	def savePdf(self):
		self.updatePreviewBox()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			"", self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			printer = self.standardPrinter()
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printFile(self):
		self.updatePreviewBox()
		printer = self.standardPrinter()
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printPreview(self):
		document = self.getDocumentForPrint()
		if document == None:
			return
		printer = self.standardPrinter()
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		basename = '.%s.retext-temp' % self.getDocumentTitle(baseName=True)
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename+self.getMarkupClass().default_extension
			self.saveFileCore(tmpname)
		command = command.replace('%of', '"out'+defaultext+'"')
		command = command.replace('%html' if html else '%if', '"'+tmpname+'"')
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()
		if of:
			QFile('out'+defaultext).rename(fileName)

	def getDocumentTitle(self, baseName=False):
		markup = self.markups[self.ind]
		realTitle = ''
		if markup and not baseName:
			text = self.editBoxes[self.ind].toPlainText()
			try:
				realTitle = markup.get_document_title(text)
			except Exception:
				self.printError()
		if realTitle:
			return realTitle
		elif self.fileNames[self.ind]:
			fileinfo = QFileInfo(self.fileNames[self.ind])
			basename = fileinfo.completeBaseName()
			return (basename if basename else fileinfo.fileName())
		return self.tr("New document")

	def autoSaveActive(self):
		return self.autoSaveEnabled and self.fileNames[self.ind] and \
		QFileInfo(self.fileNames[self.ind]).isWritable()

	def modificationChanged(self, changed):
		if self.autoSaveActive():
			changed = False
		self.actionSave.setEnabled(changed)
		self.setWindowModified(changed)

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())

	def insertChars(self, chars):
		tc = self.editBoxes[self.ind].textCursor()
		if tc.hasSelection():
			selection = tc.selectedText()
			if selection.startswith(chars) and selection.endswith(chars):
				if len(selection) > 2*len(chars):
					selection = selection[len(chars):-len(chars)]
					tc.insertText(selection)
			else:
				tc.insertText(chars+tc.selectedText()+chars)
		else:
			tc.insertText(chars)

	def insertTag(self, ut):
		if not ut:
			return
		if isinstance(ut, int):
			ut = self.usefulTags[ut - 1]
		arg = ' style=""' if ut == 'span' else ''
		tc = self.editBoxes[self.ind].textCursor()
		if ut == 'img':
			toinsert = ('<a href="' + tc.selectedText() +
			'" target="_blank"><img src="' + tc.selectedText() + '"/></a>')
		elif ut == 'a':
			toinsert = ('<a href="' + tc.selectedText() +
			'" target="_blank">' + tc.selectedText() + '</a>')
		else:
			toinsert = '<'+ut+arg+'>'+tc.selectedText()+'</'+ut+'>'
		tc.insertText(toinsert)
		self.tagsBox.setCurrentIndex(0)

	def insertSymbol(self, num):
		if num:
			self.editBoxes[self.ind].insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		ind = self.fileNames.index(fileName)
		self.tabWidget.setCurrentIndex(ind)
		if not QFile.exists(fileName):
			self.editBoxes[ind].document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not self.editBoxes[ind].document().isModified():
			# File was not modified in ReText, reload silently
			self.openFileMain()
			self.updatePreviewBox()
		else:
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				self.openFileMain()
				self.updatePreviewBox()
			else:
				self.autoSaveEnabled = False
				self.editBoxes[ind].document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://sourceforge.net/p/retext/tickets/137/
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		if self.autoSaveActive():
			self.saveFileCore(self.fileNames[self.ind])
			return True
		if not self.editBoxes[ind].document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFileMain(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for self.ind in range(self.tabWidget.count()):
			if not self.maybeSave(self.ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry and not self.isMaximized():
			globalSettings.windowGeometry = self.saveGeometry()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			htmltext = self.getHtml(includeStyleSheet=False, includeTitle=False)
		except Exception:
			return self.printError()
		winTitle = self.getDocumentTitle(baseName=True)
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('http://sourceforge.net/p/retext/home/Help and Support'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011\u2013' '2015')
		+'<br><a href="http://sourceforge.net/p/retext/">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markup):
		self.defaultMarkup = markup
		defaultName = markups.get_available_markups()[0].name
		writeToSettings('defaultMarkup', markup.name, defaultName)
		oldind = self.ind
		for self.ind in range(len(self.previewBoxes)):
			self.docTypeChanged()
		self.ind = oldind
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': '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()
        self.updatePreferences()

        # 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(50)
        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)

    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.toggle_wrap_mode(self.preferences['Line wrap'])

    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.removePath(self.filename)
                self._file_watch_timer.stop()

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

            if self.preferences['Autoreload']:
                self._file_watcher.addPath(self.filename)
                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._file_watcher.removePath(self._watched_file)
            self._watched_file = None
        if self.preferences[
                'Autoreload'] and self.filename and self.filename != self._watched_file:
            self._watched_file = self._filename
            self._file_watcher.addPath(self.filename)

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

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

    # callback triggered by QFileSystemWatcher
    def _file_changed(self):
        # neovim writes a file by removing it first
        # this causes QFileSystemWatcher to forget the file
        self._file_watcher.addPath(self._filename)
        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', self.filename)

        if filename and filename != '':
            try:
                self.load_from_file(filename)
            except IOError:
                self._logger.warning(f'could not open {filename}')
Exemple #8
0
class Scene:
    def __init__(self) -> None:
        super().__init__()  # Call super to make multiple inheritance work.

        from UM.Scene.SceneNode import SceneNode
        self._root = SceneNode(name="Root")
        self._root.setCalculateBoundingBox(False)
        self._connectSignalsRoot()
        self._active_camera = None  # type: Optional[Camera]
        self._ignore_scene_changes = False
        self._lock = threading.Lock()

        # Watching file for changes.
        self._file_watcher = QFileSystemWatcher()
        self._file_watcher.fileChanged.connect(self._onFileChanged)

    def _connectSignalsRoot(self) -> None:
        self._root.transformationChanged.connect(self.sceneChanged)
        self._root.childrenChanged.connect(self.sceneChanged)
        self._root.meshDataChanged.connect(self.sceneChanged)

    def _disconnectSignalsRoot(self) -> None:
        self._root.transformationChanged.disconnect(self.sceneChanged)
        self._root.childrenChanged.disconnect(self.sceneChanged)
        self._root.meshDataChanged.disconnect(self.sceneChanged)

    def setIgnoreSceneChanges(self, ignore_scene_changes: bool) -> None:
        if self._ignore_scene_changes != ignore_scene_changes:
            self._ignore_scene_changes = ignore_scene_changes
            if self._ignore_scene_changes:
                self._disconnectSignalsRoot()
            else:
                self._connectSignalsRoot()

    ##  Acquire the global scene lock.
    #
    #   This will prevent any read or write actions on the scene from other threads,
    #   assuming those threads also properly acquire the lock. Most notably, this
    #   prevents the rendering thread from rendering the scene while it is changing.
    #   Deprecated, use getSceneLock() instead.
    @deprecated("Please use the getSceneLock instead", "3.3")
    def acquireLock(self) -> None:
        self._lock.acquire()

    ##  Release the global scene lock.
    #   Deprecated, use getSceneLock() instead.
    @deprecated("Please use the getSceneLock instead", "3.3")
    def releaseLock(self) -> None:
        self._lock.release()

    ##  Gets the global scene lock.
    #
    #   Use this lock to prevent any read or write actions on the scene from other threads,
    #   assuming those threads also properly acquire the lock. Most notably, this
    #   prevents the rendering thread from rendering the scene while it is changing.
    def getSceneLock(self) -> threading.Lock:
        return self._lock

    ##  Get the root node of the scene.
    def getRoot(self) -> "SceneNode":
        return self._root

    ##  Change the root node of the scene
    def setRoot(self, node: "SceneNode") -> None:
        if self._root != node:
            if not self._ignore_scene_changes:
                self._disconnectSignalsRoot()
            self._root = node
            if not self._ignore_scene_changes:
                self._connectSignalsRoot()
            self.rootChanged.emit()

    rootChanged = Signal()

    ##  Get the camera that should be used for rendering.
    def getActiveCamera(self) -> Optional[Camera]:
        return self._active_camera

    def getAllCameras(self) -> List[Camera]:
        cameras = []
        for node in BreadthFirstIterator(self._root):  # type: ignore
            if isinstance(node, Camera):
                cameras.append(node)
        return cameras

    ##  Set the camera that should be used for rendering.
    #   \param name The name of the camera to use.
    def setActiveCamera(self, name: str) -> None:
        camera = self.findCamera(name)
        if camera:
            self._active_camera = camera
        else:
            Logger.log(
                "w", "Couldn't find camera with name [%s] to activate!" % name)

    ##  Signal that is emitted whenever something in the scene changes.
    #   \param object The object that triggered the change.
    sceneChanged = Signal()

    ##  Find an object by id.
    #
    #   \param object_id The id of the object to search for, as returned by the python id() method.
    #
    #   \return The object if found, or None if not.
    def findObject(self, object_id: int) -> Optional["SceneNode"]:
        for node in BreadthFirstIterator(self._root):  # type: ignore
            if id(node) == object_id:
                return node
        return None

    def findCamera(self, name: str) -> Optional[Camera]:
        for node in BreadthFirstIterator(self._root):  # type: ignore
            if isinstance(node, Camera) and node.getName() == name:
                return node
        return None

    ##  Add a file to be watched for changes.
    #   \param file_path The path to the file that must be watched.
    def addWatchedFile(self, file_path: str) -> None:
        # The QT 5.10.0 issue, only on Windows. Cura crashes after loading a stl file from USB/sd-card/Cloud-based drive
        if not Platform.isWindows():
            self._file_watcher.addPath(file_path)

    ##  Remove a file so that it will no longer be watched for changes.
    #   \param file_path The path to the file that must no longer be watched.
    def removeWatchedFile(self, file_path: str) -> None:
        # The QT 5.10.0 issue, only on Windows. Cura crashes after loading a stl file from USB/sd-card/Cloud-based drive
        if not Platform.isWindows():
            self._file_watcher.removePath(file_path)

    ##  Triggered whenever a file is changed that we currently have loaded.
    def _onFileChanged(self, file_path: str) -> None:
        if not os.path.isfile(file_path) or os.path.getsize(
                file_path) == 0:  # File doesn't exist any more, or it is empty
            return

        # Multiple nodes may be loaded from the same file at different stages. Reload them all.
        from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator  # To find which nodes to reload when files have changed.
        modified_nodes = [
            node for node in DepthFirstIterator(self.getRoot())
            if node.getMeshData()
            and node.getMeshData().getFileName() == file_path
        ]  # type: ignore

        if modified_nodes:
            self._reload_message = Message(i18n_catalog.i18nc(
                "@info", "Would you like to reload {filename}?").format(
                    filename=os.path.basename(file_path)),
                                           title=i18n_catalog.i18nc(
                                               "@info:title",
                                               "File has been modified"))
            self._reload_message.addAction(
                "reload",
                i18n_catalog.i18nc("@action:button", "Reload"),
                icon="",
                description=i18n_catalog.i18nc(
                    "@action:description",
                    "This will trigger the modified files to reload from disk."
                ))
            self._reload_callback = functools.partial(self._reloadNodes,
                                                      modified_nodes)
            self._reload_message.actionTriggered.connect(self._reload_callback)
            self._reload_message.show()

    ##  Reloads a list of nodes after the user pressed the "Reload" button.
    #   \param nodes The list of nodes that needs to be reloaded.
    #   \param message The message that triggered the action to reload them.
    #   \param action The button that triggered the action to reload them.
    def _reloadNodes(self, nodes: List["SceneNode"], message: str,
                     action: str) -> None:
        if action != "reload":
            return
        self._reload_message.hide()
        for node in nodes:
            meshdata = node.getMeshData()
            if meshdata:
                filename = meshdata.getFileName()
                if not filename or not os.path.isfile(
                        filename):  # File doesn't exist any more.
                    continue
                job = ReadMeshJob(filename)
                self._reload_finished_callback = functools.partial(
                    self._reloadJobFinished, node)
                job.finished.connect(self._reload_finished_callback)
                job.start()

    ##  Triggered when reloading has finished.
    #
    #   This then puts the resulting mesh data in the node.
    def _reloadJobFinished(self, replaced_node: SceneNode,
                           job: ReadMeshJob) -> None:
        for node in job.getResult():
            mesh_data = node.getMeshData()
            if mesh_data:
                replaced_node.setMeshData(mesh_data)
            else:
                Logger.log("w", "Could not find a mesh in reloaded node.")
Exemple #9
0
class SubFolderWatcher(QObject):
    finished = pyqtSignal(str, int)

    def __init__(self):
        super(SubFolderWatcher, self).__init__()
        self.base_path = ''
        self.subfolder = ''
        self.files = {}
        self.finished_checking = False
        self.watcher = QFileSystemWatcher()
        self.timer = QTimer()
        self.check_threshold = 3
        self.check_number = 0
        self.sub_folder = ''

    def connect(self):
        self.watcher.addPaths([self.base_path])
        self.watcher.directoryChanged.connect(
            lambda y: self.directory_changed(y))
        self.watcher.fileChanged.connect(lambda x: self.file_changed(x))
        self.timer.timeout.connect(self.check_files)
        self.timer.start(timer_delay * 1000)

    def check_files(self):
        finished = True
        if exists(self.base_path):
            for sub_path in Path(self.base_path).rglob('*'):
                new_st_mtime = int(stat(sub_path).st_mtime)
                path_name = normpath(sub_path)
                if path_name not in self.files:
                    self.files[path_name] = new_st_mtime
                if self.files[path_name] != new_st_mtime:
                    finished = False
                self.files[path_name] = new_st_mtime
            if finished:
                self.check_number += 1
            if self.check_number > self.check_threshold:
                self.timer.stop()
                self.watcher.removePath(self.base_path)
                self.copy_folder()
        else:
            self.finished_checking = True

    def copy_file(self):
        pass

    def copy_folder(self):
        try:
            shutil.move(self.base_path, join(home, self.subfolder))
        except shutil.Error:
            remove(self.base_path)
        else:
            pass
            """
            NOTE:
            On my home system, I sometimes use the following code to change groups and owner permissions of the
            copied files. 
            I would personally not use this in a production environment, especially with the 775 permissions. 
            The following code doesn't make sense to run on Windows, so you can ignore if you're on that system.
            
            local = join(home, self.subfolder)
            chmod(local, 0o775)
            shutil.chown(local, 'grp', 'usr')
            for root, dirs, files in walk(local):
                chmod(root, 0o775)
                shutil.chown(root, 'grp', 'usr')
                for file_name in files:
                    chmod(join(root, file_name), 0o775)
                    shutil.chown(join(root, file_name), 'grp', 'usr')
            """
        self.finished_checking = True
        self.finished.emit(self.base_path)

    @pyqtSlot(str)
    def directory_changed(self, path):
        if self.timer.isActive():
            self.timer.stop()
            self.timer.start(timer_delay * 1000)
        for sub_path in Path(path).rglob('*'):
            if isfile(sub_path):
                path_name = normpath(join(path, sub_path.name))
                self.files[path_name] = int(stat(self.base_path).st_mtime)

    def file_changed(self):
        pass

    def __eq__(self, other):
        try:
            return other.base_path == self.base_path
        except AttributeError:
            return other == self.base_path
Exemple #10
0
class ExportDialog(QDialog):
    def __init__(self, font, parent=None):
        super().__init__(parent, Qt.MSWindowsFixedSizeDialogHint)
        self.setWindowModality(Qt.WindowModal)
        self.setWindowTitle(self.tr("Export…"))

        self._exportDirectory = QDir.toNativeSeparators(
            QStandardPaths.standardLocations(QStandardPaths.DocumentsLocation)[0]
        )
        self.baseName = getAttrWithFallback(font.info, "postscriptFontName")

        self.formatBtnSet = ButtonSet(self)
        self.formatBtnSet.setOptions(["OTF", "TTF"])
        self.formatBtnSet.setSelectionMode(ButtonSet.OneOrMoreSelection)
        self.compressionBtnSet = ButtonSet(self)
        self.compressionBtnSet.setOptions(["None", "WOFF", "WOFF2"])
        self.compressionBtnSet.setSelectionMode(ButtonSet.OneOrMoreSelection)
        self.numberLabel = QLabel(self)
        self.formatBtnSet.clicked.connect(self.updateNumbers)
        self.compressionBtnSet.clicked.connect(self.updateNumbers)

        self.removeOverlapBox = QCheckBox(self.tr("Remove Overlap"), self)
        # self.removeOverlapBox.setChecked(True)  # XXX: implement
        self.removeOverlapBox.setEnabled(False)
        self.autohintBox = QCheckBox(self.tr("Autohint"), self)
        # self.autohintBox.setChecked(True)  # XXX: implement
        self.autohintBox.setEnabled(False)

        self.exportBox = QCheckBox(self)
        boxSize = self.exportBox.sizeHint()
        self.exportBox.setText(self.tr("Use Export Directory"))
        self.exportBox.setChecked(True)
        self.exportIcon = QLabel(self)
        icon = self.style().standardIcon(QStyle.SP_DirClosedIcon)
        iconSize = QSize(24, 24)
        self.exportIcon.setPixmap(icon.pixmap(icon.actualSize(iconSize)))
        self.exportIcon.setBaseSize(iconSize)
        self.exportDirLabel = QLabel(self)
        self.exportDirLabel.setText(self.exportDirectory)
        self.exportDirButton = QPushButton(self)
        self.exportDirButton.setText(self.tr("Choose…"))
        self.exportDirButton.clicked.connect(
            lambda: self.chooseExportDir(self.exportDirectory)
        )

        # if files are to be overwritten, put up a warning
        # + use a file system watcher to avoid TOCTOU
        self.warningIcon = QLabel(self)
        icon = icons.i_warning()
        iconSize = QSize(20, 20)
        self.warningIcon.setPixmap(icon.pixmap(icon.actualSize(iconSize)))
        self.warningIcon.setBaseSize(iconSize)
        # XXX: not sure why this is needed
        self.warningIcon.setFixedWidth(iconSize.width())
        sp = self.warningIcon.sizePolicy()
        sp.setRetainSizeWhenHidden(True)
        self.warningIcon.setSizePolicy(sp)
        self.warningLabel = QLabel(self)
        palette = self.warningLabel.palette()
        role, color = self.warningLabel.foregroundRole(), QColor(230, 20, 20)
        palette.setColor(palette.Active, role, color)
        palette.setColor(palette.Inactive, role, color)
        self.warningLabel.setPalette(palette)
        sp = self.warningLabel.sizePolicy()
        sp.setRetainSizeWhenHidden(True)
        self.warningLabel.setSizePolicy(sp)

        self.updateExportStatus()
        self.exportBox.toggled.connect(self.updateExportStatus)

        self.watcher = QFileSystemWatcher(self)
        self.watcher.addPath(self.exportDirectory)
        self.updateNumbers()
        self.watcher.directoryChanged.connect(self.updateNumbers)

        buttonBox = QDialogButtonBox(QDialogButtonBox.Cancel)
        buttonBox.addButton(self.tr("Generate…"), QDialogButtonBox.AcceptRole)
        buttonBox.accepted.connect(self.finish)
        buttonBox.rejected.connect(self.reject)

        layout = QVBoxLayout(self)
        formLayout = QFormLayout()
        formLayout.addRow(self.tr("Format"), self.formatBtnSet)
        formLayout.addRow(self.tr("Compression"), self.compressionBtnSet)
        formLayout.setHorizontalSpacing(16)
        formLayout.setContentsMargins(0, 0, 0, 4)
        layout.addLayout(formLayout)
        layout.addWidget(self.numberLabel)
        layout.addWidget(self.removeOverlapBox)
        layout.addWidget(self.autohintBox)
        layout.addWidget(self.exportBox)
        exportLayout = QHBoxLayout()
        exportLayout.addWidget(self.exportIcon)
        exportLayout.addWidget(self.exportDirLabel)
        exportLayout.addWidget(self.exportDirButton)
        exportLayout.addWidget(QWidget())
        margins = exportLayout.contentsMargins()
        margins.setLeft(margins.left() + boxSize.width() + 4)
        exportLayout.setContentsMargins(margins)
        exportLayout.setStretch(3, 1)
        layout.addLayout(exportLayout)
        warningLayout = QHBoxLayout()
        warningLayout.addWidget(self.warningIcon)
        warningLayout.addWidget(self.warningLabel)
        warningLayout.addWidget(QWidget())
        margins.setBottom(margins.bottom() + 4)
        warningLayout.setContentsMargins(margins)
        warningLayout.setStretch(3, 1)
        layout.addLayout(warningLayout)
        layout.addWidget(buttonBox)
        # XXX: check this on non-Windows platforms
        layout.setContentsMargins(16, 16, 16, 16)

        self.readSettings()

    def readSettings(self):
        attrs = [
            (settings.exportFileFormats, self.formatBtnSet.setSelectedOptions),
            (
                settings.exportCompressionFormats,
                self.compressionBtnSet.setSelectedOptions,
            ),
            (settings.exportRemoveOverlap, self.removeOverlapBox.setChecked),
            (settings.exportAutohint, self.autohintBox.setChecked),
            (settings.exportUseDirectory, self.exportBox.setChecked),
            (
                settings.exportDirectory,
                lambda attr: setattr(self, "exportDirectory", attr),
            ),
        ]
        for getValue, setter in attrs:
            value = getValue()
            if value != "":
                setter(value)
        self.exportDirLabel.setText(self.exportDirectory)
        self.updateNumbers()

    def writeSettings(self):
        attrs = [
            (settings.setExportFileFormats, self.formatBtnSet.selectedOptions),
            (
                settings.setExportCompressionFormats,
                self.compressionBtnSet.selectedOptions,
            ),
            (settings.setExportRemoveOverlap, self.removeOverlapBox.isChecked),
            (settings.setExportAutohint, self.autohintBox.isChecked),
            (settings.setExportUseDirectory, self.exportBox.isChecked),
            (settings.setExportDirectory, lambda: getattr(self, "exportDirectory")),
        ]
        for setValue, getter in attrs:
            value = getter()
            setValue(value)

    @property
    def exportDirectory(self):
        return self._exportDirectory

    @exportDirectory.setter
    def exportDirectory(self, path):
        oldValue = self._exportDirectory
        if oldValue == path:
            return
        if oldValue is not None:
            self.watcher.removePath(oldValue)
        self._exportDirectory = path
        self.watcher.addPath(self.exportDirectory)
        self.exportDirLabel.setText(self._exportDirectory)
        self.updateNumbers()

    def chooseExportDir(self, givenDir=None):
        state = settings.exportFileDialogState()
        dialog = QFileDialog(self)
        if state:
            dialog.restoreState(state)
        dialogDir = dialog.directory()
        if givenDir is not None:
            dialog.setDirectory(givenDir)
        elif dialogDir is None:
            dialog.setDirectory(
                QStandardPaths.standardLocations(QStandardPaths.DocumentsLocation)[0]
            )
        dialog.setAcceptMode(QFileDialog.AcceptOpen)
        dialog.setFileMode(QFileDialog.Directory)
        ok = dialog.exec_()
        exportDir = QDir.toNativeSeparators(dialog.directory().absolutePath())
        if givenDir is not None:
            dialog.setDirectory(dialogDir)
        settings.setExportFileDialogState(dialog.saveState())
        if ok:
            self.exportDirectory = exportDir

    def updateExportStatus(self):
        value = self.exportBox.isChecked()
        for w in (
            self.exportIcon,
            self.exportDirLabel,
            self.exportDirButton,
            self.warningIcon,
            self.warningLabel,
        ):
            w.setEnabled(value)

    def updateNumbers(self):
        formatOptions = self.formatBtnSet.selectedOptions()
        compressionOptions = self.compressionBtnSet.selectedOptions()
        # number label
        count = len(formatOptions) * len(compressionOptions)
        self.numberLabel.setText(
            self.tr(f"×%n font(s) with base name: {self.baseName}*", n=count)
        )
        # overwrite status
        # XXX: not DRY with the TFont.export logic
        count = 0
        # make a list out of this, otherwise we'll consume the iterator
        compressions = list(map(str.lower, compressionOptions))
        for format in map(str.lower, formatOptions):
            filePath = os.path.join(self.exportDirectory, f"{self.baseName}.{format}")
            for compression in compressions:
                fullPath = filePath
                if compression != "none":
                    fullPath += f".{compression}"
                count += os.path.exists(fullPath)
        visible = bool(count)
        self.warningIcon.setVisible(visible)
        self.warningLabel.setVisible(visible)
        if visible:
            self.warningLabel.setText(
                self.tr("%n file(s) will be overwritten.", n=count)
            )

    def finish(self):
        self.accept()
        export = self.exportBox.isChecked()
        # here we pick a directory, but it won't become the default export dir
        if not export:
            self.chooseExportDir()

    @classmethod
    def getExportParameters(cls, parent, font):
        dialog = cls(font, parent)
        result = dialog.exec_()
        params = dict(
            baseName=dialog.baseName,
            formats=dialog.formatBtnSet.selectedOptions(),
            compression=dialog.compressionBtnSet.selectedOptions(),
            exportDirectory=dialog.exportDirectory,
            removeOverlap=dialog.removeOverlapBox.isChecked(),
            autohint=dialog.autohintBox.isChecked(),
        )
        return (params, result)

    # ----------
    # Qt methods
    # ----------

    def accept(self):
        self.writeSettings()
        super().accept()

    def reject(self):
        self.writeSettings()
        super().reject()

    def closeEvent(self, event):
        super().closeEvent(event)
        if event.isAccepted():
            self.writeSettings()
Exemple #11
0
class PugdebugDocuments(QObject):

    watcher = None

    open_documents = {}

    document_changed = pyqtSignal(object)

    def __init__(self):
        super(PugdebugDocuments, self).__init__()

        self.watcher = QFileSystemWatcher()
        self.watcher.fileChanged.connect(self.handle_file_changed)

    def open_document(self, path):
        path_key = self.get_path_key(path)

        document = PugdebugDocument(path)
        self.open_documents[path_key] = document

        self.watcher.addPath(path)

        return document

    def close_document(self, path):
        path_key = self.get_path_key(path)
        self.open_documents.pop(path_key, None)

        self.watcher.removePath(path)

    def refresh_document(self, path):
        """Refresh a document

        Gets called when the file system watcher notices
        a change to an open document.
        """
        path_key = self.get_path_key(path)
        document = self.open_documents[path_key]

        document.read_file(path)

        self.document_changed.emit(document)

    def is_document_open(self, path):
        path_key = self.get_path_key(path)
        return path_key in self.open_documents

    def handle_file_changed(self, path):
        """Handle when a watched file gets changed

        Crazy stuff ahead.

        If a file is modified, some editors (systems?) will first remove
        the file and then write it back to the disk. And for that split
        second, the watcher will drop the file from being watched.

        But then again, maybe that file really got deleted? Who knows?!

        Anyway, when a file gets modified, we sleep a short while to
        see if that file will "get back" and if so, add it back to the
        watcher. If not, we'll assume the file got deleted.
        """
        if not self.__is_path_watched(path):
            fileinfo = QFileInfo(path)

            total_slept = 0
            file_exists = fileinfo.exists()

            while not file_exists:
                sleep_for = 0.1
                total_slept += sleep_for

                if total_slept > 1:
                    break

                time.sleep(sleep_for)
                file_exists = fileinfo.exists()

            if file_exists:
                self.watcher.addPath(path)

                self.refresh_document(path)
            else:
                # file got deleted?
                pass

    def get_path_key(self, path):
        path_key = hashlib.md5(path.encode("utf-8"))
        return path_key.hexdigest()

    def __is_path_watched(self, path):
        return path in self.watcher.files()
Exemple #12
0
class AppMain(Ui_MainWindow):
    RAM_VIEW_INITIAL_SIZE = 10000
    TEMP_MAX_RAM_USE = 1024 * 1000
    STEP_TIMER_IN_MS = 100

    def __init__(self):
        Ui_MainWindow.__init__(self)

        ## class Variables
        self.config_dialog = None
        self.rom_stream = None
        self.rom_path = None
        self.rom_model = None
        self.rom_watcher = None
        self.ram_model = None
        self.sim_real_line_old = 0
        self.sim_real_line_current = 0
        self.local_stack_model = None
        self.global_stack_model = None
        self.data_changed = None
        self.lst_parser = None
        self.sim_line = 0
        self.last_step = None
        self.asm_thread = None
        self.sim_thread = None
        self.vm_thread = None
        self.vm_task = None
        self.simulator_task = None
        self.assembler_task = None
        self.config_dialog_ui = None
        self.step_timer = None
        self.window = AppMainWindow()

        # Setup Dialog, Editor, Actions, Threads, Img Resizing
        self.setup_dialog()
        self.setupUi(self.window)
        self.setup_editor()
        self.setup_actions()
        self.setup_threads()

    def setup_editor(self):
        self.rom_stream = tempfile.SpooledTemporaryFile(
            max_size=self.TEMP_MAX_RAM_USE, mode="w+")
        self.rom_path = None
        self.lst_parser = None
        self.rom_watcher = QFileSystemWatcher()
        self.rom_watcher.fileChanged.connect(self.reload_rom)
        self.spinBox.setValue(500)
        self.on_new()

    def setup_threads(self):
        self.asm_thread = QThread()
        self.sim_thread = QThread()
        self.vm_thread = QThread()

    def setup_dialog(self):
        self.config_dialog = QDialog()
        self.config_dialog_ui = config_dialog.Ui_Dialog()
        self.config_dialog_ui.setupUi(self.config_dialog)
        self.config_dialog_ui.assemblerLineEdit.setText(
            "../jar/Z01-Assembler.jar")
        self.config_dialog_ui.rtlLineEdit.setText("../Z01-Simulator-rtl-2/")

    def setup_clean_views(self,
                          table,
                          rows=100,
                          caption="Dados",
                          line_header=None):
        model = QStandardItemModel(rows, 1, self.window)
        model.setHorizontalHeaderItem(0, QStandardItem(caption))
        table.setModel(model)
        for k in range(0, table.horizontalHeader().count()):
            table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        for l in range(0, rows):
            if line_header is None:
                model.setHeaderData(l, QtCore.Qt.Vertical, l)
            else:
                model.setHeaderData(l, QtCore.Qt.Vertical, line_header(l))
        return model

    def setup_actions(self):
        self.step_timer = QtCore.QTimer()
        self.step_timer.timeout.connect(self.on_proximo)

        self.actionNovo.triggered.connect(self.on_new)
        self.actionSalvar_ROM.triggered.connect(self.on_save)
        self.actionAbrir.triggered.connect(self.on_load)
        self.actionProximo.triggered.connect(self.on_proximo)
        self.actionExecutarFim.triggered.connect(self.on_executar_fim)
        self.actionParar.triggered.connect(self.on_parar)
        self.actionEraseRAM.triggered.connect(self.on_clear_ram)
        self.actionVoltarInicio.triggered.connect(self.on_voltar_inicio)
        self.spinBox.valueChanged.connect(self.on_voltar_inicio)
        self.config_dialog_ui.procurarButton.clicked.connect(
            self.on_search_assembler)
        self.config_dialog_ui.alterarButton.clicked.connect(
            self.config_dialog.close)
        self.actionConfiguracoes.triggered.connect(self.config_dialog.show)

    def change_rtl_dir(self, new_dir):
        self.config_dialog_ui.rtlLineEdit.setText(new_dir)

    def on_ram_tooltip(self, item):
        text = item.text().strip()

        try:
            val = int(text, 2)
        except ValueError:
            return

        item.setToolTip("{0:d} dec - {1:x} hex".format(val, val))

    def on_clear_ram(self):
        self.ram_model = self.setup_clean_views(
            self.ramView,
            rows=self.RAM_VIEW_INITIAL_SIZE,
            caption="RAM",
            line_header=asm_utils.z01_ram_name)
        for i in range(0, self.RAM_VIEW_INITIAL_SIZE):
            item = QStandardItem("0000000000000000")
            self.on_ram_tooltip(item)
            self.ram_model.setItem(i, item)

    def on_new(self):
        self.rom_path = None
        self.on_clear_ram()
        self.rom_model = self.setup_clean_views(self.romView,
                                                caption="Program")
        #self.local_stack_model = self.setup_clean_views(self.localStackView, caption="Local Stack")
        self.global_stack_model = self.setup_clean_views(
            self.globalStackView,
            caption="Global Stack",
            line_header=vm_utils.vm_global_stack_name)
        self.rom_model.itemChanged.connect(self.valid_rom)
        self.ram_model.itemChanged.connect(self.valid_ram)
        self.actionROMAssembly.setEnabled(True)

        self.clear_simulation()

    def on_voltar_inicio(self):
        self.data_changed = True
        self.clear_simulation()

    def on_parar(self):
        self.step_timer.stop()

    def on_executar_fim(self):
        self.step_timer.start(self.STEP_TIMER_IN_MS)

    def show(self):
        self.window.show()

    def on_save(self):
        filename = self.rom_path

        if self.rom_path is not None:
            self.rom_watcher.removePath(self.rom_path)

        if filename is None:
            filename = QFileDialog.getSaveFileName(self.window,
                                                   "Salve o arquivo",
                                                   os.getcwd(),
                                                   "Arquivos (*.vm)")
            if len(filename) == 0 or len(filename[0]) == 0:
                return None
            filename = filename[0]
            self.rom_path = filename

        file_utils.copy_model_to_file(self.rom_model, self.rom_stream)
        file_utils.stream_to_file(self.rom_stream, filename)

        self.rom_watcher.addPath(self.rom_path)

    def on_load(self):
        filename = QFileDialog.getOpenFileName(self.window, "Escolha arquivo",
                                               os.getcwd() + "/examples/",
                                               "Arquivos (*.vm)")
        if len(filename) == 0 or len(filename[0]) == 0:
            return None

        if self.rom_path is not None:
            self.rom_watcher.removePath(self.rom_path)

        self.on_new()
        self.rom_path = filename[0]
        self.rom_watcher.addPath(self.rom_path)
        self.reload_rom()

    def reload_rom(self):
        return self.load_rom(self.rom_path)

    def load_rom(self, filename):
        if not os.path.exists(filename):
            return

        if filename.endswith(".asm") or filename.endswith(".vm"):
            self.load_vm(filename, self.rom_model)

    def on_search_assembler(self):
        filename = QFileDialog.getOpenFileName(self.window, "Escolha arquivo",
                                               os.getcwd(),
                                               "Arquivo JAR (*.jar)")
        if len(filename) == 0 or len(filename[0]) == 0:
            return None

        self.config_dialog_ui.assemblerLineEdit.setText(filename[0])

    def on_proximo(self):

        if self.data_changed:
            if self.lst_parser is not None:
                self.lst_parser.close()
            self.sim_line = 0
            file_utils.copy_model_to_file(self.rom_model, self.rom_stream)
            self.vm_translate(self.vm_end)
            return False

        while (True):

            #time.sleep(0.4)  # esse timer nao funcionou como eu esperava !

            self.sim_real_line_old = self.sim_real_line_current

            step = self.lst_parser.advance()

            if "s_regAout" not in step:
                self.step_timer.stop()
                QMessageBox.warning(self.window, "Simulador",
                                    "Fim de simulação")
                return False

            self.update_line_edit(self.lineEdit_SP,
                                  self.model_get_value(self.ram_model, 0))
            self.update_line_edit(self.lineEdit_LCL,
                                  self.model_get_value(self.ram_model, 1))
            self.update_line_edit(self.lineEdit_ARG,
                                  self.model_get_value(self.ram_model, 2))
            self.update_line_edit(self.lineEdit_THIS,
                                  self.model_get_value(self.ram_model, 3))
            self.update_line_edit(self.lineEdit_THAT,
                                  self.model_get_value(self.ram_model, 4))

            sp_index = self.ram_model.index(0, 0)  # SP

            if self.last_step is not None:
                addr = int(step["s_regAout"], 2)
                index = self.ram_model.index(addr, 0)
                last_sp = int(self.ram_model.itemFromIndex(sp_index).text(), 2)

                if int(step["writeM"]) == 0 and int(
                        step["s_muxALUI_A"]) == 1 and int(
                            self.last_step["s_muxALUI_A"]) == 0:
                    self.ramView.setCurrentIndex(index)

                if int(step["writeM"]) == 1:
                    self.ramView.setCurrentIndex(index)
                    self.ram_model.itemFromIndex(index).setText(step["outM"])
            else:
                last_sp = 256

            ## descobrir linha com base no SP
            sp = int(self.ram_model.itemFromIndex(sp_index).text(), 2)
            if sp != last_sp:
                self.sim_line += 1

            self.refresh_stack(self.ram_model, 256, sp,
                               self.global_stack_model, self.globalStackView)

            ## update line
            pc = int(step["pcout"], 2) - 1
            self.sim_real_line_current = vm_utils.vm_command_line(
                self.assembler_task.commands_pos,
                self.assembler_task.comments_pos,
                self.assembler_task.labels_pos, pc)

            index = self.rom_model.index(self.sim_real_line_current, 0)
            self.romView.setCurrentIndex(index)

            print("PROXIMA INSTRUCAO NASM")
            self.last_step = step

            if (self.sim_real_line_current != self.sim_real_line_old):
                print("PROXIMA INSTRUCAO VM")
                break

    def model_get_value(self, model, row):
        index = model.index(row, 0)
        return model.itemFromIndex(index).text()

    def model_set_value(self, model, row, value, tooltip=False):
        index = model.index(row, 0)
        model.itemFromIndex(index).setText(value)
        self.on_ram_tooltip(model.itemFromIndex(index))
        if tooltip:
            self.on_ram_tooltip(model.itemFromIndex(index))

    def refresh_stack(self, ram_model, start_addr, end_addr, target_model,
                      target_view):
        line = 0
        for i in range(start_addr, end_addr, 1):
            self.model_set_value(target_model, line,
                                 self.model_get_value(ram_model, i))
            line += 1
        index = target_model.index(line, 0)
        target_view.setCurrentIndex(index)

        for i in range(line, line + 10):
            self.model_set_value(target_model, i, "")

    def update_line_edit(self, line_edit, new_value, ignore=False):
        if line_edit.text() != new_value:
            line_edit.setText(new_value)
            if not ignore:
                line_edit.setStyleSheet(
                    "QLineEdit {background-color: yellow;}")
            self.on_ram_tooltip(line_edit)
        else:
            line_edit.setStyleSheet("")

    def valid_rom(self, item):
        if not item.text():
            return None

        index = item.index()

        while index.row() + 50 >= self.rom_model.rowCount():
            self.rom_model.appendRow(QStandardItem(""))

        if not vm_utils.vm_valid_command(item.text()):
            item.setText("")

    def valid_binary(self, item):
        valid = True
        text = item.text().strip()

        try:
            val = int(text, 2)
        except ValueError:
            valid = False

        if not valid:
            print("Invalid BIN Instruction: {}".format(item.text()))

        return valid

    def valid_ram(self, item):
        if not item.text():
            return None
        text = item.text()
        index = item.index()

        while index.row() + 100 >= self.ram_model.rowCount():
            self.ram_model.appendRow(QStandardItem("{0:0>16b}".format(0)))

        if text.startswith("d"):
            text = text[1:]
            if text.isdigit():
                item.setText("{0:0>16b}".format(int(text)))

        valid = self.valid_binary(item)

        if valid:
            self.on_ram_tooltip(item)
        else:
            item.setText("{0:0>16b}".format(0))

    def vm_translate(self, callback):
        if self.asm_thread.isRunning() or self.sim_thread.isRunning(
        ) or self.vm_thread.isRunning():
            print(
                "[vm_translate] Tarefas de simulação em processamento por favor aguarde finalizar...."
            )
            return False

        vm_translator = "java -jar ../jar/Z01-VMTranslator.jar"
        self.vm_task = VMTask(vm_translator, "temp/", True)
        nasm_out = tempfile.SpooledTemporaryFile(
            max_size=self.TEMP_MAX_RAM_USE, mode="w+")
        self.vm_task.setup(self.rom_stream, nasm_out)
        self.vm_task.finished.connect(callback)
        self.vm_task.moveToThread(self.vm_thread)
        self.vm_thread.started.connect(self.vm_task.run)
        self.vm_thread.start()

    def assemble(self, callback, nasm_file):
        if self.asm_thread.isRunning() or self.sim_thread.isRunning():
            print(
                "[assemble] Tarefas de simulação em processamento por favor aguarde finalizar...."
            )
            return False

        assembler = "java -jar " + self.config_dialog_ui.assemblerLineEdit.text(
        )
        self.assembler_task = AssemblerTask(assembler, "temp/")
        rom_out = tempfile.SpooledTemporaryFile(max_size=self.TEMP_MAX_RAM_USE,
                                                mode="w+")
        self.assembler_task.setup(nasm_file, rom_out)
        self.assembler_task.finished.connect(callback)
        self.assembler_task.moveToThread(self.asm_thread)
        self.asm_thread.started.connect(self.assembler_task.run)
        self.asm_thread.start()

    def simulate(self, rom_file, ram_file):
        if self.asm_thread.isRunning() or self.sim_thread.isRunning():
            print(
                "[simulate] Tarefas de simulação em processamento por favor aguarde finalizar...."
            )
            return False

        self.simulator_task = SimulatorTask(
            "temp/", False, self.config_dialog_ui.simGUIBox.isChecked(),
            self.config_dialog_ui.rtlLineEdit.text())
        lst_out = tempfile.SpooledTemporaryFile(max_size=self.TEMP_MAX_RAM_USE,
                                                mode="w+")
        self.simulator_task.setup(rom_file, ram_file, lst_out,
                                  self.spinBox.value() * 10 + 10)
        self.simulator_task.finished.connect(self.simulation_end)
        self.simulator_task.moveToThread(self.sim_thread)
        self.sim_thread.started.connect(self.simulator_task.run)
        self.sim_thread.start()
        self.lock_and_show_dialog()

    def lock_and_show_dialog(self):
        ## waits for ASM thread and SIM thread to end
        self.progress_dialog = QProgressDialog("Simulando...", "Cancelar", 0,
                                               0, self.window)
        self.progress_dialog.setCancelButton(None)
        self.progress_dialog.setAutoReset(True)
        self.progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
        self.progress_dialog.setMinimumDuration(0)
        self.progress_dialog.setValue(0)
        self.progress_dialog.setWindowTitle("RESimulatorGUI")
        self.progress_dialog.setWindowFlags(self.progress_dialog.windowFlags()
                                            & ~QtCore.Qt.WindowCloseButtonHint)

        while self.asm_thread.isRunning() or self.sim_thread.isRunning():
            qapp.processEvents()

        self.progress_dialog.reset()

    def get_updated_ram(self):
        ram = tempfile.SpooledTemporaryFile(max_size=self.TEMP_MAX_RAM_USE,
                                            mode="w+")
        file_utils.copy_model_to_file(self.ram_model, ram)
        return ram

    def check_assembler_sucess(self):
        if self.assembler_task is not None and self.assembler_task.success is True:
            return True
        QMessageBox.critical(self.window, "Assembler",
                             "Erro ao traduzir assembly.")
        self.step_timer.stop()
        return False

    def check_vm_sucess(self):
        if self.vm_task is not None and self.vm_task.success is True:
            return True
        QMessageBox.critical(self.window, "VM Translator",
                             "Erro ao traduzir código VM para Assembly.")
        self.step_timer.stop()
        return False

    def vm_end(self):
        self.vm_thread.quit()
        self.vm_thread.wait()
        if not self.check_vm_sucess():
            return
        print("VM Translator done!")
        self.assemble(self.assemble_end, self.vm_task.stream_out)

    def assemble_end(self):
        self.asm_thread.quit()  # ensure end of thread
        self.asm_thread.wait()
        ram = self.get_updated_ram()
        if not self.check_assembler_sucess():
            return
        print("ASM done!")
        self.simulate(self.assembler_task.stream_out, ram)

    def simulation_end(self):
        self.sim_thread.quit()  #ensure end of thread
        self.sim_thread.wait()
        print("SIM done!")
        self.data_changed = False
        self.lst_parser = LSTParser(self.simulator_task.lst_stream)

    def clear_simulation(self):
        self.last_step = None
        self.update_line_edit(self.lineEdit_SP, "0000000000000000", True)
        self.update_line_edit(self.lineEdit_LCL, "0000000000000000", True)
        self.update_line_edit(self.lineEdit_ARG, "0000000000000000", True)
        self.update_line_edit(self.lineEdit_THIS, "0000000000000000", True)
        self.update_line_edit(self.lineEdit_THAT, "0000000000000000", True)
        self.data_changed = True
        index = self.ram_model.index(0, 0)
        self.ramView.setCurrentIndex(index)
        index = self.rom_model.index(0, 0)
        self.romView.setCurrentIndex(index)

    def load_file(self, filename, model):
        fp = open(filename, "r")
        counter = 0
        lines = file_utils.file_len(filename)
        self.rom_model = self.setup_clean_views(self.romView,
                                                rows=lines + 200,
                                                caption="Program")
        for i, l in enumerate(fp):
            if vm_utils.vm_valid_command(l.strip()):
                index = self.rom_model.index(counter, 0)
                self.rom_model.itemFromIndex(index).setText(l.strip())
                counter += 1
        fp.close()

    def load_vm(self, filename, model):
        self.load_file(filename, model)
Exemple #13
0
class Scene:
    def __init__(self) -> None:
        super().__init__()

        from UM.Scene.SceneNode import SceneNode
        self._root = SceneNode(name = "Root")
        self._root.setCalculateBoundingBox(False)
        self._connectSignalsRoot()
        self._active_camera = None  # type: Optional[Camera]
        self._ignore_scene_changes = False
        self._lock = threading.Lock()

        # Watching file for changes.
        self._file_watcher = QFileSystemWatcher()
        self._file_watcher.fileChanged.connect(self._onFileChanged)

        self._reload_message = None  # type: Optional[Message]

    def _connectSignalsRoot(self) -> None:
        self._root.transformationChanged.connect(self.sceneChanged)
        self._root.childrenChanged.connect(self.sceneChanged)
        self._root.meshDataChanged.connect(self.sceneChanged)

    def _disconnectSignalsRoot(self) -> None:
        self._root.transformationChanged.disconnect(self.sceneChanged)
        self._root.childrenChanged.disconnect(self.sceneChanged)
        self._root.meshDataChanged.disconnect(self.sceneChanged)

    def setIgnoreSceneChanges(self, ignore_scene_changes: bool) -> None:
        if self._ignore_scene_changes != ignore_scene_changes:
            self._ignore_scene_changes = ignore_scene_changes
            if self._ignore_scene_changes:
                self._disconnectSignalsRoot()
            else:
                self._connectSignalsRoot()

    ##  Gets the global scene lock.
    #
    #   Use this lock to prevent any read or write actions on the scene from other threads,
    #   assuming those threads also properly acquire the lock. Most notably, this
    #   prevents the rendering thread from rendering the scene while it is changing.
    def getSceneLock(self) -> threading.Lock:
        return self._lock

    ##  Get the root node of the scene.
    def getRoot(self) -> "SceneNode":
        return self._root

    ##  Change the root node of the scene
    def setRoot(self, node: "SceneNode") -> None:
        if self._root != node:
            if not self._ignore_scene_changes:
                self._disconnectSignalsRoot()
            self._root = node
            if not self._ignore_scene_changes:
                self._connectSignalsRoot()
            self.rootChanged.emit()

    rootChanged = Signal()

    ##  Get the camera that should be used for rendering.
    def getActiveCamera(self) -> Optional[Camera]:
        return self._active_camera

    def getAllCameras(self) -> List[Camera]:
        cameras = []
        for node in BreadthFirstIterator(self._root):  # type: ignore
            if isinstance(node, Camera):
                cameras.append(node)
        return cameras

    ##  Set the camera that should be used for rendering.
    #   \param name The name of the camera to use.
    def setActiveCamera(self, name: str) -> None:
        camera = self.findCamera(name)
        if camera:
            self._active_camera = camera
        else:
            Logger.log("w", "Couldn't find camera with name [%s] to activate!" % name)

    ##  Signal that is emitted whenever something in the scene changes.
    #   \param object The object that triggered the change.
    sceneChanged = Signal()

    ##  Find an object by id.
    #
    #   \param object_id The id of the object to search for, as returned by the python id() method.
    #
    #   \return The object if found, or None if not.
    def findObject(self, object_id: int) -> Optional["SceneNode"]:
        for node in BreadthFirstIterator(self._root):  # type: ignore
            if id(node) == object_id:
                return node
        return None

    def findCamera(self, name: str) -> Optional[Camera]:
        for node in BreadthFirstIterator(self._root):  # type: ignore
            if isinstance(node, Camera) and node.getName() == name:
                return node
        return None

    ##  Add a file to be watched for changes.
    #   \param file_path The path to the file that must be watched.
    def addWatchedFile(self, file_path: str) -> None:
        # The QT 5.10.0 issue, only on Windows. Cura crashes after loading a stl file from USB/sd-card/Cloud-based drive
        if not Platform.isWindows():
            self._file_watcher.addPath(file_path)

    ##  Remove a file so that it will no longer be watched for changes.
    #   \param file_path The path to the file that must no longer be watched.
    def removeWatchedFile(self, file_path: str) -> None:
        # The QT 5.10.0 issue, only on Windows. Cura crashes after loading a stl file from USB/sd-card/Cloud-based drive
        if not Platform.isWindows():
            self._file_watcher.removePath(file_path)

    ##  Triggered whenever a file is changed that we currently have loaded.
    def _onFileChanged(self, file_path: str) -> None:
        if not os.path.isfile(file_path) or os.path.getsize(file_path) == 0:  # File doesn't exist any more, or it is empty
            return

        # Multiple nodes may be loaded from the same file at different stages. Reload them all.
        from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator  # To find which nodes to reload when files have changed.
        modified_nodes = [node for node in DepthFirstIterator(self.getRoot()) if node.getMeshData() and node.getMeshData().getFileName() == file_path]  # type: ignore

        if modified_nodes:
            # Hide the message if it was already visible
            if self._reload_message is not None:
                self._reload_message.hide()

            self._reload_message = Message(i18n_catalog.i18nc("@info", "Would you like to reload {filename}?").format(filename = os.path.basename(file_path)),
                              title = i18n_catalog.i18nc("@info:title", "File has been modified"))
            self._reload_message.addAction("reload", i18n_catalog.i18nc("@action:button", "Reload"), icon = "", description = i18n_catalog.i18nc("@action:description", "This will trigger the modified files to reload from disk."))
            self._reload_callback = functools.partial(self._reloadNodes, modified_nodes)
            self._reload_message.actionTriggered.connect(self._reload_callback)
            self._reload_message.show()

    ##  Reloads a list of nodes after the user pressed the "Reload" button.
    #   \param nodes The list of nodes that needs to be reloaded.
    #   \param message The message that triggered the action to reload them.
    #   \param action The button that triggered the action to reload them.
    def _reloadNodes(self, nodes: List["SceneNode"], message: str, action: str) -> None:
        if action != "reload":
            return
        if self._reload_message is not None:
            self._reload_message.hide()
        for node in nodes:
            meshdata = node.getMeshData()
            if meshdata:
                filename = meshdata.getFileName()
                if not filename or not os.path.isfile(filename):  # File doesn't exist any more.
                    continue
                job = ReadMeshJob(filename)
                self._reload_finished_callback = functools.partial(self._reloadJobFinished, node)
                job.finished.connect(self._reload_finished_callback)
                job.start()

    ##  Triggered when reloading has finished.
    #
    #   This then puts the resulting mesh data in the node.
    def _reloadJobFinished(self, replaced_node: SceneNode, job: ReadMeshJob) -> None:
        for node in job.getResult():
            mesh_data = node.getMeshData()
            if mesh_data:
                replaced_node.setMeshData(mesh_data)
            else:
                Logger.log("w", "Could not find a mesh in reloaded node.")
Exemple #14
0
class EditorWorker(QObject):

    file_change_sig = pyqtSignal(bool)  # is_zim

    def __init__(self,
                 command,
                 old_text,
                 is_zim,
                 zim_folder=None,
                 docid=None,
                 zim_file=None,
                 parent=None):
        '''Observe the write/save states of a txt file to edit notes

        Args:
            command (list): command string list to pass into Popen.
            old_text (str): existing note text to paste into editor.
            is_zim (bool): if True, try open the associated zim note file.
                           if False, open a temp file to edit.
        Kwargs:
            zim_folder (str or None): if not None, the path to the zim note
                                      folder, used to search for zim notes.
            docid (int or None): if not None, id of current doc.
            parent (QWidget or None): parent widget.
        '''
        super(EditorWorker, self).__init__(parent)

        self.is_zim = is_zim
        self.zim_folder = zim_folder
        self.docid = docid
        self.logger = logging.getLogger(__name__)

        if not self.is_zim:
            self._temp_file = QTemporaryFile(self)
        else:
            if zim_file is not None:
                # use given zim file
                if os.path.exists(zim_file) and os.path.islink(zim_file):
                    self._temp_file = QFile(zim_file, self)
                    self.logger.debug('Got given zim file %s' % zim_file)
                else:
                    try:
                        zim_file = locateZimNote(self.zim_folder, self.docid)
                        self._temp_file = QFile(zim_file, self)
                        self.logger.exception(
                            'Failed to open given zim file. Get the id one.')
                    except:
                        self.logger.exception('Failed to find zim file.')
                        self._temp_file = QTemporaryFile(self)
                        self.is_zim = False
            else:
                # no given zim file, get the one in all_notes
                try:
                    zim_file = locateZimNote(self.zim_folder, self.docid)
                    self._temp_file = QFile(zim_file, self)
                    self.logger.debug('Got zim file %s' % zim_file)
                except:
                    self.logger.exception('Failed to find zim file.')
                    self._temp_file = QTemporaryFile(self)
                    self.is_zim = False

        self._process = QProcess(self)
        self._text = ""
        self._watcher = QFileSystemWatcher(self)
        self._watcher.fileChanged.connect(self.onFileChange)

        # write existing lines if temp file
        if not self.is_zim and self._temp_file.open():
            self._temp_file.write(old_text.encode('utf-8'))
            self._temp_file.close()

        # open() on temp file assumes QIODevice.ReadWrite as well.
        if self._temp_file.open(QIODevice.ReadWrite):
            self._file_path = self._temp_file.fileName()
            self._watcher.addPath(self._file_path)
            self.logger.debug('_file_path = %s' % self._file_path)

            program = command[0]
            arguments = command[1:]
            self._process.start(program,
                                arguments + [self._temp_file.fileName()])

    @pyqtSlot()
    def onFileChange(self):
        if self._temp_file.isOpen():

            #self._temp_file.seek(0)
            #self._text = self._temp_file.readAll().data().decode()

            # has to use with open and read(), the above doesn't work for
            # some editors, like xed

            # For some reason, if watching the zim file, and open in gvim
            # it reports file not found unless I wait for a while.
            wtf = os.path.exists(self._temp_file.fileName())
            while not wtf:
                wtf = os.path.exists(self._temp_file.fileName())

            with open(self._temp_file.fileName()) as tmp:
                self._text = tmp.read()

            # Re-add watch file, again for xed.
            self._watcher.removePath(self._file_path)
            self._watcher.addPath(self._file_path)

            self.file_change_sig.emit(self.is_zim)

    @property
    def text(self):
        return self._text

    def __del__(self):
        self._process.kill()
Exemple #15
0
class Scene:
    """Container object for the scene graph

    The main purpose of this class is to provide the root SceneNode.
    """

    def __init__(self) -> None:
        super().__init__()

        from UM.Scene.SceneNode import SceneNode
        self._root = SceneNode(name = "Root")
        self._root.setCalculateBoundingBox(False)
        self._connectSignalsRoot()
        self._active_camera = None  # type: Optional[Camera]
        self._ignore_scene_changes = False
        self._lock = threading.Lock()

        # Watching file for changes.
        self._file_watcher = QFileSystemWatcher()
        self._file_watcher.fileChanged.connect(self._onFileChanged)

        self._reload_message = None  # type: Optional[Message]
        self._callbacks = set() # type: Set[Callable] # Need to keep these in memory. This is a memory leak every time you refresh, but a tiny one.

    def _connectSignalsRoot(self) -> None:
        self._root.transformationChanged.connect(self.sceneChanged)
        self._root.childrenChanged.connect(self.sceneChanged)
        self._root.meshDataChanged.connect(self.sceneChanged)

    def _disconnectSignalsRoot(self) -> None:
        self._root.transformationChanged.disconnect(self.sceneChanged)
        self._root.childrenChanged.disconnect(self.sceneChanged)
        self._root.meshDataChanged.disconnect(self.sceneChanged)

    def setIgnoreSceneChanges(self, ignore_scene_changes: bool) -> None:
        if self._ignore_scene_changes != ignore_scene_changes:
            self._ignore_scene_changes = ignore_scene_changes
            if self._ignore_scene_changes:
                self._disconnectSignalsRoot()
            else:
                self._connectSignalsRoot()

    @deprecated("Scene lock is no longer used", "4.5")
    def getSceneLock(self) -> threading.Lock:
        return self._lock

    def getRoot(self) -> "SceneNode":
        """Get the root node of the scene."""

        return self._root

    def setRoot(self, node: "SceneNode") -> None:
        """Change the root node of the scene"""

        if self._root != node:
            if not self._ignore_scene_changes:
                self._disconnectSignalsRoot()
            self._root = node
            if not self._ignore_scene_changes:
                self._connectSignalsRoot()
            self.rootChanged.emit()

    rootChanged = Signal()

    def getActiveCamera(self) -> Optional[Camera]:
        """Get the camera that should be used for rendering."""

        return self._active_camera

    def getAllCameras(self) -> List[Camera]:
        cameras = []
        for node in BreadthFirstIterator(self._root):
            if isinstance(node, Camera):
                cameras.append(node)
        return cameras

    def setActiveCamera(self, name: str) -> None:
        """Set the camera that should be used for rendering.

        :param name: The name of the camera to use.
        """

        camera = self.findCamera(name)
        if camera and camera != self._active_camera:
            if self._active_camera:
                self._active_camera.perspectiveChanged.disconnect(self.sceneChanged)
            self._active_camera = camera
            self._active_camera.perspectiveChanged.connect(self.sceneChanged)
        else:
            Logger.log("w", "Couldn't find camera with name [%s] to activate!" % name)

    sceneChanged = Signal()
    """Signal that is emitted whenever something in the scene changes.

    :param object: The object that triggered the change.
    """

    def findObject(self, object_id: int) -> Optional["SceneNode"]:
        """Find an object by id.

        :param object_id: The id of the object to search for, as returned by the python id() method.

        :return: The object if found, or None if not.
        """

        for node in BreadthFirstIterator(self._root):
            if id(node) == object_id:
                return node
        return None

    def findCamera(self, name: str) -> Optional[Camera]:
        for node in BreadthFirstIterator(self._root):
            if isinstance(node, Camera) and node.getName() == name:
                return node
        return None

    def addWatchedFile(self, file_path: str) -> None:
        """Add a file to be watched for changes.

        :param file_path: The path to the file that must be watched.
        """

        # File watcher causes cura to crash on windows if threaded from removable device (usb, ...). Create QEventLoop earlier to fix this.
        if Platform.isWindows():
            QEventLoop()
        self._file_watcher.addPath(file_path)

    def removeWatchedFile(self, file_path: str) -> None:
        """Remove a file so that it will no longer be watched for changes.

        :param file_path: The path to the file that must no longer be watched.
        """

        self._file_watcher.removePath(file_path)

    def _onFileChanged(self, file_path: str) -> None:
        """Triggered whenever a file is changed that we currently have loaded."""

        try:
            if os.path.getsize(file_path) == 0:  # File is empty.
                return
        except EnvironmentError:  # Or it doesn't exist any more, or we have no access any more.
            return

        # Multiple nodes may be loaded from the same file at different stages. Reload them all.
        from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator  # To find which nodes to reload when files have changed.
        modified_nodes = [node for node in DepthFirstIterator(self.getRoot()) if node.getMeshData() and node.getMeshData().getFileName() == file_path]  # type: ignore

        if modified_nodes:
            # Hide the message if it was already visible
            if self._reload_message is not None:
                self._reload_message.hide()

            self._reload_message = Message(i18n_catalog.i18nc("@info", "Would you like to reload {filename}?").format(filename = os.path.basename(file_path)),
                              title = i18n_catalog.i18nc("@info:title", "File has been modified"))
            self._reload_message.addAction("reload", i18n_catalog.i18nc("@action:button", "Reload"), icon = "", description = i18n_catalog.i18nc("@action:description", "This will trigger the modified files to reload from disk."))
            self._reload_callback = functools.partial(self._reloadNodes, modified_nodes)
            self._reload_message.actionTriggered.connect(self._reload_callback)
            self._reload_message.show()

    def _reloadNodes(self, nodes: List["SceneNode"], message: str, action: str) -> None:
        """Reloads a list of nodes after the user pressed the "Reload" button.

        :param nodes: The list of nodes that needs to be reloaded.
        :param message: The message that triggered the action to reload them.
        :param action: The button that triggered the action to reload them.
        """

        if action != "reload":
            return
        if self._reload_message is not None:
            self._reload_message.hide()
        for node in nodes:
            meshdata = node.getMeshData()
            if meshdata:
                filename = meshdata.getFileName()
                if not filename or not os.path.isfile(filename):  # File doesn't exist any more.
                    continue
                job = ReadMeshJob(filename)
                reload_finished_callback = functools.partial(self._reloadJobFinished, node)
                self._callbacks.add(reload_finished_callback) #Store it so it won't get garbage collected. This is a memory leak, but just one partial per reload so it's not much.
                job.finished.connect(reload_finished_callback)
                job.start()

    def _reloadJobFinished(self, replaced_node: SceneNode, job: ReadMeshJob) -> None:
        """Triggered when reloading has finished.

        This then puts the resulting mesh data in the node.
        """

        for node in job.getResult():
            mesh_data = node.getMeshData()
            if mesh_data:
                replaced_node.setMeshData(mesh_data)
            else:
                Logger.log("w", "Could not find a mesh in reloaded node.")
Exemple #16
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': 'Color scheme', 'type': 'list',
         'values': ['Spyder','Monokai','Zenburn'], 'value': 'Spyder'}])

    EXTENSIONS = '*.py'

    def __init__(self,parent=None):

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

        self._filename = ''

        self.setup_editor(linenumbers=True,
                          markers=True,
                          edge_line=False,
                          tab_mode=False,
                          show_blanks=True,
                          language='Python')

        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()
        self.updatePreferences()

        # autoreload support
        self._file_watcher = QFileSystemWatcher(self)
        self._watched_file = None
        self._file_watcher.fileChanged.connect(self._file_changed)

    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'])

    def new(self):

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

    def open(self):

        fname,_ = QFileDialog.getOpenFileName(self,filter=self.EXTENSIONS)
        if fname is not '':
            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 is not '':

            if self.preferences['Autoreload']:
                self._file_watcher.removePath(self.filename)

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

            if self.preferences['Autoreload']:
                self._file_watcher.addPath(self.filename)
                self.triggerRerender.emit(True)

            self.reset_modified()

        else:
            self.save_as()

    def save_as(self):

        fname,_ = QFileDialog.getSaveFileName(self,filter=self.EXTENSIONS)
        if fname is not '':
            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._file_watcher.removePath(self._watched_file)
            self._watched_file = None
        if self.preferences['Autoreload'] and self.filename and self.filename != self._watched_file:
            self._watched_file = self._filename
            self._file_watcher.addPath(self.filename)

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

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

    # callback triggered by QFileSystemWatcher
    def _file_changed(self, val):
        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)

    def saveComponenetState(self,store):

        if self.filename is not '':
            store.setValue(self.name+'/state',self.filename)

    def restoreComponenetState(self,store):

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

        if filename and filename is not '':
            self.load_from_file(filename)
Exemple #17
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2)
		if not screenRect.contains(self.geometry()):
			self.showMaximized()
		if globalSettings.iconTheme:
			QIcon.setThemeName(globalSettings.iconTheme)
		if QIcon.themeName() in ('hicolor', ''):
			if not QFile.exists(icon_path + 'document-new.png'):
				QIcon.setThemeName(get_icon_theme())
		if QFile.exists(icon_path+'retext.png'):
			self.setWindowIcon(QIcon(icon_path+'retext.png'))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.editBoxes = []
		self.previewBoxes = []
		self.highlighters = []
		self.markups = []
		self.fileNames = []
		self.actionPreviewChecked = []
		self.actionLivePreviewChecked = []
		self.tabWidget = QTabWidget(self)
		self.initTabWidget()
		self.setCentralWidget(self.tabWidget)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh', trig=self.openFileMain)
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeEditorFont = self.act(self.tr('Change editor font'),
			trig=self.changeEditorFont)
		self.actionChangePreviewFont = self.act(self.tr('Change preview font'),
			trig=self.changePreviewFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find)
		self.actionSearch.setCheckable(True)
		self.actionSearch.triggered[bool].connect(self.searchBar.setVisible)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png'))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.editBoxes[self.ind].enableTableMode(x))
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.editBoxes[self.ind].undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.editBoxes[self.ind].redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.editBoxes[self.ind].copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.editBoxes[self.ind].cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.editBoxes[self.ind].paste(), shct=QKeySequence.Paste)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant_available:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About ReText')
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		self.defaultMarkup = availableMarkups[0] if availableMarkups else None
		if globalSettings.defaultMarkup:
			mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup)
			if mc and mc.available():
				self.defaultMarkup = mc
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup == self.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertChars('**'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertChars('*'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertTag('u'))
		self.usefulTags = ('a', 'big', 'center', 'img', 's', 'small', 'span',
			'table', 'td', 'tr', 'u')
		self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.tagsBox = QComboBox(self.editBar)
		self.tagsBox.addItem(self.tr('Tags'))
		self.tagsBox.addItems(self.usefulTags)
		self.tagsBox.activated.connect(self.insertTag)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = QMenuBar(self)
		menubar.setGeometry(QRect(0, 0, 800, 25))
		self.setMenuBar(menubar)
		menuFile = menubar.addMenu(self.tr('File'))
		menuEdit = menubar.addMenu(self.tr('Edit'))
		menuHelp = menubar.addMenu(self.tr('Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addMenu(self.menuRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addSeparator()
		if enchant_available:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionChangeEditorFont)
		menuEdit.addAction(self.actionChangePreviewFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		menuEdit.addAction(self.actionWebKit)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionLivePreview)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionTableMode)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		menubar.addMenu(menuFile)
		menubar.addMenu(menuEdit)
		menubar.addMenu(menuHelp)
		toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		toolBar.addAction(self.actionNew)
		toolBar.addSeparator()
		toolBar.addAction(self.actionOpen)
		toolBar.addAction(self.actionSave)
		toolBar.addAction(self.actionPrint)
		toolBar.addSeparator()
		toolBar.addAction(self.actionPreview)
		toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.tagsBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant_available:
			self.sl = globalSettings.spellCheckLocale
			if self.sl:
				try:
					enchant.Dict(self.sl)
				except Exception as e:
					print(e, file=sys.stderr)
					self.sl = None
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
				self.enableSpellCheck(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def updateStyleSheet(self):
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()
		else:
			self.ss = ''

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(icon_path+name+'.png'))

	def printError(self):
		import traceback
		print('Exception occured while parsing document:', file=sys.stderr)
		traceback.print_exc()

	def getSplitter(self, index):
		splitter = QSplitter(Qt.Horizontal)
		# Give both boxes a minimum size so the minimumSizeHint will be
		# ignored when splitter.setSizes is called below
		for widget in self.editBoxes[index], self.previewBoxes[index]:
			widget.setMinimumWidth(125)
			splitter.addWidget(widget)
		splitter.setSizes((50, 50))
		splitter.setChildrenCollapsible(False)
		return splitter

	def getWebView(self):
		webView = QWebView()
		if not globalSettings.handleWebLinks:
			webView.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks)
			webView.page().linkClicked.connect(QDesktopServices.openUrl)
		settings = webView.settings()
		settings.setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False)
		settings.setDefaultTextEncoding('utf-8')
		return webView

	def createTab(self, fileName):
		self.previewBlocked = False
		self.editBoxes.append(ReTextEdit(self))
		self.highlighters.append(ReTextHighlighter(self.editBoxes[-1].document()))
		if enchant_available and self.actionEnableSC.isChecked():
			self.highlighters[-1].dictionary = \
			enchant.Dict(self.sl) if self.sl else enchant.Dict()
			self.highlighters[-1].rehighlight()
		if globalSettings.useWebKit:
			self.previewBoxes.append(self.getWebView())
		else:
			self.previewBoxes.append(QTextBrowser())
			self.previewBoxes[-1].setOpenExternalLinks(True)
		self.previewBoxes[-1].setVisible(False)
		self.fileNames.append(fileName)
		markupClass = self.getMarkupClass(fileName)
		self.markups.append(self.getMarkup(fileName))
		self.highlighters[-1].docType = (markupClass.name if markupClass else '')
		liveMode = globalSettings.restorePreviewState and globalSettings.previewState
		self.actionPreviewChecked.append(liveMode)
		self.actionLivePreviewChecked.append(liveMode)
		metrics = QFontMetrics(self.editBoxes[-1].font())
		self.editBoxes[-1].setTabStopWidth(globalSettings.tabWidth * metrics.width(' '))
		self.editBoxes[-1].textChanged.connect(self.updateLivePreviewBox)
		self.editBoxes[-1].undoAvailable.connect(self.actionUndo.setEnabled)
		self.editBoxes[-1].redoAvailable.connect(self.actionRedo.setEnabled)
		self.editBoxes[-1].copyAvailable.connect(self.enableCopy)
		self.editBoxes[-1].document().modificationChanged.connect(self.modificationChanged)
		if globalSettings.useFakeVim:
			self.installFakeVimHandler(self.editBoxes[-1])
		return self.getSplitter(-1)

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.tabWidget.addTab(self.createTab(""), self.tr("New document"))
			if self.fileNames[ind]:
				self.fileSystemWatcher.removePath(self.fileNames[ind])
			del self.editBoxes[ind]
			del self.previewBoxes[ind]
			del self.highlighters[ind]
			del self.markups[ind]
			del self.fileNames[ind]
			del self.actionPreviewChecked[ind]
			del self.actionLivePreviewChecked[ind]
			self.tabWidget.removeTab(ind)

	def getMarkupClass(self, fileName=None):
		if fileName is None:
			fileName = self.fileNames[self.ind]
		if fileName:
			markupClass = markups.get_markup_for_file_name(
				fileName, return_class=True)
			if markupClass:
				return markupClass
		return self.defaultMarkup

	def getMarkup(self, fileName=None):
		if fileName is None:
			fileName = self.fileNames[self.ind]
		markupClass = self.getMarkupClass(fileName=fileName)
		if markupClass and markupClass.available():
			return markupClass(filename=fileName)

	def docTypeChanged(self):
		oldType = self.highlighters[self.ind].docType
		markupClass = self.getMarkupClass()
		newType = markupClass.name if markupClass else ''
		if oldType != newType:
			self.markups[self.ind] = self.getMarkup()
			self.updatePreviewBox()
			self.highlighters[self.ind].docType = newType
			self.highlighters[self.ind].rehighlight()
		dtMarkdown = (newType == DOCTYPE_MARKDOWN)
		dtMkdOrReST = (newType in (DOCTYPE_MARKDOWN, DOCTYPE_REST))
		self.tagsBox.setEnabled(dtMarkdown)
		self.symbolBox.setEnabled(dtMarkdown)
		self.actionUnderline.setEnabled(dtMarkdown)
		self.actionBold.setEnabled(dtMkdOrReST)
		self.actionItalic.setEnabled(dtMkdOrReST)
		canReload = bool(self.fileNames[self.ind]) and not self.autoSaveActive()
		self.actionSetEncoding.setEnabled(canReload)
		self.actionReload.setEnabled(canReload)

	def changeIndex(self, ind):
		if ind > -1:
			self.actionUndo.setEnabled(self.editBoxes[ind].document().isUndoAvailable())
			self.actionRedo.setEnabled(self.editBoxes[ind].document().isRedoAvailable())
			self.actionCopy.setEnabled(self.editBoxes[ind].textCursor().hasSelection())
			self.actionCut.setEnabled(self.editBoxes[ind].textCursor().hasSelection())
			self.actionPreview.setChecked(self.actionPreviewChecked[ind])
			self.actionLivePreview.setChecked(self.actionLivePreviewChecked[ind])
			self.actionTableMode.setChecked(self.editBoxes[ind].tableModeEnabled)
			self.editBar.setDisabled(self.actionPreviewChecked[ind])
		self.ind = ind
		if self.fileNames[ind]:
			self.setCurrentFile()
		else:
			self.setWindowTitle(self.tr('New document') + '[*]')
			self.docTypeChanged()
		self.modificationChanged(self.editBoxes[ind].document().isModified())
		if globalSettings.restorePreviewState:
			globalSettings.previewState = self.actionLivePreviewChecked[ind]
		if self.actionLivePreviewChecked[ind]:
			self.enableLivePreview(True)
		self.editBoxes[self.ind].setFocus(Qt.OtherFocusReason)

	def changeEditorFont(self):
		font, ok = QFontDialog.getFont(globalSettings.editorFont, self)
		if ok:
			globalSettings.editorFont = font
			for editor in self.editBoxes:
				editor.updateFont()

	def changePreviewFont(self):
		font, ok = QFontDialog.getFont(globalSettings.font, self)
		if ok:
			globalSettings.font = font
			self.updatePreviewBox()

	def preview(self, viewmode):
		self.actionPreviewChecked[self.ind] = viewmode
		if self.actionLivePreview.isChecked():
			self.actionLivePreview.setChecked(False)
			return self.enableLivePreview(False)
		self.editBar.setDisabled(viewmode)
		self.editBoxes[self.ind].setVisible(not viewmode)
		self.previewBoxes[self.ind].setVisible(viewmode)
		if viewmode:
			self.updatePreviewBox()

	def enableLivePreview(self, livemode):
		if globalSettings.restorePreviewState:
			globalSettings.previewState = livemode
		self.actionLivePreviewChecked[self.ind] = livemode
		self.actionPreviewChecked[self.ind] = livemode
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.previewBoxes[self.ind].setVisible(livemode)
		self.editBoxes[self.ind].setVisible(True)
		if livemode:
			self.updatePreviewBox()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		oldind = self.ind
		self.tabWidget.clear()
		for self.ind in range(len(self.editBoxes)):
			if enable:
				self.previewBoxes[self.ind] = self.getWebView()
			else:
				self.previewBoxes[self.ind] = QTextBrowser()
				self.previewBoxes[self.ind].setOpenExternalLinks(True)
			splitter = self.getSplitter(self.ind)
			self.tabWidget.addTab(splitter, self.getDocumentTitle(baseName=True))
			self.updatePreviewBox()
			self.previewBoxes[self.ind].setVisible(self.actionPreviewChecked[self.ind])
		self.ind = oldind
		self.tabWidget.setCurrentIndex(self.ind)

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def installFakeVimHandler(self, editor):
		if ReTextFakeVimHandler:
			fakeVimEditor = ReTextFakeVimHandler(editor, self)
			fakeVimEditor.setSaveAction(self.actionSave)
			fakeVimEditor.setQuitAction(self.actionQuit)
			self.actionFakeVimMode.triggered.connect(fakeVimEditor.remove)

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for editor in self.editBoxes:
				self.installFakeVimHandler(editor)
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		if yes:
			if self.sl:
				self.setAllDictionaries(enchant.Dict(self.sl))
			else:
				self.setAllDictionaries(enchant.Dict())
		else:
			self.setAllDictionaries(None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for hl in self.highlighters:
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		if self.sl:
			localedlg = LocaleDialog(self, defaultText=self.sl)
		else:
			localedlg = LocaleDialog(self)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		setdefault = localedlg.checkBox.isChecked()
		if sl:
			try:
				sl = str(sl)
				enchant.Dict(sl)
			except Exception as e:
				QMessageBox.warning(self, '', str(e))
			else:
				self.sl = sl
				self.enableSpellCheck(self.actionEnableSC.isChecked())
		else:
			self.sl = None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
		if setdefault:
			globalSettings.spellCheckLocale = sl

	def searchBarVisibilityChanged(self, visible):
		self.actionSearch.setChecked(visible)
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		editBox = self.editBoxes[self.ind]
		cursor = editBox.textCursor()
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		cursor.movePosition(QTextCursor.End if back else QTextCursor.Start)
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		self.setSearchEditColor(False)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def getHtml(self, includeStyleSheet=True, includeTitle=True,
	            includeMeta=False, webenv=False):
		if self.markups[self.ind] is None:
			markupClass = self.getMarkupClass()
			errMsg = self.tr('Could not parse file contents, check if '
			'you have the <a href="%s">necessary module</a> installed!')
			try:
				errMsg %= markupClass.attributes[MODULE_HOME_PAGE]
			except (AttributeError, KeyError):
				# Remove the link if markupClass doesn't have the needed attribute
				errMsg = errMsg.replace('<a href="%s">', '')
				errMsg = errMsg.replace('</a>', '')
			return '<p style="color: red">%s</p>' % errMsg
		text = self.editBoxes[self.ind].toPlainText()
		headers = ''
		if includeStyleSheet:
			headers += '<style type="text/css">\n' + self.ss + '</style>\n'
		cssFileName = self.getDocumentTitle(baseName=True)+'.css'
		if QFile(cssFileName).exists():
			headers += '<link rel="stylesheet" type="text/css" href="%s">\n' \
			% cssFileName
		if includeMeta:
			headers += ('<meta name="generator" content="ReText %s">\n' %
			            app_version)
		fallbackTitle = self.getDocumentTitle() if includeTitle else ''
		return self.markups[self.ind].get_whole_html(text,
			custom_headers=headers, include_stylesheet=includeStyleSheet,
			fallback_title=fallbackTitle, webenv=webenv)

	def updatePreviewBox(self):
		self.previewBlocked = False
		pb = self.previewBoxes[self.ind]
		textedit = isinstance(pb, QTextEdit)
		if textedit:
			scrollbar = pb.verticalScrollBar()
			disttobottom = scrollbar.maximum() - scrollbar.value()
		else:
			frame = pb.page().mainFrame()
			scrollpos = frame.scrollPosition()
		try:
			html = self.getHtml()
		except Exception:
			return self.printError()
		if textedit:
			pb.setHtml(html)
			pb.document().setDefaultFont(globalSettings.font)
			scrollbar.setValue(scrollbar.maximum() - disttobottom)
		else:
			pb.settings().setFontFamily(QWebSettings.StandardFont,
			                            globalSettings.font.family())
			pb.settings().setFontSize(QWebSettings.DefaultFontSize,
			                          globalSettings.font.pointSize())
			pb.setHtml(html, QUrl.fromLocalFile(self.fileNames[self.ind]))
			frame.setScrollPosition(scrollpos)

	def updateLivePreviewBox(self):
		if self.actionLivePreview.isChecked() and self.previewBlocked == False:
			self.previewBlocked = True
			QTimer.singleShot(1000, self.updatePreviewBox)

	def showInDir(self):
		if self.fileNames[self.ind]:
			path = QFileInfo(self.fileNames[self.ind]).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def setCurrentFile(self):
		self.setWindowTitle("")
		self.tabWidget.setTabText(self.ind, self.getDocumentTitle(baseName=True))
		self.setWindowFilePath(self.fileNames[self.ind])
		files = readListFromSettings("recentFileList")
		while self.fileNames[self.ind] in files:
			files.remove(self.fileNames[self.ind])
		files.insert(0, self.fileNames[self.ind])
		if len(files) > 10:
			del files[10:]
		writeListToSettings("recentFileList", files)
		QDir.setCurrent(QFileInfo(self.fileNames[self.ind]).dir().path())
		self.docTypeChanged()

	def createNew(self, text=None):
		self.tabWidget.addTab(self.createTab(""), self.tr("New document"))
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.editBoxes[self.ind].textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFuntion(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFuntion(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.getMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype == None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), "",
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i in range(self.tabWidget.count()):
			if self.fileNames[i] == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.fileNames[self.ind] or
				self.editBoxes[self.ind].toPlainText() or
				self.editBoxes[self.ind].document().isModified()
			)
			if noEmptyTab:
				self.tabWidget.addTab(self.createTab(fileName), "")
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.fileNames[self.ind] = fileName
			self.openFileMain()

	def openFileMain(self, encoding=None):
		openfile = QFile(self.fileNames[self.ind])
		openfile.open(QIODevice.ReadOnly)
		stream = QTextStream(openfile)
		if encoding:
			stream.setCodec(encoding)
		elif globalSettings.defaultCodec:
			stream.setCodec(globalSettings.defaultCodec)
		text = stream.readAll()
		openfile.close()
		markupClass = markups.get_markup_for_file_name(
			self.fileNames[self.ind], return_class=True)
		self.highlighters[self.ind].docType = (markupClass.name if markupClass else '')
		self.markups[self.ind] = self.getMarkup()
		if self.defaultMarkup:
			self.highlighters[self.ind].docType = self.defaultMarkup.name
		editBox = self.editBoxes[self.ind]
		modified = bool(encoding) and (editBox.toPlainText() != text)
		editBox.setPlainText(text)
		self.setCurrentFile()
		editBox.document().setModified(modified)
		self.setWindowModified(modified)

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in QTextCodec.availableCodecs()],
			0, False)
		if ok:
			self.openFileMain(encoding)

	def saveFile(self):
		self.saveFileMain(dlg=False)

	def saveFileAs(self):
		self.saveFileMain(dlg=True)

	def saveAll(self):
		oldind = self.ind
		for self.ind in range(self.tabWidget.count()):
			if self.fileNames[self.ind] and QFileInfo(self.fileNames[self.ind]).isWritable():
				self.saveFileCore(self.fileNames[self.ind])
				self.editBoxes[self.ind].document().setModified(False)
		self.ind = oldind

	def saveFileMain(self, dlg):
		if (not self.fileNames[self.ind]) or dlg:
			markupClass = self.getMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			newFileName = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), "", defaultExt)[0]
			if newFileName:
				if not QFileInfo(newFileName).suffix():
					newFileName += ext
				if self.fileNames[self.ind]:
					self.fileSystemWatcher.removePath(self.fileNames[self.ind])
				self.fileNames[self.ind] = newFileName
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if self.fileNames[self.ind]:
			result = self.saveFileCore(self.fileNames[self.ind])
			if result:
				self.setCurrentFile()
				self.editBoxes[self.ind].document().setModified(False)
				self.setWindowModified(False)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveFileCore(self, fn, addToWatcher=True):
		self.fileSystemWatcher.removePath(fn)
		savefile = QFile(fn)
		result = savefile.open(QIODevice.WriteOnly)
		if result:
			savestream = QTextStream(savefile)
			if globalSettings.defaultCodec:
				savestream.setCodec(globalSettings.defaultCodec)
			savestream << self.editBoxes[self.ind].toPlainText()
			savefile.close()
		if result and addToWatcher:
			self.fileSystemWatcher.addPath(fn)
		return result

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			htmltext = self.getHtml(includeStyleSheet=False, includeMeta=True,
			webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		htmlFile.open(QIODevice.WriteOnly)
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle, self.getDocumentTitle())
		if self.ss:
			td.setDefaultStyleSheet(self.ss)
		td.setHtml(self.getHtml())
		td.setDefaultFont(globalSettings.font)
		return td

	def saveOdf(self):
		try:
			document = self.textDocument()
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), "",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat("odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), "",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self):
		if globalSettings.useWebKit:
			return self.previewBoxes[self.ind]
		try:
			return self.textDocument()
		except Exception:
			self.printError()

	def standardPrinter(self):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(self.getDocumentTitle())
		printer.setCreator('ReText %s' % app_version)
		return printer

	def savePdf(self):
		self.updatePreviewBox()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			"", self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			printer = self.standardPrinter()
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printFile(self):
		self.updatePreviewBox()
		printer = self.standardPrinter()
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printPreview(self):
		document = self.getDocumentForPrint()
		if document == None:
			return
		printer = self.standardPrinter()
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		basename = '.%s.retext-temp' % self.getDocumentTitle(baseName=True)
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename+self.getMarkupClass().default_extension
			self.saveFileCore(tmpname, addToWatcher=False)
		command = command.replace('%of', '"out'+defaultext+'"')
		command = command.replace('%html' if html else '%if', '"'+tmpname+'"')
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()
		if of:
			QFile('out'+defaultext).rename(fileName)

	def getDocumentTitle(self, baseName=False):
		markup = self.markups[self.ind]
		realTitle = ''
		if markup and not baseName:
			text = self.editBoxes[self.ind].toPlainText()
			try:
				realTitle = markup.get_document_title(text)
			except Exception:
				self.printError()
		if realTitle:
			return realTitle
		elif self.fileNames[self.ind]:
			fileinfo = QFileInfo(self.fileNames[self.ind])
			basename = fileinfo.completeBaseName()
			return (basename if basename else fileinfo.fileName())
		return self.tr("New document")

	def autoSaveActive(self):
		return self.autoSaveEnabled and self.fileNames[self.ind] and \
		QFileInfo(self.fileNames[self.ind]).isWritable()

	def modificationChanged(self, changed):
		if self.autoSaveActive():
			changed = False
		self.actionSave.setEnabled(changed)
		self.setWindowModified(changed)

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())

	def insertChars(self, chars):
		tc = self.editBoxes[self.ind].textCursor()
		if tc.hasSelection():
			selection = tc.selectedText()
			if selection.startswith(chars) and selection.endswith(chars):
				if len(selection) > 2*len(chars):
					selection = selection[len(chars):-len(chars)]
					tc.insertText(selection)
			else:
				tc.insertText(chars+tc.selectedText()+chars)
		else:
			tc.insertText(chars)

	def insertTag(self, ut):
		if not ut:
			return
		if isinstance(ut, int):
			ut = self.usefulTags[ut - 1]
		arg = ' style=""' if ut == 'span' else ''
		tc = self.editBoxes[self.ind].textCursor()
		if ut == 'img':
			toinsert = ('<a href="' + tc.selectedText() +
			'" target="_blank"><img src="' + tc.selectedText() + '"/></a>')
		elif ut == 'a':
			toinsert = ('<a href="' + tc.selectedText() +
			'" target="_blank">' + tc.selectedText() + '</a>')
		else:
			toinsert = '<'+ut+arg+'>'+tc.selectedText()+'</'+ut+'>'
		tc.insertText(toinsert)
		self.tagsBox.setCurrentIndex(0)

	def insertSymbol(self, num):
		if num:
			self.editBoxes[self.ind].insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		ind = self.fileNames.index(fileName)
		self.tabWidget.setCurrentIndex(ind)
		if not QFile.exists(fileName):
			self.editBoxes[ind].document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not self.editBoxes[ind].document().isModified():
			# File was not modified in ReText, reload silently
			self.openFileMain()
			self.updatePreviewBox()
		else:
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				self.openFileMain()
				self.updatePreviewBox()
			else:
				self.autoSaveEnabled = False
				self.editBoxes[ind].document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://github.com/retext-project/retext/issues/137
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		if self.autoSaveActive():
			self.saveFileCore(self.fileNames[self.ind])
			return True
		if not self.editBoxes[ind].document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFileMain(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for self.ind in range(self.tabWidget.count()):
			if not self.maybeSave(self.ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry and not self.isMaximized():
			globalSettings.windowGeometry = self.saveGeometry()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			htmltext = self.getHtml(includeStyleSheet=False, includeTitle=False)
		except Exception:
			return self.printError()
		winTitle = self.getDocumentTitle(baseName=True)
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011\u2013' '2015')
		+'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markup):
		self.defaultMarkup = markup
		defaultName = markups.get_available_markups()[0].name
		writeToSettings('defaultMarkup', markup.name, defaultName)
		oldind = self.ind
		for self.ind in range(len(self.previewBoxes)):
			self.docTypeChanged()
		self.ind = oldind
Exemple #18
0
class NFile(QObject):
    """
    SIGNALS:
    @askForSaveFileClosing(QString)
    @fileClosing(QString)
    @fileChanged()
    @willDelete(PyQt_PyObject, PyQt_PyObject)
    @willOverWrite(PyQt_PyObject, QString, QString)
    @willMove(Qt_PyQtObject, QString, QString)
    @willSave(QString, QString)
    @savedAsNewFile(PyQt_PyObject, QString, QString)
    @gotAPath(PyQt_PyObject)
    @willAttachToExistingFile(PyQt_PyObject, QString)
    """
    fileChanged = pyqtSignal()
    fileRemoved = pyqtSignal()
    fileReaded = pyqtSignal()
    willAttachToExistingFile = pyqtSignal('PyQt_PyObject', 'QString')
    gotAPath = pyqtSignal('PyQt_PyObject')
    willSave = pyqtSignal('QString', 'QString')
    willMove = pyqtSignal('PyQt_PyObject', 'QString', 'QString')
    willOverWrite = pyqtSignal('PyQt_PyObject', 'QString', 'QString')
    willCopyTo = pyqtSignal('PyQt_PyObject', 'QString', 'QString')
    willDelete = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject')
    fileClosing = pyqtSignal('QString', bool)

    def __init__(self, path=None):
        """
        """
        self._file_path = path
        self.__created = False
        self.__watcher = None
        self.__mtime = None
        super(NFile, self).__init__()
        if not self._exists():
            self.__created = True

    @property
    def file_name(self):
        """"Returns filename of nfile"""
        file_name = None
        if self._file_path is None:
            file_name = translations.TR_NEW_DOCUMENT
        else:
            file_name = get_basename(self._file_path)
        return file_name

    @property
    def display_name(self):
        """Returns a pretty name to be displayed by tabs"""
        display_name = self.file_name
        if self._file_path is not None and not self.has_write_permission():
            display_name += translations.TR_READ_ONLY
        return display_name

    @property
    def is_new_file(self):
        return self.__created

    def file_ext(self):
        """"Returns extension of nfile"""
        if self._file_path is None:
            return ''
        return get_file_extension(self._file_path)

    @property
    def file_path(self):
        """"Returns file path of nfile"""
        return self._file_path

    def start_watching(self):
        """Create a file system watcher and connect its fileChanged
        SIGNAL to our _file_changed SLOT"""
        if self.__watcher is None:
            self.__watcher = QFileSystemWatcher(self)
            self.__watcher.fileChanged['const QString&'].connect(
                self._file_changed)
        if self._file_path is not None:
            self.__mtime = os.path.getmtime(self._file_path)
            self.__watcher.addPath(self._file_path)

    def _file_changed(self, path):
        if self._exists():
            current_mtime = os.path.getmtime(self._file_path)
            if current_mtime != self.__mtime:
                self.__mtime = current_mtime
                self.fileChanged.emit()
        else:
            self.fileRemoved.emit()

    def has_write_permission(self):
        if not self._exists():
            return True
        return os.access(self._file_path, os.W_OK)

    def _exists(self):
        """
        Check if we have been created with a path and if such path exists
        In case there is no path, we are most likely a new file.
        """
        file_exists = False
        if self._file_path and os.path.exists(self._file_path):
            file_exists = True
        return file_exists

    def attach_to_path(self, new_path):
        if os.path.exists(new_path):
            signal_handler = SignalFlowControl()
            self.willAttachToExistingFile.emit(signal_handler, new_path)
            if signal_handler.stopped():
                return
        self._file_path = new_path
        self.gotAPath.emit(self)
        return self._file_path

    def create(self):
        if self.__created:
            self.save("")
        self.__created = False

    def save(self, content, path=None):
        """
        Write a temporary file with .tnj extension and copy it over the
        original one.
        .nsf = Ninja Swap File
        # FIXME: Where to locate addExtension, does not fit here
        """
        new_path = False
        if path:
            self.attach_to_path(path)
            new_path = True

        save_path = self._file_path

        if not save_path:
            raise NinjaNoFileNameException("I am asked to write a "
                                           "file but no one told me where")
        swap_save_path = "%s.nsp" % save_path

        # If we have a file system watcher, remove the file path
        # from its watch list until we are done making changes.
        if self.__watcher is not None:
            self.__watcher.removePath(save_path)

        flags = QIODevice.WriteOnly | QIODevice.Truncate
        f = QFile(swap_save_path)
        if settings.use_platform_specific_eol():
            flags |= QIODevice.Text

        if not f.open(flags):
            raise NinjaIOException(f.errorString())

        stream = QTextStream(f)
        encoding = get_file_encoding(content)
        if encoding:
            stream.setCodec(encoding)

        encoded_stream = stream.codec().fromUnicode(content)
        f.write(encoded_stream)
        f.flush()
        f.close()
        # SIGNAL: Will save (temp, definitive) to warn folder to do something
        self.willSave.emit(swap_save_path, save_path)
        self.__mtime = os.path.getmtime(swap_save_path)
        shutil.move(swap_save_path, save_path)
        self.reset_state()

        # If we have a file system watcher, add the saved path back
        # to its watch list, otherwise create a watcher and start
        # watching
        if self.__watcher is not None:
            if new_path:
                # FIXME: what?
                # self.__watcher.removePath(self.__watcher.files()[0])
                self.__watcher.addPath(self._file_path)
            else:
                self.__watcher.addPath(save_path)
        else:
            self.start_watching()
        return self

    def reset_state(self):
        """
        #FIXE: to have a ref to changed I need to have the doc here
        """
        self.__created = False

    def read(self, path=None):
        """
        Read the file or fail
        """
        open_path = path and path or self._file_path
        self._file_path = open_path
        if not self._file_path:
            raise NinjaNoFileNameException("I am asked to read a file "
                                           "but no one told me from where")
        try:
            with open(open_path, 'r') as f:
                content = f.read()
        except IOError as reason:
            raise NinjaIOException(reason)
        self.fileReaded.emit()
        return content

    def move(self, new_path):
        """
        Phisically move the file
        """
        if self._exists():
            signal_handler = SignalFlowControl()
            # SIGNALL: WILL MOVE TO, to warn folder to exist
            self.willMove.emit(signal_handler, self._file_path, new_path)
            if signal_handler.stopped():
                return
            if os.path.exists(new_path):
                signal_handler = SignalFlowControl()
                self.willOverWrite.emit(signal_handler, self._file_path,
                                        new_path)
                if signal_handler.stopped():
                    return
            if self.__watcher is not None:
                self.__watcher.removePath(self._file_path)
            shutil.move(self._file_path, new_path)
            if self.__watcher:
                self.__watcher.addPath(new_path)
        self._file_path = new_path
        return

    def copy(self, new_path):
        """
        Copy the file to a new path
        """
        if self._exists():
            signal_handler = SignalFlowControl()
            # SIGNALL: WILL COPY TO, to warn folder to exist
            self.willCopyTo.emit(signal_handler, self._file_path, new_path)
            if signal_handler.stopped():
                return
            if os.path.exists(new_path):
                signal_handler = SignalFlowControl()
                self.willOverWrite.emit(signal_handler, self._file_path,
                                        new_path)
                if signal_handler.stopped():
                    return

            shutil.copy(self._file_path, new_path)

    def delete(self, force=False):
        """
        This deletes the object and closes the file.
        """
        # if created but exists this file migth to someone else
        self.close()
        if ((not self.__created) or force) and self._exists():
            DEBUG("Deleting our own NFile %s" % self._file_path)
            signal_handler = SignalFlowControl()
            self.willDelete.emit(signal_handler, self)
            if not signal_handler.stopped():
                if self.__watcher is not None:
                    self.__watcher.removePath(self._file_path)
                os.remove(self._file_path)

    def close(self, force_close=False):
        """
        Lets let people know we are going down so they can act upon
        As you can see close does nothing but let everyone know that we are
        not saved yet
        """
        DEBUG("About to close NFile")
        self.fileClosing.emit(self._file_path, force_close)

    def remove_watcher(self):
        if self.__watcher is not None:
            self.__watcher.removePath(self._file_path)
Exemple #19
0
class Note(QWidget):
    def __init__(self, path, notebook):
        """
        Class representing the actual tab pane for a note file, including the editor,
        the toolbar and the save/load logic.

        path is the path to the file(which does not need to exist yet)
        notebook must be the Notebook instance the note will be a part of

        """
        QWidget.__init__(self)
        self.notebook = notebook
        self.path = path

        #Set up the embedded webkit
        self.edit = WebkitNoteEditor()
        self.edit.page().setContentEditable(True)
        self.edit.settings().setAttribute(QWebSettings.JavascriptEnabled, True)

        def openLink(url):
            decoded_url = urllib.parse.unquote_plus(url.toString())
            try:
                if decoded_url.startswith("http"):
                    #Note that we just pass the unmodifed url and assume the browser will handle that better
                    webbrowser.open_new_tab(url.toString())
                    return
                if decoded_url.startswith("file://"):
                    self.notebook.open(decoded_url[len("file://"):])
                elif not decoded_url.startswith("mdnotes://"):
                    self.notebook.open(
                        os.path.join(os.path.dirname(self.path), decoded_url))
                else:
                    #Open knows how to handle these directly
                    self.notebook.open(decoded_url)
            except:
                logging.exception("Failed to open link " + str(url.toString()))

        self.edit.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.edit.page().linkClicked.connect(openLink)

        #set up the toolbar
        self.tools = QWidget()
        self.tools.lo = QHBoxLayout()
        self.tools.setLayout(self.tools.lo)

        if self.path:
            #Watch the file so we can auto reload
            self.watcher = QFileSystemWatcher()
            self.watcher.addPath(self.path)
            self.watcher.fileChanged.connect(self.onChanged)

        #from http://ralsina.me/weblog/posts/BB948.html
        #Add a search feature
        self.search = QLineEdit(
            returnPressed=lambda: self.edit.findText(self.search.text()))
        self.search.hide()
        self.showSearch = QShortcut(
            "Ctrl+F",
            self,
            activated=lambda: (self.search.show(), self.search.setFocus()))

        #Put the widgets together
        self.lo = QVBoxLayout()
        self.setLayout(self.lo)
        self.tools = NoteToolBar(self)
        self.lo.addWidget(self.tools)
        self.lo.addWidget(self.edit)

        self.reload()

    def onClose(self):
        "Handle closing the tab or the whole program"
        self.watcher.removePath(self.path)
        self.save()

    def to_gfm(self):
        doc = pandoc.Document()
        doc.html = destyle_html(
            self.edit.page().mainFrame().toHtml()).encode("utf-8")
        return doc.markdown_github.decode("utf-8")

    def save(self, name=None):
        name = name or self.path
        #Readonly html file support
        if name.endswith(".ro"):
            return
        if name.endswith(".html"):
            return
        "Save the file if it needs saving"
        if not self.edit.page().isModified():
            return

        #Back Up File
        buf = None
        #If the file exists, copy it to file~ first. If that exists, copy it to file4857475928345
        if os.path.exists(name):
            if not os.path.exists(name + "~"):
                buf = (name + "~")
                shutil.copy(name, name + "~")
            else:
                buf = name + str(time.time())
                shutil.copy(name, buf)

        #Again, pandoc to convert to the proper format
        doc = pandoc.Document()
        h = downloadAllImages(self.edit.page().mainFrame().toHtml(), name)
        doc.html = destyle_html(h).encode("utf-8")

        if util.striptrailingnumbers(name).endswith(".md"):
            with open(name, "wb") as f:
                f.write(self.pre_bytes + doc.markdown_github)

        if util.striptrailingnumbers(name).endswith(".rst"):
            with open(name, "wb") as f:
                f.write(self.pre_bytes + doc.rst)

        if buf and os.path.isfile(buf):
            os.remove(buf)

        #Reload to mark as saved, before the filesystem watcher ca get to it.
        self.reload()

    def onChanged(self):
        "Handle change in the filesystem"

        #Never let an external change completely destroy our work.
        self.save(self.path + str(time.time()))

        self.reload()
        #Three sleeps, really be sure the other process has put the file back.
        #See http://stackoverflow.com/questions/18300376/qt-qfilesystemwatcher-signal-filechanged-gets-emited-only-once
        time.sleep(0.01)
        time.sleep(0.01)
        time.sleep(0.01)

        self.watcher.addPath(self.path)

    def reload(self, dummy=True):
        "Reload the file from disk"
        if self.path.endswith('.jpg') or self.path.endswith(
                '.svg') or self.path.endswith('.png') or self.path.endswith(
                    '.jpeg'):
            self.edit.setUrl(QUrl("file://" + self.path))
            self.edit.page().setContentEditable(False)
            return

        if os.path.isfile(self.path):
            with open(self.path, "rb") as f:
                s = f.read()
            #Be compatible with files made on a certain android text editor that puts those bytes there sometimes.
            if s.startswith(b"\xff\xfe"):
                s = s[2:]
                self.pre_bytes = b"\xff\xfe"
            elif s.startswith(b"\xfe\xff"):
                s = s[2:]
                self.pre_bytes = b"\xfe\xff"
            elif s.startswith(b"\xef\xbb\xbf"):
                s = s[3:]
                self.pre_bytes = b"\xef\xbb\xbf"
            else:
                self.pre_bytes = b''  #now we are going to use pandoc to convert to html
            doc = pandoc.Document()

            #Figure out the input format. We back things up and archive them by appenting a Timestamp
            #to the end or else a ~. That function strips both of those things.
            if util.striptrailingnumbers(self.path).endswith(".html"):
                doc.html = s
                self.edit.page().setContentEditable(False)

            #The special html.ro lets us have read only HTML used for handy calculators and stuff.
            elif util.striptrailingnumbers(self.path).endswith(".html.ro"):
                doc.html = s
                self.edit.page().setContentEditable(False)

            elif util.striptrailingnumbers(self.path).endswith(".md"):
                doc.markdown_github = s

            elif util.striptrailingnumbers(self.path).endswith(".rst"):
                doc.rst = s
            else:
                raise RuntimeError("Bad filetype")
            html = doc.html.decode("utf-8")

            #Add the CSS file before the HTML
            d = "<style>" + styles.style + "</style>"
            self.header_size = len(d)
            d += html

            self.edit.setHtml(
                d,
                QUrl("file://" + self.path) if self.path else QUrl("file:///"))
Exemple #20
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width() - self.width()) // 2,
			          (screenRect.height() - self.height()) // 2)
			if not screenRect.contains(self.geometry()):
				self.showMaximized()
		if sys.platform.startswith('darwin'):
			# https://github.com/retext-project/retext/issues/198
			searchPaths = QIcon.themeSearchPaths()
			searchPaths.append('/opt/local/share/icons')
			searchPaths.append('/usr/local/share/icons')
			QIcon.setThemeSearchPaths(searchPaths)
		setIconThemeFromSettings()
		if QFile.exists(getBundledIcon('retext')):
			self.setWindowIcon(QIcon(getBundledIcon('retext')))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.tabWidget = QTabWidget(self)
		self.initTabWidget()
		self.setCentralWidget(self.tabWidget)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		self.toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		self.toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh',
			lambda: self.currentTab.readTextFromFile())
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionCloseCurrentTab = self.act(self.tr('Close tab'), 'window-close',
			lambda: self.closeTab(self.ind), shct=QKeySequence.Close)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeEditorFont = self.act(self.tr('Change editor font'),
			trig=self.changeEditorFont)
		self.actionChangePreviewFont = self.act(self.tr('Change preview font'),
			trig=self.changePreviewFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find',
			self.search, shct=QKeySequence.Find)
		self.actionGoToLine = self.act(self.tr('Go to line'),
			trig=self.goToLine, shct=Qt.CTRL+Qt.Key_G)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(getBundledIcon('document-preview')))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		menuPreview = QMenu()
		menuPreview.addAction(self.actionLivePreview)
		self.actionPreview.setMenu(menuPreview)
		self.actionInsertTable = self.act(self.tr('Insert table'),
			trig=lambda: self.insertFormatting('table'))
		self.actionTableMode = self.act(self.tr('Table editing mode'),
			shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.currentTab.editBox.enableTableMode(x))
		self.actionInsertImages = self.act(self.tr('Insert images by file path'),
			trig=lambda: self.insertImages())
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setChecked(self.isFullScreen())
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste)
		self.actionPasteImage = self.act(self.tr('Paste image'), 'edit-paste',
			lambda: self.currentTab.editBox.pasteImage(), shct=Qt.CTRL+Qt.SHIFT+Qt.Key_V)
		self.actionMoveUp = self.act(self.tr('Move line up'), 'go-up',
			lambda: self.currentTab.editBox.moveLineUp(), shct=Qt.ALT+Qt.Key_Up)
		self.actionMoveDown = self.act(self.tr('Move line down'), 'go-down',
			lambda: self.currentTab.editBox.moveLineDown(), shct=Qt.ALT+Qt.Key_Down)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant is not None:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		if ReTextWebKitPreview is None:
			globalSettings.useWebKit = False
			self.actionWebKit.setEnabled(False)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionWebEngine = self.act(self.tr('Use WebEngine (Chromium) renderer'),
			trigbool=self.enableWebEngine)
		if ReTextWebEnginePreview is None:
			globalSettings.useWebEngine = False
		self.actionWebEngine.setChecked(globalSettings.useWebEngine)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionReplace = self.act(self.tr('Replace'), 'edit-find-replace',
			lambda: self.find(replace=True))
		self.actionReplaceAll = self.act(self.tr('Replace all'), trig=self.replaceAll)
		menuReplace = QMenu()
		menuReplace.addAction(self.actionReplaceAll)
		self.actionReplace.setMenu(menuReplace)
		self.actionCloseSearch = self.act(self.tr('Close'), 'window-close',
			lambda: self.searchBar.setVisible(False),
			shct=QKeySequence.Cancel)
		self.actionCloseSearch.setPriority(QAction.LowPriority)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About ReText')
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup.name == globalSettings.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertFormatting('bold'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertFormatting('italic'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertFormatting('underline'))
		self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering',
			'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote',
			'table')
		self.usefulChars = ('deg', 'divide', 'euro', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.formattingBox = QComboBox(self.editBar)
		self.formattingBox.addItem(self.tr('Formatting'))
		self.formattingBox.addItems(self.usefulTags)
		self.formattingBox.activated[str].connect(self.insertFormatting)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = self.menuBar()
		menuFile = menubar.addMenu(self.tr('File'))
		menuEdit = menubar.addMenu(self.tr('Edit'))
		menuHelp = menubar.addMenu(self.tr('Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addAction(self.actionCloseCurrentTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addAction(self.actionPasteImage)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionMoveUp)
		menuEdit.addAction(self.actionMoveDown)
		menuEdit.addSeparator()
		if enchant is not None:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionGoToLine)
		menuEdit.addAction(self.actionChangeEditorFont)
		menuEdit.addAction(self.actionChangePreviewFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		if ReTextWebKitPreview is not None or ReTextWebEnginePreview is None:
			menuEdit.addAction(self.actionWebKit)
		else:
			menuEdit.addAction(self.actionWebEngine)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionInsertTable)
		menuEdit.addAction(self.actionTableMode)
		menuEdit.addAction(self.actionInsertImages)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		self.toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.toolBar.addAction(self.actionNew)
		self.toolBar.addSeparator()
		self.toolBar.addAction(self.actionOpen)
		self.toolBar.addAction(self.actionSave)
		self.toolBar.addAction(self.actionPrint)
		self.toolBar.addSeparator()
		self.toolBar.addAction(self.actionPreview)
		self.toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.formattingBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.replaceEdit = QLineEdit(self.searchBar)
		self.replaceEdit.setPlaceholderText(self.tr('Replace with'))
		self.replaceEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addWidget(self.replaceEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.addAction(self.actionReplace)
		self.searchBar.addAction(self.actionCloseSearch)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant is not None:
			self.sl = globalSettings.spellCheckLocale
			try:
				enchant.Dict(self.sl or None)
			except enchant.errors.Error as e:
				warnings.warn(str(e), RuntimeWarning)
				globalSettings.spellCheck = False
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def restoreLastOpenedFiles(self):
		for file in readListFromSettings("lastFileList"):
			self.openFileWrapper(file)

		# Show the tab of last opened file
		lastTabIndex = globalSettings.lastTabIndex
		if lastTabIndex >= 0 and lastTabIndex < self.tabWidget.count():
			self.tabWidget.setCurrentIndex(lastTabIndex)

	def iterateTabs(self):
		for i in range(self.tabWidget.count()):
			yield self.tabWidget.widget(i)

	def updateStyleSheet(self):
		self.ss = None
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.setMovable(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent
		self.tabWidget.setTabBarAutoHide(globalSettings.tabBarAutoHide)

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(getBundledIcon(name)))

	def printError(self):
		import traceback
		print('Exception occurred while parsing document:', file=sys.stderr)
		traceback.print_exc()

	def updateTabTitle(self, ind, tab):
		changed = tab.editBox.document().isModified()
		if changed and not self.autoSaveActive(tab):
			title = tab.getBaseName() + '*'
		else:
			title = tab.getBaseName()
		self.tabWidget.setTabText(ind, title)

	def tabFileNameChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		filename of the current tab has changed.
		'''
		if tab == self.currentTab:
			if tab.fileName:
				self.setWindowTitle("")
				if globalSettings.windowTitleFullPath:
					self.setWindowTitle(tab.fileName + '[*]')
				self.setWindowFilePath(tab.fileName)
				self.updateTabTitle(self.ind, tab)
				self.tabWidget.setTabToolTip(self.ind, tab.fileName)
				QDir.setCurrent(QFileInfo(tab.fileName).dir().path())
			else:
				self.setWindowFilePath('')
				self.setWindowTitle(self.tr('New document') + '[*]')

			canReload = bool(tab.fileName) and not self.autoSaveActive(tab)
			self.actionSetEncoding.setEnabled(canReload)
			self.actionReload.setEnabled(canReload)

	def tabActiveMarkupChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		active markup class of the current tab has changed.
		'''
		if tab == self.currentTab:
			markupClass = tab.getActiveMarkupClass()
			dtMarkdown = (markupClass == markups.MarkdownMarkup)
			dtMkdOrReST = dtMarkdown or (markupClass == markups.ReStructuredTextMarkup)
			self.formattingBox.setEnabled(dtMarkdown)
			self.symbolBox.setEnabled(dtMarkdown)
			self.actionUnderline.setEnabled(dtMarkdown)
			self.actionBold.setEnabled(dtMkdOrReST)
			self.actionItalic.setEnabled(dtMkdOrReST)

	def tabModificationStateChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		modification state of the current tab has changed.
		'''

		if tab == self.currentTab:
			changed = tab.editBox.document().isModified()
			if self.autoSaveActive(tab):
				changed = False
			self.actionSave.setEnabled(changed)
			self.updateTabTitle(self.ind, tab)
			self.setWindowModified(changed)

	def createTab(self, fileName):
		previewStatesByName = {
			'editor': PreviewDisabled,
			'normal-preview': PreviewNormal,
			'live-preview': PreviewLive,
		}
		previewState = previewStatesByName.get(globalSettings.defaultPreviewState, PreviewDisabled)
		if previewState == PreviewNormal and not fileName:
			previewState = PreviewDisabled  # Opening empty document in preview mode makes no sense
		self.currentTab = ReTextTab(self, fileName, previewState)
		self.currentTab.fileNameChanged.connect(lambda: self.tabFileNameChanged(self.currentTab))
		self.currentTab.modificationStateChanged.connect(lambda: self.tabModificationStateChanged(self.currentTab))
		self.currentTab.activeMarkupChanged.connect(lambda: self.tabActiveMarkupChanged(self.currentTab))
		self.tabWidget.addTab(self.currentTab, self.tr("New document"))
		self.currentTab.updateBoxesVisibility()
		if previewState > 0:
			QTimer.singleShot(500, self.currentTab.triggerPreviewUpdate)

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.createTab("")
			closedTab = self.tabWidget.widget(ind)
			if closedTab.fileName:
				self.fileSystemWatcher.removePath(closedTab.fileName)
			self.tabWidget.removeTab(ind)
			closedTab.deleteLater()

	def changeIndex(self, ind):
		'''
		This function is called when a different tab is selected.
		It changes the state of the window to mirror the current state
		of the newly selected tab. Future changes to this state will be
		done in response to signals emitted by the tab, to which the
		window was subscribed when the tab was created. The window is
		subscribed to all tabs like this, but only the active tab will
		logically generate these signals.
		Aside from the above this function also calls the handlers for
		the other changes that are implied by a tab switch: filename
		change, modification state change and active markup change.
		'''
		self.currentTab = self.tabWidget.currentWidget()
		editBox = self.currentTab.editBox
		previewState = self.currentTab.previewState
		self.actionUndo.setEnabled(editBox.document().isUndoAvailable())
		self.actionRedo.setEnabled(editBox.document().isRedoAvailable())
		self.actionCopy.setEnabled(editBox.textCursor().hasSelection())
		self.actionCut.setEnabled(editBox.textCursor().hasSelection())
		self.actionPreview.setChecked(previewState >= PreviewLive)
		self.actionLivePreview.setChecked(previewState == PreviewLive)
		self.actionTableMode.setChecked(editBox.tableModeEnabled)
		self.editBar.setEnabled(previewState < PreviewNormal)
		self.ind = ind
		editBox.setFocus(Qt.OtherFocusReason)

		self.tabFileNameChanged(self.currentTab)
		self.tabModificationStateChanged(self.currentTab)
		self.tabActiveMarkupChanged(self.currentTab)

	def changeEditorFont(self):
		font, ok = QFontDialog.getFont(globalSettings.editorFont, self)
		if ok:
			self.setEditorFont(font)

	def setEditorFont(self, font):
		globalSettings.editorFont = font
		for tab in self.iterateTabs():
			tab.editBox.updateFont()

	def changePreviewFont(self):
		font, ok = QFontDialog.getFont(globalSettings.font, self)
		if ok:
			self.setPreviewFont(font)

	def setPreviewFont(self, font):
		globalSettings.font = font
		for tab in self.iterateTabs():
			tab.triggerPreviewUpdate()

	def preview(self, viewmode):
		self.currentTab.previewState = viewmode * 2
		self.actionLivePreview.setChecked(False)
		self.editBar.setDisabled(viewmode)
		self.currentTab.updateBoxesVisibility()
		self.currentTab.triggerPreviewUpdate()

	def enableLivePreview(self, livemode):
		self.currentTab.previewState = int(livemode)
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.currentTab.updateBoxesVisibility()
		self.currentTab.triggerPreviewUpdate()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		globalSettings.useWebEngine = False
		for tab in self.iterateTabs():
			tab.rebuildPreviewBox()

	def enableWebEngine(self, enable):
		globalSettings.useWebKit = False
		globalSettings.useWebEngine = enable
		for tab in self.iterateTabs():
			tab.rebuildPreviewBox()

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for tab in self.iterateTabs():
				tab.editBox.installFakeVimHandler()
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		try:
			dict = enchant.Dict(self.sl or None)
		except enchant.errors.Error as e:
			QMessageBox.warning(self, '', str(e))
			self.actionEnableSC.setChecked(False)
			yes = False
		self.setAllDictionaries(dict if yes else None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for tab in self.iterateTabs():
			hl = tab.highlighter
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		localedlg = LocaleDialog(self, defaultText=self.sl)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		try:
			enchant.Dict(sl or None)
		except enchant.errors.Error as e:
			QMessageBox.warning(self, '', str(e))
		else:
			self.sl = sl or None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
			if localedlg.checkBox.isChecked():
				globalSettings.spellCheckLocale = sl

	def search(self):
		self.searchBar.setVisible(True)
		self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def goToLine(self):
		line, ok = QInputDialog.getInt(self, self.tr("Go to line"), self.tr("Type the line number"))
		if ok:
			self.currentTab.goToLine(line-1)

	def searchBarVisibilityChanged(self, visible):
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False, replace=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		replaceText = self.replaceEdit.text() if replace else None
		found = self.currentTab.find(text, flags, replaceText=replaceText)
		self.setSearchEditColor(found)

	def replaceAll(self):
		text = self.searchEdit.text()
		replaceText = self.replaceEdit.text()
		found = self.currentTab.replaceAll(text, replaceText)
		self.setSearchEditColor(found)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def showInDir(self):
		if self.currentTab.fileName:
			path = QFileInfo(self.currentTab.fileName).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def moveToTopOfRecentFileList(self, fileName):
		if fileName:
			files = readListFromSettings("recentFileList")
			if fileName in files:
				files.remove(fileName)
			files.insert(0, fileName)
			recentCount = globalSettings.recentDocumentsCount
			if len(files) > recentCount:
				del files[recentCount:]
			writeListToSettings("recentFileList", files)

	def createNew(self, text=None):
		self.createTab("")
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.currentTab.editBox.textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFunction(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFunction(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.currentTab.getActiveMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype is None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown", "text/markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), QDir.currentPath(),
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	@pyqtSlot(str)
	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i, tab in enumerate(self.iterateTabs()):
			if tab.fileName == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.currentTab.fileName or
				self.currentTab.editBox.toPlainText() or
				self.currentTab.editBox.document().isModified()
			)
			if noEmptyTab:
				self.createTab(fileName)
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.currentTab.readTextFromFile(fileName)
			self.moveToTopOfRecentFileList(self.currentTab.fileName)

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		codecsSet = set(bytes(QTextCodec.codecForName(alias).name())
		                for alias in QTextCodec.availableCodecs())
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in sorted(codecsSet)],
			0, False)
		if ok:
			self.currentTab.readTextFromFile(None, encoding)

	def saveFileAs(self):
		self.saveFile(dlg=True)

	def saveAll(self):
		for tab in self.iterateTabs():
			if (tab.fileName and tab.editBox.document().isModified()
				and QFileInfo(tab.fileName).isWritable()):
				tab.saveTextToFile()

	def saveFile(self, dlg=False):
		fileNameToSave = self.currentTab.fileName

		if (not fileNameToSave) or dlg:
			proposedFileName = ""
			markupClass = self.currentTab.getActiveMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			if fileNameToSave is not None:
				proposedFileName = fileNameToSave
			fileNameToSave = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), proposedFileName, defaultExt)[0]
			if fileNameToSave:
				if not QFileInfo(fileNameToSave).suffix():
					fileNameToSave += ext
				# Make sure we don't overwrite a file opened in other tab
				for tab in self.iterateTabs():
					if tab is not self.currentTab and tab.fileName == fileNameToSave:
						QMessageBox.warning(self, "",
							self.tr("Cannot save to file which is open in another tab!"))
						return False
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if fileNameToSave:
			if self.currentTab.saveTextToFile(fileNameToSave):
				self.moveToTopOfRecentFileList(self.currentTab.fileName)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			_, htmltext, _ = self.currentTab.getDocumentForExport(webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		result = htmlFile.open(QIODevice.WriteOnly)
		if not result:
			QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
			return
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self, title, htmltext):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle, title)
		td.setHtml(htmltext)
		td.setDefaultFont(globalSettings.font)
		return td

	def saveOdf(self):
		title, htmltext, _ = self.currentTab.getDocumentForExport()
		try:
			document = self.textDocument(title, htmltext)
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), self.currentTab.getBaseName() + ".odt",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat(b"odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), self.currentTab.getBaseName() + ".html",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self, title, htmltext, preview):
		if globalSettings.useWebKit:
			return preview
		try:
			return self.textDocument(title, htmltext)
		except Exception:
			self.printError()

	def standardPrinter(self, title):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(title)
		printer.setCreator('ReText %s' % app_version)
		if globalSettings.paperSize:
			pageSize = self.getPageSizeByName(globalSettings.paperSize)
			if pageSize is not None:
				printer.setPaperSize(pageSize)
			else:
				QMessageBox.warning(self, '',
					self.tr('Unrecognized paperSize setting "%s".') %
					globalSettings.paperSize)
		return printer

	def getPageSizeByName(self, pageSizeName):
		""" Returns a validated PageSize instance corresponding to the given
		name. Returns None if the name is not a valid PageSize.
		"""
		pageSize = None

		lowerCaseNames = {pageSize.lower(): pageSize for pageSize in
		                  self.availablePageSizes()}
		if pageSizeName.lower() in lowerCaseNames:
			pageSize = getattr(QPagedPaintDevice, lowerCaseNames[pageSizeName.lower()])

		return pageSize

	def availablePageSizes(self):
		""" List available page sizes. """

		sizes = [x for x in dir(QPagedPaintDevice)
		         if type(getattr(QPagedPaintDevice, x)) == QPagedPaintDevice.PageSize]
		return sizes

	def savePdf(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			self.currentTab.getBaseName() + ".pdf",
			self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			title, htmltext, preview = self.currentTab.getDocumentForExport()
			if globalSettings.useWebEngine and hasattr(preview.page(), "printToPdf"):
				pageSize = self.getPageSizeByName(globalSettings.paperSize)
				if pageSize is None:
					pageSize = QPageSize(QPageSize.A4)
				margins = QMarginsF(20, 20, 13, 20)  # left, top, right, bottom (in millimeters)
				layout = QPageLayout(pageSize, QPageLayout.Portrait, margins, QPageLayout.Millimeter)
				preview.page().printToPdf(fileName, layout)  # Available since Qt 5.7
				return
			printer = self.standardPrinter(title)
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint(title, htmltext, preview)
			if document != None:
				document.print(printer)

	def printFile(self):
		title, htmltext, preview = self.currentTab.getDocumentForExport()
		printer = self.standardPrinter(title)
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint(title, htmltext, preview)
			if document != None:
				document.print(printer)

	def printPreview(self):
		title, htmltext, preview = self.currentTab.getDocumentForExport()
		document = self.getDocumentForPrint(title, htmltext, preview)
		if document is None:
			return
		printer = self.standardPrinter(title)
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		import shlex
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		else:
			fileName = 'out' + defaultext
		basename = '.%s.retext-temp' % self.currentTab.getBaseName()
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename + self.currentTab.getActiveMarkupClass().default_extension
			self.currentTab.writeTextToFile(tmpname)
		command = command.replace('%of', shlex.quote(fileName))
		command = command.replace('%html' if html else '%if', shlex.quote(tmpname))
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()

	def autoSaveActive(self, tab=None):
		tab = tab if tab else self.currentTab
		return bool(self.autoSaveEnabled and tab.fileName and
			    QFileInfo(tab.fileName).isWritable())

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())
			self.actionPasteImage.setEnabled(mimeData.hasImage())

	def insertFormatting(self, formatting):
		if formatting == 'table':
			dialog = InsertTableDialog(self)
			dialog.show()
			self.formattingBox.setCurrentIndex(0)
			return

		cursor = self.currentTab.editBox.textCursor()
		text = cursor.selectedText()
		moveCursorTo = None

		def c(cursor):
			nonlocal moveCursorTo
			moveCursorTo = cursor.position()

		def ensurenl(cursor):
			if not cursor.atBlockStart():
				cursor.insertText('\n\n')

		toinsert = {
			'header': (ensurenl, '# ', text),
			'italic': ('*', text, c, '*'),
			'bold': ('**', text, c, '**'),
			'underline': ('<u>', text, c, '</u>'),
			'numbering': (ensurenl, ' 1. ', text),
			'bullets': (ensurenl, '  * ', text),
			'image': ('![', text or self.tr('Alt text'), c, '](', self.tr('URL'), ')'),
			'link': ('[', text or self.tr('Link text'), c, '](', self.tr('URL'), ')'),
			'inline code': ('`', text, c, '`'),
			'code block': (ensurenl, '    ', text),
			'blockquote': (ensurenl, '> ', text),
		}

		if formatting not in toinsert:
			return

		cursor.beginEditBlock()
		for token in toinsert[formatting]:
			if callable(token):
				token(cursor)
			else:
				cursor.insertText(token)
		cursor.endEditBlock()

		self.formattingBox.setCurrentIndex(0)
		# Bring back the focus on the editor
		self.currentTab.editBox.setFocus(Qt.OtherFocusReason)

		if moveCursorTo:
			cursor.setPosition(moveCursorTo)
			self.currentTab.editBox.setTextCursor(cursor)

	def insertSymbol(self, num):
		if num:
			self.currentTab.editBox.insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		tab = None
		for testtab in self.iterateTabs():
			if testtab.fileName == fileName:
				tab = testtab
		if tab is None:
			self.fileSystemWatcher.removePath(fileName)
			return
		if not QFile.exists(fileName):
			self.tabWidget.setCurrentWidget(tab)
			tab.editBox.document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not tab.editBox.document().isModified():
			# File was not modified in ReText, reload silently
			tab.readTextFromFile()
		else:
			self.tabWidget.setCurrentWidget(tab)
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				tab.readTextFromFile()
			else:
				self.autoSaveEnabled = False
				tab.editBox.document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://github.com/retext-project/retext/issues/137
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		tab = self.tabWidget.widget(ind)
		if self.autoSaveActive(tab):
			tab.saveTextToFile()
			return True
		if not tab.editBox.document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFile(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for ind in range(self.tabWidget.count()):
			if not self.maybeSave(ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry:
			globalSettings.windowGeometry = self.saveGeometry()
		if globalSettings.openLastFilesOnStartup:
			files = [tab.fileName for tab in self.iterateTabs()]
			writeListToSettings("lastFileList", files)
			globalSettings.lastTabIndex = self.tabWidget.currentIndex()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			_, htmltext, _ = self.currentTab.getDocumentForExport(includeStyleSheet=False)
		except Exception:
			return self.printError()
		winTitle = self.currentTab.getBaseName()
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def insertImages(self):
		supportedExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp']
		fileFilter = ' (%s);;' % ' '.join('*' + ext for ext in supportedExtensions)
		fileNames, _selectedFilter = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several images to open"), QDir.currentPath(),
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))

		cursor = self.currentTab.editBox.textCursor()

		imagesMarkup = '\n'.join(
			self.currentTab.editBox.getImageMarkup(fileName)
			for fileName in fileNames)
		cursor.insertText(imagesMarkup)

		self.formattingBox.setCurrentIndex(0)
		self.currentTab.editBox.setFocus(Qt.OtherFocusReason)

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011–2020')
		+'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markupClass):
		globalSettings.defaultMarkup = markupClass.name
		for tab in self.iterateTabs():
			if not tab.fileName:
				tab.updateActiveMarkupClass()
class ExperimentWindow(QMainWindow):
    experiment_finished = pyqtSignal([bool, bool])

    MAX_GENERATIONS = 30

    def __init__(self, mlc_local,
                 experiment_name,
                 experiment_closed_signal,
                 parent=None):
        QMainWindow.__init__(self, parent)
        self._autogenerated_object = Ui_ExperimentWindow()
        self._autogenerated_object.setupUi(self)

        # Open the experiment
        self._mlc_local = mlc_local
        self._experiment_name = experiment_name
        self.setWindowTitle("Experiment {0}".format(self._experiment_name))

        # Experiment tab parameters
        self._mlc_local.open_experiment(self._experiment_name)
        experiment_info = self._mlc_local.get_experiment_info(self._experiment_name)
        self._current_gen = int(experiment_info["generations"])

        self._load_experiment_config()
        self._update_individuals_per_generation_list()
        self._update_experiment_info()
        self._update_individuals_figure()

        # Disable save_config_button until some change is made
        self._autogenerated_object.save_config_button.setDisabled(True)

        # Connect the function that updates the graphics of the Window when the
        # experiment evaluation finished
        self.experiment_finished.connect(self._update_experiment)

        # Signal to be emitted when the experiment is closed
        self._experiment_closed_signal = experiment_closed_signal

        # Watch changes in the experiment config file
        self._file_watcher = QFileSystemWatcher()
        conf_path_list = [self._mlc_local.get_working_dir(),
                          self._experiment_name,
                          self._experiment_name + ".conf"]
        # Use the splat operator to dearrange the list
        self._experiment_conf_path = os.path.join(*conf_path_list)
        self._file_watcher.addPath(self._experiment_conf_path)
        self._file_watcher.fileChanged.connect(self._reload_experiment_config)

        # Experiment in progress chart configuration
        self._chart_conf = ChartConfiguration(self._autogenerated_object)
        indivs_per_gen = Config.get_instance().getint("POPULATION", "size")
        self._chart_conf.init(indivs_per_gen)
        self._update_scatter_chart()

        # Manager of the first individuals
        self._first_indivs_manager = FirstIndividualsManager(parent=self,
                                                             experiment_name=self._experiment_name,
                                                             autogenerated_object=self._autogenerated_object,
                                                             mlc_local=self._mlc_local)

        # Arduino board configurations
        self._board_config, self._serial_conn = mlc_local.get_board_configuration(self._experiment_name)
        ArduinoUserInterface.set_connection_builder(serial_connection_builder)

        # Update the arduino board group for the first case

        if self._autogenerated_object.disable_board_check.checkState() == Qt.Checked:
            self.on_disable_arduino_toggle(True)

        # FIXME: The code of show_convergence button is not implemented yet. Hide the button
        self._autogenerated_object.convergence_button.setVisible(False)

    def closeEvent(self, event):
        logger.debug('[EXPERIMENT {0}] [CLOSE_DIALOG] - Executing overriden closeEvent function'
                     .format(self._experiment_name))
        self._ask_if_experiment_config_must_be_saved()
        # Close the experiment
        self._file_watcher.removePath(self._experiment_conf_path)
        self._mlc_local.close_experiment(self._experiment_name)
        self._experiment_closed_signal.emit(self._experiment_name)

    def on_start_button_clicked(self):
        logger.debug('[EXPERIMENT {0}] [START_BUTTON] - Executing on_start_button_clicked function'
                     .format(self._experiment_name))

        try:
            ArduinoUserInterface.get_instance(protocol_config=self._board_config,
                                              conn_setup=self._serial_conn)

        except (ProtocolSetupException, ConnectionException) as err:
            logger.debug('[EXPERIMENT {0}] [BOARD_CONFIG] - '
                         'Serial port could not be initialized. Error Msg: {1}'
                         .format(self._experiment_name, err))
            selection = QMessageBox.warning(self, "Connection failure",
                                            "The current connection arduino setup failed during initialization "
                                            "(Error: {0}), this may generate an error in your experiment.\n"
                                            "Do you want to continue?".format(err),
                                            QMessageBox.Yes | QMessageBox.No,
                                            QMessageBox.Yes)
            if selection == QMessageBox.No:
                logger.debug('[EXPERIMENT {0}] [START_BUTTON] - Experiment cancelled by user before start'.format(
                    self._experiment_name))
                QMessageBox.information(self, "Check setup", "Please, open the arduino board configurator in order to "
                                                             "analize and resolve the error.", QMessageBox.Ok)
                return
        except Exception as err:
            self._report_arduino_unhandled_error(err)
            return

        from_gen = int(self._autogenerated_object.from_gen_combo.currentText())
        to_gen = int(self._autogenerated_object.to_gen_combo.currentText())
        number_of_gens = self._mlc_local.get_experiment_info(self._experiment_name)["generations"]

        gen_creator = None
        if number_of_gens == 0:
            from_gen = 0
            # Hide the first individuals tab
            self._autogenerated_object.tabWidget.setTabEnabled(3, False)
            gen_creator = self._first_indivs_manager.get_gen_creator()

        # Check if the experiment range is ok
        if to_gen <= from_gen:
            logger.info("[EXPERIMENT {0}] [START_BUTTON] - Experiment can not be executed. "
                        "'From Generation' index must be greater than 'To Generation' index "
                        "From Gen: {1} - To Gen:{2}"
                        .format(self._experiment_name, from_gen, to_gen))

            QMessageBox.critical(self,
                                 "Invalid Generation Range",
                                 "Experiment can not be executed. 'From Generation' index "
                                 "must be greater than 'To Generation' index")
            return

        if from_gen != number_of_gens:
            # Check if the from generation value is not the last generation. If that's the case, show a warning
            # informing the user that generations will be lost if the experiment continues
            ret = QMessageBox.warning(self,
                                      "Generation To Be Lost Warning",
                                      "'From Generation' index is not the last generation. "
                                      "Generations will be lost if you continue the experiment. "
                                      "Do you want to continue?",
                                      QMessageBox.No | QMessageBox.Yes,
                                      QMessageBox.No)

            if ret == QMessageBox.No:
                return

        logger.info('[EXPERIMENT {0}] [START_BUTTON] - Proceed to execute experiment from Generation '
                    'N°{1} to Generation N°{2}'.format(self._experiment_name, from_gen, to_gen))

        progress_dialog = ExperimentInProgress(mlc_local=self._mlc_local,
                                               parent=self,
                                               experiment_name=self._experiment_name,
                                               to_gen=to_gen,
                                               from_gen=from_gen,
                                               chart_params=self._chart_conf.chart_params(),
                                               parent_signal=self.experiment_finished,
                                               gen_creator=gen_creator)

        progress_dialog.start()

    def on_prev_gen_button_clicked(self):
        logger.debug(
            '[EXPERIMENT {0}] [PREV_GEN_BUTTON] - Executing on_prev_gen_button_clicked function'.format(self._experiment_name))
        experiment_info = self._mlc_local.get_experiment_info(self._experiment_name)
        number_of_gens = experiment_info["generations"]

        if self._current_gen > 1:
            self._current_gen -= 1
            self._update_experiment_info()
            self._update_individuals_figure()
            self._update_scatter_chart()

    def on_next_gen_button_clicked(self):
        logger.debug(
            '[EXPERIMENT {0}] [NEXT_GEN_BUTTON] - Executing on_next_gen_button_clicked function'.format(self._experiment_name))
        experiment_info = self._mlc_local.get_experiment_info(self._experiment_name)
        number_of_gens = experiment_info["generations"]

        if self._current_gen < number_of_gens:
            self._current_gen += 1
            self._update_experiment_info()
            self._update_individuals_figure()
            self._update_scatter_chart()

    def on_test_button_clicked(self):
        logger.debug('[EXPERIMENT {0}] [TEST_BUTTON] - Executing on_test_button_clicked function'
                     .format(self._experiment_name))

        test_indiv_edit = self._autogenerated_object.test_indiv_edit
        if test_indiv_edit.text() == "":
            logger.warn('[EXPERIMENT {0}] [TEST_BUTTON] - The individual value cannot be an empty string'
                        .format(self._experiment_name))
            QMessageBox.information(self, "Test Evaluation Script",
                                    "The individual value cannot be an empty string.", QMessageBox.Ok)
            return

        cost = test_individual_value(parent=self,
                                     experiment_name=self._experiment_name,
                                     log_prefix="[EXPERIMENT_WINDOW]",
                                     indiv_value=test_indiv_edit.text(),
                                     config=Config.get_instance())

        if cost != None:
            test_label_result = self._autogenerated_object.test_label_result
            test_label_result.setText(str(cost))

    def on_test_preev_indiv_button_clicked(self):
        logger.debug('[EXPERIMENT {0}] [TEST_BUTTON] - Executing on_test_button_clicked function'
                     .format(self._experiment_name))

        test_indiv_edit = self._autogenerated_object.test_preev_indiv_edit
        if test_indiv_edit.text() == "":
            logger.warn('[EXPERIMENT {0}] [TEST_BUTTON] - The individual value cannot be an empty string'
                        .format(self._experiment_name))
            QMessageBox.information(self, "Test Preevaluation Script",
                                    "The individual value cannot be an empty string.", QMessageBox.Ok)
            return

        result = check_if_indiv_pass_preevaluation(parent=self,
                                                   experiment_name=self._experiment_name,
                                                   log_prefix="[EXPERIMENT_WINDOW]",
                                                   indiv_value=test_indiv_edit.text(),
                                                   config=Config.get_instance())

        if result is not None:
            test_label_result = self._autogenerated_object.test_preev_indiv_result
            test_label_result.setText(str(result))

    def on_log_check_clicked(self):
        logger.debug('[EXPERIMENT {0}] [LOG_CHECK_CLICKED] - Executing on_log_check_clicked function'
                     .format(self._experiment_name))
        self._update_individuals_figure()

    def on_show_all_check_clicked(self):
        logger.debug('[EXPERIMENT {0}] [SHOW_ALL_CHECK_CLICKED] - Executing on_show_all_check_clicked function'
                     .format(self._experiment_name))
        self._update_individuals_figure()

    def on_dimension_check_clicked(self):
        logger.debug('[EXPERIMENT {0}] [DIMENSION_CHECK_CLICKED] - Executing on_dimension_check_clicked function'
                     .format(self._experiment_name))
        # TODO: Don't know what the 3D graphic option should do yet...

    def on_save_config_button_clicked(self):
        logger.debug('[EXPERIMENT {0}] [SAVE_CONFIG_BUTTON_CLICKED] - Executing on_save_config_button_clicked function'
                     .format(self._experiment_name))

        # Remove the config file from the Qt File watcher momentarily
        # to avoid rendering unnecesary warnings
        self._file_watcher.removePath(self._experiment_conf_path)
        self._persist_experiment_config()
        self._file_watcher.addPath(self._experiment_conf_path)

    def on_tab_changed(self, tab_index):
        logger.debug('[EXPERIMENT {0}] [TAB_CHANGED] - Executing on_tab_changed function'
                     .format(self._experiment_name))
        self._ask_if_experiment_config_must_be_saved()

    def on_import_config_button_clicked(self):
        logger.debug('[EXPERIMENT {0}] [IMPORT_CONFIG_BUTTON_CLICKED] - Executing on_import_config_button_clicked function'
                     .format(self._experiment_name))

        # Get the path of the experiment to import
        config_file_path = QFileDialog.getOpenFileName(self, "Import Experiment", ".",
                                                       "Config File (*.conf)")[0]
        if not config_file_path:
            # User clicked 'Cancel' or simply closed the Dialog
            return

        self._file_watcher.removePath(self._experiment_conf_path)
        self._mlc_local.set_experiment_configuration_from_file(self._experiment_name,
                                                               config_file_path)
        self._load_experiment_config()
        QMessageBox.information(self, "Config File Imported",
                                "New config file {0} was succesfully imported".format(config_file_path))
        logger.info("[EXPERIMENT {0}] [EXPORT_CONFIG] - New config file {1} was "
                    "succesfully imported.".format(self._experiment_name, config_file_path))
        self._autogenerated_object.save_config_button.setDisabled(True)
        self._file_watcher.addPath(self._experiment_conf_path)

    def on_export_config_button_clicked(self):
        logger.debug('[EXPERIMENT {0}] [EXPORT_CONFIG_BUTTON_CLICKED] - Executing on_export_config_button_clicked function'
                     .format(self._experiment_name))

        export_dir = QFileDialog.getExistingDirectory(self, 'Choose the directory where to export the configuration file',
                                                      '.', QFileDialog.ShowDirsOnly)
        if not export_dir:
            # User clicked 'Cancel' or simply closed the Dialog
            return

        export_file_name = os.path.join(export_dir, self._experiment_name + ".conf")
        try:
            shutil.copyfile(self._experiment_conf_path, export_file_name)
        except Exception, err:
            QMessageBox.critical(self, "Config File Not Exported",
                                 "Config File could not be exported. "
                                 "Error {0}".format(err))
            logger.error("[EXPERIMENT {0}] [EXPORT_CONFIG] - Config file could not "
                         "be exported. Error: {0}".format(err))
            return

        logger.info("[EXPERIMENT {0}] [EXPORT_CONFIG] - Config file was "
                    "succesfully exported. Location: {1}"
                    .format(self._experiment_name, export_file_name))
        QMessageBox.information(self, "Config File Exported",
                                "Config File was succesfully exported. You can find it at {0}"
                                .format(export_file_name))
Exemple #22
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2)
		if not screenRect.contains(self.geometry()):
			self.showMaximized()
		if globalSettings.iconTheme:
			QIcon.setThemeName(globalSettings.iconTheme)
		if QIcon.themeName() in ('hicolor', ''):
			if not QFile.exists(icon_path + 'document-new.png'):
				QIcon.setThemeName(get_icon_theme())
		if QFile.exists(icon_path+'retext.png'):
			self.setWindowIcon(QIcon(icon_path+'retext.png'))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.tabWidget = QTabWidget(self)
		self.initTabWidget()
		self.setCentralWidget(self.tabWidget)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh',
			lambda: self.currentTab.readTextFromFile())
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeEditorFont = self.act(self.tr('Change editor font'),
			trig=self.changeEditorFont)
		self.actionChangePreviewFont = self.act(self.tr('Change preview font'),
			trig=self.changePreviewFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find)
		self.actionSearch.setCheckable(True)
		self.actionSearch.triggered[bool].connect(self.searchBar.setVisible)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png'))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		menuPreview = QMenu()
		menuPreview.addAction(self.actionLivePreview)
		self.actionPreview.setMenu(menuPreview)
		self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.currentTab.editBox.enableTableMode(x))
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant_available:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionCloseSearch = self.act(self.tr('Close'), 'window-close',
			lambda: self.searchBar.setVisible(False))
		self.actionCloseSearch.setPriority(QAction.LowPriority)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About ReText')
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		self.defaultMarkup = availableMarkups[0] if availableMarkups else None
		if globalSettings.defaultMarkup:
			mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup)
			if mc and mc.available():
				self.defaultMarkup = mc
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup == self.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertFormatting('bold'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertFormatting('italic'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertFormatting('underline'))
		self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering',
			'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote')
		self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.formattingBox = QComboBox(self.editBar)
		self.formattingBox.addItem(self.tr('Formatting'))
		self.formattingBox.addItems(self.usefulTags)
		self.formattingBox.activated[str].connect(self.insertFormatting)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = self.menuBar()
		menuFile = menubar.addMenu(self.tr('File'))
		menuEdit = menubar.addMenu(self.tr('Edit'))
		menuHelp = menubar.addMenu(self.tr('Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addSeparator()
		if enchant_available:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionChangeEditorFont)
		menuEdit.addAction(self.actionChangePreviewFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		menuEdit.addAction(self.actionWebKit)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionTableMode)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		toolBar.addAction(self.actionNew)
		toolBar.addSeparator()
		toolBar.addAction(self.actionOpen)
		toolBar.addAction(self.actionSave)
		toolBar.addAction(self.actionPrint)
		toolBar.addSeparator()
		toolBar.addAction(self.actionPreview)
		toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.formattingBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.addAction(self.actionCloseSearch)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant_available:
			self.sl = globalSettings.spellCheckLocale
			if self.sl:
				try:
					enchant.Dict(self.sl)
				except Exception as e:
					print(e, file=sys.stderr)
					self.sl = None
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def iterateTabs(self):
		for i in range(self.tabWidget.count()):
			yield self.tabWidget.widget(i).tab

	def updateStyleSheet(self):
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()
		else:
			palette = QApplication.palette()
			self.ss = 'html { color: %s; }\n' % palette.color(QPalette.WindowText).name()
			self.ss += 'td, th { border: 1px solid #c3c3c3; padding: 0 3px 0 3px; }\n'
			self.ss += 'table { border-collapse: collapse; }\n'

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.setMovable(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(icon_path+name+'.png'))

	def printError(self):
		import traceback
		print('Exception occured while parsing document:', file=sys.stderr)
		traceback.print_exc()

	def createTab(self, fileName):
		self.currentTab = ReTextTab(self, fileName,
			previewState=int(globalSettings.livePreviewByDefault))
		self.tabWidget.addTab(self.currentTab.getSplitter(), self.tr("New document"))

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.createTab("")
			currentWidget = self.tabWidget.widget(ind)
			if currentWidget.tab.fileName:
				self.fileSystemWatcher.removePath(currentWidget.tab.fileName)
			del currentWidget.tab
			self.tabWidget.removeTab(ind)

	def docTypeChanged(self):
		markupClass = self.currentTab.getMarkupClass()
		if type(self.currentTab.markup) != markupClass:
			self.currentTab.setMarkupClass(markupClass)
			self.currentTab.updatePreviewBox()
		dtMarkdown = (markupClass == markups.MarkdownMarkup)
		dtMkdOrReST = dtMarkdown or (markupClass == markups.ReStructuredTextMarkup)
		self.formattingBox.setEnabled(dtMarkdown)
		self.symbolBox.setEnabled(dtMarkdown)
		self.actionUnderline.setEnabled(dtMarkdown)
		self.actionBold.setEnabled(dtMkdOrReST)
		self.actionItalic.setEnabled(dtMkdOrReST)
		canReload = bool(self.currentTab.fileName) and not self.autoSaveActive()
		self.actionSetEncoding.setEnabled(canReload)
		self.actionReload.setEnabled(canReload)

	def changeIndex(self, ind):
		self.currentTab = self.tabWidget.currentWidget().tab
		editBox = self.currentTab.editBox
		previewState = self.currentTab.previewState
		self.actionUndo.setEnabled(editBox.document().isUndoAvailable())
		self.actionRedo.setEnabled(editBox.document().isRedoAvailable())
		self.actionCopy.setEnabled(editBox.textCursor().hasSelection())
		self.actionCut.setEnabled(editBox.textCursor().hasSelection())
		self.actionPreview.setChecked(previewState >= PreviewLive)
		self.actionLivePreview.setChecked(previewState == PreviewLive)
		self.actionTableMode.setChecked(editBox.tableModeEnabled)
		self.editBar.setEnabled(previewState < PreviewNormal)
		self.ind = ind
		if self.currentTab.fileName:
			self.setCurrentFile()
		else:
			self.setWindowTitle(self.tr('New document') + '[*]')
			self.docTypeChanged()
		self.modificationChanged(editBox.document().isModified())
		editBox.setFocus(Qt.OtherFocusReason)

	def changeEditorFont(self):
		font, ok = QFontDialog.getFont(globalSettings.editorFont, self)
		if ok:
			globalSettings.editorFont = font
			for tab in self.iterateTabs():
				tab.editBox.updateFont()

	def changePreviewFont(self):
		font, ok = QFontDialog.getFont(globalSettings.font, self)
		if ok:
			globalSettings.font = font
			for tab in self.iterateTabs():
				tab.updatePreviewBox()

	def preview(self, viewmode):
		self.currentTab.previewState = viewmode * 2
		self.actionLivePreview.setChecked(False)
		self.editBar.setDisabled(viewmode)
		self.currentTab.updateBoxesVisibility()
		if viewmode:
			self.currentTab.updatePreviewBox()

	def enableLivePreview(self, livemode):
		self.currentTab.previewState = int(livemode)
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.currentTab.updateBoxesVisibility()
		if livemode:
			self.currentTab.updatePreviewBox()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		for i in range(self.tabWidget.count()):
			splitter = self.tabWidget.widget(i)
			tab = splitter.tab
			tab.previewBox.disconnectExternalSignals()
			tab.previewBox.setParent(None)
			tab.previewBox.deleteLater()
			tab.previewBox = tab.createPreviewBox(tab.editBox)
			tab.previewBox.setMinimumWidth(125)
			splitter.addWidget(tab.previewBox)
			splitter.setSizes((50, 50))
			tab.updatePreviewBox()
			tab.updateBoxesVisibility()

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for tab in self.iterateTabs():
				tab.installFakeVimHandler()
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		if yes:
			self.setAllDictionaries(enchant.Dict(self.sl or None))
		else:
			self.setAllDictionaries(None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for tab in self.iterateTabs():
			hl = tab.highlighter
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		if self.sl:
			localedlg = LocaleDialog(self, defaultText=self.sl)
		else:
			localedlg = LocaleDialog(self)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		setdefault = localedlg.checkBox.isChecked()
		if sl:
			try:
				sl = str(sl)
				enchant.Dict(sl)
			except Exception as e:
				QMessageBox.warning(self, '', str(e))
			else:
				self.sl = sl
				self.enableSpellCheck(self.actionEnableSC.isChecked())
		else:
			self.sl = None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
		if setdefault:
			globalSettings.spellCheckLocale = sl

	def searchBarVisibilityChanged(self, visible):
		self.actionSearch.setChecked(visible)
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		editBox = self.currentTab.editBox
		cursor = editBox.textCursor()
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		cursor.movePosition(QTextCursor.End if back else QTextCursor.Start)
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		self.setSearchEditColor(False)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def showInDir(self):
		if self.currentTab.fileName:
			path = QFileInfo(self.currentTab.fileName).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def setCurrentFile(self):
		self.setWindowTitle("")
		self.tabWidget.setTabText(self.ind, self.currentTab.getDocumentTitle(baseName=True))
		self.tabWidget.setTabToolTip(self.ind, self.currentTab.fileName or '')
		self.setWindowFilePath(self.currentTab.fileName)
		files = readListFromSettings("recentFileList")
		while self.currentTab.fileName in files:
			files.remove(self.currentTab.fileName)
		files.insert(0, self.currentTab.fileName)
		if len(files) > 10:
			del files[10:]
		writeListToSettings("recentFileList", files)
		QDir.setCurrent(QFileInfo(self.currentTab.fileName).dir().path())
		self.docTypeChanged()

	def createNew(self, text=None):
		self.createTab("")
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.currentTab.editBox.textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFunction(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFunction(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.currentTab.getMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype == None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), "",
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i, tab in enumerate(self.iterateTabs()):
			if tab.fileName == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.currentTab.fileName or
				self.currentTab.editBox.toPlainText() or
				self.currentTab.editBox.document().isModified()
			)
			if noEmptyTab:
				self.createTab(fileName)
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.currentTab.fileName = fileName
			self.currentTab.readTextFromFile()
			editBox = self.currentTab.editBox
			self.setCurrentFile()
			self.setWindowModified(editBox.document().isModified())

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in QTextCodec.availableCodecs()],
			0, False)
		if ok:
			self.currentTab.readTextFromFile(encoding)

	def saveFile(self):
		self.saveFileMain(dlg=False)

	def saveFileAs(self):
		self.saveFileMain(dlg=True)

	def saveAll(self):
		for tab in self.iterateTabs():
			if tab.fileName and QFileInfo(tab.fileName).isWritable():
				tab.saveTextToFile()
				tab.editBox.document().setModified(False)

	def saveFileMain(self, dlg):
		if (not self.currentTab.fileName) or dlg:
			markupClass = self.currentTab.getMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			newFileName = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), "", defaultExt)[0]
			if newFileName:
				if not QFileInfo(newFileName).suffix():
					newFileName += ext
				if self.currentTab.fileName:
					self.fileSystemWatcher.removePath(self.currentTab.fileName)
				self.currentTab.fileName = newFileName
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if self.currentTab.fileName:
			if self.currentTab.saveTextToFile():
				self.setCurrentFile()
				self.currentTab.editBox.document().setModified(False)
				self.setWindowModified(False)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			htmltext = self.currentTab.getHtml(includeStyleSheet=False,
				webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		htmlFile.open(QIODevice.WriteOnly)
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle,
		                      self.currentTab.getDocumentTitle())
		if self.ss:
			td.setDefaultStyleSheet(self.ss)
		td.setHtml(self.currentTab.getHtml())
		td.setDefaultFont(globalSettings.font)
		return td

	def saveOdf(self):
		try:
			document = self.textDocument()
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), "",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat(b"odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), "",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self):
		if globalSettings.useWebKit:
			return self.currentTab.previewBox
		try:
			return self.textDocument()
		except Exception:
			self.printError()

	def standardPrinter(self):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(self.currentTab.getDocumentTitle())
		printer.setCreator('ReText %s' % app_version)
		return printer

	def savePdf(self):
		self.currentTab.updatePreviewBox()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			"", self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			printer = self.standardPrinter()
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printFile(self):
		self.currentTab.updatePreviewBox()
		printer = self.standardPrinter()
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printPreview(self):
		document = self.getDocumentForPrint()
		if document == None:
			return
		printer = self.standardPrinter()
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		basename = '.%s.retext-temp' % self.currentTab.getDocumentTitle(baseName=True)
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename + self.currentTab.getMarkupClass().default_extension
			self.currentTab.saveTextToFile(fileName=tmpname, addToWatcher=False)
		command = command.replace('%of', '"out'+defaultext+'"')
		command = command.replace('%html' if html else '%if', '"'+tmpname+'"')
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()
		if of:
			QFile('out'+defaultext).rename(fileName)

	def autoSaveActive(self, ind=None):
		tab = self.currentTab if ind is None else self.tabWidget.widget(ind).tab
		return (self.autoSaveEnabled and tab.fileName and
			QFileInfo(tab.fileName).isWritable())

	def modificationChanged(self, changed):
		if self.autoSaveActive():
			changed = False
		self.actionSave.setEnabled(changed)
		self.setWindowModified(changed)

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())

	def insertFormatting(self, formatting):
		cursor = self.currentTab.editBox.textCursor()
		text = cursor.selectedText()
		moveCursorTo = None

		def c(cursor):
			nonlocal moveCursorTo
			moveCursorTo = cursor.position()

		def ensurenl(cursor):
			if not cursor.atBlockStart():
				cursor.insertText('\n\n')

		toinsert = {
			'header': (ensurenl, '# ', text),
			'italic': ('*', text, c, '*'),
			'bold': ('**', text, c, '**'),
			'underline': ('<u>', text, c, '</u>'),
			'numbering': (ensurenl, ' 1. ', text),
			'bullets': (ensurenl, '  * ', text),
			'image': ('![', text or self.tr('Alt text'), c, '](', self.tr('URL'), ')'),
			'link': ('[', text or self.tr('Link text'), c, '](', self.tr('URL'), ')'),
			'inline code': ('`', text, c, '`'),
			'code block': (ensurenl, '    ', text),
			'blockquote': (ensurenl, '> ', text),
		}

		if formatting not in toinsert:
			return

		cursor.beginEditBlock()
		for token in toinsert[formatting]:
			if callable(token):
				token(cursor)
			else:
				cursor.insertText(token)
		cursor.endEditBlock()

		self.formattingBox.setCurrentIndex(0)
		# Bring back the focus on the editor
		self.currentTab.editBox.setFocus(Qt.OtherFocusReason)

		if moveCursorTo:
			cursor.setPosition(moveCursorTo)
			self.currentTab.editBox.setTextCursor(cursor)

	def insertSymbol(self, num):
		if num:
			self.currentTab.editBox.insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		ind = None
		for testind, tab in enumerate(self.iterateTabs()):
			if tab.fileName == fileName:
				ind = testind
		if ind is None:
			self.fileSystemWatcher.removePath(fileName)
		self.tabWidget.setCurrentIndex(ind)
		if not QFile.exists(fileName):
			self.currentTab.editBox.document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not self.currentTab.editBox.document().isModified():
			# File was not modified in ReText, reload silently
			self.currentTab.readTextFromFile()
			self.currentTab.updatePreviewBox()
		else:
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				self.currentTab.readTextFromFile()
				self.currentTab.updatePreviewBox()
			else:
				self.autoSaveEnabled = False
				self.currentTab.editBox.document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://github.com/retext-project/retext/issues/137
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		tab = self.tabWidget.widget(ind).tab
		if self.autoSaveActive(ind):
			tab.saveTextToFile()
			return True
		if not tab.editBox.document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFileMain(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for ind in range(self.tabWidget.count()):
			if not self.maybeSave(ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry and not self.isMaximized():
			globalSettings.windowGeometry = self.saveGeometry()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			htmltext = self.currentTab.getHtml(includeStyleSheet=False)
		except Exception:
			return self.printError()
		winTitle = self.currentTab.getDocumentTitle(baseName=True)
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011–2016')
		+'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markupClass):
		self.defaultMarkup = markupClass
		defaultName = markups.get_available_markups()[0].name
		writeToSettings('defaultMarkup', markupClass.name, defaultName)
		for tab in self.iterateTabs():
			if not tab.fileName:
				tab.setMarkupClass(markupClass)
				tab.updatePreviewBox()
		self.docTypeChanged()
Exemple #23
0
class AppMain(Ui_MainWindow):
    RAM_VIEW_INITIAL_SIZE = 10000
    TEMP_MAX_RAM_USE = 1024 * 1000
    STEP_TIMER_IN_MS = 1000

    def __init__(self):
        Ui_MainWindow.__init__(self)

        ## class Variables
        self.config_dialog = None
        self.rom_stream = None
        self.rom_path = None
        self.rom_type_sel = None
        self.rom_model = None
        self.rom_watcher = None
        self.ram_model = None
        self.data_changed = None
        self.lst_parser = None
        self.last_step = None
        self.editor_converting = False
        self.pixmap_ALU = None
        self.asm_thread = None
        self.sim_thread = None
        self.config_dialog_ui = None
        self.actionROMGroup = None
        self.actionRAMGroup = None
        self.actionREGGroup = None
        self.step_timer = None
        self.window = AppMainWindow()

        # Setup Dialog, Editor, Actions, Threads, Img Resizing
        self.setup_dialog()
        self.setupUi(self.window)
        self.setup_editor()
        self.setup_actions()
        self.setup_threads()

    def show_alu(self):
        QDesktopServices.openUrl(QUrl.fromLocalFile("theme/alu.png"))

    def setup_editor(self):
        self.rom_stream = tempfile.SpooledTemporaryFile(
            max_size=self.TEMP_MAX_RAM_USE, mode="w+")
        self.rom_path = None
        self.rom_type_sel = self.actionROMAssembly
        self.lst_parser = None
        self.rom_watcher = QFileSystemWatcher()
        self.rom_watcher.fileChanged.connect(self.reload_rom)
        self.editor_converting = False
        self.spinBox.setValue(100)
        self.on_new()

    def setup_threads(self):
        self.asm_thread = QThread()
        self.sim_thread = QThread()

    def setup_dialog(self):
        self.config_dialog = QDialog()
        self.config_dialog_ui = config_dialog.Ui_Dialog()
        self.config_dialog_ui.setupUi(self.config_dialog)
        self.config_dialog_ui.assemblerLineEdit.setText(
            "../jar/Z01-Assembler.jar")
        self.config_dialog_ui.rtlLineEdit.setText("../Z01-Simulator-rtl-2/")

    def setup_clean_views(self,
                          table,
                          rows=100,
                          caption="Dados",
                          line_header=None):
        model = QStandardItemModel(rows, 1, self.window)
        model.setHorizontalHeaderItem(0, QStandardItem(caption))
        table.setModel(model)
        for k in range(0, table.horizontalHeader().count()):
            table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        for l in range(0, rows):
            if line_header is None:
                model.setHeaderData(l, QtCore.Qt.Vertical, l)
            else:
                model.setHeaderData(l, QtCore.Qt.Vertical, line_header(l))
        return model

    def setup_actions(self):
        self.step_timer = QtCore.QTimer()
        self.step_timer.timeout.connect(self.on_proximo)
        self.actionNovo.triggered.connect(self.on_new)
        self.actionSalvar_ROM.triggered.connect(self.on_save)
        self.actionAbrir.triggered.connect(self.on_load)
        self.actionProximo.triggered.connect(self.on_proximo)
        self.actionExecutarFim.triggered.connect(self.on_executar_fim)
        self.actionParar.triggered.connect(self.on_parar)
        self.actionEraseRAM.triggered.connect(self.on_clear_ram)
        self.actionVoltarInicio.triggered.connect(self.on_voltar_inicio)
        self.spinBox.valueChanged.connect(self.on_voltar_inicio)
        self.actionROMAssembly.triggered.connect(self.on_rom_assembly)
        self.actionROMBinario.triggered.connect(self.on_rom_binary)
        self.actionROMGroup = QActionGroup(self.window)
        self.actionROMGroup.addAction(self.actionROMAssembly)
        self.actionROMGroup.addAction(self.actionROMBinario)
        self.actionROMAssembly.setChecked(True)
        self.but_ALU.clicked.connect(self.show_alu)
        self.config_dialog_ui.procurarButton.clicked.connect(
            self.on_search_assembler)
        self.config_dialog_ui.alterarButton.clicked.connect(
            self.config_dialog.close)
        self.actionConfiguracoes.triggered.connect(self.config_dialog.show)

    def change_rtl_dir(self, new_dir):
        self.config_dialog_ui.rtlLineEdit.setText(new_dir)

    def on_rom_assembly(self):
        self.editor_converting = True
        file_utils.copy_file_to_model(self.rom_stream, self.rom_model)
        self.rom_type_sel = self.actionROMAssembly
        self.editor_converting = False

    def on_rom_binary(self):
        self.editor_converting = True
        if self.rom_type_sel == self.actionROMAssembly:
            file_utils.copy_model_to_file(self.rom_model, self.rom_stream)
            self.assemble(self.load_converted_asm_bin)

        self.rom_type_sel = self.actionROMBinario

    def on_ram_tooltip(self, item):
        text = item.text().strip()

        try:
            val = int(text, 2)
        except ValueError:
            return

        item.setToolTip("{0:d} dec - {1:x} hex".format(val, val))

    def on_clear_ram(self):
        self.ram_model = self.setup_clean_views(
            self.ramView,
            rows=self.RAM_VIEW_INITIAL_SIZE,
            caption="RAM",
            line_header=asm_utils.z01_ram_name)
        for i in range(0, self.RAM_VIEW_INITIAL_SIZE):
            item = QStandardItem("0000000000000000")
            self.on_ram_tooltip(item)
            self.ram_model.setItem(i, item)

    def on_new(self):
        self.rom_path = None
        self.on_clear_ram()
        self.rom_model = self.setup_clean_views(self.romView, caption="ROM")
        self.rom_model.itemChanged.connect(self.valid_rom)
        self.ram_model.itemChanged.connect(self.valid_ram)
        self.actionROMAssembly.setEnabled(True)

        self.clear_simulation()

    def on_voltar_inicio(self):
        self.data_changed = True
        self.clear_simulation()

    def on_parar(self):
        self.step_timer.stop()

    def on_executar_fim(self):
        self.step_timer.start(self.STEP_TIMER_IN_MS)

    def show(self):
        self.window.show()

    def on_save(self):
        filename = self.rom_path

        if self.rom_path is not None:
            self.rom_watcher.removePath(self.rom_path)

        if filename is None:
            filename = QFileDialog.getSaveFileName(self.window,
                                                   "Salve o arquivo",
                                                   os.getcwd(),
                                                   "Arquivos (*.hack *.nasm)")
            if len(filename) == 0 or len(filename[0]) == 0:
                return None
            filename = filename[0]
            self.rom_path = filename

        if self.actionROMAssembly.isChecked():
            file_utils.copy_model_to_file(self.rom_model, self.rom_stream)

        file_utils.stream_to_file(self.rom_stream, filename)
        self.rom_watcher.addPath(self.rom_path)

    def on_load(self):
        filename = QFileDialog.getOpenFileName(
            self.window, "Escolha arquivo", os.getcwd(),
            "Arquivos (*.asm *.hack *.nasm)")
        if len(filename) == 0 or len(filename[0]) == 0:
            return None

        if self.rom_path is not None:
            self.rom_watcher.removePath(self.rom_path)

        self.on_new()
        self.rom_path = filename[0]
        self.rom_watcher.addPath(self.rom_path)
        self.reload_rom()

    def reload_rom(self):
        return self.load_rom(self.rom_path)

    def load_rom(self, filename):
        if not os.path.exists(filename):
            return

        if filename.endswith(".asm") or filename.endswith(".nasm"):
            self.load_asm(filename, self.rom_model)
        elif filename.endswith(".bin") or filename.endswith(".hack"):
            self.load_bin(filename, self.rom_model)

    def on_search_assembler(self):
        filename = QFileDialog.getOpenFileName(self.window, "Escolha arquivo",
                                               os.getcwd(),
                                               "Arquivo JAR (*.jar)")
        if len(filename) == 0 or len(filename[0]) == 0:
            return None

        self.config_dialog_ui.assemblerLineEdit.setText(filename[0])

    def on_proximo(self):
        if self.data_changed:
            if self.lst_parser is not None:
                self.lst_parser.close()

            if self.actionROMAssembly.isChecked():
                file_utils.copy_model_to_file(self.rom_model, self.rom_stream)
                self.assemble(self.assemble_end)
            else:
                tmp_rom = tempfile.SpooledTemporaryFile(
                    max_size=self.TEMP_MAX_RAM_USE, mode="w+")
                file_utils.copy_model_to_file(self.rom_model, tmp_rom)
                tmp_ram = self.get_updated_ram()
                self.simulate(tmp_rom, tmp_ram)
            return False

        step = self.lst_parser.advance()

        if "s_regAout" not in step:
            self.step_timer.stop()
            QMessageBox.warning(self.window, "Simulador", "Fim de simulação")
            return

        self.update_line_edit(self.lineEdit_A, step["s_regAout"])
        self.update_line_edit(self.lineEdit_S, step["s_regSout"])
        self.update_line_edit(self.lineEdit_D, step["s_regDout"])
        self.update_line_edit(self.lineEdit_inM, step["inM"])
        self.update_line_edit(self.lineEdit_outM, step["outM"])

        if self.last_step is not None:
            addr = int(step["s_regAout"], 2)
            index = self.ram_model.index(addr, 0)
            last_pc_counter = int(self.last_step["pcout"], 2) - 1

            if int(step["writeM"]) == 0 and int(
                    step["s_muxALUI_A"]) == 1 and int(
                        self.last_step["s_muxALUI_A"]) == 0:
                self.ramView.setCurrentIndex(index)

            if int(step["writeM"]) == 1:
                self.ramView.setCurrentIndex(index)
                self.ram_model.itemFromIndex(index).setText(step["outM"])
        else:
            last_pc_counter = -1

        ## update ROM line
        pc_counter = int(step["pcout"], 2) - 1

        if pc_counter < 0:
            pc_counter = 0

        if pc_counter != last_pc_counter + 1:
            print("JUMP - Executando NOP")
            pc_counter = last_pc_counter  # Mantem

        if self.actionROMAssembly.isChecked():
            rom_line = asm_utils.z01_real_line(self.assembler_task.labels_pos,
                                               pc_counter)
        else:
            rom_line = pc_counter

        index = self.rom_model.index(rom_line, 0)
        self.romView.setCurrentIndex(index)

        print("PROXIMO")
        self.last_step = step

    def update_line_edit(self, line_edit, new_value, ignore=False):
        if line_edit.text() != new_value:
            line_edit.setText(new_value)
            if not ignore:
                line_edit.setStyleSheet(
                    "QLineEdit {background-color: yellow;}")
            valid = self.valid_binary(line_edit)
            if valid:
                self.on_ram_tooltip(line_edit)
        else:
            line_edit.setStyleSheet("")

    def valid_rom(self, item):
        if not item.text():
            return None

        text = item.text()
        index = item.index()

        while index.row() + 50 >= self.rom_model.rowCount():
            self.rom_model.appendRow(QStandardItem(""))

        if self.actionROMAssembly.isChecked():
            valid = asm_utils.z01_valid_assembly(item.text())
        elif self.actionROMBinario.isChecked():
            valid = self.valid_binary(item)
        else:
            valid = True

        if valid:
            if (self.actionROMBinario.isChecked()
                ) and self.editor_converting is False:
                self.actionROMAssembly.setEnabled(False)
            self.data_changed = True
        else:
            item.setText("")

    def valid_ram(self, item):
        if not item.text():
            return None
        text = item.text()
        index = item.index()

        while index.row() + 100 >= self.ram_model.rowCount():
            self.ram_model.appendRow(QStandardItem("{0:0>16b}".format(0)))

        if text.startswith("d"):
            text = text[1:]
            if text.isdigit():
                item.setText("{0:0>16b}".format(int(text)))

        valid = self.valid_binary(item)

        if valid:
            #self.data_changed = True
            self.on_ram_tooltip(item)
        else:
            item.setText("{0:0>16b}".format(0))

    def assemble(self, callback):
        if self.asm_thread.isRunning() or self.sim_thread.isRunning():
            print("Assembler está sendo executado...")
            return False

        assembler = "java -jar " + self.config_dialog_ui.assemblerLineEdit.text(
        )
        self.assembler_task = AssemblerTask(assembler, "temp/")
        rom_in = tempfile.SpooledTemporaryFile(max_size=self.TEMP_MAX_RAM_USE,
                                               mode="w+")
        rom_out = tempfile.SpooledTemporaryFile(max_size=self.TEMP_MAX_RAM_USE,
                                                mode="w+")
        file_utils.copy_file_to_file(self.rom_stream, rom_in)
        self.assembler_task.setup(rom_in, rom_out)
        self.assembler_task.finished.connect(callback)
        self.assembler_task.moveToThread(self.asm_thread)
        self.asm_thread.started.connect(self.assembler_task.run)
        self.asm_thread.start()

    def simulate(self, rom_file, ram_file):
        if self.asm_thread.isRunning() or self.sim_thread.isRunning():
            print("Simulador está sendo executado...")
            return False

        self.simulator_task = SimulatorTask(
            "temp/", False, self.config_dialog_ui.simGUIBox.isChecked(),
            self.config_dialog_ui.rtlLineEdit.text())
        lst_out = tempfile.SpooledTemporaryFile(max_size=self.TEMP_MAX_RAM_USE,
                                                mode="w+")
        self.simulator_task.setup(rom_file, ram_file, lst_out,
                                  self.spinBox.value() * 10 + 10)
        self.simulator_task.finished.connect(self.simulation_end)
        self.simulator_task.moveToThread(self.sim_thread)
        self.sim_thread.started.connect(self.simulator_task.run)
        self.sim_thread.start()
        self.lock_and_show_dialog()

    def lock_and_show_dialog(self):
        ## waits for ASM thread and SIM thread to end
        self.progress_dialog = QProgressDialog("Simulando...", "Cancelar", 0,
                                               0, self.window)
        self.progress_dialog.setCancelButton(None)
        self.progress_dialog.setAutoReset(True)
        self.progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
        self.progress_dialog.setMinimumDuration(0)
        self.progress_dialog.setValue(0)
        self.progress_dialog.setWindowTitle("RESimulatorGUI")
        self.progress_dialog.setWindowFlags(self.progress_dialog.windowFlags()
                                            & ~QtCore.Qt.WindowCloseButtonHint)

        while self.asm_thread.isRunning() or self.sim_thread.isRunning():
            qapp.processEvents()

        self.progress_dialog.reset()

    def get_updated_ram(self):
        ram = tempfile.SpooledTemporaryFile(max_size=self.TEMP_MAX_RAM_USE,
                                            mode="w+")
        file_utils.copy_model_to_file(self.ram_model, ram)
        return ram

    def check_assembler_sucess(self):
        if self.assembler_task is not None and self.assembler_task.success is True:
            return True
        QMessageBox.critical(self.window, "Assembler",
                             "Erro ao traduzir assembly.")
        self.step_timer.stop()
        return False

    def assemble_end(self):
        self.asm_thread.quit()  # ensure end of thread
        self.asm_thread.wait()
        ram = self.get_updated_ram()
        if not self.check_assembler_sucess():
            return
        print("ASM done!")
        self.simulate(self.assembler_task.stream_out, ram)

    def simulation_end(self):
        self.sim_thread.quit()  #ensure end of thread
        self.sim_thread.wait()
        print("SIM done!")
        self.data_changed = False
        self.lst_parser = LSTParser(self.simulator_task.lst_stream)

    def load_converted_asm_bin(self):
        self.asm_thread.quit()
        self.asm_thread.wait()
        if not self.check_assembler_sucess():
            return
        file_utils.copy_file_to_model(self.assembler_task.stream_out,
                                      self.rom_model)
        self.editor_converting = False

    def load_converted_asm_hex(self):
        self.asm_thread.quit()
        self.asm_thread.wait()
        if not self.check_assembler_sucess():
            return
        file_utils.copy_file_to_model(self.assembler_task.stream_out,
                                      self.rom_model, asm_utils.bin_str_to_hex)
        self.editor_converting = False

    def valid_binary(self, item):
        valid = True
        text = item.text().strip()

        try:
            val = int(text, 2)
        except ValueError:
            valid = False

        if not valid:
            print("Invalid BIN Instruction: {}".format(item.text()))

        return valid

    def clear_simulation(self):
        self.last_step = None
        self.update_line_edit(self.lineEdit_A, "0000000000000000", True)
        self.update_line_edit(self.lineEdit_S, "0000000000000000", True)
        self.update_line_edit(self.lineEdit_D, "0000000000000000", True)
        self.update_line_edit(self.lineEdit_inM, "0000000000000000", True)
        self.update_line_edit(self.lineEdit_outM, "0000000000000000", True)
        self.data_changed = True
        index = self.ram_model.index(0, 0)
        self.ramView.setCurrentIndex(index)
        index = self.rom_model.index(0, 0)
        self.romView.setCurrentIndex(index)

    def load_file(self, filename, model):
        fp = open(filename, "r")
        counter = 0
        lines = file_utils.file_len(filename)
        self.rom_model = self.setup_clean_views(self.romView,
                                                rows=lines + 200,
                                                caption="ROM")
        for i, l in enumerate(fp):
            if asm_utils.z01_valid_assembly(l.strip()):
                index = self.rom_model.index(counter, 0)
                self.rom_model.itemFromIndex(index).setText(l.strip())
                counter += 1
        fp.close()

    def load_asm(self, filename, model):
        self.actionROMAssembly.setChecked(True)
        self.load_file(filename, model)

    def load_bin(self, filename, model):
        self.actionROMBinario.setChecked(True)
        self.actionROMAssembly.setEnabled(False)
        self.load_file(filename, model)