Beispiel #1
0
class ClientWidget(QWidget):
    """ Displays the descendants of the current user
        in the invitation tree of the chain and displays
        them in a tree like fashion.
        Args:
            parent: The parent widget
    """
    def __init__(self, parent: QWidget):
        super(ClientWidget, self).__init__(parent)
        self.layout = QVBoxLayout()

        self.clients = QTreeWidget()
        self.clients.setColumnCount(1)
        self.clients.setColumnWidth(0, 400)

        self.layout.addWidget(self.clients)

        self.setLayout(self.layout)

    def load_data_from_tree(self,
                            tree: Node,
                            parent_item: QTreeWidgetItem = None):
        """ Takes a node object and initializes the tree view with the contents
            of the nodes.
            Args:
                tree: the 'root' node of the given tree
                parent_item: Specifies the parent item to append new items to.
                    Used for recursive calls of the function
        """
        if parent_item is None:
            parent_item = QTreeWidgetItem()
            parent_item.setText(0, str(tree.content))
            self.clients.addTopLevelItem(parent_item)
        for child in tree.children:
            child_item = QTreeWidgetItem()
            child_item.setText(0, str(child.content))
            parent_item.addChild(child_item)
            self.load_data_from_tree(child, child_item)

    def add_to_tree(self, sender: str, key: str):
        """ Takes a key and adds it to the children of the item
            specified by a given sender.
            Args:
                sender: Specifies who invited the user
                key: public key of the invited user
        """
        parent = self.find_item(sender)
        item = QTreeWidgetItem()
        item.setText(0, key)
        parent.addChild(item)

    def remove_from_tree(self, key: str):
        """ Takes a key and removes it from the tree
            Args:
                key: public key of the uninvited user
        """
        item = self.find_item(key)
        parent = item.parent()
        for i in range(item.childCount()):
            child = item.child(i)
            parent.addChild(child)
        parent.removeChild(item)

    def find_item(self, key: str) -> QTreeWidgetItem:
        """ Takes a key and returns the item holding it
            Args:
                key: public key of some user.
        """
        item = self.clients.findItems(key, QtCore.Qt.MatchRecursive, 0)[0]
        return item

    def react_to_operation(self, transaction: DDosTransaction):
        """ Takes a transaction and reacts to it.
            Args:
                transaction: DDoSTransaction object.
                    If the transaction holds an (un-)invitation,
                    the respective functions are called.
        """
        if transaction.data.type == 'ui':
            self.remove_from_tree(str(transaction.data.data))
        elif transaction.data.type == 'i':
            self.add_to_tree(str(transaction.sender),
                             str(transaction.data.data))
class ParserGUI(QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):

        # Data structures
        self.outListSet = set()
        self.outParamDict = defaultdict(set)

        self.save_file = ""

        # ______Actions______
        setSaveLoc = QAction('&Save...', self)
        setSaveLoc.setShortcut('Ctrl+S')
        setSaveLoc.setStatusTip('Set Save Location and Name')
        setSaveLoc.triggered.connect(self.saveFile)

        exitAction = QAction('&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit Application')
        exitAction.triggered.connect(qApp.quit)

        # ______MenuBar______
        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(setSaveLoc)
        fileMenu.addAction(exitAction)

        # ______Push Buttons______
        chooseLogBtn = QPushButton('Open log file...')
        chooseLogBtn.clicked.connect(self.openFile)
        parseBtn = QPushButton('Parse!')
        parseBtn.clicked.connect(self.parser)

        # ______Labels______
        paramLabel = QLabel('Log Parameters', self)
        outParamLabel = QLabel('Selected Parameters', self)

        # ______QTreeVWidget_________
        self.paramList = QTreeWidget(self)
        self.outList = QTreeWidget(self)

        # ______Actions______

        # Add and subtract items on double click
        self.paramList.itemDoubleClicked.connect(self.addParam)
        self.outList.itemDoubleClicked.connect(self.deleteParam)

        # Map return key to adding to second list
        self.addItem = QShortcut(QKeySequence(Qt.Key_Return), self.paramList)
        self.addItem.activated.connect(
            lambda: self.addParam(self.paramList.currentItem()))

        # Map delete key for removing from second list
        self.deleteItem = QShortcut(QKeySequence(Qt.Key_Delete), self.outList)
        self.deleteItem.activated.connect(
            lambda: self.deleteParam(self.outList.currentItem()))

        centralWidget = QWidget()

        # ______Layout______

        # Create horizontal box layout
        hbox1 = QHBoxLayout()
        hbox1.addWidget(paramLabel)
        hbox1.addWidget(outParamLabel)

        hbox2 = QHBoxLayout()
        hbox2.addWidget(self.paramList)
        hbox2.addWidget(self.outList)

        # Create vertical box layout
        vbox = QVBoxLayout()
        vbox.addWidget(chooseLogBtn)
        vbox.addLayout(hbox1)
        vbox.addLayout(hbox2)
        vbox.addWidget(parseBtn)

        centralWidget.setLayout(vbox)

        self.setCentralWidget(centralWidget)

        self.resize(WINDOW_WIDTH, WINDOW_HEIGHT)
        self.center()

        self.setWindowTitle('Mission Planner TLog Parser')
        # self.setWindowIcon(QIcon('web.png'))
        self.show()

        self.setupParams()

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

    def openFile(self):
        error_message = "Bad file type or corrupted file."

        try:
            self.log_file = QFileDialog.getOpenFileName(
                self, 'Open file', None)[0]
            self.log_chosen = 1
        except ValueError:
            QMessageBox.information(self, "Error", error_message,
                                    QMessageBox.Ok, QMessageBox.Ok)
        finally:
            if not self.log_file.lower().endswith(".csv"):
                QMessageBox.information(self, "Error", "Not a CSV file!",
                                        QMessageBox.Ok, QMessageBox.Ok)

    def saveFile(self):
        self.save_file = QFileDialog.getSaveFileName(self, 'Save File')[0]

    def parser(self):

        header_row = []
        header_row.append("Timestamp")
        row = {"Timestamp": 0}

        # Check to see if save file name and location set
        # Otherwise, set to default directory and name
        if os.path.exists(os.path.dirname(self.save_file)):
            csvfile = open(self.save_file, "w")
        else:
            print(self.log_file)
            csvfile = open(self.log_file.split(".")[0] + "_parsed.csv", "w")

        for key in self.outParamDict:
            for item in self.outParamDict[key]:
                header_row.append(key + "." + item)
                row[key + "." + item] = 0

        csvwriter = csv.writer(csvfile, delimiter=",")
        csvwriter.writerow(header_row)

        try:
            with open(self.log_file, "r") as lf:
                for line in lf:
                    values = line.split(",")
                    # Timestamp
                    row["Timestamp"] = values[0]
                    for key in values:
                        if key in self.outParamDict:
                            for item in self.outParamDict[key]:
                                index = values.index(item) + 1
                                row[key + "." + item] = values[index]
                    csvwriter.writerow(row.values())

            QMessageBox.information(self, "Info", "Finished!", QMessageBox.Ok,
                                    QMessageBox.Ok)
        except UnicodeDecodeError:
            QMessageBox.information(self, "Error",
                                    "Bad file type or corrupted file",
                                    QMessageBox.Ok, QMessageBox.Ok)

    def setupParams(self):
        parent_text = ""

        with open("./tlog_params.txt") as f:
            for line in f:
                values = line.rstrip().split(".")

                if values[1].lstrip("_") != parent_text:
                    child_text = values[0].rstrip("_")
                    parent_text = values[1].lstrip("_")
                    parent = QTreeWidgetItem(self.paramList)
                    parent.setText(0, parent_text)
                    child = QTreeWidgetItem(parent)
                    child.setText(0, child_text)
                else:
                    child_text = values[0].rstrip("_")
                    child = QTreeWidgetItem(parent)
                    child.setText(0, child_text)

        self.paramList.show()

    def addParam(self, parameter):
        # Check that item has a parent (is a child)
        if parameter.parent():
            if parameter.parent().text(0) not in self.outParamDict:
                self.parent = QTreeWidgetItem(self.outList)
                self.parent.setText(0, parameter.parent().text(0))
                self.child = QTreeWidgetItem(self.parent)
                self.child.setText(0, parameter.text(0))
            # Check to see if child is not already in outParamDict values
            elif not any(
                    parameter.text(0) in values
                    for values in self.outParamDict.values()):
                parent_text = parameter.parent().text(0)
                parent_item = self.outList.findItems(parent_text,
                                                     Qt.MatchContains)[0]
                self.child = QTreeWidgetItem(parent_item)
                self.child.setText(0, parameter.text(0))

            self.outParamDict[parameter.parent().text(0)].add(
                parameter.text(0))

        # If it's a parent item
        else:
            child_count = parameter.childCount()
            if parameter.text(0) not in self.outParamDict:
                self.parent = QTreeWidgetItem(self.outList)
                self.parent.setText(0, parameter.text(0))

                # Get number of children, iterate through all children and add them to the outList
                for i in range(0, child_count):
                    child = QTreeWidgetItem(self.parent)
                    child.setText(0, parameter.child(i).text(0))
                    self.outParamDict[parameter.text(0)].add(
                        parameter.child(i).text(0))

            else:
                # Clear current contents
                for i in reversed(range(child_count)):
                    self.parent.removeChild(self.parent.child(i))

                # Rewrite all children in correct order
                for i in range(0, child_count):
                    child = QTreeWidgetItem(self.parent)
                    child.setText(0, parameter.child(i).text(0))
                    self.outParamDict[parameter.text(0)].add(
                        parameter.child(i).text(0))

        print(self.outParamDict)

    def deleteParam(self, parameter):
        # If it has a parent (is a child)
        if parameter.parent():
            self.outParamDict[parameter.parent().text(0)].remove(
                parameter.text(0))
            parameter.parent().removeChild(parameter)

        else:
            # If it's a parent
            root = self.outList.invisibleRootItem()
            root.removeChild(parameter)
            del self.outParamDict[parameter.text(0)]

        print(self.outParamDict)
Beispiel #3
0
class SnapshotRestoreFileSelector(QWidget):
    """
    Widget for visual representation (and selection) of existing saved_value
    files.
    """

    files_selected = QtCore.pyqtSignal(list)
    files_updated = QtCore.pyqtSignal(dict)

    def __init__(self, snapshot, common_settings, parent=None, **kw):
        QWidget.__init__(self, parent, **kw)

        self.snapshot = snapshot
        self.selected_files = list()
        self.common_settings = common_settings

        self.file_list = dict()
        self.pvs = dict()

        # Filter handling
        self.file_filter = dict()
        self.file_filter["keys"] = list()
        self.file_filter["comment"] = ""

        self.filter_input = SnapshotFileFilterWidget(self.common_settings,
                                                     self)

        self.filter_input.file_filter_updated.connect(
            self.filter_file_list_selector)

        # Create list with: file names, comment, labels, machine params.
        # This is done with a single-level QTreeWidget instead of QTableWidget
        # because it is line-oriented whereas a table is cell-oriented.
        self.file_selector = QTreeWidget(self)
        self.file_selector.setRootIsDecorated(False)
        self.file_selector.setUniformRowHeights(True)
        self.file_selector.setIndentation(0)
        self.file_selector.setColumnCount(FileSelectorColumns.params)
        self.column_labels = ["File name", "Comment", "Labels"]
        self.file_selector.setHeaderLabels(self.column_labels)
        self.file_selector.setAllColumnsShowFocus(True)
        self.file_selector.setSortingEnabled(True)
        # Sort by file name (alphabetical order)
        self.file_selector.sortItems(FileSelectorColumns.filename,
                                     Qt.DescendingOrder)

        self.file_selector.itemSelectionChanged.connect(self.select_files)
        self.file_selector.setContextMenuPolicy(Qt.CustomContextMenu)
        self.file_selector.customContextMenuRequested.connect(self.open_menu)

        # Set column sizes
        self.file_selector.resizeColumnToContents(FileSelectorColumns.filename)
        self.file_selector.setColumnWidth(FileSelectorColumns.comment, 350)

        # Applies following behavior for multi select:
        #   click            selects only current file
        #   Ctrl + click     adds current file to selected files
        #   Shift + click    adds all files between last selected and current
        #                    to selected
        self.file_selector.setSelectionMode(QTreeWidget.ExtendedSelection)

        self.filter_file_list_selector()

        # Add to main layout
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.filter_input)
        layout.addWidget(self.file_selector)

    def handle_new_snapshot_instance(self, snapshot):
        self.clear_file_selector()
        self.filter_input.clear()
        self.snapshot = snapshot

    def rebuild_file_list(self, already_parsed_files=None):
        background_workers.suspend()
        self.clear_file_selector()
        self.file_selector.setSortingEnabled(False)
        if already_parsed_files:
            save_files, err_to_report = already_parsed_files
        else:
            save_dir = self.common_settings["save_dir"]
            req_file_path = self.common_settings["req_file_path"]
            save_files, err_to_report = get_save_files(save_dir, req_file_path)

        self._update_file_list_selector(save_files)
        self.filter_file_list_selector()

        # Report any errors with snapshot files to the user
        if err_to_report:
            show_snapshot_parse_errors(self, err_to_report)

        self.file_selector.setSortingEnabled(True)
        self.files_updated.emit(save_files)
        background_workers.resume()

    def _update_file_list_selector(self, file_list):
        new_labels = set()
        new_params = set()
        for new_file, new_data in file_list.items():
            meta_data = new_data["meta_data"]
            labels = meta_data.get("labels", [])
            params = meta_data.get("machine_params", {})

            assert (new_file not in self.file_list)
            new_labels.update(labels)
            new_params.update(params.keys())

        new_labels = list(new_labels)
        new_params = list(new_params)
        defined_params = list(self.common_settings['machine_params'].keys())
        all_params = defined_params + \
            [p for p in new_params if p not in defined_params]

        for new_file, new_data in file_list.items():
            meta_data = new_data["meta_data"]
            labels = meta_data.get("labels", [])
            params = meta_data.get("machine_params", {})
            comment = meta_data.get("comment", "")

            row = [new_file, comment, " ".join(labels)]
            assert (len(row) == FileSelectorColumns.params)
            param_vals = [None] * len(all_params)
            for p, v in params.items():
                string = SnapshotPv.value_to_display_str(
                    v['value'],
                    v['precision'] if v['precision'] is not None else 0)
                idx = all_params.index(p)
                param_vals[idx] = string
            selector_item = QTreeWidgetItem(row + param_vals)
            self.file_selector.addTopLevelItem(selector_item)
            self.file_list[new_file] = new_data
            self.file_list[new_file]["file_selector"] = selector_item

        self.common_settings["existing_labels"] = new_labels
        self.common_settings["existing_params"] = new_params
        self.filter_input.update_params()

        # Add units to column headers; get units from the latest file that has
        # them.
        params_mtimes = [(data['meta_data']['machine_params'],
                          data['modif_time']) for data in file_list.values()]
        params_mtimes.sort(key=lambda d: d[1], reverse=True)
        for i, p in enumerate(all_params):
            for file_params, _ in params_mtimes:
                if file_params.get(p, {}).get('units', None):
                    all_params[i] += f" ({file_params[p]['units']})"
                    break

        self.file_selector.setHeaderLabels(self.column_labels + all_params)
        for col in range(self.file_selector.columnCount()):
            self.file_selector.resizeColumnToContents(col)

        # There can be some rather long comments in the snapshots, so let's
        # make sure that they don't push out more useful stuff.
        if self.file_selector.columnWidth(FileSelectorColumns.comment) \
           > self.file_selector.columnWidth(FileSelectorColumns.filename):
            self.file_selector.setColumnWidth(
                FileSelectorColumns.comment,
                self.file_selector.columnWidth(FileSelectorColumns.filename))

    def filter_file_list_selector(self):
        file_filter = self.filter_input.file_filter

        def ensure_nums_or_strings(*vals):
            """Variables have to be all numbers or all strings. If this is not
            the case, convert everything to strings."""
            if not all((isinstance(x, (int, float)) for x in vals)):
                return tuple((str(x) for x in vals))
            return vals

        def check_params(params_filter, file_params):
            """
            file_params is a dict of machine params and their data (being a
            dict containing 'value' and 'precision').
            params_filter is a dict of machine params and corresponding lists.
            These lists have either one or two elements, causing either an
            equality or in-range check.

            Returns True if all checks pass.
            """
            for p, vals in params_filter.items():
                if p not in file_params:
                    return False
                if len(vals) == 1:
                    v1 = vals[0]
                    v2 = file_params[p]['value']
                    v1, v2 = ensure_nums_or_strings(v1, v2)
                    if isinstance(v2, float):
                        # If precision is defined, compare with tolerance.
                        # The default precision is 6, which matches string
                        # formatting behaviour. It makes no sense to do
                        # comparison to a higher precision than what the user
                        # can see.
                        prec = file_params[p]['precision']
                        tol = 10**(-prec) if (prec and prec > 0) else 10**-6
                        if abs(v1 - v2) > tol:
                            return False
                    else:
                        if v1 != v2:
                            return False

                elif len(vals) == 2:
                    vals = ensure_nums_or_strings(*vals)
                    low = min(vals)
                    high = max(vals)
                    v = file_params[p]['value']
                    v, low, high = ensure_nums_or_strings(v, low, high)
                    if not (v >= low and v <= high):
                        return False
            return True

        for file_name in self.file_list:
            file_line = self.file_list[file_name]["file_selector"]
            file_to_filter = self.file_list.get(file_name)

            if not file_filter:
                file_line.setHidden(False)
            else:
                keys_filter = file_filter.get("keys")
                comment_filter = file_filter.get("comment")
                name_filter = file_filter.get("name")
                params_filter = file_filter.get("params")

                if keys_filter:
                    keys_status = False
                    for key in file_to_filter["meta_data"]["labels"]:
                        # Break when first found
                        if key and (key in keys_filter):
                            keys_status = True
                            break
                else:
                    keys_status = True

                if comment_filter:
                    comment_status = comment_filter in file_to_filter[
                        "meta_data"]["comment"]
                else:
                    comment_status = True

                if name_filter:
                    name_status = name_filter in file_name
                else:
                    name_status = True

                params_status = True
                if params_filter:
                    params_status = check_params(
                        params_filter,
                        file_to_filter['meta_data']['machine_params'])

                # Set visibility if any of the filters conditions met
                file_line.setHidden(not (name_status and keys_status
                                         and comment_status and params_status))

    def open_menu(self, point):
        item_idx = self.file_selector.indexAt(point)
        if not item_idx.isValid():
            return

        text = item_idx.data()
        field = self.file_selector.model().headerData(item_idx.column(),
                                                      Qt.Horizontal)
        clipboard = QGuiApplication.clipboard()

        menu = QMenu(self)
        if item_idx.column() < FileSelectorColumns.params:
            menu.addAction(f"Copy {field.lower()}",
                           lambda: clipboard.setText(text))
        else:
            # Machine param fields end with the unit in parentheses which needs
            # to be stripped to recognize them.
            try:
                param_name = field[:field.rindex('(')].rstrip()
            except ValueError:
                param_name = field

            menu.addAction(f"Copy {param_name} name",
                           lambda: clipboard.setText(param_name))
            menu.addAction(f"Copy {param_name} value",
                           lambda: clipboard.setText(text))
            if param_name in self.common_settings['machine_params']:
                pv_name = self.common_settings['machine_params'][param_name]
                menu.addAction(f"Copy {param_name} PV name",
                               lambda: clipboard.setText(pv_name))

        menu.addAction("Delete selected files", self.delete_files)
        menu.addAction("Edit file meta-data", self.update_file_metadata)

        menu.exec(QCursor.pos())
        menu.deleteLater()

    def select_files(self):
        # Pre-process selected items, to a list of files
        self.selected_files = list()
        if self.file_selector.selectedItems():
            for item in self.file_selector.selectedItems():
                self.selected_files.append(
                    item.text(FileSelectorColumns.filename))

        self.files_selected.emit(self.selected_files)

    def delete_files(self):
        if self.selected_files:
            msg = "Do you want to delete selected files?"
            reply = QMessageBox.question(self, 'Message', msg, QMessageBox.Yes,
                                         QMessageBox.No)
            if reply == QMessageBox.Yes:
                background_workers.suspend()
                symlink_file = self.common_settings["save_file_prefix"] \
                    + 'latest' + save_file_suffix
                symlink_path = os.path.join(self.common_settings["save_dir"],
                                            symlink_file)
                symlink_target = os.path.realpath(symlink_path)

                files = self.selected_files[:]
                paths = [
                    os.path.join(self.common_settings["save_dir"],
                                 selected_file)
                    for selected_file in self.selected_files
                ]

                if any((path == symlink_target for path in paths)) \
                   and symlink_file not in files:
                    files.append(symlink_file)
                    paths.append(symlink_path)

                for selected_file, file_path in zip(files, paths):
                    try:
                        os.remove(file_path)
                        self.file_list.pop(selected_file)
                        self.pvs = dict()
                        items = self.file_selector.findItems(
                            selected_file, Qt.MatchCaseSensitive,
                            FileSelectorColumns.filename)
                        self.file_selector.takeTopLevelItem(
                            self.file_selector.indexOfTopLevelItem(items[0]))

                    except OSError as e:
                        warn = "Problem deleting file:\n" + str(e)
                        QMessageBox.warning(self, "Warning", warn,
                                            QMessageBox.Ok,
                                            QMessageBox.NoButton)
                self.files_updated.emit(self.file_list)
                background_workers.resume()

    def update_file_metadata(self):
        if self.selected_files:
            if len(self.selected_files) == 1:
                settings_window = SnapshotEditMetadataDialog(
                    self.file_list.get(self.selected_files[0])["meta_data"],
                    self.common_settings, self)
                settings_window.resize(800, 200)
                # if OK was pressed, update actual file and reflect changes in the list
                if settings_window.exec_():
                    background_workers.suspend()
                    file_data = self.file_list.get(self.selected_files[0])
                    try:
                        self.snapshot.replace_metadata(file_data['file_path'],
                                                       file_data['meta_data'])
                    except OSError as e:
                        warn = "Problem modifying file:\n" + str(e)
                        QMessageBox.warning(self, "Warning", warn,
                                            QMessageBox.Ok,
                                            QMessageBox.NoButton)

                    self.rebuild_file_list()
                    background_workers.resume()
            else:
                QMessageBox.information(self, "Information",
                                        "Please select one file only",
                                        QMessageBox.Ok, QMessageBox.NoButton)

    def clear_file_selector(self):
        self.file_selector.clear(
        )  # Clears and "deselects" itmes on file selector
        self.select_files()  # Process new,empty list of selected files
        self.pvs = dict()
        self.file_list = dict()
Beispiel #4
0
class View(QMainWindow):

    def __init__(self):
        super().__init__()
        self.treeView = None
        self.graphBuilder = None
        self.commentStatus = False
        self.init_gui()

    def init_gui(self):
        import_tptp_file_action = QAction('&Import TPTP syntax file', self)
        import_tptp_file_action.setShortcut('Ctrl+I')
        import_tptp_file_action.triggered.connect(self.load_tptp_syntax_file)

        import_tptp_file_from_web_action = QAction('&Import TPTP syntax file from web', self)
        import_tptp_file_from_web_action.setShortcut('Ctrl+W')
        import_tptp_file_from_web_action.triggered.connect(self.get_tptp_syntax_from_web)

        save_with_control_file_action = QAction('&Reduce and save TPTP syntax with control file', self)
        save_with_control_file_action.setShortcut('Ctrl+S')
        save_with_control_file_action.triggered.connect(self.output_tptp_syntax_from_control_file_without_comments)

        save_tptp_grammar_file_from_selection_action = QAction('&Reduce and save TPTP syntax with selection', self)
        save_tptp_grammar_file_from_selection_action.setShortcut('Ctrl+D')
        save_tptp_grammar_file_from_selection_action.triggered.connect(self.create_tptp_syntax_file_from_selection_without_comments)

        produce_reduced_tptp_grammar_action = QAction('&Reduce TPTP syntax with selection', self)
        produce_reduced_tptp_grammar_action.setShortcut('Ctrl+R')
        produce_reduced_tptp_grammar_action.triggered.connect(self.reduce_tptp_syntax_with_selection)

        save_control_file_action = QAction('&Produce and save control file from selection', self)
        save_control_file_action.setShortcut('Ctrl+F')
        save_control_file_action.triggered.connect(self.output_control_file)

        toggle_comments_action = QAction('&Toggle comments', self)
        toggle_comments_action.setShortcut('Ctrl+C')
        toggle_comments_action.triggered.connect(self.toggle_comments)

        import_control_file_action = QAction('&Import control file', self)
        import_control_file_action.setShortcut('Ctrl+O')
        import_control_file_action.triggered.connect(self.load_control_file)

        save_with_control_file_comments_action = QAction('&Reduce and save TPTP syntax with control file with external comment syntax', self)
        save_with_control_file_comments_action.triggered.connect(self.create_tptp_syntax_from_control_file_with_comments)

        save_tptp_grammar_file_from_selection_comments_action = QAction('&Reduce and save TPTP syntax from selection with external comment syntax', self)
        save_tptp_grammar_file_from_selection_comments_action.triggered.connect(self.create_tptp_syntax_file_from_selection_with_comments)

        menubar = QMenuBar()
        self.setMenuBar(menubar)
        menubar.setNativeMenuBar(False)

        import_menu = menubar.addMenu("Import syntax")
        import_menu.addAction(import_tptp_file_action)
        import_menu.addAction(import_tptp_file_from_web_action)

        save_menu = menubar.addMenu("Save syntax")
        save_menu.addAction(save_tptp_grammar_file_from_selection_action)
        save_menu.addAction(save_with_control_file_action)
        save_menu.addSeparator()
        save_menu.addAction(save_with_control_file_comments_action)
        save_menu.addAction(save_tptp_grammar_file_from_selection_comments_action)

        reduce_menu = menubar.addMenu("Reduce")
        reduce_menu.addAction(produce_reduced_tptp_grammar_action)

        control_file_menu = menubar.addMenu("Control file")
        control_file_menu.addAction(import_control_file_action)
        control_file_menu.addAction(save_control_file_action)

        view_menu = menubar.addMenu("View")
        view_menu.addAction(toggle_comments_action)

        self.setWindowTitle('TPTP sub-syntax extractor')
        self.showMaximized()

    def init_tree_view(self) -> None:
        self.treeView = QTreeWidget()
        self.treeView.setHeaderLabels(['Non Terminal', 'Production Type', 'Production'])
        nodes_list = list(self.graphBuilder.nodes_dictionary.values())
        nodes_list.sort(key=lambda x: x.position)
        for node in nodes_list:
            if node.position >= 0:
                rule_type = ""
                if node.rule_type == GraphBuilder.RuleType.GRAMMAR:
                    rule_type = "GRAMMAR"
                elif node.rule_type == GraphBuilder.RuleType.STRICT:
                    rule_type = "STRICT"
                elif node.rule_type == GraphBuilder.RuleType.MACRO:
                    rule_type = "MACRO"
                elif node.rule_type == GraphBuilder.RuleType.TOKEN:
                    rule_type = "TOKEN"
                item = QTreeWidgetItem([node.value, rule_type, ''])
                item.setCheckState(0, QtCore.Qt.Unchecked)

                light_gray = QColor(237, 244, 248)
                item.setBackground(0, light_gray)
                item.setBackground(1, light_gray)
                item.setBackground(2, light_gray)
                for production in node.productions_list.list:
                    child_item = QTreeWidgetItem(['', '', str(production)])
                    child_item.setCheckState(0, QtCore.Qt.Checked)
                    item.addChild(child_item)
                if node.comment_block is not None:
                    comment = "\n".join(node.comment_block.comment_lines)
                    comment_item = QTreeWidgetItem([comment])
                    self.treeView.addTopLevelItem(comment_item)
                    comment_item.setHidden(False)
                    comment_item.setFlags(item.flags() ^ Qt.ItemIsUserCheckable)
                self.treeView.addTopLevelItem(item)

        layout = QVBoxLayout()
        layout.addWidget(self.treeView)
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        self.treeView.resizeColumnToContents(0)

    def toggle_comments(self):
        """Shows/hides comments in gui.

        """
        new_status = not self.commentStatus
        if self.treeView is not None:
            for item in self.treeView.findItems("", Qt.MatchContains | Qt.MatchRecursive):
                flags = item.flags()
                if not (Qt.ItemIsUserCheckable & flags):
                    item.setHidden(new_status)
            self.commentStatus = new_status

    def check_start_symbol(self, start_symbol: str):
        for item in self.treeView.findItems(start_symbol, Qt.MatchFixedString | Qt.MatchRecursive):
            item.setCheckState(0, QtCore.Qt.Checked)

    def load_control_file(self):
        """Loads control file and checks tree view items accordingly.

        """
        if self.treeView is not None:
            control_filename, _ = QFileDialog.getOpenFileName(None, "Open Control File", "", "Control File (*.txt);;")

            if control_filename:
                # uncheck all left symbols, check all right symbols
                for item in self.treeView.findItems("", Qt.MatchContains | Qt.MatchRecursive):
                    if item.parent() is None:
                        flags = item.flags()
                        if Qt.ItemIsUserCheckable & flags:
                            # item is not a comment
                            item.setCheckState(0, QtCore.Qt.Unchecked)
                    else:
                        item.setCheckState(0, QtCore.Qt.Checked)

                disable_rules_string = Input.read_text_from_file(control_filename)

                lines = disable_rules_string.splitlines()
                start_symbol = lines[0]
                for item in self.treeView.findItems(start_symbol, Qt.MatchFixedString | Qt.MatchRecursive):
                    item.setCheckState(0, QtCore.Qt.Checked)
                del lines[0]

                for i in lines:
                    data = i.split(",")
                    nt_name = data[0]
                    rule_symbol = data[1]
                    del data[0:2]
                    data = list(map(int, data))
                    rule_type_name = ""
                    if rule_symbol == "::=":
                        rule_type_name = "GRAMMAR"
                    elif rule_symbol == "::-":
                        rule_type_name = "TOKEN"
                    elif rule_symbol == ":==":
                        rule_type_name = "STRICT"
                    elif rule_symbol == ":::":
                        rule_type_name = "MACRO"

                    # find nt
                    for item in self.treeView.findItems(nt_name, Qt.MatchFixedString | Qt.MatchRecursive):
                        if item.parent() is None and item.text(1) == rule_type_name:
                            for index in data:
                                child = item.child(index)
                                # if child exists
                                if child is not None:
                                    child.setCheckState(0, QtCore.Qt.Unchecked)

    def output_control_file(self):
        try:
            control_string, _ = self.produce_control_file()
            filename, _ = QFileDialog.getSaveFileName(None, "QFileDialog.getOpenFileName()", "",
                                                      "Control File (*.txt);;")
            Output.save_text_to_file(control_string, filename)
        except NoStartSymbolError:
            QMessageBox.about(self, "Error", "A start symbol has to be selected")
        except MultipleStartSymbolsError:
            QMessageBox.about(self, "Error", "Multiple start symbols are not allowed")
        except NoImportedGrammarError:
            QMessageBox.about(self, "Error", "No grammar imported")

    def produce_control_file(self):
        """Produces control file string from gui selection.

        :return: control file string and
        :rtype: (str,str)
        :raises:
            NoImportedGrammarError: No grammar has been imported.
            MultipleStartSymbolsError: Multiple start symbols have been selected.
            NoStartSymbolError: No start symbol has been selected.
        """
        Entry = namedtuple("Entry", ["value", "rule_type"])
        entry_dictionary = {}
        start_symbol_selection = []
        if self.treeView is None:
            raise NoImportedGrammarError

        for item in self.treeView.findItems("", Qt.MatchContains | Qt.MatchRecursive):
            parent = item.parent()

            if (item.checkState(0) == 0) and (parent is not None):
                rule_type = parent.text(1)
                entry = None
                if rule_type == "GRAMMAR":
                    entry = Entry(parent.text(0), GraphBuilder.RuleType.GRAMMAR)
                elif rule_type == "STRICT":
                    entry = Entry(parent.text(0), GraphBuilder.RuleType.STRICT)
                elif rule_type == "MACRO":
                    entry = Entry(parent.text(0), GraphBuilder.RuleType.MACRO)
                elif rule_type == "TOKEN":
                    entry = Entry(parent.text(0), GraphBuilder.RuleType.TOKEN)
                indexOfChild = parent.indexOfChild(item)
                if entry not in entry_dictionary:
                    entry_dictionary[entry] = [indexOfChild]
                else:
                    entry_dictionary[entry].append(indexOfChild)
            elif (item.checkState(0) > 0) and (parent is None):
                start_symbol_selection.append(item.text(0))

        # if multiple start symbols are selected
        multiple_start_symbols = not all(elem == start_symbol_selection[0] for elem in start_symbol_selection)
        if multiple_start_symbols:
            raise MultipleStartSymbolsError("Multiple start symbols have been selected")

        if len(start_symbol_selection) == 0:
            raise NoStartSymbolError("Multiple start symbols have been selected")

        control_string = start_symbol_selection[0] + "\n"
        for key, value in entry_dictionary.items():
            rule_string = ""
            if key.rule_type == GraphBuilder.RuleType.GRAMMAR:
                rule_string = "::="
            elif key.rule_type == GraphBuilder.RuleType.STRICT:
                rule_string = ":=="
            elif key.rule_type == GraphBuilder.RuleType.MACRO:
                rule_string = ":::"
            elif key.rule_type == GraphBuilder.RuleType.TOKEN:
                rule_string = "::-"
            control_string += key.value + "," + rule_string + ","
            control_string += ','.join(map(str, value))  # add indexes separated by comma
            control_string += "\n"
        return control_string, start_symbol_selection[0]

    def reduce_tptp_syntax_with_selection(self):
        try:
            control_string, start_symbol = self.produce_control_file()
            self.graphBuilder.disable_rules(control_string)
            self.init_tree_view()
            self.check_start_symbol(start_symbol)
        except NoStartSymbolError:
            QMessageBox.about(self, "Error", "A start symbol has to be selected")
        except MultipleStartSymbolsError:
            QMessageBox.about(self, "Error", "Multiple start symbols are not allowed")
        except NoImportedGrammarError:
            QMessageBox.about(self, "Error", "No grammar imported")

    def create_tptp_syntax_file_from_selection_with_comments(self):
        self.create_tptp_syntax_file_from_selection(with_comments=True)

    def create_tptp_syntax_file_from_selection_without_comments(self):
        self.create_tptp_syntax_file_from_selection(with_comments=False)

    def create_tptp_syntax_file_from_selection(self, with_comments: bool):
        filename, _ = QFileDialog.getSaveFileName(None, "Save TPTP Grammar File", "", "TPTP Grammar File(*.txt);;")
        if filename:
            try:
                control_string, start_symbol = self.produce_control_file()
                graphBuilder = GraphBuilder.TPTPGraphBuilder()
                graphBuilder.nodes_dictionary = copy.deepcopy(self.graphBuilder.nodes_dictionary)
                graphBuilder.init_tree(start_symbol)
                graphBuilder.disable_rules(control_string)
                start_node = graphBuilder.nodes_dictionary.get(
                    GraphBuilder.Node_Key("<start_symbol>", GraphBuilder.RuleType.GRAMMAR))
                if start_node is not None:
                    if with_comments:
                        Output.save_ordered_rules_from_graph_with_comments(filename, start_node)
                    else:
                        Output.save_ordered_rules_from_graph(filename, start_node)
                else:
                    Output.save_text_to_file("", filename)
            except NoStartSymbolError:
                QMessageBox.about(self, "Error", "A start symbol has to be selected")
            except MultipleStartSymbolsError:
                QMessageBox.about(self, "Error", "Multiple start symbols are not allowed")
            except NoImportedGrammarError:
                QMessageBox.about(self, "Error", "No grammar imported")

    def create_tptp_syntax_from_control_file_with_comments(self):
        self.create_tptp_syntax_from_control_file(with_comments=True)

    def output_tptp_syntax_from_control_file_without_comments(self):
        self.create_tptp_syntax_from_control_file(with_comments=False)

    def create_tptp_syntax_from_control_file(self, with_comments: bool):
        control_filename, _ = QFileDialog.getOpenFileName(None, "Open Control File", "", "Control File (*.txt);;")
        if control_filename:
            save_filename, _ = QFileDialog.getSaveFileName(None, "Save TPTP Grammar File", "",
                                                           "TPTP Grammar File (*.txt);;")
            if save_filename:
                control_string = Input.read_text_from_file(control_filename)
                if self.treeView is not None:
                    graphBuilder = GraphBuilder.TPTPGraphBuilder()
                    graphBuilder.nodes_dictionary = copy.deepcopy(self.graphBuilder.nodes_dictionary)
                    graphBuilder.init_tree(control_string.splitlines()[0])
                    graphBuilder.disable_rules(control_string)
                    start_node = graphBuilder.nodes_dictionary.get(
                        GraphBuilder.Node_Key("<start_symbol>", GraphBuilder.RuleType.GRAMMAR))
                    if start_node is not None:
                        if with_comments:
                            Output.save_ordered_rules_from_graph_with_comments(save_filename, start_node)
                        else:
                            Output.save_ordered_rules_from_graph(save_filename, start_node)
                    else:
                        Output.save_text_to_file("", save_filename)
                else:
                    QMessageBox.about(self, "Error", "No grammar imported")

    def load_tptp_syntax_file(self):
        filename, _ = QFileDialog.getOpenFileName(self, "Open TPTP Grammar File", "", "TPTP Grammar File (*.txt);;")
        # if filename is not empty
        if filename:
            start_symbol, ok_pressed = QInputDialog.getText(self, "Input the desired start symbol", "Start Symbol:",
                                                            QLineEdit.Normal, "<TPTP_file>")
            if ok_pressed:
                if start_symbol == '':
                    QMessageBox.about(self, "Error", "A start symbol has to be specified")
                else:
                    # todo check if start symbol exists
                    self.create_tptp_view(start_symbol, Input.read_text_from_file(filename))

    def get_tptp_syntax_from_web(self):
        file = Input.import_tptp_syntax_from_web()
        start_symbol, okPressed = QInputDialog.getText(self, "Input the desired start symbol", "Start Symbol:",
                                                       QLineEdit.Normal, "<TPTP_file>")
        if okPressed and start_symbol != '':
            self.create_tptp_view(start_symbol, file)

    def create_tptp_view(self, start_symbol:str, file:str):
        self.graphBuilder = GraphBuilder.TPTPGraphBuilder()
        self.graphBuilder.run(start_symbol=start_symbol, file=file)
        self.init_tree_view()
        self.check_start_symbol(start_symbol)
class Window(QMainWindow):

	def __init__(self):
		super().__init__()
		self.devices = []
		self.init_search_for_cameras()

		# Menu Bar
		self.menu_bar = self.menuBar()
		self.status_bar = self.statusBar()

		# Root Menus
		file_menu = self.menu_bar.addMenu("File")
		view_menu = self.menu_bar.addMenu("View")
		view_language_menu = view_menu.addMenu ("Language")
		tools_menu = self.menu_bar.addMenu("Tools")
		help_menu = self.menu_bar.addMenu("Help")

		# Create Menu Actions
		self.quit_action = QAction("Exit", self)
		self.quit_action.setShortcut("Ctrl+q")

		self.refresh_action = QAction("Refresh", self)
		self.refresh_action.setShortcut("Ctrl+r")

		self.help_action = QAction("About AXIS IP Utility", self)
		self.help_action.setShortcut("Ctrl+h")

		self.assign_network_parameters_action = QAction("Assign Network Parameters", self)
		self.assign_network_parameters_action.setShortcut("Ctrl+c")

		self.assign_ip_address_action = QAction("Assign IP Address", self)
		self.assign_ip_address_action.setShortcut("Ctrl+a")

		self.assign_serial_no_ip_address_action = QAction("Assign IP Address Using Serial Number", self)
		self.assign_serial_no_ip_address_action.setShortcut("Ctrl+n")

		self.test_ip_address_action = QAction("Test IP Address", self)
		self.test_ip_address_action.setShortcut("Ctrl+t")

		self.open_in_web_browser = QAction("Open in Web Browser", self)
		self.open_in_web_browser.setShortcut("Ctrl+o")

		self.language_english_action = QAction("English", self)

		# Add Menu Actions
		file_menu.addAction(self.quit_action)

		tools_menu.addAction(self.assign_network_parameters_action)
		tools_menu.addAction(self.assign_ip_address_action)
		tools_menu.addAction(self.assign_serial_no_ip_address_action)
		tools_menu.addSeparator()
		tools_menu.addAction(self.test_ip_address_action)
		tools_menu.addSeparator()
		tools_menu.addAction(self.refresh_action)

		help_menu.addAction(self.help_action)

		view_language_menu.addAction(self.language_english_action)

		# List
		self.devices = []
		self.devices_tree = QTreeWidget()
		self.devices_tree.setSortingEnabled(True)

		self.devices_tree.headerItem().setText(0, "Name")
		self.devices_tree.headerItem().setText(1, "IP Address")
		self.devices_tree.headerItem().setText(2, "Serial Number")

		self.devices_tree.setColumnWidth(0, 260)
		self.devices_tree.setColumnWidth(1, 130)

		self.devices_tree.header().setSectionResizeMode(0, QHeaderView.Interactive)
		self.devices_tree.header().setSectionResizeMode(1, QHeaderView.Interactive)
		self.devices_tree.header().setSectionResizeMode(2, QHeaderView.Interactive)

		self.devices_tree.customContextMenuRequested.connect(self.open_menu)
		self.devices_tree.setContextMenuPolicy(Qt.CustomContextMenu)

		# Layout
		self.text_filter = QLineEdit()
		self.text_filter.setPlaceholderText("Type to filter...")

		self.layout = QVBoxLayout()
		self.layout.addWidget(self.text_filter)
		self.layout.addWidget(self.devices_tree)
		self.main_widget = QWidget()
		self.main_widget.setLayout(self.layout)
		self.setCentralWidget(self.main_widget)

		# Events
		self.quit_action.triggered.connect(self.quit_trigger)
		self.refresh_action.triggered.connect(self.refresh_trigger)

		self.text_filter.textChanged.connect(self.search_devices)

		self.open_in_web_browser.triggered.connect(self.open_in_web_browser_trigger)
		self.devices_tree.itemDoubleClicked.connect(self.open_in_web_browser_trigger)

		# Show Window
		self.setWindowTitle("AXIS IP Utility in Python - Erik Wilhelm Gren 2019")

		self.label = QLabel()
		self.label.setText("Interface: {}".format(socket.gethostbyname(socket.gethostname())))
		self.status_bar.addPermanentWidget(self.label)
		self.update_status_bar()
		self.resize(640, 300)
		self.show()

	def update_status_bar(self):
		self.set_status_bar(("{} device{}".format(len(self.devices), ("" if len(self.devices) == 1 else "s"))))

	def set_status_bar(self, text):
		self.status_bar.showMessage(text)

	def search_devices(self):
		query = self.text_filter.text()
		# print(query)
		if query == "":
			all_items = self.devices_tree.findItems("", Qt.MatchFlag(1), 0)
			for item in all_items:
				item.setHidden(False)
			self.update_status_bar()
		else:
			all_items = self.devices_tree.findItems("", Qt.MatchFlag(1), 0)
			for item in all_items:
				item.setHidden(True)

			items = self.devices_tree.findItems(query, Qt.MatchFlag(1), 0) + self.devices_tree.findItems(query, Qt.MatchFlag(1), 1) + self.devices_tree.findItems(query, Qt.MatchFlag(1), 2)
			for item in items:
				item.setHidden(False)
				print(item.text(0))
			self.set_status_bar("{} device{}".format(len(items), ("" if len(items) == 1 else "s")))

	def add_camera(self, camera):
		self.devices.append(camera)

		devices_tree_item = QTreeWidgetItem(self.devices_tree)
		devices_tree_item.setText(0, camera.name)
		devices_tree_item.setText(1, camera.ip_address)
		devices_tree_item.setText(2, camera.serial_number)

		self.update_status_bar()

	def refresh_trigger(self):
		self.reset()
		self.reset_search_for_cameras() # SOMETHING'S BROKEN ON WINDOWS 10!

	def reset(self):
		if(self.zeroconf):
			self.zeroconf.close()

		if(self.devices_tree):
			self.devices_tree.clearSelection()
			self.devices_tree.clear()

		if(self.devices):
			self.devices = []

		self.update_status_bar()

	def reset_search_for_cameras(self):
		self.zeroconf = Zeroconf()
		self.listener = CameraListener(self)
		self.browser = ServiceBrowser(self.zeroconf, "_http._tcp.local.", self.listener)

	def init_search_for_cameras(self):
		self.zeroconf = Zeroconf()
		self.listener = CameraListener(self)
		self.browser = ServiceBrowser(self.zeroconf, "_http._tcp.local.", self.listener)

	def open_menu(self, position):
		menu = QMenu()

		menu.addAction(self.open_in_web_browser)
		menu.exec_(self.devices_tree.viewport().mapToGlobal(position))

	def open_in_web_browser_trigger(self):
		item = self.devices_tree.selectedItems()
		if item:
			ip_address = item[0].text(1)
			url = "http://{}/".format(ip_address)
			webbrowser.open(url)

	def quit_trigger(self):
		qApp.quit()
Beispiel #6
0
class MiscTab(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.name = self.tr("Misc")

        self.markColorLabel = QLabel(self.tr("Default flag colors:"), self)
        # TODO: enforce duplicate names avoidance
        self.markColorWidget = QTreeWidget(self)
        self.markColorWidget.setHeaderLabels(
            (self.tr("Color"), self.tr("Name")))
        self.markColorWidget.setRootIsDecorated(False)
        self.markColorWidget.setSelectionBehavior(
            QAbstractItemView.SelectRows)
        # TODO: make this work correctly, top-level items only
        # self.markColorWidget.setDragDropMode(QAbstractItemView.InternalMove)
        self.addItemButton = QPushButton("+", self)
        self.addItemButton.clicked.connect(self.addItem)
        self.removeItemButton = QPushButton("−", self)
        self.removeItemButton.clicked.connect(self.removeItem)

        self.loadRecentFileBox = QCheckBox(
            self.tr("Load most recent file on start"), self)

        layout = QGridLayout(self)
        l = 0
        layout.addWidget(self.markColorLabel, l, 0, 1, 3)
        l += 1
        layout.addWidget(self.markColorWidget, l, 0, 1, 3)
        l += 1
        layout.addWidget(self.addItemButton, l, 0)
        layout.addWidget(self.removeItemButton, l, 1)
        l += 1
        layout.addWidget(self.loadRecentFileBox, l, 0, 1, 3)
        self.setLayout(layout)

        self.readSettings()

    def addItem(self):
        def mangleNewName():
            name = self.tr("New")
            index = 0
            while self.markColorWidget.findItems(name, Qt.MatchExactly, 1):
                index += 1
                name = "{0} ({1})".format(name, index)
            return name

        # TODO: not DRY with ctor
        item = QTreeWidgetItem(self.markColorWidget)
        item.setFlags(item.flags() | Qt.ItemIsEditable)
        widget = ColorVignette(self)
        widget.setColor(QColor(Qt.white))
        widget.setMargins(2, 2, 2, 2)
        widget.setMayClearColor(False)
        self.markColorWidget.setItemWidget(item, 0, widget)
        item.setText(1, mangleNewName())

        self.markColorWidget.setCurrentItem(item)
        self.markColorWidget.editItem(item, 1)
        self.removeItemButton.setEnabled(True)

    def removeItem(self):
        i = self.markColorWidget.selectionModel().currentIndex().row()
        self.markColorWidget.takeTopLevelItem(i)
        if not self.markColorWidget.topLevelItemCount():
            self.removeItemButton.setEnabled(False)

    def readSettings(self):
        entries = settings.readMarkColors()
        for name, color in entries.items():
            item = QTreeWidgetItem(self.markColorWidget)
            item.setFlags(item.flags() | Qt.ItemIsEditable)
            widget = ColorVignette(self)
            widget.setColor(color)
            widget.setMargins(2, 2, 2, 2)
            widget.setMayClearColor(False)
            self.markColorWidget.setItemWidget(item, 0, widget)
            item.setText(1, name)
        if not len(entries):
            self.removeItemButton.setEnabled(False)

        loadRecentFile = settings.loadRecentFile()
        self.loadRecentFileBox.setChecked(loadRecentFile)

    def writeSettings(self):
        markColors = OrderedDict()
        for i in range(self.markColorWidget.topLevelItemCount()):
            item = self.markColorWidget.topLevelItem(i)
            name = item.text(1)
            color = self.markColorWidget.itemWidget(item, 0).color()
            markColors[name] = color
        settings.writeMarkColors(markColors)

        loadRecentFile = self.loadRecentFileBox.isChecked()
        settings.setLoadRecentFile(loadRecentFile)
class ConfigurationWidget(QWidget):
    """
    Class implementing a dialog for the configuration of eric6.
    
    @signal preferencesChanged() emitted after settings have been changed
    @signal masterPasswordChanged(str, str) emitted after the master
        password has been changed with the old and the new password
    @signal accepted() emitted to indicate acceptance of the changes
    @signal rejected() emitted to indicate rejection of the changes
    """
    preferencesChanged = pyqtSignal()
    masterPasswordChanged = pyqtSignal(str, str)
    accepted = pyqtSignal()
    rejected = pyqtSignal()

    DefaultMode = 0
    HelpBrowserMode = 1
    TrayStarterMode = 2

    def __init__(self, parent=None, fromEric=True, displayMode=DefaultMode):
        """
        Constructor
        
        @param parent The parent widget of this dialog. (QWidget)
        @keyparam fromEric flag indicating a dialog generation from within the
            eric6 ide (boolean)
        @keyparam displayMode mode of the configuration dialog
            (DefaultMode, HelpBrowserMode, TrayStarterMode)
        @exception RuntimeError raised to indicate an invalid dialog mode
        """
        assert displayMode in (ConfigurationWidget.DefaultMode,
                               ConfigurationWidget.HelpBrowserMode,
                               ConfigurationWidget.TrayStarterMode)

        super(ConfigurationWidget, self).__init__(parent)
        self.fromEric = fromEric
        self.displayMode = displayMode

        self.__setupUi()

        self.itmDict = {}

        if not fromEric:
            from PluginManager.PluginManager import PluginManager
            try:
                self.pluginManager = e5App().getObject("PluginManager")
            except KeyError:
                self.pluginManager = PluginManager(self)
                e5App().registerObject("PluginManager", self.pluginManager)

        if displayMode == ConfigurationWidget.DefaultMode:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function create to
                # create the configuration page. This must have the method
                # save to save the settings.
                "applicationPage": [
                    self.tr("Application"), "preferences-application.png",
                    "ApplicationPage", None, None
                ],
                "cooperationPage": [
                    self.tr("Cooperation"), "preferences-cooperation.png",
                    "CooperationPage", None, None
                ],
                "corbaPage": [
                    self.tr("CORBA"), "preferences-orbit.png", "CorbaPage",
                    None, None
                ],
                "emailPage": [
                    self.tr("Email"), "preferences-mail_generic.png",
                    "EmailPage", None, None
                ],
                "graphicsPage": [
                    self.tr("Graphics"), "preferences-graphics.png",
                    "GraphicsPage", None, None
                ],
                "iconsPage": [
                    self.tr("Icons"), "preferences-icons.png", "IconsPage",
                    None, None
                ],
                "ircPage": [self.tr("IRC"), "irc.png", "IrcPage", None, None],
                "networkPage": [
                    self.tr("Network"), "preferences-network.png",
                    "NetworkPage", None, None
                ],
                "notificationsPage": [
                    self.tr("Notifications"), "preferences-notifications.png",
                    "NotificationsPage", None, None
                ],
                "pluginManagerPage": [
                    self.tr("Plugin Manager"), "preferences-pluginmanager.png",
                    "PluginManagerPage", None, None
                ],
                "printerPage": [
                    self.tr("Printer"), "preferences-printer.png",
                    "PrinterPage", None, None
                ],
                "pythonPage": [
                    self.tr("Python"), "preferences-python.png", "PythonPage",
                    None, None
                ],
                "qtPage": [
                    self.tr("Qt"), "preferences-qtlogo.png", "QtPage", None,
                    None
                ],
                "securityPage": [
                    self.tr("Security"), "preferences-security.png",
                    "SecurityPage", None, None
                ],
                "shellPage": [
                    self.tr("Shell"), "preferences-shell.png", "ShellPage",
                    None, None
                ],
                "tasksPage":
                [self.tr("Tasks"), "task.png", "TasksPage", None, None],
                "templatesPage": [
                    self.tr("Templates"), "preferences-template.png",
                    "TemplatesPage", None, None
                ],
                "trayStarterPage": [
                    self.tr("Tray Starter"), "erict.png", "TrayStarterPage",
                    None, None
                ],
                "vcsPage": [
                    self.tr("Version Control Systems"), "preferences-vcs.png",
                    "VcsPage", None, None
                ],
                "0debuggerPage": [
                    self.tr("Debugger"), "preferences-debugger.png", None,
                    None, None
                ],
                "debuggerGeneralPage": [
                    self.tr("General"), "preferences-debugger.png",
                    "DebuggerGeneralPage", "0debuggerPage", None
                ],
                "debuggerPythonPage": [
                    self.tr("Python"), "preferences-pyDebugger.png",
                    "DebuggerPythonPage", "0debuggerPage", None
                ],
                "debuggerPython3Page": [
                    self.tr("Python3"), "preferences-pyDebugger.png",
                    "DebuggerPython3Page", "0debuggerPage", None
                ],
                "debuggerRubyPage": [
                    self.tr("Ruby"), "preferences-rbDebugger.png",
                    "DebuggerRubyPage", "0debuggerPage", None
                ],
                "0editorPage": [
                    self.tr("Editor"), "preferences-editor.png", None, None,
                    None
                ],
                "editorAPIsPage": [
                    self.tr("APIs"), "preferences-api.png", "EditorAPIsPage",
                    "0editorPage", None
                ],
                "editorAutocompletionPage": [
                    self.tr("Autocompletion"),
                    "preferences-autocompletion.png",
                    "EditorAutocompletionPage", "0editorPage", None
                ],
                "editorAutocompletionQScintillaPage": [
                    self.tr("QScintilla"), "qscintilla.png",
                    "EditorAutocompletionQScintillaPage",
                    "editorAutocompletionPage", None
                ],
                "editorCalltipsPage": [
                    self.tr("Calltips"), "preferences-calltips.png",
                    "EditorCalltipsPage", "0editorPage", None
                ],
                "editorCalltipsQScintillaPage": [
                    self.tr("QScintilla"), "qscintilla.png",
                    "EditorCalltipsQScintillaPage", "editorCalltipsPage", None
                ],
                "editorGeneralPage": [
                    self.tr("General"), "preferences-general.png",
                    "EditorGeneralPage", "0editorPage", None
                ],
                "editorFilePage": [
                    self.tr("Filehandling"), "preferences-filehandling.png",
                    "EditorFilePage", "0editorPage", None
                ],
                "editorSearchPage": [
                    self.tr("Searching"), "preferences-search.png",
                    "EditorSearchPage", "0editorPage", None
                ],
                "editorSpellCheckingPage": [
                    self.tr("Spell checking"), "preferences-spellchecking.png",
                    "EditorSpellCheckingPage", "0editorPage", None
                ],
                "editorStylesPage": [
                    self.tr("Style"), "preferences-styles.png",
                    "EditorStylesPage", "0editorPage", None
                ],
                "editorSyntaxPage": [
                    self.tr("Code Checkers"), "preferences-debugger.png",
                    "EditorSyntaxPage", "0editorPage", None
                ],
                "editorTypingPage": [
                    self.tr("Typing"), "preferences-typing.png",
                    "EditorTypingPage", "0editorPage", None
                ],
                "editorExportersPage": [
                    self.tr("Exporters"), "preferences-exporters.png",
                    "EditorExportersPage", "0editorPage", None
                ],
                "1editorLexerPage": [
                    self.tr("Highlighters"),
                    "preferences-highlighting-styles.png", None, "0editorPage",
                    None
                ],
                "editorHighlightersPage": [
                    self.tr("Filetype Associations"),
                    "preferences-highlighter-association.png",
                    "EditorHighlightersPage", "1editorLexerPage", None
                ],
                "editorHighlightingStylesPage": [
                    self.tr("Styles"), "preferences-highlighting-styles.png",
                    "EditorHighlightingStylesPage", "1editorLexerPage", None
                ],
                "editorKeywordsPage": [
                    self.tr("Keywords"), "preferences-keywords.png",
                    "EditorKeywordsPage", "1editorLexerPage", None
                ],
                "editorPropertiesPage": [
                    self.tr("Properties"), "preferences-properties.png",
                    "EditorPropertiesPage", "1editorLexerPage", None
                ],
                "0helpPage":
                [self.tr("Help"), "preferences-help.png", None, None, None],
                "helpAppearancePage": [
                    self.tr("Appearance"), "preferences-styles.png",
                    "HelpAppearancePage", "0helpPage", None
                ],
                "helpDocumentationPage": [
                    self.tr("Help Documentation"),
                    "preferences-helpdocumentation.png",
                    "HelpDocumentationPage", "0helpPage", None
                ],
                "helpViewersPage": [
                    self.tr("Help Viewers"), "preferences-helpviewers.png",
                    "HelpViewersPage", "0helpPage", None
                ],
                "helpWebBrowserPage": [
                    self.tr("eric6 Web Browser"), "ericWeb.png",
                    "HelpWebBrowserPage", "0helpPage", None
                ],
                "0projectPage": [
                    self.tr("Project"), "preferences-project.png", None, None,
                    None
                ],
                "projectBrowserPage": [
                    self.tr("Project Viewer"), "preferences-project.png",
                    "ProjectBrowserPage", "0projectPage", None
                ],
                "projectPage": [
                    self.tr("Project"), "preferences-project.png",
                    "ProjectPage", "0projectPage", None
                ],
                "multiProjectPage": [
                    self.tr("Multiproject"), "preferences-multiproject.png",
                    "MultiProjectPage", "0projectPage", None
                ],
                "0interfacePage": [
                    self.tr("Interface"), "preferences-interface.png", None,
                    None, None
                ],
                "interfacePage": [
                    self.tr("Interface"), "preferences-interface.png",
                    "InterfacePage", "0interfacePage", None
                ],
                "viewmanagerPage": [
                    self.tr("Viewmanager"), "preferences-viewmanager.png",
                    "ViewmanagerPage", "0interfacePage", None
                ],
            }

            self.configItems.update(
                e5App().getObject("PluginManager").getPluginConfigData())
        elif displayMode == ConfigurationWidget.HelpBrowserMode:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function create to
                # create the configuration page. This must have the method
                # save to save the settings.
                "interfacePage": [
                    self.tr("Interface"), "preferences-interface.png",
                    "HelpInterfacePage", None, None
                ],
                "networkPage": [
                    self.tr("Network"), "preferences-network.png",
                    "NetworkPage", None, None
                ],
                "printerPage": [
                    self.tr("Printer"), "preferences-printer.png",
                    "PrinterPage", None, None
                ],
                "securityPage": [
                    self.tr("Security"), "preferences-security.png",
                    "SecurityPage", None, None
                ],
                "0helpPage":
                [self.tr("Help"), "preferences-help.png", None, None, None],
                "helpAppearancePage": [
                    self.tr("Appearance"), "preferences-styles.png",
                    "HelpAppearancePage", "0helpPage", None
                ],
                "helpDocumentationPage": [
                    self.tr("Help Documentation"),
                    "preferences-helpdocumentation.png",
                    "HelpDocumentationPage", "0helpPage", None
                ],
                "helpWebBrowserPage": [
                    self.tr("eric6 Web Browser"), "ericWeb.png",
                    "HelpWebBrowserPage", "0helpPage", None
                ],
            }
        elif displayMode == ConfigurationWidget.TrayStarterMode:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function create to
                # create the configuration page. This must have the method
                # save to save the settings.
                "trayStarterPage": [
                    self.tr("Tray Starter"), "erict.png", "TrayStarterPage",
                    None, None
                ],
            }
        else:
            raise RuntimeError("Illegal mode value: {0}".format(displayMode))

        # generate the list entries
        for key in sorted(self.configItems.keys()):
            pageData = self.configItems[key]
            if pageData[3]:
                pitm = self.itmDict[pageData[3]]  # get the parent item
            else:
                pitm = self.configList
            self.itmDict[key] = ConfigurationPageItem(pitm, pageData[0], key,
                                                      pageData[1])
            self.itmDict[key].setData(0, Qt.UserRole, key)
            self.itmDict[key].setExpanded(True)
        self.configList.sortByColumn(0, Qt.AscendingOrder)

        # set the initial size of the splitter
        self.configSplitter.setSizes([200, 600])

        self.configList.itemActivated.connect(self.__showConfigurationPage)
        self.configList.itemClicked.connect(self.__showConfigurationPage)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.rejected)

        if displayMode != ConfigurationWidget.TrayStarterMode:
            self.__initLexers()

    def accept(self):
        """
        Public slot to accept the buttonBox accept signal.
        """
        if not isMacPlatform():
            wdg = self.focusWidget()
            if wdg == self.configList:
                return

        self.accepted.emit()

    def __setupUi(self):
        """
        Private method to perform the general setup of the configuration
        widget.
        """
        self.setObjectName("ConfigurationDialog")
        self.resize(900, 650)
        self.verticalLayout_2 = QVBoxLayout(self)
        self.verticalLayout_2.setSpacing(6)
        self.verticalLayout_2.setContentsMargins(6, 6, 6, 6)
        self.verticalLayout_2.setObjectName("verticalLayout_2")

        self.configSplitter = QSplitter(self)
        self.configSplitter.setOrientation(Qt.Horizontal)
        self.configSplitter.setObjectName("configSplitter")

        self.configListWidget = QWidget(self.configSplitter)
        self.leftVBoxLayout = QVBoxLayout(self.configListWidget)
        self.leftVBoxLayout.setContentsMargins(0, 0, 0, 0)
        self.leftVBoxLayout.setSpacing(0)
        self.leftVBoxLayout.setObjectName("leftVBoxLayout")
        self.configListFilter = E5ClearableLineEdit(
            self, self.tr("Enter filter text..."))
        self.configListFilter.setObjectName("configListFilter")
        self.leftVBoxLayout.addWidget(self.configListFilter)
        self.configList = QTreeWidget()
        self.configList.setObjectName("configList")
        self.leftVBoxLayout.addWidget(self.configList)
        self.configListFilter.textChanged.connect(self.__filterTextChanged)

        self.scrollArea = QScrollArea(self.configSplitter)
        self.scrollArea.setFrameShape(QFrame.NoFrame)
        self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scrollArea.setWidgetResizable(False)
        self.scrollArea.setObjectName("scrollArea")

        self.configStack = QStackedWidget()
        self.configStack.setFrameShape(QFrame.Box)
        self.configStack.setFrameShadow(QFrame.Sunken)
        self.configStack.setObjectName("configStack")
        self.scrollArea.setWidget(self.configStack)

        self.emptyPage = QWidget()
        self.emptyPage.setGeometry(QRect(0, 0, 372, 591))
        self.emptyPage.setObjectName("emptyPage")
        self.vboxlayout = QVBoxLayout(self.emptyPage)
        self.vboxlayout.setSpacing(6)
        self.vboxlayout.setContentsMargins(6, 6, 6, 6)
        self.vboxlayout.setObjectName("vboxlayout")
        spacerItem = QSpacerItem(20, 20, QSizePolicy.Minimum,
                                 QSizePolicy.Expanding)
        self.vboxlayout.addItem(spacerItem)
        self.emptyPagePixmap = QLabel(self.emptyPage)
        self.emptyPagePixmap.setAlignment(Qt.AlignCenter)
        self.emptyPagePixmap.setObjectName("emptyPagePixmap")
        self.emptyPagePixmap.setPixmap(
            QPixmap(os.path.join(getConfig('ericPixDir'), 'eric.png')))
        self.vboxlayout.addWidget(self.emptyPagePixmap)
        self.textLabel1 = QLabel(self.emptyPage)
        self.textLabel1.setAlignment(Qt.AlignCenter)
        self.textLabel1.setObjectName("textLabel1")
        self.vboxlayout.addWidget(self.textLabel1)
        spacerItem1 = QSpacerItem(20, 40, QSizePolicy.Minimum,
                                  QSizePolicy.Expanding)
        self.vboxlayout.addItem(spacerItem1)
        self.configStack.addWidget(self.emptyPage)

        self.verticalLayout_2.addWidget(self.configSplitter)

        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Apply
                                          | QDialogButtonBox.Cancel
                                          | QDialogButtonBox.Ok
                                          | QDialogButtonBox.Reset)
        self.buttonBox.setObjectName("buttonBox")
        if not self.fromEric and \
                self.displayMode == ConfigurationWidget.DefaultMode:
            self.buttonBox.button(QDialogButtonBox.Apply).hide()
        self.buttonBox.button(QDialogButtonBox.Apply).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Reset).setEnabled(False)
        self.verticalLayout_2.addWidget(self.buttonBox)

        self.setWindowTitle(self.tr("Preferences"))

        self.configList.header().hide()
        self.configList.header().setSortIndicator(0, Qt.AscendingOrder)
        self.configList.setSortingEnabled(True)
        self.textLabel1.setText(
            self.tr("Please select an entry of the list \n"
                    "to display the configuration page."))

        QMetaObject.connectSlotsByName(self)
        self.setTabOrder(self.configList, self.configStack)

        self.configStack.setCurrentWidget(self.emptyPage)

        self.configList.setFocus()

    def __filterTextChanged(self, filter):
        """
        Private slot to handle a change of the filter.
        
        @param filter text of the filter line edit (string)
        """
        self.__filterChildItems(self.configList.invisibleRootItem(), filter)

    def __filterChildItems(self, parent, filter):
        """
        Private method to filter child items based on a filter string.
        
        @param parent reference to the parent item (QTreeWidgetItem)
        @param filter filter string (string)
        @return flag indicating a visible child item (boolean)
        """
        childVisible = False
        filter = filter.lower()
        for index in range(parent.childCount()):
            itm = parent.child(index)
            if itm.childCount() > 0:
                visible = self.__filterChildItems(itm, filter) or \
                    filter == "" or filter in itm.text(0).lower()
            else:
                visible = filter == "" or filter in itm.text(0).lower()
            if visible:
                childVisible = True
            itm.setHidden(not visible)

        return childVisible

    def __initLexers(self):
        """
        Private method to initialize the dictionary of preferences lexers.
        """
        import QScintilla.Lexers
        from .PreferencesLexer import PreferencesLexer, \
            PreferencesLexerLanguageError

        self.lexers = {}
        for language in QScintilla.Lexers.getSupportedLanguages():
            if language not in self.lexers:
                try:
                    self.lexers[language] = PreferencesLexer(language, self)
                except PreferencesLexerLanguageError:
                    pass

    def __importConfigurationPage(self, name):
        """
        Private method to import a configuration page module.
        
        @param name name of the configuration page module (string)
        @return reference to the configuration page module
        """
        modName = "Preferences.ConfigurationPages.{0}".format(name)
        try:
            mod = __import__(modName)
            components = modName.split('.')
            for comp in components[1:]:
                mod = getattr(mod, comp)
            return mod
        except ImportError:
            E5MessageBox.critical(
                self, self.tr("Configuration Page Error"),
                self.tr("""<p>The configuration page <b>{0}</b>"""
                        """ could not be loaded.</p>""").format(name))
            return None

    def __showConfigurationPage(self, itm, column):
        """
        Private slot to show a selected configuration page.
        
        @param itm reference to the selected item (QTreeWidgetItem)
        @param column column that was selected (integer) (ignored)
        """
        pageName = itm.getPageName()
        self.showConfigurationPageByName(pageName, setCurrent=False)

    def __initPage(self, pageData):
        """
        Private method to initialize a configuration page.
        
        @param pageData data structure for the page to initialize
        @return reference to the initialized page
        """
        page = None
        if isinstance(pageData[2], types.FunctionType):
            page = pageData[2](self)
        else:
            mod = self.__importConfigurationPage(pageData[2])
            if mod:
                page = mod.create(self)
        if page is not None:
            self.configStack.addWidget(page)
            pageData[-1] = page
            try:
                page.setMode(self.displayMode)
            except AttributeError:
                pass
        return page

    def showConfigurationPageByName(self, pageName, setCurrent=True):
        """
        Public slot to show a named configuration page.
        
        @param pageName name of the configuration page to show (string)
        @param setCurrent flag indicating to set the current item (boolean)
        """
        if pageName == "empty" or pageName not in self.configItems:
            page = self.emptyPage
        else:
            pageData = self.configItems[pageName]
            if pageData[-1] is None and pageData[2] is not None:
                # the page was not loaded yet, create it
                page = self.__initPage(pageData)
            else:
                page = pageData[-1]
            if page is None:
                page = self.emptyPage
            elif setCurrent:
                items = self.configList.findItems(
                    pageData[0], Qt.MatchFixedString | Qt.MatchRecursive)
                for item in items:
                    if item.data(0, Qt.UserRole) == pageName:
                        self.configList.setCurrentItem(item)
        self.configStack.setCurrentWidget(page)
        ssize = self.scrollArea.size()
        if self.scrollArea.horizontalScrollBar():
            ssize.setHeight(ssize.height() -
                            self.scrollArea.horizontalScrollBar().height() - 2)
        if self.scrollArea.verticalScrollBar():
            ssize.setWidth(ssize.width() -
                           self.scrollArea.verticalScrollBar().width() - 2)
        psize = page.minimumSizeHint()
        self.configStack.resize(max(ssize.width(), psize.width()),
                                max(ssize.height(), psize.height()))

        if page != self.emptyPage:
            page.polishPage()
            self.buttonBox.button(QDialogButtonBox.Apply).setEnabled(True)
            self.buttonBox.button(QDialogButtonBox.Reset).setEnabled(True)
        else:
            self.buttonBox.button(QDialogButtonBox.Apply).setEnabled(False)
            self.buttonBox.button(QDialogButtonBox.Reset).setEnabled(False)

        # reset scrollbars
        for sb in [
                self.scrollArea.horizontalScrollBar(),
                self.scrollArea.verticalScrollBar()
        ]:
            if sb:
                sb.setValue(0)

        self.__currentConfigurationPageName = pageName

    def getConfigurationPageName(self):
        """
        Public method to get the page name of the current page.
        
        @return page name of the current page (string)
        """
        return self.__currentConfigurationPageName

    def calledFromEric(self):
        """
        Public method to check, if invoked from within eric.
        
        @return flag indicating invocation from within eric (boolean)
        """
        return self.fromEric

    def getPage(self, pageName):
        """
        Public method to get a reference to the named page.
        
        @param pageName name of the configuration page (string)
        @return reference to the page or None, indicating page was
            not loaded yet
        """
        return self.configItems[pageName][-1]

    def getLexers(self):
        """
        Public method to get a reference to the lexers dictionary.
        
        @return reference to the lexers dictionary
        """
        return self.lexers

    def setPreferences(self):
        """
        Public method called to store the selected values into the preferences
        storage.
        """
        for key, pageData in list(self.configItems.items()):
            if pageData[-1]:
                pageData[-1].save()
                # page was loaded (and possibly modified)
                QApplication.processEvents()  # ensure HMI is responsive

    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Apply):
            self.on_applyButton_clicked()
        elif button == self.buttonBox.button(QDialogButtonBox.Reset):
            self.on_resetButton_clicked()

    @pyqtSlot()
    def on_applyButton_clicked(self):
        """
        Private slot called to apply the settings of the current page.
        """
        if self.configStack.currentWidget() != self.emptyPage:
            page = self.configStack.currentWidget()
            savedState = page.saveState()
            page.save()
            self.preferencesChanged.emit()
            if savedState is not None:
                page.setState(savedState)

    @pyqtSlot()
    def on_resetButton_clicked(self):
        """
        Private slot called to reset the settings of the current page.
        """
        if self.configStack.currentWidget() != self.emptyPage:
            currentPage = self.configStack.currentWidget()
            savedState = currentPage.saveState()
            pageName = self.configList.currentItem().getPageName()
            self.configStack.removeWidget(currentPage)
            if pageName == "editorHighlightingStylesPage":
                self.__initLexers()
            self.configItems[pageName][-1] = None

            self.showConfigurationPageByName(pageName)
            if savedState is not None:
                self.configStack.currentWidget().setState(savedState)
Beispiel #8
0
class ClientUI(QMainWindow):
    def __init__(self):
        super(ClientUI, self).__init__()
        loadUi("client.ui", self)
        self.setWindowTitle("Simple Client")

        # quick DEBUG
        self.host.setText("127.0.0.1")
        self.username.setText("anonymous")
        self.password.setText("anonymous@")
        self.port.setText("20001")

        self.host.setText("59.66.136.21")
        self.username.setText("ssast")
        self.password.setText("ssast")
        self.port.setText("21")

        remote_header = ['Name', 'Size', 'Type', 'Last Modifed', 'Mode', 'Owner']
        self.remoteFileWidget.setColumnCount(len(remote_header))
        self.remoteFileWidget.setHeaderLabels(remote_header)
        self.remoteFileWidget.header().setSectionResizeMode(0, QHeaderView.Stretch)
        for col in range(1, len(remote_header)):
            self.remoteFileWidget.header().setSectionResizeMode(col, QHeaderView.ResizeToContents)

        self.transferWidget = QTreeWidget()
        transfer_header = ['Server/Local File', 'Direction', 'Remote File', 'Size', 'Start Time', 'End Time', 'Status', 'Operation']
        self.transferWidget.setColumnCount(len(transfer_header))
        self.transferWidget.setHeaderLabels(transfer_header)
        self.transferWidget.header().setSectionResizeMode(0, QHeaderView.Stretch)
        self.transferWidget.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        self.transferWidget.header().setSectionResizeMode(2, QHeaderView.Stretch)
        for col in range(3, len(transfer_header) - 1):
            self.transferWidget.header().setSectionResizeMode(col, QHeaderView.ResizeToContents)
        self.transferWidget.setColumnWidth(len(transfer_header) - 1, 0)

        self.finishedWidget = QTreeWidget()
        finished_header = ['Server/Local File', 'Direction', 'Remote File', 'Size', 'Start Time', 'End Time',
                           'Elapsed time', 'Status']
        self.finishedWidget.setColumnCount(len(finished_header))
        self.finishedWidget.setHeaderLabels(finished_header)
        self.finishedWidget.header().setSectionResizeMode(0, QHeaderView.Stretch)
        self.finishedWidget.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        self.finishedWidget.header().setSectionResizeMode(2, QHeaderView.Stretch)
        for col in range(3, len(finished_header) - 1):
            self.finishedWidget.header().setSectionResizeMode(col, QHeaderView.ResizeToContents)
        self.finishedWidget.setColumnWidth(len(transfer_header) - 1, 0)

        self.tabWidget.clear()
        self.tabWidget.addTab(self.transferWidget, "Transferring")
        self.tabWidget.addTab(self.finishedWidget, "Finished")

    def refresh_remote_widget(self, files):
        self.remoteFileWidget.clear()
        for file in files:
            self.remoteFileWidget.addTopLevelItem(QTreeWidgetItem(file))

    def refresh_transfer_widget(self, running_proc, pause_resume_callback, cancel_callback):
        self.transferWidget.clear()
        with threading.Lock():
            for proc_name in running_proc:
                proc = running_proc[proc_name]
                item = QTreeWidgetItem([proc.local_file,
                                        '<<--' if proc.download else '-->>',
                                        proc.remote_file,
                                        str(proc.trans_size) + "/" + str(proc.total_size),
                                        # proc.start_time.strftime("%Y-%m-%d %H:%M:%S"),
                                        str(proc.start_time),
                                        '----',
                                        proc.status.value,
                                        ])

                btnWidget = QWidget()
                layout = QHBoxLayout()
                prBtn = QPushButton("pause/resume")
                cancelBtn = QPushButton("cancel")
                layout.addWidget(prBtn)
                layout.addWidget(cancelBtn)
                prBtn.clicked.connect(partial(pause_resume_callback, proc))
                cancelBtn.clicked.connect(partial(cancel_callback, proc))
                btnWidget.setLayout(layout)

                # pbar = QProgressBar()
                # pbar.setValue(100 * proc.trans_size / proc.total_size)

                self.transferWidget.addTopLevelItem(item)
                # self.transferWidget.setItemWidget(item, RunningProcessHeader.Progress.value, pbar)
                self.transferWidget.setItemWidget(item, RunningProcessHeader.Btn.value, btnWidget)

    def update_transfer_item(self, proc):
        items = self.transferWidget.findItems(str(proc.start_time), Qt.MatchExactly,
                                              RunningProcessHeader.StartTime.value)
        for item in items:
            item.setText(RunningProcessHeader.Size.value, str(proc.trans_size) + "/" + str(proc.total_size))
            item.setText(RunningProcessHeader.Status.value, proc.status.value)

    def refresh_finished_widget(self, finished_proc):
        self.finishedWidget.clear()
        with threading.Lock():
            for proc in finished_proc:
                if proc.trans_size < proc.total_size:
                    size_str = humanize.naturalsize(proc.trans_size) + "/" + humanize.naturalsize(proc.total_size)
                else:
                    size_str = humanize.naturalsize(proc.total_size)

                item = QTreeWidgetItem([proc.local_file,
                                        '<<--' if proc.download else '-->>',
                                        proc.remote_file,
                                        size_str,
                                        # proc.start_time.strftime("%Y-%m-%d %H:%M:%S"),
                                        # proc.end_time.strftime("%Y-%m-%d %H:%M:%S"),
                                        str(proc.start_time),
                                        str(proc.end_time),
                                        humanize.naturaldelta(proc.end_time - proc.start_time),
                                        proc.status.value,
                                        ])

                self.finishedWidget.addTopLevelItem(item)
class BandList(QWidget):

    __LOG: Logger = LogHelper.logger("BandList")

    bandSelected = pyqtSignal(QTreeWidgetItem)
    rgbSelected = pyqtSignal(RGBSelectedBands)

    def __init__(self, parent: QWidget):
        super().__init__(parent)
        self.title = 'Bands'
        self.left = 0
        self.top = 0
        self.width = 260
        self.height = 675
        self.__init_ui()
        self.__parent_items = list()
        self.__show_bad_bands_prompt: bool = True

    def __init_ui(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.__type_selector = TypeSelector(self)
        self.__type_selector.greyscale_selected.connect(
            self.__handle_greyscale_selected)
        self.__type_selector.rgb_selected.connect(self.__handle_rgb_selected)
        self.__type_selector.open_clicked.connect(self.__handle_open_clicked)

        self.__treeWidget = QTreeWidget(self)
        self.__treeWidget.setColumnCount(1)
        self.__treeWidget.setHeaderLabel("File / Bands")
        self.__treeWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.__treeWidget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.__treeWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.__treeWidget.move(0, 0)

        # table item selection
        self.__treeWidget.itemDoubleClicked.connect(
            self.__handle_item_double_click)
        self.__treeWidget.itemSelectionChanged.connect(
            self.__handle_item_selection_changed)

        # Add box layout, add table to box layout and add box layout to widget
        self.__layout = QVBoxLayout()
        self.__layout.addWidget(self.__type_selector)
        self.__layout.addWidget(self.__treeWidget)
        self.setLayout(self.__layout)

        # Show widget
        self.show()

    def add_file(self, file_name: str, band_count: int,
                 band_tools: OpenSpectraBandTools):
        parent_item = QTreeWidgetItem()
        parent_item.setText(0, file_name)

        # add the band information for the file
        for band_index in range(band_count):
            child = QTreeWidgetItem(parent_item)
            band_descriptor = band_tools.band_descriptor(band_index)
            if band_descriptor.is_bad_band():
                child.setToolTip(0, "Bad band")
                child.setForeground(0, Qt.red)

            child.setText(0, band_descriptor.band_label())
            child.setData(0, Qt.UserRole, band_descriptor)

        # unselected any selected items from another file
        [item.setSelected(False) for item in self.__treeWidget.selectedItems()]

        # collapse any other file band lists that are expanded
        for index in range(0, self.__treeWidget.topLevelItemCount()):
            self.__treeWidget.topLevelItem(index).setExpanded(False)

        # add the new file with bands and expand the tree
        self.__treeWidget.addTopLevelItem(parent_item)
        parent_item.setExpanded(True)
        self.__parent_items.append(parent_item)
        return parent_item

    def remove_file(self, file_name: str):
        items = self.__treeWidget.findItems(file_name, Qt.MatchExactly)
        if len(items) == 1:
            index = self.__treeWidget.indexOfTopLevelItem(items[0])
            self.__treeWidget.takeTopLevelItem(index)
        else:
            dialog = QMessageBox()
            dialog.setIcon(QMessageBox.Critical)
            dialog.setText(
                "An internal error occurred, file '{}' doesn't appear to be open"
                .format(file_name))
            dialog.addButton(QMessageBox.Ok)
            dialog.exec()

    def selected_file(self) -> str:
        selected_items: List[
            QTreeWidgetItem] = self.__treeWidget.selectedItems()
        selected_file: str = None
        if len(selected_items) > 0:
            if selected_items[0].parent() is None:
                selected_file = selected_items[0].text(0)
            else:
                selected_file = selected_items[0].parent().text(0)

        return selected_file

    @pyqtSlot()
    def __handle_greyscale_selected(self):
        selected_items: List[
            QTreeWidgetItem] = self.__treeWidget.selectedItems()
        for item in selected_items:
            if item.parent() is not None:
                item.setSelected(False)

        self.__treeWidget.setSelectionMode(QAbstractItemView.SingleSelection)

    @pyqtSlot()
    def __handle_rgb_selected(self):
        self.__treeWidget.setSelectionMode(QAbstractItemView.MultiSelection)

    @pyqtSlot(QTreeWidgetItem)
    def __handle_item_double_click(self, item: QTreeWidgetItem):
        if self.__type_selector.is_greyscale_selected() and item.parent(
        ) is not None:
            item.setSelected(False)
            self.__open_item(item)

    @pyqtSlot()
    def __handle_item_selection_changed(self):
        selected_items: List[
            QTreeWidgetItem] = self.__treeWidget.selectedItems()
        BandList.__LOG.debug(
            "start __handle_item_selection_changed, selected size: {}, selected items: {}, state: {}",
            len(selected_items), str(selected_items),
            self.__treeWidget.state())

        # When clicking or dragging down then list items are appended to the end of the
        # selected_items list so selected_items[len(selected_items) - 1] is last selected
        # However when dragging up the list the order is reversed!
        if self.__type_selector.is_rgb_selected() and len(selected_items) > 0:
            last_selected = selected_items[len(selected_items) - 1]
            if last_selected.parent() is None:
                # A file item was selected
                for selected_item in selected_items:
                    if selected_item != last_selected:
                        selected_item.setSelected(False)
            else:
                # A band item was selected
                for selected_item in selected_items:
                    if selected_item.parent() is None:
                        # if we previously had a file selected, unselect it
                        selected_item.setSelected(False)
                    elif selected_item.parent() != last_selected.parent():
                        # otherwise check to make sure all selected bands are from
                        # the same file
                        selected_item.setSelected(False)

                selected_items = self.__treeWidget.selectedItems()
                if len(selected_items) > 3:
                    BandList.__LOG.debug("unselecting item: {}",
                                         selected_items[0])
                    selected_items[0].setSelected(False)

            # Now see what we're left with
            selected_items = self.__treeWidget.selectedItems()
            BandList.__LOG.debug(
                "end __handle_item_selection_changed, selected size: {}, selected items: {}",
                len(selected_items), str(selected_items))
            if len(selected_items) == 3:
                self.__type_selector.open_enabled(True)
            else:
                self.__type_selector.open_enabled(False)
        else:
            # grey scale is selected
            if len(selected_items) == 1 and selected_items[0].parent(
            ) is not None:
                self.__type_selector.open_enabled(True)
            else:
                self.__type_selector.open_enabled(False)

    @pyqtSlot()
    def __handle_open_clicked(self):
        items: List[QTreeWidgetItem] = self.__treeWidget.selectedItems()
        if self.__type_selector.is_rgb_selected():
            if len(items) == 3:

                if items[0].parent() is None or items[1].parent(
                ) is None or items[2].parent() is None:
                    self.__show_error(
                        "Only bands should be selected, not files")
                    return

                if not (items[0].parent() == items[1].parent() ==
                        items[2].parent()):
                    self.__show_error("All bands must be from the same file")
                    return

                result: int = QMessageBox.Yes
                for index in range(3):
                    descriptor: BandDescriptor = items[index].data(
                        0, Qt.UserRole)
                    if self.__show_bad_bands_prompt and descriptor.is_bad_band(
                    ):
                        result = self.__bad_band_prompt(descriptor)
                        if result == QMessageBox.Cancel:
                            break

                if result == QMessageBox.Yes:
                    [item.setSelected(False) for item in items]
                    selected = RGBSelectedBands(items[0].parent(), items[0],
                                                items[1], items[2])
                    self.rgbSelected.emit(selected)

        if self.__type_selector.is_greyscale_selected():
            if len(items) == 1:
                item: QTreeWidgetItem = items[0]
                item.setSelected(False)
                self.__open_item(item)

    def __open_item(self, item: QTreeWidgetItem):
        parent_item = item.parent()
        # if it has no parent it's a file, ignore it
        if parent_item is not None:
            result: int = QMessageBox.Yes
            descriptor: BandDescriptor = item.data(0, Qt.UserRole)
            if self.__show_bad_bands_prompt and descriptor.is_bad_band():
                result = self.__bad_band_prompt(descriptor)

            if result == QMessageBox.Yes:
                self.bandSelected.emit(item)
        else:
            self.__show_error("A band must be selected not a file.")

    def __bad_band_prompt(self, descriptor: BandDescriptor) -> int:
        dialog = QMessageBox(self)
        dialog.setIcon(QMessageBox.Warning)
        dialog.setText(
            "Band '{0}' from file '{1}' is marked as a 'bad band' in it's header file.  Do you wish to continue?"
            .format(descriptor.band_name(), descriptor.file_name()))
        check_box = QCheckBox("Don't ask again", dialog)
        check_box.setCheckState(not self.__show_bad_bands_prompt)
        dialog.setCheckBox(check_box)
        dialog.addButton(QMessageBox.Cancel)
        dialog.addButton(QMessageBox.Yes)
        result = dialog.exec()
        self.__show_bad_bands_prompt = not dialog.checkBox().checkState(
        ) == Qt.Checked
        return result

    def __show_error(self, message: str):
        dialog = QMessageBox(self)
        dialog.setIcon(QMessageBox.Critical)
        dialog.setText(message)
        dialog.addButton(QMessageBox.Ok)
        dialog.exec()
Beispiel #10
0
class AuctionWidget(QWidget):
    """ Provides a tree view to inspect ongoing auctions
        Args:
            parent: the parent widget
    """
    def __init__(self, parent: QWidget):
        super(AuctionWidget, self).__init__(parent)
        self.layout = QVBoxLayout()

        self.auctions = QTreeWidget()
        self.auctions.setColumnCount(2)
        self.auctions.setColumnWidth(0, 200)

        self.layout.addWidget(self.auctions)

        self.setLayout(self.layout)

    def new_auction(self, auction: Tuple[DNS_Transaction, DNS_Transaction],
                    expiration_block: str):
        """ Creates a new tree item for the auctions tree and appends it
            Args:
                auction: Tuple of transactions 1) the offered domain, 2) the initial bid
                expiration_block: The index of the block when the auction is resolved
        """
        offer, bid = auction
        offer_item = ChainHistoryWidget.create_transaction_item(offer, 0)
        offer_item.setText(0, 'Domain:')
        offer_item.setText(1, offer.data.domain_name)

        bid_item = ChainHistoryWidget.create_transaction_item(bid, 0)
        bid_item.setText(0, 'Highest Bid:')
        bid_item.setText(1, str(bid.amount))

        expiration_item = QTreeWidgetItem()
        expiration_item.setText(0, 'Expires in Block:')
        expiration_item.setText(1, expiration_block)

        offer_item.addChild(expiration_item)
        offer_item.addChild(bid_item)

        self.auctions.addTopLevelItem(offer_item)

    def auction_expired(self, offer: DNS_Transaction):
        """ Removes the latest auction from the tree
        """
        offer_item = self.auctions.findItems(offer.data.domain_name,
                                             QtCore.Qt.MatchExactly,
                                             column=1)[0]
        self.auctions.takeTopLevelItem(
            self.auctions.indexOfTopLevelItem(offer_item))

    def bid_placed(self, bid: DNS_Transaction):
        """ Removes the bid_item from the offer_item and replaces it
            with a new bid_item
            Args:
                bid: The new bid on the offer
        """
        # Replace the bid item within the offer item
        offer_item = self.auctions.findItems(bid.data.domain_name,
                                             QtCore.Qt.MatchExactly,
                                             column=1)[0]
        offer_item.takeChild(offer_item.childCount() - 1)

        bid_item = ChainHistoryWidget.create_transaction_item(bid, 0)
        bid_item.setText(0, 'Highest Bid:')
        bid_item.setText(1, str(bid.amount))

        offer_item.addChild(bid_item)
Beispiel #11
-1
class ConfigurationWidget(QWidget):
    """
    Class implementing a dialog for the configuration of eric6.
    
    @signal preferencesChanged() emitted after settings have been changed
    @signal masterPasswordChanged(str, str) emitted after the master
        password has been changed with the old and the new password
    @signal accepted() emitted to indicate acceptance of the changes
    @signal rejected() emitted to indicate rejection of the changes
    """
    preferencesChanged = pyqtSignal()
    masterPasswordChanged = pyqtSignal(str, str)
    accepted = pyqtSignal()
    rejected = pyqtSignal()
    
    DefaultMode = 0
    HelpBrowserMode = 1
    TrayStarterMode = 2
    HexEditorMode = 3
    
    def __init__(self, parent=None, fromEric=True, displayMode=DefaultMode,
                 expandedEntries=[]):
        """
        Constructor
        
        @param parent The parent widget of this dialog. (QWidget)
        @keyparam fromEric flag indicating a dialog generation from within the
            eric6 ide (boolean)
        @keyparam displayMode mode of the configuration dialog
            (DefaultMode, HelpBrowserMode, TrayStarterMode, HexEditorMode)
        @exception RuntimeError raised to indicate an invalid dialog mode
        @keyparam expandedEntries list of entries to be shown expanded
            (list of strings)
        """
        assert displayMode in (
            ConfigurationWidget.DefaultMode,
            ConfigurationWidget.HelpBrowserMode,
            ConfigurationWidget.TrayStarterMode,
            ConfigurationWidget.HexEditorMode,
        )
        
        super(ConfigurationWidget, self).__init__(parent)
        self.fromEric = fromEric
        self.displayMode = displayMode
        
        self.__setupUi()
        
        self.itmDict = {}
        
        if not fromEric:
            from PluginManager.PluginManager import PluginManager
            try:
                self.pluginManager = e5App().getObject("PluginManager")
            except KeyError:
                self.pluginManager = PluginManager(self)
                e5App().registerObject("PluginManager", self.pluginManager)
        
        if displayMode == ConfigurationWidget.DefaultMode:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "applicationPage":
                [self.tr("Application"), "preferences-application.png",
                 "ApplicationPage", None, None],
                "cooperationPage":
                [self.tr("Cooperation"), "preferences-cooperation.png",
                 "CooperationPage", None, None],
                "corbaPage":
                [self.tr("CORBA"), "preferences-orbit.png",
                 "CorbaPage", None, None],
                "emailPage":
                [self.tr("Email"), "preferences-mail_generic.png",
                 "EmailPage", None, None],
                "graphicsPage":
                [self.tr("Graphics"), "preferences-graphics.png",
                 "GraphicsPage", None, None],
                "hexEditorPage":
                [self.tr("Hex Editor"), "hexEditor.png",
                 "HexEditorPage", None, None],
                "iconsPage":
                [self.tr("Icons"), "preferences-icons.png",
                 "IconsPage", None, None],
                "ircPage":
                [self.tr("IRC"), "irc.png",
                 "IrcPage", None, None],
                "logViewerPage":
                [self.tr("Log-Viewer"), "preferences-logviewer.png",
                 "LogViewerPage", None, None],
                "mimeTypesPage":
                [self.tr("Mimetypes"), "preferences-mimetypes.png",
                 "MimeTypesPage", None, None],
                "networkPage":
                [self.tr("Network"), "preferences-network.png",
                 "NetworkPage", None, None],
                "notificationsPage":
                [self.tr("Notifications"),
                 "preferences-notifications.png",
                 "NotificationsPage", None, None],
                "pluginManagerPage":
                [self.tr("Plugin Manager"),
                 "preferences-pluginmanager.png",
                 "PluginManagerPage", None, None],
                "printerPage":
                [self.tr("Printer"), "preferences-printer.png",
                 "PrinterPage", None, None],
                "pythonPage":
                [self.tr("Python"), "preferences-python.png",
                 "PythonPage", None, None],
                "qtPage":
                [self.tr("Qt"), "preferences-qtlogo.png",
                 "QtPage", None, None],
                "securityPage":
                [self.tr("Security"), "preferences-security.png",
                 "SecurityPage", None, None],
                "shellPage":
                [self.tr("Shell"), "preferences-shell.png",
                 "ShellPage", None, None],
                "tasksPage":
                [self.tr("Tasks"), "task.png",
                 "TasksPage", None, None],
                "templatesPage":
                [self.tr("Templates"), "preferences-template.png",
                 "TemplatesPage", None, None],
                "trayStarterPage":
                [self.tr("Tray Starter"), "erict.png",
                 "TrayStarterPage", None, None],
                "vcsPage":
                [self.tr("Version Control Systems"),
                 "preferences-vcs.png",
                 "VcsPage", None, None],
                
                "0debuggerPage":
                [self.tr("Debugger"), "preferences-debugger.png",
                 None, None, None],
                "debuggerGeneralPage":
                [self.tr("General"), "preferences-debugger.png",
                 "DebuggerGeneralPage", "0debuggerPage", None],
                "debuggerPythonPage":
                [self.tr("Python"), "preferences-pyDebugger.png",
                 "DebuggerPythonPage", "0debuggerPage", None],
                "debuggerPython3Page":
                [self.tr("Python3"), "preferences-pyDebugger.png",
                 "DebuggerPython3Page", "0debuggerPage", None],
                
                "0editorPage":
                [self.tr("Editor"), "preferences-editor.png",
                 None, None, None],
                "editorAPIsPage":
                [self.tr("APIs"), "preferences-api.png",
                 "EditorAPIsPage", "0editorPage", None],
                "editorAutocompletionPage":
                [self.tr("Autocompletion"),
                 "preferences-autocompletion.png",
                 "EditorAutocompletionPage", "0editorPage", None],
                "editorAutocompletionQScintillaPage":
                [self.tr("QScintilla"), "qscintilla.png",
                 "EditorAutocompletionQScintillaPage",
                 "editorAutocompletionPage", None],
                "editorCalltipsPage":
                [self.tr("Calltips"), "preferences-calltips.png",
                 "EditorCalltipsPage", "0editorPage", None],
                "editorCalltipsQScintillaPage":
                [self.tr("QScintilla"), "qscintilla.png",
                 "EditorCalltipsQScintillaPage", "editorCalltipsPage", None],
                "editorGeneralPage":
                [self.tr("General"), "preferences-general.png",
                 "EditorGeneralPage", "0editorPage", None],
                "editorFilePage":
                [self.tr("Filehandling"),
                 "preferences-filehandling.png",
                 "EditorFilePage", "0editorPage", None],
                "editorSearchPage":
                [self.tr("Searching"), "preferences-search.png",
                 "EditorSearchPage", "0editorPage", None],
                "editorSpellCheckingPage":
                [self.tr("Spell checking"),
                 "preferences-spellchecking.png",
                 "EditorSpellCheckingPage", "0editorPage", None],
                "editorStylesPage":
                [self.tr("Style"), "preferences-styles.png",
                 "EditorStylesPage", "0editorPage", None],
                "editorSyntaxPage":
                [self.tr("Code Checkers"), "preferences-debugger.png",
                 "EditorSyntaxPage", "0editorPage", None],
                "editorTypingPage":
                [self.tr("Typing"), "preferences-typing.png",
                 "EditorTypingPage", "0editorPage", None],
                "editorExportersPage":
                [self.tr("Exporters"), "preferences-exporters.png",
                 "EditorExportersPage", "0editorPage", None],
                
                "1editorLexerPage":
                [self.tr("Highlighters"),
                 "preferences-highlighting-styles.png",
                 None, "0editorPage", None],
                "editorHighlightersPage":
                [self.tr("Filetype Associations"),
                 "preferences-highlighter-association.png",
                 "EditorHighlightersPage", "1editorLexerPage", None],
                "editorHighlightingStylesPage":
                [self.tr("Styles"),
                 "preferences-highlighting-styles.png",
                 "EditorHighlightingStylesPage", "1editorLexerPage", None],
                "editorKeywordsPage":
                [self.tr("Keywords"), "preferences-keywords.png",
                 "EditorKeywordsPage", "1editorLexerPage", None],
                "editorPropertiesPage":
                [self.tr("Properties"), "preferences-properties.png",
                 "EditorPropertiesPage", "1editorLexerPage", None],
                
                "1editorMouseClickHandlers":
                [self.tr("Mouse Click Handlers"),
                 "preferences-mouse-click-handler.png",
                 "EditorMouseClickHandlerPage", "0editorPage", None],
                
                "0helpPage":
                [self.tr("Help"), "preferences-help.png",
                 None, None, None],
                "helpDocumentationPage":
                [self.tr("Help Documentation"),
                 "preferences-helpdocumentation.png",
                 "HelpDocumentationPage", "0helpPage", None],
                "helpViewersPage":
                [self.tr("Help Viewers"),
                 "preferences-helpviewers.png",
                 "HelpViewersPage", "0helpPage", None],
                
                "0projectPage":
                [self.tr("Project"), "preferences-project.png",
                 None, None, None],
                "projectBrowserPage":
                [self.tr("Project Viewer"), "preferences-project.png",
                 "ProjectBrowserPage", "0projectPage", None],
                "projectPage":
                [self.tr("Project"), "preferences-project.png",
                 "ProjectPage", "0projectPage", None],
                "multiProjectPage":
                [self.tr("Multiproject"),
                 "preferences-multiproject.png",
                 "MultiProjectPage", "0projectPage", None],
                
                "0interfacePage":
                [self.tr("Interface"), "preferences-interface.png",
                 None, None, None],
                "interfacePage":
                [self.tr("Interface"), "preferences-interface.png",
                 "InterfacePage", "0interfacePage", None],
                "viewmanagerPage":
                [self.tr("Viewmanager"), "preferences-viewmanager.png",
                 "ViewmanagerPage", "0interfacePage", None],
            }
            try:
                from PyQt5 import QtWebKit      # __IGNORE_WARNING__
                self.configItems.update({
                    "helpAppearancePage":
                    [self.tr("Appearance"), "preferences-styles.png",
                     "HelpAppearancePage", "0helpPage", None],
                    "helpFlashCookieManagerPage":
                    [self.tr("Flash Cookie Manager"),
                     "flashCookie16.png",
                     "HelpFlashCookieManagerPage", "0helpPage", None],
                    "helpVirusTotalPage":
                    [self.tr("VirusTotal Interface"), "virustotal.png",
                     "HelpVirusTotalPage", "0helpPage", None],
                    "helpWebBrowserPage":
                    [self.tr("eric6 Web Browser"), "ericWeb.png",
                     "HelpWebBrowserPage", "0helpPage", None],
                })
            except ImportError:
                pass
            
            self.configItems.update(
                e5App().getObject("PluginManager").getPluginConfigData())
        
        elif displayMode == ConfigurationWidget.HelpBrowserMode:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "interfacePage":
                [self.tr("Interface"), "preferences-interface.png",
                 "HelpInterfacePage", None, None],
                "networkPage":
                [self.tr("Network"), "preferences-network.png",
                 "NetworkPage", None, None],
                "printerPage":
                [self.tr("Printer"), "preferences-printer.png",
                 "PrinterPage", None, None],
                "securityPage":
                [self.tr("Security"), "preferences-security.png",
                 "SecurityPage", None, None],
                
                "0helpPage":
                [self.tr("Help"), "preferences-help.png",
                 None, None, None],
                "helpDocumentationPage":
                [self.tr("Help Documentation"),
                 "preferences-helpdocumentation.png",
                 "HelpDocumentationPage", "0helpPage", None],
            }
            try:
                from PyQt5 import QtWebKit      # __IGNORE_WARNING__
                self.configItems.update({
                    "helpAppearancePage":
                    [self.tr("Appearance"), "preferences-styles.png",
                     "HelpAppearancePage", "0helpPage", None],
                    "helpFlashCookieManagerPage":
                    [self.tr("Flash Cookie Manager"),
                     "flashCookie16.png",
                     "HelpFlashCookieManagerPage", "0helpPage", None],
                    "helpVirusTotalPage":
                    [self.tr("VirusTotal Interface"), "virustotal.png",
                     "HelpVirusTotalPage", "0helpPage", None],
                    "helpWebBrowserPage":
                    [self.tr("eric6 Web Browser"), "ericWeb.png",
                     "HelpWebBrowserPage", "0helpPage", None],
                })
            except ImportError:
                pass
        
        elif displayMode == ConfigurationWidget.TrayStarterMode:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "trayStarterPage":
                [self.tr("Tray Starter"), "erict.png",
                 "TrayStarterPage", None, None],
            }
        
        elif displayMode == ConfigurationWidget.HexEditorMode:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "hexEditorPage":
                [self.tr("Hex Editor"), "hexEditor.png",
                 "HexEditorPage", None, None],
            }
        
        else:
            raise RuntimeError("Illegal mode value: {0}".format(displayMode))
        
        # generate the list entries
        self.__expandedEntries = []
        for key in sorted(self.configItems.keys()):
            pageData = self.configItems[key]
            if pageData[3]:
                if pageData[3] in self.itmDict:
                    pitm = self.itmDict[pageData[3]]  # get the parent item
                else:
                    continue
            else:
                pitm = self.configList
            self.itmDict[key] = ConfigurationPageItem(pitm, pageData[0], key,
                                                      pageData[1])
            self.itmDict[key].setData(0, Qt.UserRole, key)
            if (not self.fromEric or
                displayMode != ConfigurationWidget.DefaultMode or
                    key in expandedEntries):
                self.itmDict[key].setExpanded(True)
        self.configList.sortByColumn(0, Qt.AscendingOrder)
        
        # set the initial size of the splitter
        self.configSplitter.setSizes([200, 600])
        
        self.configList.itemActivated.connect(self.__showConfigurationPage)
        self.configList.itemClicked.connect(self.__showConfigurationPage)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.rejected)
        
        if displayMode in [ConfigurationWidget.HelpBrowserMode,
                           ConfigurationWidget.TrayStarterMode,
                           ConfigurationWidget.HexEditorMode]:
            self.configListSearch.hide()
        
        if displayMode not in [ConfigurationWidget.TrayStarterMode,
                               ConfigurationWidget.HexEditorMode]:
            self.__initLexers()
        
    def accept(self):
        """
        Public slot to accept the buttonBox accept signal.
        """
        if not isMacPlatform():
            wdg = self.focusWidget()
            if wdg == self.configList:
                return
        
        self.accepted.emit()
        
    def __setupUi(self):
        """
        Private method to perform the general setup of the configuration
        widget.
        """
        self.setObjectName("ConfigurationDialog")
        self.resize(900, 650)
        self.verticalLayout_2 = QVBoxLayout(self)
        self.verticalLayout_2.setSpacing(6)
        self.verticalLayout_2.setContentsMargins(6, 6, 6, 6)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        
        self.configSplitter = QSplitter(self)
        self.configSplitter.setOrientation(Qt.Horizontal)
        self.configSplitter.setObjectName("configSplitter")
        
        self.configListWidget = QWidget(self.configSplitter)
        self.leftVBoxLayout = QVBoxLayout(self.configListWidget)
        self.leftVBoxLayout.setContentsMargins(0, 0, 0, 0)
        self.leftVBoxLayout.setSpacing(0)
        self.leftVBoxLayout.setObjectName("leftVBoxLayout")
        self.configListSearch = E5ClearableLineEdit(
            self, self.tr("Enter search text..."))
        self.configListSearch.setObjectName("configListSearch")
        self.leftVBoxLayout.addWidget(self.configListSearch)
        self.configList = QTreeWidget()
        self.configList.setObjectName("configList")
        self.leftVBoxLayout.addWidget(self.configList)
        self.configListSearch.textChanged.connect(self.__searchTextChanged)
        
        self.scrollArea = QScrollArea(self.configSplitter)
        self.scrollArea.setFrameShape(QFrame.NoFrame)
        self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scrollArea.setWidgetResizable(False)
        self.scrollArea.setObjectName("scrollArea")
        
        self.configStack = QStackedWidget()
        self.configStack.setFrameShape(QFrame.Box)
        self.configStack.setFrameShadow(QFrame.Sunken)
        self.configStack.setObjectName("configStack")
        self.scrollArea.setWidget(self.configStack)
        
        self.emptyPage = QWidget()
        self.emptyPage.setGeometry(QRect(0, 0, 372, 591))
        self.emptyPage.setObjectName("emptyPage")
        self.vboxlayout = QVBoxLayout(self.emptyPage)
        self.vboxlayout.setSpacing(6)
        self.vboxlayout.setContentsMargins(6, 6, 6, 6)
        self.vboxlayout.setObjectName("vboxlayout")
        spacerItem = QSpacerItem(
            20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.vboxlayout.addItem(spacerItem)
        self.emptyPagePixmap = QLabel(self.emptyPage)
        self.emptyPagePixmap.setAlignment(Qt.AlignCenter)
        self.emptyPagePixmap.setObjectName("emptyPagePixmap")
        self.emptyPagePixmap.setPixmap(
            QPixmap(os.path.join(getConfig('ericPixDir'), 'eric.png')))
        self.vboxlayout.addWidget(self.emptyPagePixmap)
        self.textLabel1 = QLabel(self.emptyPage)
        self.textLabel1.setAlignment(Qt.AlignCenter)
        self.textLabel1.setObjectName("textLabel1")
        self.vboxlayout.addWidget(self.textLabel1)
        spacerItem1 = QSpacerItem(
            20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.vboxlayout.addItem(spacerItem1)
        self.configStack.addWidget(self.emptyPage)
        
        self.verticalLayout_2.addWidget(self.configSplitter)
        
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(
            QDialogButtonBox.Apply | QDialogButtonBox.Cancel |
            QDialogButtonBox.Ok | QDialogButtonBox.Reset)
        self.buttonBox.setObjectName("buttonBox")
        if not self.fromEric and \
                self.displayMode == ConfigurationWidget.DefaultMode:
            self.buttonBox.button(QDialogButtonBox.Apply).hide()
        self.buttonBox.button(QDialogButtonBox.Apply).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Reset).setEnabled(False)
        self.verticalLayout_2.addWidget(self.buttonBox)

        self.setWindowTitle(self.tr("Preferences"))
        
        self.configList.header().hide()
        self.configList.header().setSortIndicator(0, Qt.AscendingOrder)
        self.configList.setSortingEnabled(True)
        self.textLabel1.setText(
            self.tr("Please select an entry of the list \n"
                    "to display the configuration page."))
        
        QMetaObject.connectSlotsByName(self)
        self.setTabOrder(self.configList, self.configStack)
        
        self.configStack.setCurrentWidget(self.emptyPage)
        
        self.configList.setFocus()
    
    def __searchTextChanged(self, text):
        """
        Private slot to handle a change of the search text.
        
        @param text text to search for (string)
        """
        self.__searchChildItems(self.configList.invisibleRootItem(), text)
    
    def __searchChildItems(self, parent, text):
        """
        Private method to enable child items based on a search string.
        
        @param parent reference to the parent item (QTreeWidgetItem)
        @param text text to search for (string)
        @return flag indicating an enabled child item (boolean)
        """
        childEnabled = False
        text = text.lower()
        for index in range(parent.childCount()):
            itm = parent.child(index)
            if itm.childCount() > 0:
                enable = self.__searchChildItems(itm, text) or \
                    text == "" or text in itm.text(0).lower()
            else:
                enable = text == "" or text in itm.text(0).lower()
            if enable:
                childEnabled = True
            itm.setDisabled(not enable)
        
        return childEnabled
    
    def __initLexers(self):
        """
        Private method to initialize the dictionary of preferences lexers.
        """
        import QScintilla.Lexers
        from .PreferencesLexer import PreferencesLexer, \
            PreferencesLexerLanguageError
        
        self.lexers = {}
        for language in QScintilla.Lexers.getSupportedLanguages():
            if language not in self.lexers:
                try:
                    self.lexers[language] = PreferencesLexer(language, self)
                except PreferencesLexerLanguageError:
                    pass
        
    def __importConfigurationPage(self, name):
        """
        Private method to import a configuration page module.
        
        @param name name of the configuration page module (string)
        @return reference to the configuration page module
        """
        modName = "Preferences.ConfigurationPages.{0}".format(name)
        try:
            mod = __import__(modName)
            components = modName.split('.')
            for comp in components[1:]:
                mod = getattr(mod, comp)
            return mod
        except ImportError:
            E5MessageBox.critical(
                self,
                self.tr("Configuration Page Error"),
                self.tr("""<p>The configuration page <b>{0}</b>"""
                        """ could not be loaded.</p>""").format(name))
            return None
        
    def __showConfigurationPage(self, itm, column):
        """
        Private slot to show a selected configuration page.
        
        @param itm reference to the selected item (QTreeWidgetItem)
        @param column column that was selected (integer) (ignored)
        """
        pageName = itm.getPageName()
        self.showConfigurationPageByName(pageName, setCurrent=False)
        
    def __initPage(self, pageData):
        """
        Private method to initialize a configuration page.
        
        @param pageData data structure for the page to initialize
        @return reference to the initialized page
        """
        page = None
        if isinstance(pageData[2], types.FunctionType):
            page = pageData[2](self)
        else:
            mod = self.__importConfigurationPage(pageData[2])
            if mod:
                page = mod.create(self)
        if page is not None:
            self.configStack.addWidget(page)
            pageData[-1] = page
            try:
                page.setMode(self.displayMode)
            except AttributeError:
                pass
        return page
        
    def showConfigurationPageByName(self, pageName, setCurrent=True):
        """
        Public slot to show a named configuration page.
        
        @param pageName name of the configuration page to show (string)
        @param setCurrent flag indicating to set the current item (boolean)
        """
        if pageName == "empty" or pageName not in self.configItems:
            page = self.emptyPage
        else:
            pageData = self.configItems[pageName]
            if pageData[-1] is None and pageData[2] is not None:
                # the page was not loaded yet, create it
                page = self.__initPage(pageData)
            else:
                page = pageData[-1]
            if page is None:
                page = self.emptyPage
            elif setCurrent:
                items = self.configList.findItems(
                    pageData[0],
                    Qt.MatchFixedString | Qt.MatchRecursive)
                for item in items:
                    if item.data(0, Qt.UserRole) == pageName:
                        self.configList.setCurrentItem(item)
        self.configStack.setCurrentWidget(page)
        ssize = self.scrollArea.size()
        if self.scrollArea.horizontalScrollBar():
            ssize.setHeight(
                ssize.height() -
                self.scrollArea.horizontalScrollBar().height() - 2)
        if self.scrollArea.verticalScrollBar():
            ssize.setWidth(
                ssize.width() -
                self.scrollArea.verticalScrollBar().width() - 2)
        psize = page.minimumSizeHint()
        self.configStack.resize(max(ssize.width(), psize.width()),
                                max(ssize.height(), psize.height()))
        
        if page != self.emptyPage:
            page.polishPage()
            self.buttonBox.button(QDialogButtonBox.Apply).setEnabled(True)
            self.buttonBox.button(QDialogButtonBox.Reset).setEnabled(True)
        else:
            self.buttonBox.button(QDialogButtonBox.Apply).setEnabled(False)
            self.buttonBox.button(QDialogButtonBox.Reset).setEnabled(False)
        
        # reset scrollbars
        for sb in [self.scrollArea.horizontalScrollBar(),
                   self.scrollArea.verticalScrollBar()]:
            if sb:
                sb.setValue(0)
        
        self.__currentConfigurationPageName = pageName
        
    def getConfigurationPageName(self):
        """
        Public method to get the page name of the current page.
        
        @return page name of the current page (string)
        """
        return self.__currentConfigurationPageName
        
    def calledFromEric(self):
        """
        Public method to check, if invoked from within eric.
        
        @return flag indicating invocation from within eric (boolean)
        """
        return self.fromEric
        
    def getPage(self, pageName):
        """
        Public method to get a reference to the named page.
        
        @param pageName name of the configuration page (string)
        @return reference to the page or None, indicating page was
            not loaded yet
        """
        return self.configItems[pageName][-1]
        
    def getLexers(self):
        """
        Public method to get a reference to the lexers dictionary.
        
        @return reference to the lexers dictionary
        """
        return self.lexers
        
    def setPreferences(self):
        """
        Public method called to store the selected values into the preferences
        storage.
        """
        for key, pageData in list(self.configItems.items()):
            if pageData[-1]:
                pageData[-1].save()
                # page was loaded (and possibly modified)
                QApplication.processEvents()    # ensure HMI is responsive
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Apply):
            self.on_applyButton_clicked()
        elif button == self.buttonBox.button(QDialogButtonBox.Reset):
            self.on_resetButton_clicked()
        
    @pyqtSlot()
    def on_applyButton_clicked(self):
        """
        Private slot called to apply the settings of the current page.
        """
        if self.configStack.currentWidget() != self.emptyPage:
            page = self.configStack.currentWidget()
            savedState = page.saveState()
            page.save()
            self.preferencesChanged.emit()
            if savedState is not None:
                page.setState(savedState)
            page.polishPage()
        
    @pyqtSlot()
    def on_resetButton_clicked(self):
        """
        Private slot called to reset the settings of the current page.
        """
        if self.configStack.currentWidget() != self.emptyPage:
            currentPage = self.configStack.currentWidget()
            savedState = currentPage.saveState()
            pageName = self.configList.currentItem().getPageName()
            self.configStack.removeWidget(currentPage)
            if pageName == "editorHighlightingStylesPage":
                self.__initLexers()
            self.configItems[pageName][-1] = None
            
            self.showConfigurationPageByName(pageName)
            if savedState is not None:
                self.configStack.currentWidget().setState(savedState)
        
    def getExpandedEntries(self):
        """
        Public method to get a list of expanded entries.
        
        @return list of expanded entries (list of string)
        """
        return self.__expandedEntries
    
    @pyqtSlot(QTreeWidgetItem)
    def on_configList_itemCollapsed(self, item):
        """
        Private slot handling a list entry being collapsed.
        
        @param item reference to the collapsed item (QTreeWidgetItem)
        """
        pageName = item.data(0, Qt.UserRole)
        if pageName in self.__expandedEntries:
            self.__expandedEntries.remove(pageName)
    
    @pyqtSlot(QTreeWidgetItem)
    def on_configList_itemExpanded(self, item):
        """
        Private slot handling a list entry being expanded.
        
        @param item reference to the expanded item (QTreeWidgetItem)
        """
        pageName = item.data(0, Qt.UserRole)
        if pageName not in self.__expandedEntries:
            self.__expandedEntries.append(pageName)