예제 #1
0
class ParameterWindow(QMainWindow):
    def __init__(self, dplugin, api, pl_win_name = '',parent = None):
        QMainWindow.__init__(self, parent)

        # Build the tree for the parameters
        self.parameterTree = QTreeView(self)
        self.parameterTree.setObjectName("parameterTree")
        # Add it as the central widget
        self.setCentralWidget(self.parameterTree)
        # Add the DParameterTreeModel to the parameter tree
        self.dparameterModel = DParameterTreeModel()
        self.dparameterModel.setHorizontalHeaderLabels(['Name',''])
        self.parameterTree.setModel(self.dparameterModel)
        self.parameterTree.setUniformRowHeights(True)
        # connect the callback function for value changes
        self.dparameterModel.dataChanged.connect(self.data_changed_parameter_model)

        self.dpluign_object = dplugin
        self.api = api

        self.setWindowTitle(pl_win_name+' Parameter')



    def show_paramters(self, para_list):
        """
        Shows the list of parameters and values in the parameter window
        :param para_list:
        :return:
        """
        for dparameter_name in sorted(para_list):
            dparameter = para_list[dparameter_name]
            dparameter_item = DParameterTreeItem(dparameter)
            self.dparameterModel.appendRow(dparameter_item)
            self.parameterTree.resizeColumnToContents(0)
            self.parameterTree.resizeColumnToContents(1)
        self.parameterTree.expandAll()

        fh = self.parameterTree.fontMetrics().height()

        if len(para_list.keys()) > 8:
            self.setFixedHeight(fh*9+fh+25)
        else:
             self.setFixedHeight(fh*len(para_list.keys())+fh+fh+25)




    def data_changed_parameter_model(self, index, n):
        """
        This function is called when a dparameter value is changed by editing the 'value'-column.

        :param index: Index of current changed dparameter
        :param n: None
        :return:
        """

        dparameter = self.parameterTree.model().data(index, Qt.UserRole)

        self.api.do_set_parameter(self.dpluign_object.id, dparameter.name, dparameter.value)
예제 #2
0
파일: views.py 프로젝트: spikespaz/archives
class BinaryDetailsDialog(QDialog):
    def __init__(self, data, *args, title=None, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowFlag(Qt.WindowContextHelpButtonHint, False)

        self._data = data

        if not title:
            self.setWindowTitle(f"{self._data.binaries[0].binary_name} ({self._data.release_name})")

        self.setup_interface()

    def setup_interface(self):
        self.resize(600, 380)

        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.details_tree_model = BinaryDetailsTreeModel(self._data)
        self.details_tree_view = QTreeView()

        self.details_tree_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.details_tree_view.setAnimated(True)
        self.details_tree_view.setModel(self.details_tree_model)
        self.details_tree_view.expandAll()
        self.details_tree_view.resizeColumnToContents(0)
        self.details_tree_view.doubleClicked.connect(
            lambda index: QApplication.clipboard().setText(
                self.details_tree_model.data(index, Qt.DisplayRole)
            )
        )

        self.layout.addWidget(self.details_tree_view)
        self.setLayout(self.layout)
예제 #3
0
 def __init__(self, model):
     super().__init__()
     self.setWindowTitle("Treeview for nested dict/list")
     self.setGeometry(300, 300, 600, 800)
     tree_view = QTreeView()
     tree_view.setModel(model)
     tree_view.expandAll()
     tree_view.resizeColumnToContents(0)
     self.setCentralWidget(tree_view)
예제 #4
0
def main(args):
    app = QApplication (args)
    view = QTreeView()
    view.setModel(StorageModel(view))
    view.resize(640, 480)
    view.setSelectionBehavior(QAbstractItemView.SelectRows)
    for column in range(view.model().columnCount()):
        view.resizeColumnToContents(column)
    view.show()
    return app.exec_()
예제 #5
0
class _LocatorDialog(QDialog):
    """Locator widget and implementation
    """
    def __init__(self, parent, commandClasses):
        QDialog.__init__(self, parent)
        self._terminated = False
        self._commandClasses = commandClasses

        self._createUi()

        self._loadingTimer = QTimer(self)
        self._loadingTimer.setSingleShot(True)
        self._loadingTimer.setInterval(200)
        self._loadingTimer.timeout.connect(self._applyLoadingCompleter)

        self._completerLoaderThread = _CompleterLoaderThread(self)

        self.finished.connect(self._terminate)

        self._command = None
        self._updateCurrentCommand()

    def _createUi(self):
        self.setWindowTitle(core.project().path() or 'Locator')

        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(1)

        biggerFont = self.font()
        biggerFont.setPointSizeF(biggerFont.pointSizeF() * 2)
        self.setFont(biggerFont)

        self._edit = _CompletableLineEdit(self)
        self._edit.updateCurrentCommand.connect(self._updateCurrentCommand)
        self._edit.enterPressed.connect(self._onEnterPressed)
        self._edit.installEventFilter(self)  # catch Up, Down
        self.layout().addWidget(self._edit)
        self.setFocusProxy(self._edit)

        self._table = QTreeView(self)
        self._table.setFont(biggerFont)
        self._model = _CompleterModel()
        self._table.setModel(self._model)
        self._table.setItemDelegate(HTMLDelegate(self._table))
        self._table.setRootIsDecorated(False)
        self._table.setHeaderHidden(True)
        self._table.clicked.connect(self._onItemClicked)
        self._table.setAlternatingRowColors(True)
        self._table.installEventFilter(
            self)  # catch focus and give to the edit
        self.layout().addWidget(self._table)

        width = QFontMetrics(self.font()).width('x' *
                                                64)  # width of 64 'x' letters
        self.resize(width, width * 0.62)

    def _terminate(self):
        if not self._terminated:
            if self._command is not None:
                self._command.terminate()
                self._command = None

            self._edit.terminate()

            self._completerLoaderThread.terminate()
            core.workspace().focusCurrentDocument()
            self._terminated = True

    def _updateCurrentCommand(self):
        """Try to parse line edit text and set current command
        """
        if self._terminated:
            return

        newCommand = self._parseCurrentCommand()

        if newCommand is not self._command:
            if self._command is not None:
                self._command.updateCompleter.disconnect(
                    self._updateCompletion)
                self._command.terminate()

            self._command = newCommand
            if self._command is not None:
                self._command.updateCompleter.connect(self._updateCompletion)

        self._updateCompletion()

    def _updateCompletion(self):
        """User edited text or moved cursor. Update inline and TreeView completion
        """
        if self._command is not None:
            completer = self._command.completer()

            if completer is not None and completer.mustBeLoaded:
                self._loadingTimer.start()
                self._completerLoaderThread.loadCompleter(
                    self._command, completer)
            else:
                self._applyCompleter(self._command, completer)
        else:
            self._applyCompleter(None, _HelpCompleter(self._commandClasses))

    def _applyLoadingCompleter(self):
        """Set 'Loading...' message
        """
        self._applyCompleter(None, StatusCompleter('<i>Loading...</i>'))

    def onCompleterLoaded(self, command, completer):
        """The method called from _CompleterLoaderThread when the completer is ready
        This code works in the GUI thread
        """
        self._applyCompleter(command, completer)

    def _applyCompleter(self, command, completer):
        """Apply completer. Called by _updateCompletion or by thread function when Completer is constructed
        """
        self._loadingTimer.stop()

        if command is not None:
            command.onCompleterLoaded(completer)

        if completer is None:
            completer = _HelpCompleter([command])

        if self._edit.cursorPosition() == len(
                self._edit.text()):  # if cursor at the end of text
            self._edit.setInlineCompletion(completer.inline())

        self._model.setCompleter(completer)
        if completer.columnCount() > 1:
            self._table.resizeColumnToContents(0)
            self._table.setColumnWidth(0,
                                       self._table.columnWidth(0) +
                                       20)  # 20 px spacing between columns

        selItem = completer.autoSelectItem()
        if selItem:
            index = self._model.createIndex(selItem[0], selItem[1])
            self._table.setCurrentIndex(index)

    def _onItemClicked(self, index):
        """Item in the TreeView has been clicked.
        Open file, if user selected it
        """
        if self._command is not None:
            fullText = self._model.completer.getFullText(index.row())
            if fullText is not None:
                self._command.onItemClicked(fullText)
                if self._tryExecCurrentCommand():
                    self.accept()
                    return
                else:
                    self._edit.setText(self._command.lineEditText())
                    self._updateCurrentCommand()

        self._edit.setFocus()

    def _onEnterPressed(self):
        """User pressed Enter or clicked item. Execute command, if possible
        """
        if self._table.currentIndex().isValid():
            self._onItemClicked(self._table.currentIndex())
        else:
            self._tryExecCurrentCommand()

    def _tryExecCurrentCommand(self):
        if self._command is not None and self._command.isReadyToExecute():
            self._command.execute()
            self.accept()
            return True
        else:
            return False

    def _chooseCommand(self, words):
        for cmd in self._commandClasses:
            if cmd.command == words[0]:
                return cmd, words[1:]

        isPath = words and (words[0].startswith('/')
                            or words[0].startswith('./')
                            or words[0].startswith('../')
                            or words[0].startswith('~/')
                            or words[0][1:3] == ':\\' or words[0][1:3] == ':/')
        isNumber = len(words) == 1 and all([c.isdigit() for c in words[0]])

        def matches(cmd):
            if isPath:
                return cmd.isDefaultPathCommand
            elif isNumber:
                return cmd.isDefaultNumericCommand
            else:
                return cmd.isDefaultCommand

        for cmd in self._commandClasses:
            if matches(cmd):
                return cmd, words

    def _parseCurrentCommand(self):
        """ Parse text and try to get (command, completable word index)
        Return None if failed to parse
        """
        # Split line
        text = self._edit.commandText()
        words = splitLine(text)
        if not words:
            return None

        # Find command
        cmdClass, args = self._chooseCommand(words)

        if isinstance(self._command, cmdClass):
            command = self._command
        else:
            command = cmdClass()

        # Try to make command object
        try:
            command.setArgs(args)
        except InvalidCmdArgs:
            return None
        else:
            return command

    def eventFilter(self, obj, event):
        if obj is self._edit:
            if event.type() == QEvent.KeyPress and \
               event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown):
                return self._table.event(event)
        elif obj is self._table:
            if event.type() == QEvent.FocusIn:
                self._edit.setFocus()
                return True

        return False
예제 #6
0
class StandardLibraryPage(QSplitter):
    """ The GUI for the standard library page of a project. """

    # The page's label.
    label = "Standard Library"

    @property
    def project(self):
        """ The project property getter. """

        return self._project

    @project.setter
    def project(self, value):
        """ The project property setter. """

        if self._project != value:
            self._project = value

            self._update_page()

    def __init__(self):
        """ Initialise the page. """

        super().__init__()

        self._project = None

        # Create the page's GUI.
        stdlib_pane = QWidget()
        stdlib_layout = QVBoxLayout()

        self._stdlib_edit = QTreeWidget(
                whatsThis="This shows the packages and modules in the target "
                        "Python version's standard library. Check those "
                        "packages and modules that are explicitly imported by "
                        "the application. A module will be partially checked "
                        "(and automatically included) if another module "
                        "requires it.")
        self._stdlib_edit.setHeaderLabels(["Package"])
        self._stdlib_edit.itemChanged.connect(self._module_changed)

        stdlib_layout.addWidget(self._stdlib_edit)

        stdlib_pane.setLayout(stdlib_layout)
        self.addWidget(stdlib_pane)

        extlib_pane = QWidget()
        extlib_layout = QVBoxLayout()
        extlib_sublayout = QFormLayout()

        self._version_edit = QComboBox(
                whatsThis="Select the target Python version. This will cause "
                        "the standard library package hierarchy to be "
                        "updated.")
        self._version_edit.addItems(get_supported_python_versions())
        self._version_edit.currentIndexChanged.connect(self._version_changed)
        extlib_sublayout.addRow("Target Python version", self._version_edit)

        self._ssl_edit = QCheckBox(
                whatsThis="Enable SSL for the standard library modules "
                        "that have optional support for it.",
                stateChanged=self._ssl_changed)
        extlib_sublayout.addRow("Enable optional SSL support", self._ssl_edit)

        extlib_layout.addLayout(extlib_sublayout)

        plat_gb = QGroupBox("Use standard Python shared library")
        plat_gb_layout = QVBoxLayout()
        self._platform_buttons = []

        for scope, plat, subscopes in PLATFORM_SCOPES:
            plat_cb = QCheckBox(plat,
                    whatsThis="Enable the use of the standard Python shared "
                            "library on {0} rather than a statically compiled "
                            "library.".format(plat),
                    stateChanged=self._platforms_changed)
            plat_cb._scope = scope
            plat_gb_layout.addWidget(plat_cb)
            self._platform_buttons.append(plat_cb)

        plat_gb.setLayout(plat_gb_layout)
        extlib_layout.addWidget(plat_gb)

        self._extlib_edit = QTreeView(
                whatsThis="This is the list of external libraries that must "
                        "be linked with the application. A library will only "
                        "be enabled if a module in the standard library uses "
                        "it. Double-click in the <b>DEFINES</b>, "
                        "<b>INCLUDEPATH</b> and <b>LIBS</b> columns to modify "
                        "the corresponding <tt>qmake</tt> variable as "
                        "required. Values may be prefixed by a platform "
                        "specific <tt>qmake</tt> scope.")
        self._extlib_edit.setRootIsDecorated(False)
        self._extlib_edit.setEditTriggers(
                QTreeView.DoubleClicked|QTreeView.SelectedClicked|
                QTreeView.EditKeyPressed)

        model = QStandardItemModel(self._extlib_edit)
        model.setHorizontalHeaderLabels(
                ("External Library", 'DEFINES', 'INCLUDEPATH', 'LIBS'))
        model.itemChanged.connect(self._extlib_changed)

        for extlib in external_libraries_metadata:
            name_itm = QStandardItem(extlib.user_name)

            extlib._items = (name_itm, QStandardItem(), QStandardItem(),
                    QStandardItem())

            model.appendRow(extlib._items)

        self._extlib_edit.setModel(model)

        for col in range(3):
            self._extlib_edit.resizeColumnToContents(col)

        extlib_layout.addWidget(self._extlib_edit)

        self._ignore_extlib_changes = False

        extlib_pane.setLayout(extlib_layout)
        self.addWidget(extlib_pane)

    def _update_page(self):
        """ Update the page using the current project. """

        project = self.project

        blocked = self._version_edit.blockSignals(True)
        self._version_edit.setCurrentIndex(
                get_supported_python_version_index(
                        project.python_target_version))
        self._version_edit.blockSignals(blocked)

        blocked = self._ssl_edit.blockSignals(True)
        self._ssl_edit.setCheckState(
                Qt.Checked if project.python_ssl else Qt.Unchecked)
        self._ssl_edit.blockSignals(blocked)

        for plat in self._platform_buttons:
            blocked = plat.blockSignals(True)
            plat.setCheckState(
                    Qt.Checked if plat._scope in project.python_use_platform
                            else Qt.Unchecked)
            plat.blockSignals(blocked)

        self._update_extlib_editor()
        self._update_stdlib_editor()

    def _update_stdlib_editor(self):
        """ Update the standard library module editor. """

        project = self.project
        editor = self._stdlib_edit

        metadata = get_python_metadata(project.python_target_version)

        blocked = editor.blockSignals(True)

        editor.clear()

        def add_module(name, module, parent):
            itm = QTreeWidgetItem(parent, name.split('.')[-1:])
            itm.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
            itm._name = name

            # Handle any sub-modules.
            if module.modules is not None:
                for submodule_name in module.modules:
                    # We assume that a missing sub-module is because it is not
                    # in the current version rather than bad meta-data.
                    submodule = metadata.get(submodule_name)
                    if submodule is not None:
                        add_module(submodule_name, submodule, itm)

        for name, module in metadata.items():
            if not module.internal and '.' not in name:
                add_module(name, module, editor)

        editor.sortItems(0, Qt.AscendingOrder)

        editor.blockSignals(blocked)

        self._set_dependencies()

    def _set_dependencies(self):
        """ Set the dependency information. """

        project = self.project
        editor = self._stdlib_edit

        required_modules, required_libraries = project.get_stdlib_requirements()

        blocked = editor.blockSignals(True)

        it = QTreeWidgetItemIterator(editor)
        itm = it.value()
        while itm is not None:
            external = required_modules.get(itm._name)
            expanded = False
            if external is None:
                state = Qt.Unchecked
            elif external:
                state = Qt.Checked
                expanded = True
            else:
                state = Qt.PartiallyChecked

            itm.setCheckState(0, state)

            # Make sure every explicitly checked item is visible.
            if expanded:
                parent = itm.parent()
                while parent is not None:
                    parent.setExpanded(True)
                    parent = parent.parent()

            it += 1
            itm = it.value()

        editor.blockSignals(blocked)

        model = self._extlib_edit.model()

        # Note that we can't simply block the model's signals as this would
        # interfere with the model/view interactions.
        self._ignore_extlib_changes = True

        for extlib in external_libraries_metadata:
            if extlib.name in required_libraries:
                for idx, itm in enumerate(extlib._items):
                    itm.setFlags(
                            Qt.ItemIsEnabled|Qt.ItemIsEditable if idx != 0
                                    else Qt.ItemIsEnabled)
            else:
                for itm in extlib._items:
                    itm.setFlags(Qt.NoItemFlags)

        self._ignore_extlib_changes = False

    def _update_extlib_editor(self):
        """ Update the external library editor. """

        project = self.project
        model = self._extlib_edit.model()

        blocked = model.blockSignals(True)

        for extlib in external_libraries_metadata:
            _, defs, incp, libs = extlib._items

            for prj_extlib in project.external_libraries:
                if prj_extlib.name == extlib.name:
                    defs.setText(prj_extlib.defines)
                    incp.setText(prj_extlib.includepath)
                    libs.setText(prj_extlib.libs)
                    break
            else:
                defs.setText('')
                incp.setText('')
                libs.setText(extlib.libs)

        model.blockSignals(blocked)

    def _version_changed(self, idx):
        """ Invoked when the target Python version changes. """

        project = self.project

        project.python_target_version = get_supported_python_version(idx)
        self._update_page()

        project.modified = True

    def _ssl_changed(self, state):
        """ Invoked when the SSL support changes. """

        project = self.project

        project.python_ssl = (state == Qt.Checked)
        self._set_dependencies()

        project.modified = True

    def _platforms_changed(self, state):
        """ Invoked when the platforms change. """

        project = self._project

        project.python_use_platform = []

        for plat in self._platform_buttons:
            if plat.checkState() == Qt.Checked:
                project.python_use_platform.append(plat._scope)

        project.modified = True

    def _module_changed(self, itm, col):
        """ Invoked when a standard library module has changed. """

        project = self._project
        name = itm._name

        if name in project.standard_library:
            project.standard_library.remove(name)
        else:
            project.standard_library.append(name)

        self._set_dependencies()

        project.modified = True

    def _extlib_changed(self, itm):
        """ Invoked when an external library has changed. """

        if self._ignore_extlib_changes:
            return

        self._ignore_extlib_changes = True

        project = self.project

        idx = self._extlib_edit.model().indexFromItem(itm)
        extlib = external_libraries_metadata[idx.row()]
        col = idx.column()

        # Get the project entry, creating it if necessary.
        for prj_extlib in project.external_libraries:
            if prj_extlib.name == extlib.name:
                break
        else:
            prj_extlib = ExternalLibrary(extlib.name, '', '', extlib.libs)
            project.external_libraries.append(prj_extlib)

        # Update the project.
        text = itm.text().strip()

        if col == 1:
            prj_extlib.defines = text
        elif col == 2:
            prj_extlib.includepath = text
        elif col == 3:
            if text == '':
                text = extlib.libs
                itm.setText(text)

            prj_extlib.libs = text

        # If the project entry corresponds to the default then remove it.
        if prj_extlib.defines == '' and prj_extlib.includepath == '' and prj_extlib.libs == extlib.libs:
            project.external_libraries.remove(prj_extlib)

        project.modified = True

        self._ignore_extlib_changes = False
class AttributeController(QMainWindow):
    _visibility_dict = {
        'Beginner': EVisibility.Beginner,
        'Expert': EVisibility.Expert,
        'Guru': EVisibility.Guru,
        'All': EVisibility.Invisible,
    }

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

        #
        self.setWindowTitle('Attribute Controller')

        #
        self._view = QTreeView()
        self._view.setFont(get_system_font())

        #
        self._node_map = node_map
        self._model = FeatureTreeModel(node_map=self._node_map, )

        #
        self._proxy = FilterProxyModel()
        self._proxy.setSourceModel(self._model)
        self._proxy.setDynamicSortFilter(False)

        #
        self._delegate = FeatureEditDelegate(proxy=self._proxy)
        self._view.setModel(self._proxy)
        self._view.setItemDelegate(self._delegate)
        self._view.setUniformRowHeights(True)

        #
        unit = 260
        for i in range(2):
            self._view.setColumnWidth(i, unit)

        w, h = 700, 600
        self._view.setGeometry(100, 100, w, h)

        self.setCentralWidget(self._view)
        self.setGeometry(100, 100, unit * 2, 640)

        self._combo_box_visibility = None
        self._line_edit_search_box = None

        #
        self._setup_toolbars()

    def _setup_toolbars(self):
        #
        group_filter = self.addToolBar('Node Visibility')
        group_manipulation = self.addToolBar('Node Tree Manipulation')

        #
        label_visibility = QLabel()
        label_visibility.setText('Visibility')
        label_visibility.setFont(get_system_font())

        #
        self._combo_box_visibility = QComboBox()
        self._combo_box_visibility.setSizeAdjustPolicy(
            QComboBox.AdjustToContents)

        #
        items = ('Beginner', 'Expert', 'Guru', 'All')
        for item in items:
            self._combo_box_visibility.addItem(item)

        shortcut_key = 'Ctrl+v'
        shortcut = QShortcut(QKeySequence(shortcut_key), self)

        def show_popup():
            self._combo_box_visibility.showPopup()

        shortcut.activated.connect(show_popup)

        self._combo_box_visibility.setToolTip(
            compose_tooltip('Filter the nodes to show', shortcut_key))
        self._combo_box_visibility.setFont(get_system_font())
        self._combo_box_visibility.currentIndexChanged.connect(
            self._invalidate_feature_tree_by_visibility)

        #
        button_expand_all = ActionExpandAll(icon='expand.png',
                                            title='Expand All',
                                            parent=self,
                                            action=self.action_on_expand_all)
        shortcut_key = 'Ctrl+e'
        button_expand_all.setToolTip(
            compose_tooltip('Expand the node tree', shortcut_key))
        button_expand_all.setShortcut(shortcut_key)
        button_expand_all.toggle()

        #
        button_collapse_all = ActionCollapseAll(
            icon='collapse.png',
            title='Collapse All',
            parent=self,
            action=self.action_on_collapse_all)
        shortcut_key = 'Ctrl+c'
        button_collapse_all.setToolTip(
            compose_tooltip('Collapse the node tree', shortcut_key))
        button_collapse_all.setShortcut(shortcut_key)
        button_collapse_all.toggle()

        #
        label_search = QLabel()
        label_search.setText('RegEx Search')
        label_search.setFont(get_system_font())

        #
        self._line_edit_search_box = QLineEdit()
        self._line_edit_search_box.setFont(get_system_font())
        self._line_edit_search_box.textEdited.connect(
            self._invalidate_feature_tree_by_keyword)

        #
        group_filter.addWidget(label_visibility)
        group_filter.addWidget(self._combo_box_visibility)
        group_filter.addWidget(label_search)
        group_filter.addWidget(self._line_edit_search_box)
        group_filter.setStyleSheet('QToolBar{spacing:6px;}')

        #
        group_manipulation.addAction(button_expand_all)
        group_manipulation.addAction(button_collapse_all)

        #
        group_manipulation.actionTriggered[QAction].connect(
            self.on_button_clicked_action)

        #
        self._combo_box_visibility.setCurrentIndex(
            self._visibility_dict['Expert'])

    def _invalidate_feature_tree_by_visibility(self):
        visibility = self._visibility_dict[
            self._combo_box_visibility.currentText()]
        self._proxy.setVisibility(visibility)
        self._view.expandAll()

    @pyqtSlot('QString')
    def _invalidate_feature_tree_by_keyword(self, keyword):
        self._proxy.setKeyword(keyword)
        self._view.expandAll()

    @staticmethod
    def on_button_clicked_action(action):
        action.execute()

    def expand_all(self):
        self._view.expandAll()

    def collapse_all(self):
        self._view.collapseAll()

    def resize_column_width(self):
        for i in range(self._model.columnCount()):
            self._view.resizeColumnToContents(i)

    def action_on_expand_all(self):
        self.expand_all()

    def action_on_collapse_all(self):
        self.collapse_all()
예제 #8
0
파일: tdm.py 프로젝트: kylegordon/tdm
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self._version = "0.1.20"
        self.setWindowIcon(QIcon("GUI/icons/logo.png"))
        self.setWindowTitle("Tasmota Device Manager {}".format(self._version))

        self.main_splitter = QSplitter()
        self.devices_splitter = QSplitter(Qt.Vertical)

        self.mqtt_queue = []
        self.devices = {}

        self.fulltopic_queue = []
        old_settings = QSettings()

        self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()),
                                  QSettings.IniFormat)
        self.setMinimumSize(QSize(1280, 800))

        for k in old_settings.allKeys():
            self.settings.setValue(k, old_settings.value(k))
            old_settings.remove(k)

        self.device_model = TasmotaDevicesModel()
        self.telemetry_model = TasmotaDevicesTree()
        self.console_model = ConsoleModel()

        self.sorted_console_model = QSortFilterProxyModel()
        self.sorted_console_model.setSourceModel(self.console_model)
        self.sorted_console_model.setFilterKeyColumn(CnsMdl.FRIENDLY_NAME)

        self.setup_mqtt()
        self.setup_telemetry_view()
        self.setup_main_layout()
        self.add_devices_tab()
        self.build_toolbars()
        self.setStatusBar(QStatusBar())

        self.queue_timer = QTimer()
        self.queue_timer.timeout.connect(self.mqtt_publish_queue)
        self.queue_timer.start(500)

        self.auto_timer = QTimer()
        self.auto_timer.timeout.connect(self.autoupdate)

        self.load_window_state()

        if self.settings.value("connect_on_startup", False, bool):
            self.actToggleConnect.trigger()

    def setup_main_layout(self):
        self.mdi = QMdiArea()
        self.mdi.setActivationOrder(QMdiArea.ActivationHistoryOrder)
        self.mdi.setViewMode(QMdiArea.TabbedView)
        self.mdi.setDocumentMode(True)

        mdi_widget = QWidget()
        mdi_widget.setLayout(VLayout())
        mdi_widget.layout().addWidget(self.mdi)

        self.devices_splitter.addWidget(mdi_widget)

        vl_console = VLayout()
        hl_filter = HLayout()
        self.cbFilter = QCheckBox("Console filtering")
        self.cbxFilterDevice = QComboBox()
        self.cbxFilterDevice.setEnabled(False)
        self.cbxFilterDevice.setFixedWidth(200)
        self.cbxFilterDevice.setModel(self.device_model)
        self.cbxFilterDevice.setModelColumn(DevMdl.FRIENDLY_NAME)
        hl_filter.addWidgets([self.cbFilter, self.cbxFilterDevice])
        hl_filter.addStretch(0)
        vl_console.addLayout(hl_filter)

        self.console_view = TableView()
        self.console_view.setModel(self.console_model)
        self.console_view.setupColumns(columns_console)
        self.console_view.setAlternatingRowColors(True)
        self.console_view.verticalHeader().setDefaultSectionSize(20)
        self.console_view.setMinimumHeight(200)

        vl_console.addWidget(self.console_view)

        console_widget = QWidget()
        console_widget.setLayout(vl_console)

        self.devices_splitter.addWidget(console_widget)
        self.main_splitter.insertWidget(0, self.devices_splitter)
        self.setCentralWidget(self.main_splitter)
        self.console_view.clicked.connect(self.select_cons_entry)
        self.console_view.doubleClicked.connect(self.view_payload)

        self.cbFilter.toggled.connect(self.toggle_console_filter)
        self.cbxFilterDevice.currentTextChanged.connect(
            self.select_console_filter)

    def setup_telemetry_view(self):
        tele_widget = QWidget()
        vl_tele = VLayout()
        self.tview = QTreeView()
        self.tview.setMinimumWidth(300)
        self.tview.setModel(self.telemetry_model)
        self.tview.setAlternatingRowColors(True)
        self.tview.setUniformRowHeights(True)
        self.tview.setIndentation(15)
        self.tview.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum))
        self.tview.expandAll()
        self.tview.resizeColumnToContents(0)
        vl_tele.addWidget(self.tview)
        tele_widget.setLayout(vl_tele)
        self.main_splitter.addWidget(tele_widget)

    def setup_mqtt(self):
        self.mqtt = MqttClient()
        self.mqtt.connecting.connect(self.mqtt_connecting)
        self.mqtt.connected.connect(self.mqtt_connected)
        self.mqtt.disconnected.connect(self.mqtt_disconnected)
        self.mqtt.connectError.connect(self.mqtt_connectError)
        self.mqtt.messageSignal.connect(self.mqtt_message)

    def add_devices_tab(self):
        tabDevicesList = DevicesListWidget(self)
        self.mdi.addSubWindow(tabDevicesList)
        tabDevicesList.setWindowState(Qt.WindowMaximized)

    def load_window_state(self):
        wndGeometry = self.settings.value('window_geometry')
        if wndGeometry:
            self.restoreGeometry(wndGeometry)
        spltState = self.settings.value('splitter_state')
        if spltState:
            self.main_splitter.restoreState(spltState)

    def build_toolbars(self):
        main_toolbar = Toolbar(orientation=Qt.Horizontal,
                               iconsize=16,
                               label_position=Qt.ToolButtonTextBesideIcon)
        main_toolbar.setObjectName("main_toolbar")
        self.addToolBar(main_toolbar)

        main_toolbar.addAction(QIcon("./GUI/icons/connections.png"), "Broker",
                               self.setup_broker)
        self.actToggleConnect = QAction(QIcon("./GUI/icons/disconnect.png"),
                                        "MQTT")
        self.actToggleConnect.setCheckable(True)
        self.actToggleConnect.toggled.connect(self.toggle_connect)
        main_toolbar.addAction(self.actToggleConnect)

        self.actToggleAutoUpdate = QAction(QIcon("./GUI/icons/automatic.png"),
                                           "Auto telemetry")
        self.actToggleAutoUpdate.setCheckable(True)
        self.actToggleAutoUpdate.toggled.connect(self.toggle_autoupdate)
        main_toolbar.addAction(self.actToggleAutoUpdate)

        main_toolbar.addSeparator()
        main_toolbar.addAction(QIcon("./GUI/icons/bssid.png"), "BSSId",
                               self.bssid)
        main_toolbar.addAction(QIcon("./GUI/icons/export.png"), "Export list",
                               self.export)

    def initial_query(self, idx, queued=False):
        for q in initial_queries:
            topic = "{}status".format(self.device_model.commandTopic(idx))
            if queued:
                self.mqtt_queue.append([topic, q])
            else:
                self.mqtt.publish(topic, q, 1)
            self.console_log(topic, "Asked for STATUS {}".format(q), q)

    def setup_broker(self):
        brokers_dlg = BrokerDialog()
        if brokers_dlg.exec_(
        ) == QDialog.Accepted and self.mqtt.state == self.mqtt.Connected:
            self.mqtt.disconnect()

    def toggle_autoupdate(self, state):
        if state:
            self.auto_timer.setInterval(5000)
            self.auto_timer.start()

    def toggle_connect(self, state):
        if state and self.mqtt.state == self.mqtt.Disconnected:
            self.broker_hostname = self.settings.value('hostname', 'localhost')
            self.broker_port = self.settings.value('port', 1883, int)
            self.broker_username = self.settings.value('username')
            self.broker_password = self.settings.value('password')

            self.mqtt.hostname = self.broker_hostname
            self.mqtt.port = self.broker_port

            if self.broker_username:
                self.mqtt.setAuth(self.broker_username, self.broker_password)
            self.mqtt.connectToHost()
        elif not state and self.mqtt.state == self.mqtt.Connected:
            self.mqtt_disconnect()

    def autoupdate(self):
        if self.mqtt.state == self.mqtt.Connected:
            for d in range(self.device_model.rowCount()):
                idx = self.device_model.index(d, 0)
                cmnd = self.device_model.commandTopic(idx)
                self.mqtt.publish(cmnd + "STATUS", payload=8)

    def mqtt_connect(self):
        self.broker_hostname = self.settings.value('hostname', 'localhost')
        self.broker_port = self.settings.value('port', 1883, int)
        self.broker_username = self.settings.value('username')
        self.broker_password = self.settings.value('password')

        self.mqtt.hostname = self.broker_hostname
        self.mqtt.port = self.broker_port

        if self.broker_username:
            self.mqtt.setAuth(self.broker_username, self.broker_password)

        if self.mqtt.state == self.mqtt.Disconnected:
            self.mqtt.connectToHost()

    def mqtt_disconnect(self):
        self.mqtt.disconnectFromHost()

    def mqtt_connecting(self):
        self.statusBar().showMessage("Connecting to broker")

    def mqtt_connected(self):
        self.actToggleConnect.setIcon(QIcon("./GUI/icons/connect.png"))
        self.statusBar().showMessage("Connected to {}:{} as {}".format(
            self.broker_hostname, self.broker_port,
            self.broker_username if self.broker_username else '[anonymous]'))

        self.mqtt_subscribe()

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            self.initial_query(idx)

    def mqtt_subscribe(self):
        main_topics = ["+/stat/+", "+/tele/+", "stat/#", "tele/#"]

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            if not self.device_model.isDefaultTemplate(idx):
                main_topics.append(self.device_model.commandTopic(idx))
                main_topics.append(self.device_model.statTopic(idx))

        for t in main_topics:
            self.mqtt.subscribe(t)

    def mqtt_publish_queue(self):
        for q in self.mqtt_queue:
            t, p = q
            self.mqtt.publish(t, p)
            self.mqtt_queue.pop(self.mqtt_queue.index(q))

    def mqtt_disconnected(self):
        self.actToggleConnect.setIcon(QIcon("./GUI/icons/disconnect.png"))
        self.statusBar().showMessage("Disconnected")

    def mqtt_connectError(self, rc):
        reason = {
            1: "Incorrect protocol version",
            2: "Invalid client identifier",
            3: "Server unavailable",
            4: "Bad username or password",
            5: "Not authorized",
        }
        self.statusBar().showMessage("Connection error: {}".format(reason[rc]))
        self.actToggleConnect.setChecked(False)

    def mqtt_message(self, topic, msg):
        found = self.device_model.findDevice(topic)
        if found.reply == 'LWT':
            if not msg:
                msg = "offline"

            if found.index.isValid():
                self.console_log(topic, "LWT update: {}".format(msg), msg)
                self.device_model.updateValue(found.index, DevMdl.LWT, msg)
                self.initial_query(found.index, queued=True)

            elif msg == "Online":
                self.console_log(
                    topic,
                    "LWT for unknown device '{}'. Asking for FullTopic.".
                    format(found.topic), msg, False)
                self.mqtt_queue.append(
                    ["cmnd/{}/fulltopic".format(found.topic), ""])
                self.mqtt_queue.append(
                    ["{}/cmnd/fulltopic".format(found.topic), ""])

        elif found.reply == 'RESULT':
            try:
                full_topic = loads(msg).get('FullTopic')
                new_topic = loads(msg).get('Topic')
                template_name = loads(msg).get('NAME')
                ota_url = loads(msg).get('OtaUrl')
                teleperiod = loads(msg).get('TelePeriod')

                if full_topic:
                    # TODO: update FullTopic for existing device AFTER the FullTopic changes externally (the message will arrive from new FullTopic)
                    if not found.index.isValid():
                        self.console_log(
                            topic, "FullTopic for {}".format(found.topic), msg,
                            False)

                        new_idx = self.device_model.addDevice(found.topic,
                                                              full_topic,
                                                              lwt='online')
                        tele_idx = self.telemetry_model.addDevice(
                            TasmotaDevice, found.topic)
                        self.telemetry_model.devices[found.topic] = tele_idx
                        #TODO: add QSortFilterProxyModel to telemetry treeview and sort devices after adding

                        self.initial_query(new_idx)
                        self.console_log(
                            topic,
                            "Added {} with fulltopic {}, querying for STATE".
                            format(found.topic, full_topic), msg)
                        self.tview.expand(tele_idx)
                        self.tview.resizeColumnToContents(0)

                elif new_topic:
                    if found.index.isValid() and found.topic != new_topic:
                        self.console_log(
                            topic, "New topic for {}".format(found.topic), msg)

                        self.device_model.updateValue(found.index,
                                                      DevMdl.TOPIC, new_topic)

                        tele_idx = self.telemetry_model.devices.get(
                            found.topic)

                        if tele_idx:
                            self.telemetry_model.setDeviceName(
                                tele_idx, new_topic)
                            self.telemetry_model.devices[
                                new_topic] = self.telemetry_model.devices.pop(
                                    found.topic)

                elif template_name:
                    self.device_model.updateValue(
                        found.index, DevMdl.MODULE,
                        "{} (0)".format(template_name))

                elif ota_url:
                    self.device_model.updateValue(found.index, DevMdl.OTA_URL,
                                                  ota_url)

                elif teleperiod:
                    self.device_model.updateValue(found.index,
                                                  DevMdl.TELEPERIOD,
                                                  teleperiod)

            except JSONDecodeError as e:
                self.console_log(
                    topic,
                    "JSON payload decode error. Check error.log for additional info."
                )
                with open("{}/TDM/error.log".format(QDir.homePath()),
                          "a+") as l:
                    l.write("{}\t{}\t{}\t{}\n".format(
                        QDateTime.currentDateTime().toString(
                            "yyyy-MM-dd hh:mm:ss"), topic, msg, e.msg))

        elif found.index.isValid():
            ok = False
            try:
                if msg.startswith("{"):
                    payload = loads(msg)
                else:
                    payload = msg
                ok = True
            except JSONDecodeError as e:
                self.console_log(
                    topic,
                    "JSON payload decode error. Check error.log for additional info."
                )
                with open("{}/TDM/error.log".format(QDir.homePath()),
                          "a+") as l:
                    l.write("{}\t{}\t{}\t{}\n".format(
                        QDateTime.currentDateTime().toString(
                            "yyyy-MM-dd hh:mm:ss"), topic, msg, e.msg))

            if ok:
                try:
                    if found.reply == 'STATUS':
                        self.console_log(topic, "Received device status", msg)
                        payload = payload['Status']
                        self.device_model.updateValue(
                            found.index, DevMdl.FRIENDLY_NAME,
                            payload['FriendlyName'][0])
                        self.telemetry_model.setDeviceFriendlyName(
                            self.telemetry_model.devices[found.topic],
                            payload['FriendlyName'][0])
                        module = payload['Module']
                        if module == 0:
                            self.mqtt.publish(
                                self.device_model.commandTopic(found.index) +
                                "template")
                        else:
                            self.device_model.updateValue(
                                found.index, DevMdl.MODULE,
                                modules.get(module, 'Unknown'))
                        self.device_model.updateValue(found.index,
                                                      DevMdl.MODULE_ID, module)

                    elif found.reply == 'STATUS1':
                        self.console_log(topic, "Received program information",
                                         msg)
                        payload = payload['StatusPRM']
                        self.device_model.updateValue(
                            found.index, DevMdl.RESTART_REASON,
                            payload.get('RestartReason'))
                        self.device_model.updateValue(found.index,
                                                      DevMdl.OTA_URL,
                                                      payload.get('OtaUrl'))

                    elif found.reply == 'STATUS2':
                        self.console_log(topic,
                                         "Received firmware information", msg)
                        payload = payload['StatusFWR']
                        self.device_model.updateValue(found.index,
                                                      DevMdl.FIRMWARE,
                                                      payload['Version'])
                        self.device_model.updateValue(found.index, DevMdl.CORE,
                                                      payload['Core'])

                    elif found.reply == 'STATUS3':
                        self.console_log(topic, "Received syslog information",
                                         msg)
                        payload = payload['StatusLOG']
                        self.device_model.updateValue(found.index,
                                                      DevMdl.TELEPERIOD,
                                                      payload['TelePeriod'])

                    elif found.reply == 'STATUS5':
                        self.console_log(topic, "Received network status", msg)
                        payload = payload['StatusNET']
                        self.device_model.updateValue(found.index, DevMdl.MAC,
                                                      payload['Mac'])
                        self.device_model.updateValue(found.index, DevMdl.IP,
                                                      payload['IPAddress'])

                    elif found.reply in ('STATE', 'STATUS11'):
                        self.console_log(topic, "Received device state", msg)
                        if found.reply == 'STATUS11':
                            payload = payload['StatusSTS']
                        self.parse_state(found.index, payload)

                    elif found.reply in ('SENSOR', 'STATUS8'):
                        self.console_log(topic, "Received telemetry", msg)
                        if found.reply == 'STATUS8':
                            payload = payload['StatusSNS']
                        self.parse_telemetry(found.index, payload)

                    elif found.reply.startswith('POWER'):
                        self.console_log(
                            topic, "Received {} state".format(found.reply),
                            msg)
                        payload = {found.reply: msg}
                        self.parse_power(found.index, payload)

                except KeyError as k:
                    self.console_log(
                        topic,
                        "JSON key error. Check error.log for additional info.")
                    with open("{}/TDM/error.log".format(QDir.homePath()),
                              "a+") as l:
                        l.write("{}\t{}\t{}\tKeyError: {}\n".format(
                            QDateTime.currentDateTime().toString(
                                "yyyy-MM-dd hh:mm:ss"), topic, payload,
                            k.args[0]))

    def parse_power(self, index, payload, from_state=False):
        old = self.device_model.power(index)
        power = {
            k: payload[k]
            for k in payload.keys() if k.startswith("POWER")
        }
        # TODO: fix so that number of relays get updated properly after module/no. of relays change
        needs_update = False
        if old:
            # if from_state and len(old) != len(power):
            #     needs_update = True
            #
            # else:
            for k in old.keys():
                needs_update |= old[k] != power.get(k, old[k])
                if needs_update:
                    break
        else:
            needs_update = True

        if needs_update:
            self.device_model.updateValue(index, DevMdl.POWER, power)

    def parse_state(self, index, payload):
        bssid = payload['Wifi'].get('BSSId')
        if not bssid:
            bssid = payload['Wifi'].get('APMac')
        self.device_model.updateValue(index, DevMdl.BSSID, bssid)
        self.device_model.updateValue(index, DevMdl.SSID,
                                      payload['Wifi']['SSId'])
        self.device_model.updateValue(index, DevMdl.CHANNEL,
                                      payload['Wifi'].get('Channel', "n/a"))
        self.device_model.updateValue(index, DevMdl.RSSI,
                                      payload['Wifi']['RSSI'])
        self.device_model.updateValue(index, DevMdl.UPTIME, payload['Uptime'])
        self.device_model.updateValue(index, DevMdl.LOADAVG,
                                      payload.get('LoadAvg'))
        self.device_model.updateValue(index, DevMdl.LINKCOUNT,
                                      payload['Wifi'].get('LinkCount', "n/a"))
        self.device_model.updateValue(index, DevMdl.DOWNTIME,
                                      payload['Wifi'].get('Downtime', "n/a"))

        self.parse_power(index, payload, True)

        tele_idx = self.telemetry_model.devices.get(
            self.device_model.topic(index))

        if tele_idx:
            tele_device = self.telemetry_model.getNode(tele_idx)
            self.telemetry_model.setDeviceFriendlyName(
                tele_idx, self.device_model.friendly_name(index))

            pr = tele_device.provides()
            for k in pr.keys():
                self.telemetry_model.setData(pr[k], payload.get(k))

    def parse_telemetry(self, index, payload):
        device = self.telemetry_model.devices.get(
            self.device_model.topic(index))
        if device:
            node = self.telemetry_model.getNode(device)
            time = node.provides()['Time']
            if 'Time' in payload:
                self.telemetry_model.setData(time, payload.pop('Time'))

            temp_unit = "C"
            pres_unit = "hPa"

            if 'TempUnit' in payload:
                temp_unit = payload.pop('TempUnit')

            if 'PressureUnit' in payload:
                pres_unit = payload.pop('PressureUnit')

            for sensor in sorted(payload.keys()):
                if sensor == 'DS18x20':
                    for sns_name in payload[sensor].keys():
                        d = node.devices().get(sensor)
                        if not d:
                            d = self.telemetry_model.addDevice(
                                DS18x20, payload[sensor][sns_name]['Type'],
                                device)
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                        payload[sensor][sns_name]['Id'] = payload[sensor][
                            sns_name].pop('Address')

                        pr = self.telemetry_model.getNode(d).provides()
                        for pk in pr.keys():
                            self.telemetry_model.setData(
                                pr[pk], payload[sensor][sns_name].get(pk))
                        self.tview.expand(d)

                elif sensor.startswith('DS18B20'):
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            DS18x20, sensor, device)
                    self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)

                if sensor == 'COUNTER':
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            CounterSns, "Counter", device)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)

                else:
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            sensor_map.get(sensor, Node), sensor, device)
                    pr = self.telemetry_model.getNode(d).provides()
                    if 'Temperature' in pr:
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    if 'Pressure' in pr or 'SeaPressure' in pr:
                        self.telemetry_model.getNode(d).setPresUnit(pres_unit)
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)
        # self.tview.resizeColumnToContents(0)

    def console_log(self, topic, description, payload="", known=True):
        longest_tp = 0
        longest_fn = 0
        short_topic = "/".join(topic.split("/")[0:-1])
        fname = self.devices.get(short_topic, "")
        if not fname:
            device = self.device_model.findDevice(topic)
            fname = self.device_model.friendly_name(device.index)
            self.devices.update({short_topic: fname})
        self.console_model.addEntry(topic, fname, description, payload, known)

        if len(topic) > longest_tp:
            longest_tp = len(topic)
            self.console_view.resizeColumnToContents(1)

        if len(fname) > longest_fn:
            longest_fn = len(fname)
            self.console_view.resizeColumnToContents(1)

    def view_payload(self, idx):
        if self.cbFilter.isChecked():
            idx = self.sorted_console_model.mapToSource(idx)
        row = idx.row()
        timestamp = self.console_model.data(
            self.console_model.index(row, CnsMdl.TIMESTAMP))
        topic = self.console_model.data(
            self.console_model.index(row, CnsMdl.TOPIC))
        payload = self.console_model.data(
            self.console_model.index(row, CnsMdl.PAYLOAD))

        dlg = PayloadViewDialog(timestamp, topic, payload)
        dlg.exec_()

    def select_cons_entry(self, idx):
        self.cons_idx = idx

    def export(self):
        fname, _ = QFileDialog.getSaveFileName(self,
                                               "Export device list as...",
                                               directory=QDir.homePath(),
                                               filter="CSV files (*.csv)")
        if fname:
            if not fname.endswith(".csv"):
                fname += ".csv"

            with open(fname, "w", encoding='utf8') as f:
                column_titles = [
                    'mac', 'topic', 'friendly_name', 'full_topic',
                    'cmnd_topic', 'stat_topic', 'tele_topic', 'module',
                    'module_id', 'firmware', 'core'
                ]
                c = csv.writer(f)
                c.writerow(column_titles)

                for r in range(self.device_model.rowCount()):
                    d = self.device_model.index(r, 0)
                    c.writerow([
                        self.device_model.mac(d),
                        self.device_model.topic(d),
                        self.device_model.friendly_name(d),
                        self.device_model.fullTopic(d),
                        self.device_model.commandTopic(d),
                        self.device_model.statTopic(d),
                        self.device_model.teleTopic(d),
                        modules.get(self.device_model.module(d)),
                        self.device_model.module(d),
                        self.device_model.firmware(d),
                        self.device_model.core(d)
                    ])

    def bssid(self):
        BSSIdDialog().exec_()
        # if dlg.exec_() == QDialog.Accepted:

    def toggle_console_filter(self, state):
        self.cbxFilterDevice.setEnabled(state)
        if state:
            self.console_view.setModel(self.sorted_console_model)
        else:
            self.console_view.setModel(self.console_model)

    def select_console_filter(self, fname):
        self.sorted_console_model.setFilterFixedString(fname)

    def closeEvent(self, e):
        self.settings.setValue("window_geometry", self.saveGeometry())
        self.settings.setValue("splitter_state",
                               self.main_splitter.saveState())
        self.settings.sync()
        e.accept()
예제 #9
0
class DanaBrowseWindow(QMainWindow):
    def __init__(self, args):
        super(DanaBrowseWindow, self).__init__()
        #Leeo la configuracion
        self.configFile = args.configFile
        self.secure = args.secure
        self.cubeFile = args.cubeFile
        self.sysExclude = args.sysExclude

        self.maxLevel = 1  #para poder modificarlo luego
        self.dictionary = DataDict(defFile=args.configFile,
                                   secure=args.secure,
                                   sysExclude=args.sysExclude)
        #TODO variables asociadas del diccionario. Reevaluar al limpiar

        self.baseModel = self.dictionary.baseModel
        self.configData = self.dictionary.configData
        self.conn = self.dictionary.conn
        if self.dictionary.isEmpty:
            self.newConfigData()
            #self.dictionary._cargaModelo(self.dictionary.baseModel)
        self.setupView()
        self.cubeMgr = None  # necesito mas adelante que este definida
        if config.DEBUG:
            print('inicializacion completa')
        #CHANGE here

        self.queryView = TableBrowse(None)

        self.dictMenu = self.menuBar().addMenu("&Conexiones")
        self.dictMenu.addAction("&New ...", self.newConnection, "Ctrl+N")
        self.dictMenu.addAction("&Modify ...", self.modConnection, "Ctrl+M")
        self.dictMenu.addAction("&Delete ...", self.delConnection, "Ctrl+D")
        self.dictMenu.addAction("&Save Config File", self.saveConfigFile,
                                "Ctrl+S")
        self.dictMenu.addAction("E&xit", self.close, "Ctrl+Q")

        self.queryMenu = self.menuBar().addMenu('Consulta de &datos')
        self.queryMenu.addAction("Cerrar", self.hideDatabrowse)
        self.queryMenu.setEnabled(False)

        self.cubeMenu = self.menuBar().addMenu("C&ubo")
        self.cubeMenu.addAction("&Salvar", self.saveCubeFile, "Ctrl+S")
        #self.cubeMenu.addAction("&Restaurar", self.restoreCubeFile, "Ctrl+M")
        self.cubeMenu.addAction("S&alir", self.hideCube, "Ctrl+C")
        self.cubeMenu.addSeparator()
        self.cubeMenu.addAction("Ver &ejemplo de datos del cubo",
                                self.previewCube, "Ctrl+E")
        self.cubeMenu.setEnabled(False)

        #self.queryModel = self.queryView.baseModel

        self.querySplitter = QSplitter(Qt.Vertical)
        self.querySplitter.addWidget(self.view)
        #self.querySplitter.addWidget(self.queryView)

        self.configSplitter = QSplitter(Qt.Horizontal)
        self.configSplitter.addWidget(self.querySplitter)

        self.setCentralWidget(self.configSplitter)

        self.setWindowTitle("Visualizador de base de datos")
        """
            estas funciones son para soportar para los decoradores keep position y tal
        """

    def model(self):
        return self.baseModel

    def isExpanded(self, idx):
        return self.view.isExpanded(idx)

    def setExpanded(self, idx, state):
        return self.view.setExpanded(idx, state)

    def setupView(self):
        self.view = QTreeView(self)
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.openContextMenu)
        self.view.doubleClicked.connect(self.test)
        self.view.setModel(self.baseModel)
        #self.view.resizeColumnToContents(0)
        self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)

        self.view.expandAll()
        for m in range(self.baseModel.columnCount()):
            self.view.resizeColumnToContents(m)
        self.view.collapseAll()
        self.view.expandToDepth(0)
        #self.view.setHeaderHidden(True)
        #self.view.setSortingEnabled(True)
        #self.view.setRootIsDecorated(False)
        self.view.setAlternatingRowColors(True)
        #self.view.sortByColumn(0, Qt.AscendingOrder)

    def newConfigData(self):
        self.configData = dict()
        self.configData['Conexiones'] = dict()
        self.editConnection(None)
        if self.configData['Conexiones']:
            self.saveConfigFile()
            print(self.configData)
            self.dictionary._cargaModelo(confData=self.configData['Conexiones']
                                         )  #self.dictionary.baseModel)
        else:
            QMessageBox.critical(
                self, "Error Fatal",
                "No se ha encontrado una conexión valida.\nFin de proceso")
            self.close()

    def saveConfigFile(self):
        dump_config(self.configData,
                    getConfigFileName(self.configFile),
                    secure=self.secure)  #TODO de momento

    def closeEvent(self, event):
        self.close()

    def close(self):

        if self.cubeMgr:
            self.saveConfigFile()

        for conid in self.conn:
            if self.conn[conid] is None:
                continue
            if self.conn[conid].closed:
                self.conn[conid].close()
        self.saveConfigFile()

        sys.exit()

    def newConnection(self):
        confName = self.editConnection(None)
        # esta claro que sobran parametros
        self.dictionary.appendConnection(confName)

    def modConnection(self, nombre=None):
        if nombre is None:
            selDialog = SelectConnectionDlg(self.configData['Conexiones'])
            if selDialog.exec_():
                confName = selDialog.conexion
            else:
                return
        else:
            confName = nombre
        self.editConnection(confName)
        self.updateModel(confName)

    @waiting_effects
    def updateModel(self, nombre=None):
        self.dictionary.updateModel(nombre)

    def delConnection(self, nombre=None):
        if nombre is None:
            selDialog = SelectConnectionDlg(self.configData['Conexiones'])
            if selDialog.exec_():
                confName = selDialog.conexion
            else:
                return
        else:
            confName = nombre
        self.dictionary.dropConnection(confName)

    def editConnection(self, nombre=None):
        attr_list = ('driver', 'dbname', 'dbhost', 'dbuser', 'dbpass',
                     'dbport', 'debug')
        if nombre is None:
            datos = [None for k in range(len(attr_list) + 1)]
        else:
            datos = [
                nombre,
            ] + dict2row(self.configData['Conexiones'][nombre], attr_list)
            datos[1] = DRIVERS.index(datos[1])
        #contexto
        context = (
            (
                'Nombre',
                QLineEdit,
                {
                    'setReadOnly': True
                } if nombre is not None else None,
                None,
            ),
            # driver
            (
                "Driver ",
                QComboBox,
                None,
                DRIVERS,
            ),
            (
                "DataBase Name",
                QLineEdit,
                None,
                None,
            ),
            (
                "Host",
                QLineEdit,
                None,
                None,
            ),
            (
                "User",
                QLineEdit,
                None,
                None,
            ),
            (
                "Password",
                QLineEdit,
                {
                    'setEchoMode': QLineEdit.Password
                },
                None,
            ),
            (
                "Port",
                QLineEdit,
                None,
                None,
            ),
            (
                "Debug",
                QCheckBox,
                None,
                None,
            ))
        parmDialog = ConnectionSheetDlg('Edite la conexion', context, datos,
                                        self)
        if parmDialog.exec_():
            #TODO deberia verificar que se han cambiado los datos
            #datos[1]=DRIVERS[datos[1]]
            self.configData['Conexiones'][datos[0]] = row2dict(
                datos[1:], attr_list)
            return datos[0]

    @keep_tree_layout()
    def openContextMenu(self, position):
        """
        """
        item = None
        indexes = self.view.selectedIndexes()
        if len(indexes) > 0:
            index = indexes[0]
            item = self.baseModel.itemFromIndex(index)
        menu = QMenu()
        if item:
            item.setMenuActions(menu, self)
            action = menu.exec_(self.view.viewport().mapToGlobal(position))
        #getContextMenu(item,action,self)

    @waiting_effects
    def databrowse(self, confName, schema, table, iters=0):
        #print(confName,schema,table,self.dictionary.conn[confName])
        self.queryView.reconnect(
            self.queryView.getConnection(self.dictionary, confName, schema,
                                         table, iters))
        self.queryView.executeNewScript(
            self.queryView.generateSQL(confName,
                                       schema,
                                       table,
                                       iters,
                                       pFilter=None))
        if self.queryView.isHidden():
            self.queryView.show()
        self.queryMenu.setEnabled(True)
        if self.querySplitter.count(
        ) == 1:  #de momento parece un modo sencillo de no multiplicar en exceso
            self.querySplitter.addWidget(self.queryView)

    def hideDatabrowse(self):
        self.queryView.hide()
        self.queryMenu.setEnabled(False)

    def prepareNewCube(self, confName, schema, table):
        # aqui tiene que venir un dialogo para seleccionar nombre del cubo
        maxLevel = self.maxLevel
        parmDlg = GenerationSheetDlg('Parámetros de generación', table,
                                     maxLevel)
        if parmDlg.exec_():
            kname = parmDlg.data[0]
            maxLevel = parmDlg.data[1]
        infox = info2cube(self.dictionary, confName, schema, table, maxLevel)
        if kname != table:
            infox[kname] = infox.pop(table)
        return infox

    def cubebrowse(self, confName, schema, table):

        infox = self.prepareNewCube(confName, schema, table)
        if self.cubeMgr and not self.cubeMgr.isHidden():
            self.hideCube()
        self.cubeMgr = CubeMgr(self,
                               confName,
                               schema,
                               table,
                               self.dictionary,
                               rawCube=infox,
                               cubeFile=self.cubeFile)
        self.cubeMgr.expandToDepth(1)
        #if self.configSplitter.count() == 1:  #de momento parece un modo sencillo de no multiplicar en exceso
        self.configSplitter.addWidget(self.cubeMgr)

        self.cubeMgr.show()
        self.cubeMenu.setEnabled(True)

    def saveCubeFile(self):
        self.cubeMgr.saveCubeFile()

    def restoreCubeFile(self):
        self.cubeMgr.restoreConfigFile()

    def hideCube(self):
        self.cubeMgr.saveCubeFile()
        self.cubeMgr.hide()
        self.cubeMenu.setEnabled(False)

    def test(self, index):
        return
        print(index.row(), index.column())
        item = self.baseModel.itemFromIndex(index)
        print(item.text(), item.model())

    def refreshTable(self):
        self.baseModel.emitModelReset()

    def previewCube(self):
        startItem = self.cubeMgr.model().item(0, 0)
        conName = self.cubeMgr.defaultConnection
        self.queryView.reconnect(
            self.queryView.getConnection(self.dictionary, confName=conName))
        query = self.cubeMgr.getPreviewQuery(startItem)
        self.queryView.executeNewScript(query)
        if self.queryView.isHidden():
            self.queryView.show()
        self.queryMenu.setEnabled(True)
        if self.querySplitter.count(
        ) == 1:  #de momento parece un modo sencillo de no multiplicar en exceso
            self.querySplitter.addWidget(self.queryView)
예제 #10
0
class Widget(QWidget):
    def __init__(self, panel):
        super(Widget, self).__init__(panel)
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        layout.setSpacing(0)
        
        self.searchEntry = SearchLineEdit()
        self.treeView = QTreeView(contextMenuPolicy=Qt.CustomContextMenu)
        self.textView = QTextBrowser()
        
        applyButton = QToolButton(autoRaise=True)
        editButton = QToolButton(autoRaise=True)
        addButton = QToolButton(autoRaise=True)
        self.menuButton = QPushButton(flat=True)
        menu = QMenu(self.menuButton)
        self.menuButton.setMenu(menu)
        
        splitter = QSplitter(Qt.Vertical)
        top = QHBoxLayout()
        layout.addLayout(top)
        splitter.addWidget(self.treeView)
        splitter.addWidget(self.textView)
        layout.addWidget(splitter)
        splitter.setSizes([200, 100])
        splitter.setCollapsible(0, False)
        
        top.addWidget(self.searchEntry)
        top.addWidget(applyButton)
        top.addSpacing(10)
        top.addWidget(addButton)
        top.addWidget(editButton)
        top.addWidget(self.menuButton)
        
        # action generator for actions added to search entry
        def act(slot, icon=None):
            a = QAction(self, triggered=slot)
            self.addAction(a)
            a.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            icon and a.setIcon(icons.get(icon))
            return a
        
        # hide if ESC pressed in lineedit
        a = act(self.slotEscapePressed)
        a.setShortcut(QKeySequence(Qt.Key_Escape))
        
        # import action
        a = self.importAction = act(self.slotImport, 'document-open')
        menu.addAction(a)
        
        # export action
        a = self.exportAction = act(self.slotExport, 'document-save-as')
        menu.addAction(a)
        
        # apply button
        a = self.applyAction = act(self.slotApply, 'edit-paste')
        applyButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)
        
        # add button
        a = self.addAction_ = act(self.slotAdd, 'list-add')
        a.setShortcut(QKeySequence(Qt.Key_Insert))
        addButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)
        
        # edit button
        a = self.editAction = act(self.slotEdit, 'document-edit')
        a.setShortcut(QKeySequence(Qt.Key_F2))
        editButton.setDefaultAction(a)
        menu.addAction(a)
        
        # set shortcut action
        a = self.shortcutAction = act(self.slotShortcut, 'preferences-desktop-keyboard-shortcuts')
        menu.addAction(a)
        
        # delete action
        a = self.deleteAction = act(self.slotDelete, 'list-remove')
        a.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Delete))
        menu.addAction(a)
        
        # restore action
        a = self.restoreAction = act(self.slotRestore)
        menu.addSeparator()
        menu.addAction(a)
        
        # help button
        a = self.helpAction = act(self.slotHelp, 'help-contents')
        menu.addSeparator()
        menu.addAction(a)
        
        self.treeView.setSelectionBehavior(QTreeView.SelectRows)
        self.treeView.setSelectionMode(QTreeView.ExtendedSelection)
        self.treeView.setRootIsDecorated(False)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setModel(model.model())
        self.treeView.setCurrentIndex(QModelIndex())
        
        # signals
        self.searchEntry.returnPressed.connect(self.slotReturnPressed)
        self.searchEntry.textChanged.connect(self.updateFilter)
        self.treeView.doubleClicked.connect(self.slotDoubleClicked)
        self.treeView.customContextMenuRequested.connect(self.showContextMenu)
        self.treeView.selectionModel().currentChanged.connect(self.updateText)
        self.treeView.model().dataChanged.connect(self.updateFilter)
        
        # highlight text
        self.highlighter = highlight.Highlighter(self.textView.document())
        
        # complete on snippet variables
        self.searchEntry.setCompleter(QCompleter([
            ':icon', ':indent', ':menu', ':name', ':python', ':selection',
            ':set', ':symbol', ':template', ':template-run'], self.searchEntry))
        self.readSettings()
        app.settingsChanged.connect(self.readSettings)
        app.translateUI(self)
        self.updateColumnSizes()
        self.setAcceptDrops(True)

    def dropEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            filename = ev.mimeData().urls()[0].toLocalFile()
            if filename:
                ev.accept()
                from . import import_export
                import_export.load(filename, self)
        
    def dragEnterEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            ev.accept()
        
    def translateUI(self):
        try:
            self.searchEntry.setPlaceholderText(_("Search..."))
        except AttributeError:
            pass # not in Qt 4.6
        shortcut = lambda a: a.shortcut().toString(QKeySequence.NativeText)
        self.menuButton.setText(_("&Menu"))
        self.addAction_.setText(_("&Add..."))
        self.addAction_.setToolTip(
            _("Add a new snippet. ({key})").format(key=shortcut(self.addAction_)))
        self.editAction.setText(_("&Edit..."))
        self.editAction.setToolTip(
            _("Edit the current snippet. ({key})").format(key=shortcut(self.editAction)))
        self.shortcutAction.setText(_("Configure Keyboard &Shortcut..."))
        self.deleteAction.setText(_("&Remove"))
        self.deleteAction.setToolTip(_("Remove the selected snippets."))
        self.applyAction.setText(_("A&pply"))
        self.applyAction.setToolTip(_("Apply the current snippet."))
        self.importAction.setText(_("&Import..."))
        self.importAction.setToolTip(_("Import snippets from a file."))
        self.exportAction.setText(_("E&xport..."))
        self.exportAction.setToolTip(_("Export snippets to a file."))
        self.restoreAction.setText(_("Restore &Built-in Snippets..."))
        self.restoreAction.setToolTip(
            _("Restore deleted or changed built-in snippets."))
        self.helpAction.setText(_("&Help"))
        self.searchEntry.setToolTip(_(
            "Enter text to search in the snippets list.\n"
            "See \"What's This\" for more information."))
        self.searchEntry.setWhatsThis(''.join(map("<p>{0}</p>\n".format, (
            _("Enter text to search in the snippets list, and "
              "press Enter to apply the currently selected snippet."),
            _("If the search text fully matches the value of the '{name}' variable "
              "of a snippet, that snippet is selected.").format(name="name"),
            _("If the search text starts with a colon ':', the rest of the "
              "search text filters snippets that define the given variable. "
              "After a space a value can also be entered, snippets will then "
              "match if the value of the given variable contains the text after "
              "the space."),
            _("E.g. entering {menu} will show all snippets that are displayed "
              "in the insert menu.").format(menu="<code>:menu</code>"),
            ))))
    
    def sizeHint(self):
        return self.parent().mainwindow().size() / 4
        
    def readSettings(self):
        data = textformats.formatData('editor')
        self.textView.setFont(data.font)
        self.textView.setPalette(data.palette())

    def showContextMenu(self, pos):
        """Called when the user right-clicks the tree view."""
        self.menuButton.menu().popup(self.treeView.viewport().mapToGlobal(pos))
    
    def slotReturnPressed(self):
        """Called when the user presses Return in the search entry. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
            self.parent().hide() # make configurable?
            view.setFocus()

    def slotEscapePressed(self):
        """Called when the user presses ESC in the search entry. Hides the panel."""
        self.parent().hide()
        self.parent().mainwindow().currentView().setFocus()
    
    def slotDoubleClicked(self, index):
        name = self.treeView.model().name(index)
        view = self.parent().mainwindow().currentView()
        insert.insert(name, view)
        
    def slotAdd(self):
        """Called when the user wants to add a new snippet."""
        edit.Edit(self, None)
        
    def slotEdit(self):
        """Called when the user wants to edit a snippet."""
        name = self.currentSnippet()
        if name:
            edit.Edit(self, name)
        
    def slotShortcut(self):
        """Called when the user selects the Configure Shortcut action."""
        from widgets import shortcuteditdialog
        name = self.currentSnippet()
        if name:
            collection = self.parent().snippetActions
            action = actions.action(name, None, collection)
            default = collection.defaults().get(name)
            mgr = actioncollectionmanager.manager(self.parent().mainwindow())
            cb = mgr.findShortcutConflict
            dlg = shortcuteditdialog.ShortcutEditDialog(self, cb, (collection, name))
            
            if dlg.editAction(action, default):
                mgr.removeShortcuts(action.shortcuts())
                collection.setShortcuts(name, action.shortcuts())
                self.treeView.update()
            
    def slotDelete(self):
        """Called when the user wants to delete the selected rows."""
        rows = sorted(set(i.row() for i in self.treeView.selectedIndexes()), reverse=True)
        if rows:
            for row in rows:
                name = self.treeView.model().names()[row]
                self.parent().snippetActions.setShortcuts(name, [])
                self.treeView.model().removeRow(row)
            self.updateFilter()
    
    def slotApply(self):
        """Called when the user clicks the apply button. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
    
    def slotImport(self):
        """Called when the user activates the import action."""
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files"))
        caption = app.caption(_("dialog title", "Import Snippets"))
        filename = None
        filename = QFileDialog.getOpenFileName(self, caption, filename, filetypes)[0]
        if filename:
            from . import import_export
            import_export.load(filename, self)
        
    def slotExport(self):
        """Called when the user activates the export action."""
        allrows = [row for row in range(model.model().rowCount())
                       if not self.treeView.isRowHidden(row, QModelIndex())]
        selectedrows = [i.row() for i in self.treeView.selectedIndexes()
                                if i.column() == 0 and i.row() in allrows]
        names = self.treeView.model().names()
        names = [names[row] for row in selectedrows or allrows]
        
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files"))
        n = len(names)
        caption = app.caption(_("dialog title",
            "Export {num} Snippet", "Export {num} Snippets", n).format(num=n))
        filename = QFileDialog.getSaveFileName(self, caption, None, filetypes)[0]
        if filename:
            from . import import_export
            try:
                import_export.save(names, filename)
            except (IOError, OSError) as e:
                QMessageBox.critical(self, _("Error"), _(
                    "Can't write to destination:\n\n{url}\n\n{error}").format(
                    url=filename, error=e.strerror))
        
    def slotRestore(self):
        """Called when the user activates the Restore action."""
        from . import restore
        dlg = restore.RestoreDialog(self)
        dlg.setWindowModality(Qt.WindowModal)
        dlg.populate()
        dlg.show()
        dlg.finished.connect(dlg.deleteLater)
        
    def slotHelp(self):
        """Called when the user clicks the small help button."""
        userguide.show("snippets")
        
    def currentSnippet(self):
        """Returns the name of the current snippet if it is visible."""
        row = self.treeView.currentIndex().row()
        if row != -1 and not self.treeView.isRowHidden(row, QModelIndex()):
            return self.treeView.model().names()[row]

    def updateFilter(self):
        """Called when the text in the entry changes, updates search results."""
        text = self.searchEntry.text()
        ltext = text.lower()
        filterVars = text.startswith(':')
        if filterVars:
            try:
                fvar, fval = text[1:].split(None, 1)
                fhide = lambda v: v.get(fvar) in (True, None) or fval not in v.get(fvar)
            except ValueError:
                fvar = text[1:].strip()
                fhide = lambda v: not v.get(fvar)
        for row in range(self.treeView.model().rowCount()):
            name = self.treeView.model().names()[row]
            nameid = snippets.get(name).variables.get('name', '')
            if filterVars:
                hide = fhide(snippets.get(name).variables)
            elif nameid == text:
                i = self.treeView.model().createIndex(row, 0)
                self.treeView.selectionModel().setCurrentIndex(i, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
                hide = False
            elif nameid.lower().startswith(ltext):
                hide = False
            elif ltext in snippets.title(name).lower():
                hide = False
            else:
                hide = True
            self.treeView.setRowHidden(row, QModelIndex(), hide)
        self.updateText()
            
    def updateText(self):
        """Called when the current snippet changes."""
        name = self.currentSnippet()
        self.textView.clear()
        if name:
            s = snippets.get(name)
            self.highlighter.setPython('python' in s.variables)
            self.textView.setPlainText(s.text)
        
    def updateColumnSizes(self):
        self.treeView.resizeColumnToContents(0)
        self.treeView.resizeColumnToContents(1)
예제 #11
0
class DataSourceWidget(_AbstractCtrlWidget):
    """DataSourceWidget class.

    Widgets provide data source management and monitoring.
    """

    _source_types = {
        "Run directory": DataSource.FILE,
        "ZeroMQ bridge": DataSource.BRIDGE,
    }

    SPLITTER_HANDLE_WIDTH = 9

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._con_view = QTableView()
        self._con_model = ConnectionTableModel(self._source_types)
        self._con_view.setModel(self._con_model)
        self._con_src_type_delegate = ComboBoxDelegate(self._source_types)
        self._con_addr_delegate = LineEditItemDelegateN(self)
        self._con_port_delegate = LineEditItemDelegateN(
            self, validator=QIntValidator(0, 65535))
        self._con_view.setItemDelegateForColumn(1, self._con_src_type_delegate)
        self._con_view.setItemDelegateForColumn(2, self._con_addr_delegate)
        self._con_view.setItemDelegateForColumn(3, self._con_port_delegate)

        self._src_view = QTreeView()
        self._src_tree_model = DataSourceItemModel(self)
        self._src_device_delegate = LineEditItemDelegate(self)
        self._src_ppt_delegate = LineEditItemDelegate(self)
        self._src_slicer_delegate = SliceItemDelegate(self)
        self._src_boundary_delegate = BoundaryItemDelegate(self)
        self._src_view.setModel(self._src_tree_model)
        self._src_view.setItemDelegateForColumn(0, self._src_device_delegate)
        self._src_view.setItemDelegateForColumn(1, self._src_ppt_delegate)
        self._src_view.setItemDelegateForColumn(2, self._src_slicer_delegate)
        self._src_view.setItemDelegateForColumn(3, self._src_boundary_delegate)

        self._monitor_tb = QTabWidget()
        self._avail_src_view = QListView()
        self._avail_src_model = DataSourceListModel()
        self._avail_src_view.setModel(self._avail_src_model)
        self._process_mon_view = QTableView()
        self._process_mon_model = ProcessMonitorTableModel()
        self._process_mon_view.setModel(self._process_mon_model)
        self._process_mon_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

        self.initUI()
        self.initConnections()

        self._non_reconfigurable_widgets = [
            self._con_view,
        ]

        self._mon = MonProxy()

        self._avail_src_timer = QTimer()
        self._avail_src_timer.timeout.connect(self.updateSourceList)
        self._avail_src_timer.start(config["SOURCE_AVAIL_UPDATE_TIMER"])

        self._process_mon_timer = QTimer()
        self._process_mon_timer.timeout.connect(self.updateProcessInfo)
        self._process_mon_timer.start(config["PROCESS_MONITOR_UPDATE_TIMER"])

    def initUI(self):
        """Override."""
        self._monitor_tb.setTabPosition(QTabWidget.TabPosition.South)
        self._monitor_tb.addTab(self._avail_src_view, "Available sources")
        self._monitor_tb.addTab(self._process_mon_view, "Process monitor")

        splitter = QSplitter(Qt.Vertical)
        splitter.setHandleWidth(self.SPLITTER_HANDLE_WIDTH)
        splitter.setChildrenCollapsible(False)
        splitter.addWidget(self._con_view)
        splitter.addWidget(self._src_view)
        splitter.addWidget(self._monitor_tb)
        splitter.setStretchFactor(0, 3)
        splitter.setStretchFactor(1, 1)
        h = splitter.sizeHint().height()
        splitter.setSizes([0.1 * h, 0.6 * h, 0.3 * h])

        layout = QVBoxLayout()
        layout.addWidget(splitter)
        self.setLayout(layout)

        self._con_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

        self._src_view.expandToDepth(1)
        self._src_view.resizeColumnToContents(0)
        self._src_view.resizeColumnToContents(1)

    def initConnections(self):
        """Override."""
        mediator = self._mediator

        mediator.file_stream_initialized_sgn.connect(self.updateMetaData)

    def updateMetaData(self):
        """Override."""
        try:
            cons = self._con_model.connections()
            self._mediator.onBridgeConnectionsChange(cons)
        except ValueError as e:
            logger.error(e)
            return False
        return True

    def updateSourceList(self):
        available_sources = self._mon.get_available_sources()
        if available_sources is not None:  # for unittest
            self._avail_src_model.setupModelData(list(
                available_sources.keys()))

    def updateProcessInfo(self):
        info = []
        for p in list_foam_processes():
            info.append(list(p))
        self._process_mon_model.setupModelData(info)
class _PlatformGui(QWidget):
    """ The platform-specific GUI. """
    def __init__(self, platform_name):
        """ Initialise the object. """

        super().__init__()

        self._project = None
        self._platform_name = platform_name

        self._ignore_extlib_changes = False

        layout = QVBoxLayout()

        self._pyshlib_cb = QCheckBox(
            "Use standard Python shared library",
            whatsThis="Use the standard Python shared library rather than "
            "a statically compiled library.",
            stateChanged=self._pyshlib_changed)
        layout.addWidget(self._pyshlib_cb)

        self._extlib_edit = QTreeView(
            whatsThis="This is the list of external libraries that must "
            "be linked with the application for this platform. A "
            "library will only be enabled if a module in the "
            "standard library uses it. Double-click in the "
            "<b>DEFINES</b>, <b>INCLUDEPATH</b> and <b>LIBS</b> "
            "columns to modify the corresponding <tt>qmake</tt> "
            "variable as required.")
        self._extlib_edit.setRootIsDecorated(False)
        self._extlib_edit.setEditTriggers(QTreeView.DoubleClicked
                                          | QTreeView.SelectedClicked
                                          | QTreeView.EditKeyPressed)

        model = QStandardItemModel(self._extlib_edit)
        model.setHorizontalHeaderLabels(
            ("External Library", 'DEFINES', 'INCLUDEPATH', 'LIBS'))
        model.itemChanged.connect(self._extlib_changed)

        model._items = {}

        for extlib in external_libraries_metadata:
            name_itm = QStandardItem(extlib.user_name)

            items = (name_itm, QStandardItem(), QStandardItem(),
                     QStandardItem())

            model.appendRow(items)

            model._items[extlib.name] = items

        self._extlib_edit.setModel(model)

        for col in range(3):
            self._extlib_edit.resizeColumnToContents(col)

        layout.addWidget(self._extlib_edit)

        self.setLayout(layout)

    def update_from_project(self, project):
        """ Update the GUI to reflect the current state of the project. """

        self._project = project

        platform_name = self._platform_name

        # Update the shared library state.
        blocked = self._pyshlib_cb.blockSignals(True)
        self._pyshlib_cb.setCheckState(Qt.Checked if platform_name in project.
                                       python_use_platform else Qt.Unchecked)
        self._pyshlib_cb.blockSignals(blocked)

        # Update the external libraries.
        model = self._extlib_edit.model()

        blocked = model.blockSignals(True)

        external_libs = project.external_libraries.get(platform_name, [])

        for extlib in external_libraries_metadata:
            _, defs, incp, libs = model._items[extlib.name]

            for prj_extlib in external_libs:
                if prj_extlib.name == extlib.name:
                    defs.setText(prj_extlib.defines)
                    incp.setText(prj_extlib.includepath)
                    libs.setText(prj_extlib.libs)
                    break
            else:
                defs.setText(extlib.defines)
                incp.setText(extlib.includepath)
                libs.setText(extlib.get_libs(platform_name))

        model.blockSignals(blocked)

    def update_from_required_libraries(self, required_libraries):
        """ Update the GUI as the required external libraries changes. """

        items = self._extlib_edit.model()._items

        # Note that we can't simply block the model's signals as this would
        # interfere with the model/view interactions.
        self._ignore_extlib_changes = True

        for extlib in external_libraries_metadata:
            if extlib.name in required_libraries:
                for idx, itm in enumerate(items[extlib.name]):
                    itm.setFlags(
                        Qt.ItemIsEnabled
                        | Qt.ItemIsEditable if idx != 0 else Qt.ItemIsEnabled)
            else:
                for itm in items[extlib.name]:
                    itm.setFlags(Qt.NoItemFlags)

        self._ignore_extlib_changes = False

    def _pyshlib_changed(self, state):
        """ Invoked when the shared library state changes. """

        project = self._project
        platform_name = self._platform_name

        if state == Qt.Checked:
            project.python_use_platform.append(platform_name)
        else:
            project.python_use_platform.remove(platform_name)

        project.modified = True

    def _extlib_changed(self, itm):
        """ Invoked when an external library has changed. """

        if self._ignore_extlib_changes:
            return

        self._ignore_extlib_changes = True

        project = self._project
        platform_name = self._platform_name

        idx = self._extlib_edit.model().indexFromItem(itm)
        extlib = external_libraries_metadata[idx.row()]
        col = idx.column()

        # Get the project entry, creating it if necessary.
        external_libs = project.external_libraries.get(platform_name, [])

        for prj_extlib in external_libs:
            if prj_extlib.name == extlib.name:
                break
        else:
            prj_extlib = ExternalLibrary(extlib.name, '', '',
                                         extlib.get_libs(platform_name))
            external_libs.append(prj_extlib)
            project.external_libraries[platform_name] = external_libs

        # Update the project.
        text = itm.text().strip()

        if col == 1:
            prj_extlib.defines = text
        elif col == 2:
            prj_extlib.includepath = text
        elif col == 3:
            prj_extlib.libs = text

        # If the project entry corresponds to the default then remove it.
        if prj_extlib.defines == extlib.defines and prj_extlib.includepath == extlib.includepath and prj_extlib.libs == extlib.get_libs(
                platform_name):
            external_libs.remove(prj_extlib)
            if len(external_libs) == 0:
                del project.external_libraries[platform_name]

        project.modified = True

        self._ignore_extlib_changes = False
예제 #13
0
파일: main.py 프로젝트: ysglyl/yutools
class YuToolsNoteViewer(QWidget):

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

        self.tv_notes = QTreeView(self)
        self.tv_notes.setGeometry(10, 40, 311, 630)
        self.tv_notes.setExpandsOnDoubleClick(False)
        self.tv_notes.clicked.connect(self.click_tv_item)
        self.tv_notes.doubleClicked.connect(self.doubleclick_tv_item)

        self.te_note = QTextEdit(self)
        font_default = QFont()
        font_default.setPointSize(12)
        self.te_note.setFont(font_default)
        self.te_note.setGeometry(330, 40, 451, 630)

        self.btn_add_root = QPushButton(self)
        self.btn_add_root.setText('Add Root')
        self.btn_add_root.setGeometry(10, 10, 75, 23)
        self.btn_add_root.clicked.connect(self.btn_click)

        self.btn_add_sub = QPushButton(self)
        self.btn_add_sub.setText('Add Sub')
        self.btn_add_sub.setDisabled(True)
        self.btn_add_sub.setGeometry(100, 10, 75, 23)
        self.btn_add_sub.clicked.connect(self.btn_click)

        self.btn_add_note = QPushButton(self)
        self.btn_add_note.setText('Add Note')
        self.btn_add_note.setDisabled(True)
        self.btn_add_note.setGeometry(190, 10, 75, 23)
        self.btn_add_note.clicked.connect(self.btn_click)

        self.rb_fonts_small = QRadioButton(self)
        self.rb_fonts_small.setText('Small')
        self.rb_fonts_small.setGeometry(350, 10, 60, 23)
        self.rb_fonts_small.clicked.connect(self.change_font_size)
        self.rb_fonts_normal = QRadioButton(self)
        self.rb_fonts_normal.setText('Normal')
        self.rb_fonts_normal.setGeometry(420, 10, 60, 23)
        self.rb_fonts_normal.clicked.connect(self.change_font_size)
        self.rb_fonts_normal.setChecked(True)
        self.rb_fonts_big = QRadioButton(self)
        self.rb_fonts_big.setText('Big')
        self.rb_fonts_big.setGeometry(490, 10, 60, 23)
        self.rb_fonts_big.clicked.connect(self.change_font_size)
        self.rb_fonts_bigger = QRadioButton(self)
        self.rb_fonts_bigger.setText('Bigger')
        self.rb_fonts_bigger.setGeometry(560, 10, 60, 23)
        self.rb_fonts_bigger.clicked.connect(self.change_font_size)
        self.rb_fonts_biggest = QRadioButton(self)
        self.rb_fonts_biggest.setText('Biggest')
        self.rb_fonts_biggest.setGeometry(630, 10, 60, 23)
        self.rb_fonts_biggest.clicked.connect(self.change_font_size)

        self.btn_save_note = QPushButton(self)
        self.btn_save_note.setText('Save')
        self.btn_save_note.setDisabled(True)
        self.btn_save_note.setGeometry(710, 10, 75, 23)
        self.btn_save_note.clicked.connect(self.btn_click)

        self.select_path = None

        self.init_tree()

    def init_tree(self):
        model = QFileSystemModel()
        path = os.path.dirname(os.path.realpath(__file__)) + os.path.sep + 'notes'
        model.setRootPath(path)
        self.tv_notes.setModel(model)
        self.tv_notes.setRootIndex(model.index(path))
        self.tv_notes.setHeaderHidden(True)
        self.tv_notes.resizeColumnToContents(0)
        self.tv_notes.setColumnHidden(1, True)
        self.tv_notes.setColumnHidden(2, True)
        self.tv_notes.setColumnHidden(3, True)

    def click_tv_item(self, index):
        is_dir = index.model().isDir(index)
        self.select_path = index.model().filePath(index)
        if is_dir:
            self.btn_add_sub.setDisabled(False)
            self.btn_add_note.setDisabled(False)
            self.btn_save_note.setDisabled(True)
            self.te_note.clear()
            self.te_note.setDisabled(True)
        else:
            self.te_note.clear()
            self.btn_add_sub.setDisabled(True)
            self.btn_add_note.setDisabled(True)
            self.btn_save_note.setDisabled(False)
            self.te_note.setDisabled(False)
            file = open(self.select_path, mode='r', encoding='utf-8')
            for line in file.readlines():
                self.te_note.append(line.strip('\n'))
            file.close()

    def doubleclick_tv_item(self, index):
        is_dir = index.model().isDir(index)
        self.select_path = index.model().filePath(index)
        path, ordinary_filename = os.path.split(self.select_path)
        file_name, success = QInputDialog.getText(self, "Title", "File Name:", QLineEdit.Normal,
                                                  ordinary_filename.split('.')[0])
        if success:
            if file_name.strip(' ') == '':
                return
            if is_dir:
                os.rename(self.select_path, path + os.path.sep + file_name.strip(' '))
            else:
                os.rename(self.select_path, path + os.path.sep + file_name.strip(' ') + '.txt')

    def btn_click(self):
        btn = self.sender()
        if btn == self.btn_save_note:
            content = self.te_note.toPlainText()
            lines = content.split('\n')
            file = open(self.select_path, mode='w+', encoding='utf-8')
            for line in lines:
                file.write(line + '\n')
            file.close()
        else:
            file_name, success = QInputDialog.getText(self, "Title", "File Name:", QLineEdit.Normal, '')
            if success:
                if file_name.strip(' ') == '':
                    return
                if btn == self.btn_add_root:
                    path = os.path.dirname(
                        os.path.realpath(__file__)) + os.path.sep + 'notes' + os.path.sep + file_name.strip(' ')
                    if not os.path.exists(path):
                        os.mkdir(path)
                elif btn == self.btn_add_sub:
                    path = self.select_path + os.path.sep + file_name.strip(' ')
                    if not os.path.exists(path):
                        os.mkdir(path)
                elif btn == self.btn_add_note:
                    path = self.select_path + os.path.sep + file_name.strip(' ') + '.txt'
                    if not os.path.exists(path):
                        open(path, mode='w', encoding='utf-8')

    def change_font_size(self):
        rb = self.sender()
        font = QFont()
        if rb == self.rb_fonts_small:
            font.setPointSize(10)
        elif rb == self.rb_fonts_normal:
            font.setPointSize(12)
        elif rb == self.rb_fonts_big:
            font.setPointSize(14)
        elif rb == self.rb_fonts_bigger:
            font.setPointSize(18)
        elif rb == self.rb_fonts_biggest:
            font.setPointSize(20)
        self.te_note.setFont(font)
예제 #14
0
class Dialog_ImageFolder():
    def __init__(self, parent, title, init_path):
        self.w = QDialog(parent)

        self.parent = parent
        self.left = 300
        self.top = 300
        self.width = 600
        self.height = 400
        self.title = title

        self.dirModel = QFileSystemModel()
        self.dirModel.setRootPath(init_path)
        self.dirModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)
        self.treeview = QTreeView()
        self.treeview.setModel(self.dirModel)
        self.treeview.setRootIndex(self.dirModel.index(""))
        self.treeview.clicked.connect(self.on_clicked)
        #--- Hide All Header Sections Except First ----
        header = self.treeview.header()
        for sec in range(1, header.count()):
            header.setSectionHidden(sec, True)
        #--- ---- ---- ---- ---- ---- ---- ---- ---- --

        focus_index = self.dirModel.index(init_path)
        self.treeview.setCurrentIndex(focus_index)
        self.current_row_changed()

        self.listview = QListView()
        self.listview.setViewMode(QListView.IconMode)
        self.listview.setIconSize(QSize(192, 192))

        targetfiles1 = glob.glob(os.path.join(init_path, '*.png'))
        targetfiles2 = glob.glob(os.path.join(init_path, '*.tif'))
        targetfiles3 = glob.glob(os.path.join(init_path, '*.tiff'))
        targetfiles = targetfiles1 + targetfiles2 + targetfiles3
        lm = _MyListModel(targetfiles, self.parent)
        self.listview.setModel(lm)

        self.sub_layout = QHBoxLayout()
        self.sub_layout.addWidget(self.treeview)
        self.sub_layout.addWidget(self.listview)

        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open
                                          | QDialogButtonBox.Cancel)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        self.main_layout = QVBoxLayout()
        self.main_layout.addLayout(self.sub_layout)
        self.main_layout.addWidget(self.buttonBox)

        self.w.setGeometry(self.left, self.top, self.width, self.height)
        self.w.setWindowTitle(self.title)
        self.w.setWindowIcon(QIcon(os.path.join(icon_dir, 'Mojo2_16.png')))
        self.w.setLayout(self.main_layout)

    def current_row_changed(self):
        index = self.treeview.currentIndex()
        self.treeview.scrollTo(index, QAbstractItemView.EnsureVisible)
        self.treeview.resizeColumnToContents(0)

    def on_clicked(self, index):
        path = self.dirModel.fileInfo(index).absoluteFilePath()

        targetfiles1 = glob.glob(os.path.join(path, '*.png'))
        targetfiles2 = glob.glob(os.path.join(path, '*.tif'))
        targetfiles3 = glob.glob(os.path.join(path, '*.tiff'))
        targetfiles = targetfiles1 + targetfiles2 + targetfiles3

        lm = _MyListModel(targetfiles, self.parent)
        self.listview.setModel(lm)

    def accept(self):
        index = self.treeview.currentIndex()
        self.newdir = self.dirModel.filePath(index)
        self.w.done(1)

    def reject(self):
        self.w.done(0)

    def GetValue(self):
        index = self.treeview.currentIndex()
        self.newdir = self.dirModel.filePath(index)
        return self.newdir
예제 #15
0
파일: DeviceConfig.py 프로젝트: rodcnx/tdm
    def tabInformation(self):
        info = QWidget()
        vl = VLayout()

        self.program_model = QStandardItemModel()
        for d in [
                "Program version", "Build date & time", "Core/SDK version",
                "Flash write count", "Boot count", "Restart reason",
                "Friendly Name 1", "Friendly Name 2", "Friendly Name 3",
                "Friendly Name 4"
        ]:
            k = QStandardItem(d)
            k.setEditable(False)
            v = QStandardItem()
            v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
            v.setEditable(False)
            self.program_model.appendRow([k, v])

        gbPrgm = GroupBoxH("Program")
        gbPrgm.setFlat(True)
        tvPrgm = QTreeView()
        tvPrgm.setHeaderHidden(True)
        tvPrgm.setRootIsDecorated(False)
        tvPrgm.setModel(self.program_model)
        tvPrgm.resizeColumnToContents(0)
        gbPrgm.addWidget(tvPrgm)

        self.esp_model = QStandardItemModel()
        for d in [
                "ESP Chip Id", "Flash Chip Id", "Flash Size",
                "Program Flash Size", "Program Size", "Free Program Space",
                "Free Memory"
        ]:
            k = QStandardItem(d)
            k.setEditable(False)
            v = QStandardItem()
            v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
            v.setEditable(False)
            self.esp_model.appendRow([k, v])

        gbESP = GroupBoxH("ESP")
        gbESP.setFlat(True)
        tvESP = QTreeView()
        tvESP.setHeaderHidden(True)
        tvESP.setRootIsDecorated(False)
        tvESP.setModel(self.esp_model)
        tvESP.resizeColumnToContents(0)
        gbESP.addWidget(tvESP)

        # self.emul_model = QStandardItemModel()
        # for d in ["Emulation", "mDNS Discovery"]:
        #     k = QStandardItem(d)
        #     k.setEditable(False)
        #     v = QStandardItem()
        #     v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
        #     v.setEditable(False)
        #     self.emul_model.appendRow([k, v])
        #
        # gbEmul = GroupBoxH("Emulation")
        # gbEmul.setFlat(True)
        # tvEmul = QTreeView()
        # tvEmul.setHeaderHidden(True)
        # tvEmul.setRootIsDecorated(False)
        # tvEmul.setModel(self.emul_model)
        # tvEmul.resizeColumnToContents(0)
        # gbEmul.addWidget(tvEmul)

        self.wifi_model = QStandardItemModel()
        for d in [
                "AP1 SSId (RSSI)", "Hostname", "IP Address", "Gateway",
                "Subnet Mask", "DNS Server", "MAC Address"
        ]:
            k = QStandardItem(d)
            k.setEditable(False)
            v = QStandardItem()
            v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
            v.setEditable(False)
            self.wifi_model.appendRow([k, v])

        gbWifi = GroupBoxH("Wifi")
        gbWifi.setFlat(True)
        tvWifi = QTreeView()
        tvWifi.setHeaderHidden(True)
        tvWifi.setRootIsDecorated(False)
        tvWifi.setModel(self.wifi_model)
        tvWifi.resizeColumnToContents(0)
        gbWifi.addWidget(tvWifi)

        self.mqtt_model = QStandardItemModel()
        for d in [
                "MQTT Host", "MQTT Port", "MQTT User", "MQTT Client",
                "MQTT Topic", "MQTT Group Topic", "MQTT Full Topic",
                "MQTT Fallback Topic"
        ]:
            k = QStandardItem(d)
            k.setEditable(False)
            v = QStandardItem()
            v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
            v.setEditable(False)
            self.mqtt_model.appendRow([k, v])

        gbMQTT = GroupBoxH("MQTT")
        gbMQTT.setFlat(True)
        tvMQTT = QTreeView()
        tvMQTT.setHeaderHidden(True)
        tvMQTT.setRootIsDecorated(False)
        tvMQTT.setModel(self.mqtt_model)
        tvMQTT.resizeColumnToContents(0)
        gbMQTT.addWidget(tvMQTT)

        hl = HLayout(0)
        vl_lc = VLayout(0, 3)
        vl_rc = VLayout(0, 3)

        vl_lc.addWidgets([gbPrgm, gbESP])
        vl_rc.addWidgets([gbWifi, gbMQTT])

        vl_rc.setStretch(0, 2)
        vl_rc.setStretch(1, 2)
        vl_rc.setStretch(2, 1)

        hl.addLayout(vl_lc)
        hl.addLayout(vl_rc)
        vl.addLayout(hl)
        info.setLayout(vl)
        return info
예제 #16
0
class TreeView(QWidget):
    def __init__(self, table, parent):
        QWidget.__init__(self, parent)
        self.window = parent
        self.tree = QTreeView(self)
        indent = self.tree.indentation()
        self.tree.setIndentation(indent / 2)

        self.model = DataModel(table)
        self.sorter = sorter = FilterModel(self)
        sorter.setSourceModel(self.model)
        self.tree.setModel(sorter)
        for col in range(3, 9):
            self.tree.setItemDelegateForColumn(col, PercentDelegate(self))
        self.tree.header().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.header().customContextMenuRequested.connect(
            self._on_header_menu)
        self.tree.setSortingEnabled(True)
        self.tree.setAutoExpandDelay(0)
        self.tree.resizeColumnToContents(0)
        self.tree.resizeColumnToContents(NAME_COLUMN)
        self.tree.expand(self.sorter.index(0, 0))
        #self.tree.expandAll()

        self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self._on_tree_menu)

        searchbox = QHBoxLayout()
        self.search = QLineEdit(self)

        searchbox.addWidget(self.search)

        self.search_type = QComboBox(self)
        self.search_type.addItem("Contains", SEARCH_CONTAINS)
        self.search_type.addItem("Exact", SEARCH_EXACT)
        self.search_type.addItem("Reg.Exp", SEARCH_REGEXP)
        searchbox.addWidget(self.search_type)

        btn = QPushButton("&Search", self)
        searchbox.addWidget(btn)
        btn.clicked.connect(self._on_search)

        btn = QPushButton("&Next", self)
        searchbox.addWidget(btn)
        btn.clicked.connect(self._on_search_next)

        filterbox = QHBoxLayout()

        label = QLabel("Time Individual", self)
        filterbox.addWidget(label)
        self.individual_time = QSpinBox(self)
        self.individual_time.setMinimum(0)
        self.individual_time.setMaximum(100)
        self.individual_time.setSuffix(" %")
        filterbox.addWidget(self.individual_time)

        label = QLabel("Alloc Individual", self)
        filterbox.addWidget(label)
        self.individual_alloc = QSpinBox(self)
        self.individual_alloc.setMinimum(0)
        self.individual_alloc.setMaximum(100)
        self.individual_alloc.setSuffix(" %")
        filterbox.addWidget(self.individual_alloc)

        label = QLabel("Time Inherited", self)
        filterbox.addWidget(label)
        self.inherited_time = QSpinBox(self)
        self.inherited_time.setMinimum(0)
        self.inherited_time.setMaximum(100)
        self.inherited_time.setSuffix(" %")
        filterbox.addWidget(self.inherited_time)

        label = QLabel("Alloc Inherited", self)
        filterbox.addWidget(label)
        self.inherited_alloc = QSpinBox(self)
        self.inherited_alloc.setMinimum(0)
        self.inherited_alloc.setMaximum(100)
        self.inherited_alloc.setSuffix(" %")
        filterbox.addWidget(self.inherited_alloc)

        btn = QPushButton("&Filter", self)
        btn.clicked.connect(self._on_filter)
        filterbox.addWidget(btn)
        btn = QPushButton("&Reset", self)
        filterbox.addWidget(btn)
        btn.clicked.connect(self._on_reset_filter)

        vbox = QVBoxLayout()
        vbox.addLayout(searchbox)
        vbox.addLayout(filterbox)
        vbox.addWidget(self.tree)
        self.setLayout(vbox)

        self._search_idxs = None
        self._search_idx_no = 0

    def _expand_to(self, idx):
        idxs = [idx]
        parent = idx
        while parent and parent.isValid():
            parent = self.sorter.parent(parent)
            idxs.append(parent)
        #print(idxs)
        for idx in reversed(idxs[:-1]):
            data = self.sorter.data(idx, QtCore.Qt.DisplayRole)
            #print(data)
            self.tree.expand(idx)

    def _on_search(self):
        text = self.search.text()
        selected = self.tree.selectedIndexes()
        #         if selected:
        #             start = selected[0]
        #         else:
        start = self.sorter.index(0, NAME_COLUMN)
        search_type = self.search_type.currentData()
        if search_type == SEARCH_EXACT:
            method = QtCore.Qt.MatchFixedString
        elif search_type == SEARCH_CONTAINS:
            method = QtCore.Qt.MatchContains
        else:
            method = QtCore.Qt.MatchRegExp

        self._search_idxs = idxs = self.sorter.search(start, text, search_type)
        if idxs:
            self.window.statusBar().showMessage(
                "Found: {} occurence(s)".format(len(idxs)))
            self._search_idx_no = 0
            idx = idxs[0]
            self._locate(idx)
        else:
            self.window.statusBar().showMessage("Not found")

    def _locate(self, idx):
        self.tree.resizeColumnToContents(0)
        self.tree.resizeColumnToContents(NAME_COLUMN)
        self._expand_to(idx)
        self.tree.setCurrentIndex(idx)
        #self.tree.selectionModel().select(idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Current | QItemSelectionModel.Rows)
        #self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter)

    def _on_search_next(self):
        if self._search_idxs:
            n = len(self._search_idxs)
            self._search_idx_no = (self._search_idx_no + 1) % n
            idx = self._search_idxs[self._search_idx_no]
            self.window.statusBar().showMessage("Occurence {} of {}".format(
                self._search_idx_no, n))
            self._locate(idx)
        else:
            self.window.statusBar().showMessage("No search results")

    def _on_filter(self):
        self.sorter.setFilter(self.search.text(), self.individual_time.value(),
                              self.individual_alloc.value(),
                              self.inherited_time.value(),
                              self.inherited_alloc.value())

    def _on_reset_filter(self):
        self.sorter.reset()

    def _on_header_menu(self, pos):
        menu = make_header_menu(self.tree)
        menu.exec_(self.mapToGlobal(pos))

    def _on_tree_menu(self, pos):
        index = self.tree.indexAt(pos)
        #print("index: {}".format(index))
        if index.isValid():
            record = self.sorter.data(index, QtCore.Qt.UserRole + 1)
            #print("okay?..")
            #print("context: {}".format(record))
            menu = self.window.make_item_menu(self.model, record)
            menu.exec_(self.tree.viewport().mapToGlobal(pos))
예제 #17
0
파일: locator.py 프로젝트: bjones1/enki
class _LocatorDialog(QDialog):
    """Locator widget and implementation
    """

    def __init__(self, parent, commandClasses):
        QDialog.__init__(self, parent)
        self._terminated = False
        self._commandClasses = commandClasses

        self._createUi()

        self._loadingTimer = QTimer(self)
        self._loadingTimer.setSingleShot(True)
        self._loadingTimer.setInterval(200)
        self._loadingTimer.timeout.connect(self._applyLoadingCompleter)

        self._completerLoaderThread = _CompleterLoaderThread(self)

        self.finished.connect(self._terminate)

        self._command = None
        self._updateCurrentCommand()

    def _createUi(self):
        self.setWindowTitle(core.project().path().replace(os.sep, '/') or 'Locator')

        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(1)

        biggerFont = self.font()
        biggerFont.setPointSizeF(biggerFont.pointSizeF() * 2)
        self.setFont(biggerFont)

        self._edit = _CompletableLineEdit(self)
        self._edit.updateCurrentCommand.connect(self._updateCurrentCommand)
        self._edit.enterPressed.connect(self._onEnterPressed)
        self._edit.installEventFilter(self)  # catch Up, Down
        self._edit.setFont(biggerFont)
        self.layout().addWidget(self._edit)
        self.setFocusProxy(self._edit)

        self._table = QTreeView(self)
        self._table.setFont(biggerFont)
        self._model = _CompleterModel()
        self._table.setModel(self._model)
        self._table.setItemDelegate(HTMLDelegate(self._table))
        self._table.setRootIsDecorated(False)
        self._table.setHeaderHidden(True)
        self._table.clicked.connect(self._onItemClicked)
        self._table.setAlternatingRowColors(True)
        self._table.installEventFilter(self)  # catch focus and give to the edit
        self.layout().addWidget(self._table)

        width = QFontMetrics(self.font()).width('x' * 64)  # width of 64 'x' letters
        self.resize(width, width * 0.62)

    def _terminate(self):
        if not self._terminated:
            if self._command is not None:
                self._command.terminate()
                self._command = None

            self._edit.terminate()

            self._completerLoaderThread.terminate()
            if self._model:
                self._model.terminate()
            core.workspace().focusCurrentDocument()
            self._terminated = True

    def _updateCurrentCommand(self):
        """Try to parse line edit text and set current command
        """
        if self._terminated:
            return

        newCommand = self._parseCurrentCommand()

        if newCommand is not self._command:
            if self._command is not None:
                self._command.updateCompleter.disconnect(self._updateCompletion)
                self._command.terminate()

            self._command = newCommand
            if self._command is not None:
                self._command.updateCompleter.connect(self._updateCompletion)

        self._updateCompletion()

    def _updateCompletion(self):
        """User edited text or moved cursor. Update inline and TreeView completion
        """
        if self._command is not None:
            completer = self._command.completer()

            if completer is not None and completer.mustBeLoaded:
                self._loadingTimer.start()
                self._completerLoaderThread.loadCompleter(self._command, completer)
            else:
                self._applyCompleter(self._command, completer)
        else:
            self._applyCompleter(None, _HelpCompleter(self._commandClasses))

    def _applyLoadingCompleter(self):
        """Set 'Loading...' message
        """
        self._applyCompleter(None, StatusCompleter('<i>Loading...</i>'))

    def onCompleterLoaded(self, command, completer):
        """The method called from _CompleterLoaderThread when the completer is ready
        This code works in the GUI thread
        """
        self._applyCompleter(command, completer)

    def _applyCompleter(self, command, completer):
        """Apply completer. Called by _updateCompletion or by thread function when Completer is constructed
        """
        self._loadingTimer.stop()

        if command is not None:
            command.onCompleterLoaded(completer)

        if completer is None:
            completer = _HelpCompleter([command])

        if self._edit.cursorPosition() == len(self._edit.text()):  # if cursor at the end of text
            self._edit.setInlineCompletion(completer.inline())

        self._model.setCompleter(completer)
        if completer.columnCount() > 1:
            self._table.resizeColumnToContents(0)
            self._table.setColumnWidth(0, self._table.columnWidth(0) + 20)  # 20 px spacing between columns

        selItem = completer.autoSelectItem()
        if selItem:
            index = self._model.createIndex(selItem[0],
                                            selItem[1])
            self._table.setCurrentIndex(index)

    def _onItemClicked(self, index):
        """Item in the TreeView has been clicked.
        Open file, if user selected it
        """
        if self._command is not None:
            fullText = self._model.completer.getFullText(index.row())
            if fullText is not None:
                self._command.onItemClicked(fullText)
                if self._tryExecCurrentCommand():
                    self.accept()
                    return
                else:
                    self._edit.setText(self._command.lineEditText())
                    self._updateCurrentCommand()

        self._edit.setFocus()

    def _onEnterPressed(self):
        """User pressed Enter or clicked item. Execute command, if possible
        """
        if self._table.currentIndex().isValid():
            self._onItemClicked(self._table.currentIndex())
        else:
            self._tryExecCurrentCommand()

    def _tryExecCurrentCommand(self):
        if self._command is not None and self._command.isReadyToExecute():
            self._command.execute()
            self.accept()
            return True
        else:
            return False

    def _chooseCommand(self, words):
        for cmd in self._commandClasses:
            if cmd.command == words[0]:
                return cmd, words[1:]

        isPath = words and (words[0].startswith('/') or
                            words[0].startswith('./') or
                            words[0].startswith('../') or
                            words[0].startswith('~/') or
                            words[0][1:3] == ':\\' or
                            words[0][1:3] == ':/'
                            )
        isNumber = len(words) == 1 and all([c.isdigit() for c in words[0]])

        def matches(cmd):
            if isPath:
                return cmd.isDefaultPathCommand
            elif isNumber:
                return cmd.isDefaultNumericCommand
            else:
                return cmd.isDefaultCommand

        for cmd in self._commandClasses:
            if matches(cmd):
                return cmd, words

    def _parseCurrentCommand(self):
        """ Parse text and try to get (command, completable word index)
        Return None if failed to parse
        """
        # Split line
        text = self._edit.commandText()
        words = splitLine(text)
        if not words:
            return None

        # Find command
        cmdClass, args = self._chooseCommand(words)

        if isinstance(self._command, cmdClass):
            command = self._command
        else:
            command = cmdClass()

        # Try to make command object
        try:
            command.setArgs(args)
        except InvalidCmdArgs:
            return None
        else:
            return command

    def eventFilter(self, obj, event):
        if obj is self._edit:
            if event.type() == QEvent.KeyPress and \
               event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown):
                return self._table.event(event)
        elif obj is self._table:
            if event.type() == QEvent.FocusIn:
                self._edit.setFocus()
                return True

        return False
예제 #18
0
class App(QWidget):

    FROM, TO, DATE, SUBJECT, MESSAGE = range(5)

    def __init__(self,
                 mails_lst=[],
                 my_mail="",
                 send=None,
                 receive=None,
                 labels=None):
        super().__init__()
        # self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.sendClass = send
        self.receiveClass = receive
        self.labelsClass = labels
        self.mails_lst = mails_lst
        self.mails = mails_lst[1]
        self.my_mail = my_mail
        self.labels = ["All", "Inbox", "Sent", "Trash"]
        self.title = 'SMTP Email Client'
        self.left = 0
        self.top = 0
        self.width = 1024
        self.height = 600
        self.total_cols = 5
        self.ret_val = False
        self.is_first_ret_val = True
        self.is_reload_mails = False
        self.messages_file = 'messages.data'
        self.initUI()

    def toolbarButtonClick(self, i):
        def buttonClick():
            if self.send_button.isChecked():
                self.send_button.setChecked(False)
                self.sendMenuToggleClick(False)
            # for l in self.label_buttons:
            #     print(l.isChecked())
            if self.label_buttons[i].isChecked():
                # if self.is_reload_mails:
                #     self.reloadMails()
                #     print("mails reloaded")
                #     self.is_reload_mails = False
                print(f"displaying label category '{self.labels[i]}'")
                for index, label_button in enumerate(self.label_buttons):
                    if index != i:
                        label_button.setChecked(False)
                self.mails = self.mails_lst[i]
                self.reloadMails()
            else:
                self.label_buttons[i].setChecked(True)

        return buttonClick

    def parallelReloading(self):
        temp_mails_lst = []
        for label in self.labelsClass:
            temp_mails_lst.append(
                self.receiveClass.get_message(category=label))
        index = [label.isChecked() for label in self.label_buttons].index(True)
        self.mails_lst = temp_mails_lst
        self.mails = temp_mails_lst[index]
        write_to_file(self.messages_file, self.mails_lst)
        self.is_reload_mails = True

    def parallelSending(self, to_addr, subj, msg):
        self.sendClass.send_message(to_addr, subj, msg)

    def reloadButtonClick(self, s):
        if self.receiveClass:
            if self.is_first_ret_val:
                print("reloading all mails")
                self.ret_val = threading.Thread(target=self.parallelReloading,
                                                args=())
                self.ret_val.start()
                self.is_first_ret_val = False
            elif not self.ret_val.isAlive():
                print("reloading all mails")
                self.ret_val = threading.Thread(target=self.parallelReloading,
                                                args=())
                self.ret_val.start()
            else:
                print("reloading already taking place in background")
        else:
            print("unable to reload as 'receiveClass' missing")
        self.reload_button.setChecked(False)

    def sendMenuToggleClick(self, s):
        if s:
            self.toBox.setFixedWidth(self.contentView.width())
            self.contentView.hide()
            self.toBox.show()
            self.subjectBox.show()
            self.messageBox.show()
            self.sendButtonBox.show()
        else:
            self.toBox.hide()
            self.subjectBox.hide()
            self.messageBox.hide()
            self.sendButtonBox.hide()
            self.contentView.show()

    def logoutButtonClick(self, s):
        print("loging out and closing app")
        sys.exit()

    def rowSelectionClick(self):
        if self.send_button.isChecked():
            self.send_button.setChecked(False)
            self.sendMenuToggleClick(False)
        self.dataView.showColumn(1)
        self.dataView.showColumn(4)
        item = self.dataView.selectedIndexes()
        lst = []
        for i in range(self.total_cols):
            text = item[i].model().itemFromIndex(item[i]).text()
            if i == 0 or i == 1:
                text = text.split()
            lst.append(text)
        self.dataView.hideColumn(1)
        self.dataView.hideColumn(4)

        msg = self.htmlString(lst)
        self.contentView.setPlainText("")
        self.contentView.textCursor().insertHtml(msg)

    def sendButtonClick(self):
        if self.sendClass:
            self.reloadButtonClick(True)
            # to_addr = re.split(r'[, ]', self.toBox.text())
            to_addr = re.findall(r'[\w\.-]+@[\w\.-]+', self.toBox.text())
            subj = self.subjectBox.text()
            msg = self.messageBox.toPlainText()
            send_ret_val = threading.Thread(target=self.parallelSending,
                                            args=(to_addr, subj, msg))
            send_ret_val.start()
        else:
            print("unable to send as 'sendClass' missing")
        self.toBox.setText("")
        self.subjectBox.setText("")
        self.messageBox.setPlainText("")
        self.sendMenuToggleClick(False)
        self.send_button.setChecked(False)

    def initUI(self):

        # windows title and geometry set
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.globalLayout = QVBoxLayout()
        menuLayout = QHBoxLayout()
        dataLayout = QHBoxLayout()

        labelLayout = QToolBar("Labels")
        self.label_buttons = [QAction(label, self) for label in self.labels]
        # button_action.setStatusTip("This is your button")
        for i, label_button in enumerate(self.label_buttons):
            label_button.triggered.connect(self.toolbarButtonClick(i))
            labelLayout.addAction(label_button)
            label_button.setCheckable(True)
            # labelLayout.addAction(label_button)
        self.label_buttons[1].setChecked(True)

        optionLayout = QToolBar("Options")
        self.send_button = QAction(QIcon("images/icons8-email-60.png"),
                                   "Send Mail", self)
        self.reload_button = QAction(QIcon("images/icons8-reset-60.png"),
                                     "Reload Page", self)
        logout_button = QAction(QIcon("images/icons8-shutdown-60.png"),
                                "Logout", self)

        self.send_button.triggered.connect(self.sendMenuToggleClick)
        self.reload_button.triggered.connect(self.reloadButtonClick)
        logout_button.triggered.connect(self.logoutButtonClick)
        optionLayout.addAction(self.send_button)
        optionLayout.addAction(self.reload_button)
        optionLayout.addAction(logout_button)
        self.send_button.setCheckable(True)
        self.reload_button.setCheckable(True)
        logout_button.setCheckable(True)
        # w1.setContentsMargins(0, 0, 0, 0)
        # w2.setContentsMargins(0, 0, 0, 0)
        # menuLayout.setSpacing(0)
        menuLayout.setContentsMargins(0, 0, 0, 0)
        optionLayout.setFixedWidth(106)
        menuLayout.addWidget(labelLayout, 10)
        menuLayout.addWidget(QLabel(self.my_mail), 1)
        menuLayout.addWidget(optionLayout)

        # dataview with non editable columns (from, date, subject etc)
        self.dataView = QTreeView()
        self.dataView.setRootIsDecorated(False)
        self.dataView.setAlternatingRowColors(True)
        self.dataView.setEditTriggers(QAbstractItemView.NoEditTriggers)

        # content view to display complete email message
        self.contentView = QPlainTextEdit()
        self.contentView.setReadOnly(True)

        self.sendLayout = QVBoxLayout()
        self.toBox = QLineEdit()
        self.subjectBox = QLineEdit()
        self.messageBox = QPlainTextEdit()
        self.sendButtonBox = QPushButton("Send")
        self.toBox.setPlaceholderText("To")
        self.subjectBox.setPlaceholderText("Subject")
        self.messageBox.setPlaceholderText("Message")
        self.sendLayout.addWidget(self.toBox)
        self.sendLayout.addWidget(self.subjectBox)
        self.sendLayout.addWidget(self.messageBox)
        self.sendLayout.addWidget(self.sendButtonBox)
        self.sendLayout.setSpacing(0)
        self.sendLayout.setContentsMargins(0, 0, 0, 0)

        # set layout of columns and content box horizontally
        dataLayout.addWidget(self.dataView, 3)
        dataLayout.addWidget(self.contentView, 2)
        dataLayout.addLayout(self.sendLayout)
        self.contentView.show()
        self.toBox.hide()
        self.subjectBox.hide()
        self.messageBox.hide()
        self.sendButtonBox.hide()

        self.sendButtonBox.clicked.connect(self.sendButtonClick)

        # create mail model to add to data view
        self.model = self.createMailModel(self)
        self.dataView.setModel(self.model)
        self.dataView.clicked.connect(self.rowSelectionClick)

        self.globalLayout.addLayout(menuLayout, 1)
        self.globalLayout.addLayout(dataLayout, 20)
        self.setLayout(self.globalLayout)

        self.addAllMails()
        self.autoColumnWidths()
        self.show()

        self.reloadButtonClick(True)

    def reloadMails(self):
        # self.mails = mails
        self.model.removeRows(0, self.model.rowCount())
        self.addAllMails()
        self.autoColumnWidths()

        # set headers text for the created model
    def createMailModel(self, parent):
        model = QStandardItemModel(0, self.total_cols, parent)
        model.setHeaderData(self.FROM, Qt.Horizontal, "From")
        model.setHeaderData(self.TO, Qt.Horizontal, "To")
        model.setHeaderData(self.DATE, Qt.Horizontal, "Date")
        model.setHeaderData(self.SUBJECT, Qt.Horizontal, "Subject")
        model.setHeaderData(self.MESSAGE, Qt.Horizontal, "Message")
        return model

    # add content of mail to data view
    def addAllMails(self):
        today = date.today()
        today_date = today.strftime('%a, %d %b %Y')
        for mail in self.mails:
            if today_date == mail[2]:
                date_temp = mail[3]
            else:
                date_temp = mail[2]
            self.addMail(self.model, mail[0], mail[1], date_temp, mail[4],
                         mail[5])
        if self.mails:
            # msg = self.htmlString(
            #     [self.mails[-1][0], self.mails[-1][1], self.mails[-1][4], self.mails[-1][5]])
            msg = self.htmlString(self.mails[-1])
            self.contentView.setPlainText("")
            self.contentView.textCursor().insertHtml(msg)

    def addMail(self, model, mailFrom, mailTo, date, subject, message):
        model.insertRow(0)
        mailFrom = ' '.join(map(str, mailFrom))
        mailTo = ' '.join(map(str, mailTo))
        model.setData(model.index(0, self.FROM), mailFrom)
        model.setData(model.index(0, self.TO), mailTo)
        model.setData(model.index(0, self.DATE), date)
        model.setData(model.index(0, self.SUBJECT), subject)
        model.setData(model.index(0, self.MESSAGE), message)

    def autoColumnWidths(self):
        width_plus = 30
        self.dataView.setColumnWidth(1, 0)
        self.dataView.setColumnWidth(4, 0)
        for i in range(self.total_cols):
            self.dataView.resizeColumnToContents(i)
            width = self.dataView.columnWidth(i)
            self.dataView.setColumnWidth(i, width + width_plus)
        self.dataView.hideColumn(1)
        self.dataView.hideColumn(4)

    def htmlString(self, lst):
        fr_addr = "<b>From</b><br>"
        for l in lst[0]:
            fr_addr += f"{l}<br>"
        fr_addr += "<br>"

        to_addr = "<b>To</b><br>"
        for l in lst[1]:
            to_addr += f"{l}<br>"
        to_addr += "<br>"

        subject = f"<b>Subject</b><br>{lst[-2]}<br><br>"
        message = lst[-1].replace("\n", "<br>")
        message = f"<b>Message</b><br>{message}<br><br>"
        return fr_addr + to_addr + subject + message
예제 #19
0
파일: tdm.py 프로젝트: mechromonger/tdm
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self._version = "0.1.11"
        self.setWindowIcon(QIcon("GUI/icons/logo.png"))
        self.setWindowTitle("Tasmota Device Manager {}".format(self._version))

        self.main_splitter = QSplitter()
        self.devices_splitter = QSplitter(Qt.Vertical)

        self.fulltopic_queue = []
        self.settings = QSettings()
        self.setMinimumSize(QSize(1280,800))

        self.device_model = TasmotaDevicesModel()
        self.telemetry_model = TasmotaDevicesTree()
        self.console_model = ConsoleModel()

        self.sorted_console_model = QSortFilterProxyModel()
        self.sorted_console_model.setSourceModel(self.console_model)
        self.sorted_console_model.setFilterKeyColumn(CnsMdl.FRIENDLY_NAME)

        self.setup_mqtt()
        self.setup_telemetry_view()
        self.setup_main_layout()
        self.add_devices_tab()
        self.build_toolbars()
        self.setStatusBar(QStatusBar())

        self.queue_timer = QTimer()
        self.queue_timer.setSingleShot(True)
        self.queue_timer.timeout.connect(self.mqtt_ask_for_fulltopic)

        self.build_cons_ctx_menu()

        self.load_window_state()

    def setup_main_layout(self):
        self.mdi = QMdiArea()
        self.mdi.setActivationOrder(QMdiArea.ActivationHistoryOrder)
        self.mdi.setViewMode(QMdiArea.TabbedView)
        self.mdi.setDocumentMode(True)

        mdi_widget = QWidget()
        mdi_widget.setLayout(VLayout())
        mdi_widget.layout().addWidget(self.mdi)

        self.devices_splitter.addWidget(mdi_widget)

        vl_console = VLayout()
        self.console_view = TableView()
        self.console_view.setModel(self.sorted_console_model)
        self.console_view.setupColumns(columns_console)
        self.console_view.setAlternatingRowColors(True)
        self.console_view.setSortingEnabled(True)
        self.console_view.sortByColumn(CnsMdl.TIMESTAMP, Qt.DescendingOrder)
        self.console_view.verticalHeader().setDefaultSectionSize(20)
        self.console_view.setMinimumHeight(200)
        self.console_view.setContextMenuPolicy(Qt.CustomContextMenu)

        vl_console.addWidget(self.console_view)

        console_widget = QWidget()
        console_widget.setLayout(vl_console)

        self.devices_splitter.addWidget(console_widget)
        self.main_splitter.insertWidget(0, self.devices_splitter)
        self.setCentralWidget(self.main_splitter)
        self.console_view.clicked.connect(self.select_cons_entry)
        self.console_view.doubleClicked.connect(self.view_payload)
        self.console_view.customContextMenuRequested.connect(self.show_cons_ctx_menu)

    def setup_telemetry_view(self):
        tele_widget = QWidget()
        vl_tele = VLayout()
        self.tview = QTreeView()
        self.tview.setMinimumWidth(300)
        self.tview.setModel(self.telemetry_model)
        self.tview.setAlternatingRowColors(True)
        self.tview.setUniformRowHeights(True)
        self.tview.setIndentation(15)
        self.tview.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum))
        self.tview.expandAll()
        self.tview.resizeColumnToContents(0)
        vl_tele.addWidget(self.tview)
        tele_widget.setLayout(vl_tele)
        self.main_splitter.addWidget(tele_widget)

    def setup_mqtt(self):
        self.mqtt = MqttClient()
        self.mqtt.connecting.connect(self.mqtt_connecting)
        self.mqtt.connected.connect(self.mqtt_connected)
        self.mqtt.disconnected.connect(self.mqtt_disconnected)
        self.mqtt.connectError.connect(self.mqtt_connectError)
        self.mqtt.messageSignal.connect(self.mqtt_message)

    def add_devices_tab(self):
        tabDevicesList = DevicesListWidget(self)
        self.mdi.addSubWindow(tabDevicesList)
        tabDevicesList.setWindowState(Qt.WindowMaximized)

    def load_window_state(self):
        wndGeometry = self.settings.value('window_geometry')
        if wndGeometry:
            self.restoreGeometry(wndGeometry)
        spltState = self.settings.value('splitter_state')
        if spltState:
            self.main_splitter.restoreState(spltState)

    def build_toolbars(self):
        main_toolbar = Toolbar(orientation=Qt.Horizontal, iconsize=32, label_position=Qt.ToolButtonIconOnly)
        main_toolbar.setObjectName("main_toolbar")
        self.addToolBar(main_toolbar)

        main_toolbar.addAction(QIcon("./GUI/icons/connections.png"), "Configure MQTT broker", self.setup_broker)
        agBroker = QActionGroup(self)
        agBroker.setExclusive(True)

        self.actConnect = CheckableAction(QIcon("./GUI/icons/connect.png"), "Connect to the broker", agBroker)
        self.actDisconnect = CheckableAction(QIcon("./GUI/icons/disconnect.png"), "Disconnect from broker", agBroker)

        self.actDisconnect.setChecked(True)

        self.actConnect.triggered.connect(self.mqtt_connect)
        self.actDisconnect.triggered.connect(self.mqtt_disconnect)

        main_toolbar.addActions(agBroker.actions())
        main_toolbar.addSeparator()

    def initial_query(self, idx):
        for q in initial_queries:
            topic = "{}status".format(self.device_model.commandTopic(idx))
            self.mqtt.publish(topic, q)
            q = q if q else ''
            self.console_log(topic, "Asked for STATUS {}".format(q), q)

    def setup_broker(self):
        brokers_dlg = BrokerDialog()
        if brokers_dlg.exec_() == QDialog.Accepted and self.mqtt.state == self.mqtt.Connected:
            self.mqtt.disconnect()

    def mqtt_connect(self):
        self.broker_hostname = self.settings.value('hostname', 'localhost')
        self.broker_port = self.settings.value('port', 1883, int)
        self.broker_username = self.settings.value('username')
        self.broker_password = self.settings.value('password')

        self.mqtt.hostname = self.broker_hostname
        self.mqtt.port = self.broker_port

        if self.broker_username:
            self.mqtt.setAuth(self.broker_username, self.broker_password)

        if self.mqtt.state == self.mqtt.Disconnected:
            self.mqtt.connectToHost()

    def mqtt_disconnect(self):
        self.mqtt.disconnectFromHost()

    def mqtt_connecting(self):
        self.statusBar().showMessage("Connecting to broker")

    def mqtt_connected(self):
        self.statusBar().showMessage("Connected to {}:{} as {}".format(self.broker_hostname, self.broker_port, self.broker_username if self.broker_username else '[anonymous]'))

        self.mqtt_subscribe()

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            self.initial_query(idx)

    def mqtt_subscribe(self):
        main_topics = ["+/stat/+", "+/tele/+", "stat/#", "tele/#"]

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            if not self.device_model.isDefaultTemplate(idx):
                main_topics.append(self.device_model.commandTopic(idx))
                main_topics.append(self.device_model.statTopic(idx))

        for t in main_topics:
            self.mqtt.subscribe(t)

    def mqtt_ask_for_fulltopic(self):
        for i in range(len(self.fulltopic_queue)):
            self.mqtt.publish(self.fulltopic_queue.pop(0))

    def mqtt_disconnected(self):
        self.statusBar().showMessage("Disconnected")

    def mqtt_connectError(self, rc):
        reason = {
            1: "Incorrect protocol version",
            2: "Invalid client identifier",
            3: "Server unavailable",
            4: "Bad username or password",
            5: "Not authorized",
        }
        self.statusBar().showMessage("Connection error: {}".format(reason[rc]))
        self.actDisconnect.setChecked(True)

    def mqtt_message(self, topic, msg):
        found = self.device_model.findDevice(topic)
        if found.reply == 'LWT':
            if not msg:
                msg = "offline"

            if found.index.isValid():
                self.console_log(topic, "LWT update: {}".format(msg), msg)
                self.device_model.updateValue(found.index, DevMdl.LWT, msg)

            elif msg == "Online":
                self.console_log(topic, "LWT for unknown device '{}'. Asking for FullTopic.".format(found.topic), msg, False)
                self.fulltopic_queue.append("cmnd/{}/fulltopic".format(found.topic))
                self.fulltopic_queue.append("{}/cmnd/fulltopic".format(found.topic))
                self.queue_timer.start(1500)

        elif found.reply == 'RESULT':
            full_topic = loads(msg).get('FullTopic')
            new_topic = loads(msg).get('Topic')
            template_name = loads(msg).get('NAME')

            if full_topic:
                # TODO: update FullTopic for existing device AFTER the FullTopic changes externally (the message will arrive from new FullTopic)
                if not found.index.isValid():
                    self.console_log(topic, "FullTopic for {}".format(found.topic), msg, False)

                    new_idx = self.device_model.addDevice(found.topic, full_topic, lwt='online')
                    tele_idx = self.telemetry_model.addDevice(TasmotaDevice, found.topic)
                    self.telemetry_model.devices[found.topic] = tele_idx
                    #TODO: add QSortFilterProxyModel to telemetry treeview and sort devices after adding

                    self.initial_query(new_idx)
                    self.console_log(topic, "Added {} with fulltopic {}, querying for STATE".format(found.topic, full_topic), msg)
                    self.tview.expand(tele_idx)
                    self.tview.resizeColumnToContents(0)

            if new_topic:
                if found.index.isValid() and found.topic != new_topic:
                    self.console_log(topic, "New topic for {}".format(found.topic), msg)

                    self.device_model.updateValue(found.index, DevMdl.TOPIC, new_topic)

                    tele_idx = self.telemetry_model.devices.get(found.topic)

                    if tele_idx:
                        self.telemetry_model.setDeviceName(tele_idx, new_topic)
                        self.telemetry_model.devices[new_topic] = self.telemetry_model.devices.pop(found.topic)

            if template_name:
                self.device_model.updateValue(found.index, DevMdl.MODULE, template_name)

        elif found.index.isValid():
            if found.reply == 'STATUS':
                self.console_log(topic, "Received device status", msg)
                payload = loads(msg)['Status']
                self.device_model.updateValue(found.index, DevMdl.FRIENDLY_NAME, payload['FriendlyName'][0])
                self.telemetry_model.setDeviceFriendlyName(self.telemetry_model.devices[found.topic], payload['FriendlyName'][0])
                self.tview.resizeColumnToContents(0)
                module = payload['Module']
                if module == '0':
                    self.mqtt.publish(self.device_model.commandTopic(found.index)+"template")
                else:
                    self.device_model.updateValue(found.index, DevMdl.MODULE, module)

            elif found.reply == 'STATUS1':
                self.console_log(topic, "Received program information", msg)
                payload = loads(msg)['StatusPRM']
                self.device_model.updateValue(found.index, DevMdl.RESTART_REASON, payload['RestartReason'])

            elif found.reply == 'STATUS2':
                self.console_log(topic, "Received firmware information", msg)
                payload = loads(msg)['StatusFWR']
                self.device_model.updateValue(found.index, DevMdl.FIRMWARE, payload['Version'])
                self.device_model.updateValue(found.index, DevMdl.CORE, payload['Core'])

            elif found.reply == 'STATUS3':
                self.console_log(topic, "Received syslog information", msg)
                payload = loads(msg)['StatusLOG']
                self.device_model.updateValue(found.index, DevMdl.TELEPERIOD, payload['TelePeriod'])

            elif found.reply == 'STATUS5':
                self.console_log(topic, "Received network status", msg)
                payload = loads(msg)['StatusNET']
                self.device_model.updateValue(found.index, DevMdl.MAC, payload['Mac'])
                self.device_model.updateValue(found.index, DevMdl.IP, payload['IPAddress'])

            elif found.reply == 'STATUS8':
                self.console_log(topic, "Received telemetry", msg)
                payload = loads(msg)['StatusSNS']
                self.parse_telemetry(found.index, payload)

            elif found.reply == 'STATUS11':
                self.console_log(topic, "Received device state", msg)
                payload = loads(msg)['StatusSTS']
                self.parse_state(found.index, payload)

            elif found.reply == 'SENSOR':
                self.console_log(topic, "Received telemetry", msg)
                payload = loads(msg)
                self.parse_telemetry(found.index, payload)

            elif found.reply == 'STATE':
                self.console_log(topic, "Received device state", msg)
                payload = loads(msg)
                self.parse_state(found.index, payload)

            elif found.reply.startswith('POWER'):
                self.console_log(topic, "Received {} state".format(found.reply), msg)
                payload = {found.reply: msg}
                self.parse_power(found.index, payload)

    def parse_power(self, index, payload):
        old = self.device_model.power(index)
        power = {k: payload[k] for k in payload.keys() if k.startswith("POWER")}
        needs_update = False
        if old:
            for k in old.keys():
                needs_update |= old[k] != power.get(k, old[k])
                if needs_update:
                    break
        else:
            needs_update = True

        if needs_update:
            self.device_model.updateValue(index, DevMdl.POWER, power)

    def parse_state(self, index, payload):
        bssid = payload['Wifi'].get('BSSId')
        if not bssid:
            bssid = payload['Wifi'].get('APMac')
        self.device_model.updateValue(index, DevMdl.BSSID, bssid)
        self.device_model.updateValue(index, DevMdl.SSID, payload['Wifi']['SSId'])
        self.device_model.updateValue(index, DevMdl.CHANNEL, payload['Wifi'].get('Channel'))
        self.device_model.updateValue(index, DevMdl.RSSI, payload['Wifi']['RSSI'])
        self.device_model.updateValue(index, DevMdl.UPTIME, payload['Uptime'])
        self.device_model.updateValue(index, DevMdl.LOADAVG, payload.get('LoadAvg'))

        self.parse_power(index, payload)

        tele_idx = self.telemetry_model.devices.get(self.device_model.topic(index))

        if tele_idx:
            tele_device = self.telemetry_model.getNode(tele_idx)
            self.telemetry_model.setDeviceFriendlyName(tele_idx, self.device_model.friendly_name(index))

            pr = tele_device.provides()
            for k in pr.keys():
                self.telemetry_model.setData(pr[k], payload.get(k))

    def parse_telemetry(self, index, payload):
        device = self.telemetry_model.devices.get(self.device_model.topic(index))
        if device:
            node = self.telemetry_model.getNode(device)
            time = node.provides()['Time']
            if 'Time' in payload:
                self.telemetry_model.setData(time, payload.pop('Time'))

            temp_unit = "C"
            pres_unit = "hPa"

            if 'TempUnit' in payload:
                temp_unit = payload.pop('TempUnit')

            if 'PressureUnit' in payload:
                pres_unit = payload.pop('PressureUnit')

            for sensor in sorted(payload.keys()):
                if sensor == 'DS18x20':
                    for sns_name in payload[sensor].keys():
                        d = node.devices().get(sensor)
                        if not d:
                            d = self.telemetry_model.addDevice(DS18x20, payload[sensor][sns_name]['Type'], device)
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                        payload[sensor][sns_name]['Id'] = payload[sensor][sns_name].pop('Address')

                        pr = self.telemetry_model.getNode(d).provides()
                        for pk in pr.keys():
                            self.telemetry_model.setData(pr[pk], payload[sensor][sns_name].get(pk))
                        self.tview.expand(d)

                elif sensor.startswith('DS18B20'):
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(DS18x20, sensor, device)
                    self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)

                if sensor == 'COUNTER':
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(CounterSns, "Counter", device)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)

                else:
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(sensor_map.get(sensor, Node), sensor, device)
                    pr = self.telemetry_model.getNode(d).provides()
                    if 'Temperature' in pr:
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    if 'Pressure' in pr or 'SeaPressure' in pr:
                        self.telemetry_model.getNode(d).setPresUnit(pres_unit)
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)
        self.tview.resizeColumnToContents(0)

    def console_log(self, topic, description, payload, known=True):
        device = self.device_model.findDevice(topic)
        fname = self.device_model.friendly_name(device.index)
        self.console_model.addEntry(topic, fname, description, payload, known)
        self.console_view.resizeColumnToContents(1)

    def view_payload(self, idx):
        idx = self.sorted_console_model.mapToSource(idx)
        row = idx.row()
        timestamp = self.console_model.data(self.console_model.index(row, CnsMdl.TIMESTAMP))
        topic = self.console_model.data(self.console_model.index(row, CnsMdl.TOPIC))
        payload = self.console_model.data(self.console_model.index(row, CnsMdl.PAYLOAD))

        dlg = PayloadViewDialog(timestamp, topic, payload)
        dlg.exec_()

    def select_cons_entry(self, idx):
        self.cons_idx = idx

    def build_cons_ctx_menu(self):
        self.cons_ctx_menu = QMenu()
        self.cons_ctx_menu.addAction("View payload", lambda: self.view_payload(self.cons_idx))
        self.cons_ctx_menu.addSeparator()
        self.cons_ctx_menu.addAction("Show only this device", lambda: self.cons_set_filter(self.cons_idx))
        self.cons_ctx_menu.addAction("Show all devices", self.cons_set_filter)

    def show_cons_ctx_menu(self, at):
        self.select_cons_entry(self.console_view.indexAt(at))
        self.cons_ctx_menu.popup(self.console_view.viewport().mapToGlobal(at))

    def cons_set_filter(self, idx=None):
        if idx:
            idx = self.sorted_console_model.mapToSource(idx)
            topic = self.console_model.data(self.console_model.index(idx.row(), CnsMdl.FRIENDLY_NAME))
            self.sorted_console_model.setFilterFixedString(topic)
        else:
            self.sorted_console_model.setFilterFixedString("")

    def closeEvent(self, e):
        self.settings.setValue("window_geometry", self.saveGeometry())
        self.settings.setValue("splitter_state", self.main_splitter.saveState())
        self.settings.sync()
        e.accept()
예제 #20
0
class UIFilterManager(object):

    def __init__(self):
        self.mainDialog = filtermanagerdialog.FilterManagerDialog()
        self.mainLayout = QVBoxLayout(self.mainDialog)
        self.formLayout = QFormLayout()
        self.buttonBox = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel)

        self.kritaInstance = krita.Krita.instance()
        self._filters = sorted(self.kritaInstance.filters())
        self._documents = self.kritaInstance.documents()
        self.treeModel = filtermanagertreemodel.FilterManagerTreeModel(self)

        self.documentsTreeView = QTreeView()
        self.filterComboBox = filtercombobox.FilterComboBox(self)

        self.buttonBox.accepted.connect(self.confirmButton)
        self.buttonBox.rejected.connect(self.mainDialog.close)

        self.documentsTreeView.setSelectionMode(
            QAbstractItemView.SingleSelection)
        self.mainDialog.setWindowModality(Qt.NonModal)

    def initialize(self):
        self.documentsTreeView.setModel(self.treeModel)
        self.documentsTreeView.setWindowTitle(i18n("Document Tree Model"))
        self.documentsTreeView.resizeColumnToContents(0)
        self.documentsTreeView.resizeColumnToContents(1)
        self.documentsTreeView.resizeColumnToContents(2)

        self.formLayout.addRow(i18nc("Python filters", "Filters:"), self.filterComboBox)

        self.line = QFrame()
        self.line.setFrameShape(QFrame.HLine)
        self.line.setFrameShadow(QFrame.Sunken)

        self.mainLayout.addWidget(self.documentsTreeView)
        self.mainLayout.addLayout(self.formLayout)
        self.mainLayout.addWidget(self.line)
        self.mainLayout.addWidget(self.buttonBox)

        self.mainDialog.resize(500, 300)
        self.mainDialog.setWindowTitle(i18n("Filter Manager"))
        self.mainDialog.setSizeGripEnabled(True)
        self.mainDialog.show()
        self.mainDialog.activateWindow()

    def confirmButton(self):
        documentsIndexes = []

        selectionModel = self.documentsTreeView.selectionModel()
        for index in selectionModel.selectedRows():
            node = self.treeModel.data(index, Qt.UserRole + 1)
            documentIndex = self.treeModel.data(index, Qt.UserRole + 2)
            _type = self.treeModel.data(index, Qt.UserRole + 3)

            if _type == 'Document':
                self.applyFilterOverDocument(self.documents[documentIndex])
            else:
                self.applyFilterOverNode(node, self.documents[documentIndex])

            documentsIndexes.append(documentIndex)

        self.refreshDocumentsProjections(set(documentsIndexes))

    def refreshDocumentsProjections(self, indexes):
        for index in indexes:
            document = self.documents[index]
            document.refreshProjection()

    def applyFilterOverNode(self, node, document):
        _filter = self.kritaInstance.filter(self.filterComboBox.currentText())
        _filter.apply(node, 0, 0, document.width(), document.height())

    def applyFilterOverDocument(self, document):
        """This method applies the selected filter just to topLevelNodes,
        then if topLevelNodes are GroupLayers, that filter will not be
        applied."""

        for node in document.topLevelNodes():
            self.applyFilterOverNode(node, document)

    @property
    def filters(self):
        return self._filters

    @property
    def documents(self):
        return self._documents
예제 #21
0
    def init_ui(self, window: QMainWindow, system_info):

        v_box = QVBoxLayout()
        button_style = "background-color: #006699; padding-left:20px; padding-right:20px;" \
                       "padding-top:5px; padding-bottom:5px;"

        # proceed to the json - button
        json_button = QPushButton("Proceed to JSON")
        json_button.setStyleSheet(button_style)
        json_button.clicked.connect(
            lambda: self.display_plaintext_data(window, system_info))

        v_box.addSpacing(20)

        # if system_info is empty
        if not system_info:
            nothing_found = QLabel("Nothing was found.")
            v_box.addWidget(nothing_found, alignment=Qt.AlignCenter)

        layout_grid = QGridLayout()
        for i, component in enumerate(system_info):
            if i == 0:
                prev_type = component["type"]
            else:
                prev_type = system_info[i - 1]["type"]

            if component["type"] != prev_type or i == 0:
                if component['type'] == 'I':
                    title = QLabel('ITEMS')
                    layout_grid.addWidget(title, 0, 0)
                else:
                    title = QLabel('PRODUCTS')
                    layout_grid.addWidget(title, 0, 1)
                # noinspection PyArgumentList
                title.setFont(QFont("futura", pointSize=16, italic=False))
                if i != 0:
                    v_box.addSpacing(10)
                tree = QTreeView()
                root_model = QStandardItemModel()
                root_model.setHorizontalHeaderLabels(['Name', 'Value'])
                tree.setModel(root_model)

            parent = root_model.invisibleRootItem()
            if component['type'] == 'I':
                self.list_element(component, parent)
                index = 0
            else:
                index = 1
                name = component['features'].get('type',
                                                 'Unknown component').upper()
                parent.appendRow([QStandardItem(name), QStandardItem('')])
                new_parent = parent.child(parent.rowCount() - 1)
                for feature in component.items():
                    if feature[0] != "type":
                        if feature[0] == 'features':
                            self.list_features(str(feature[1]), new_parent)
                        elif feature[0] == 'contents':
                            self.list_contents(str(feature[1]), new_parent)
                        else:
                            self.list_data(feature[0], feature[1], new_parent)

            tree.expandAll()

            layout_grid.addWidget(tree, 1, index)
            tree.resizeColumnToContents(0)
            v_box.addLayout(layout_grid)

        v_box.addWidget(json_button, alignment=Qt.AlignCenter)
        v_box.addSpacing(20)

        self.setLayout(v_box)
예제 #22
0
class MainWindow(QMainWindow):
    def __init__(self, dataBaseName):
        super().__init__()
        self._dataBase = myDatabase.MyDataBase(dataBaseName)

        self._dictexe = {
            "первый запрос": (self.firstQuery, self.firstExe),
            "второй запрос": (self.secondQuery, self.secondExe),
            "третий запрос": (self.thirdQuery, self.thirdExe)
        }

        self._view = QTreeView()

        self._buttonAdd = QPushButton("Добавить")
        self._buttonAdd.clicked.connect(self.getItems)
        self._addSpinBox = QSpinBox()
        self._addComboBox = QComboBox()
        self._addComboBox.addItems(self._dataBase.dishes())

        self._layout = QHBoxLayout()
        self._qSpinBox = QSpinBox()
        self._qComboBox = QComboBox()
        self._qLineEdit = QLineEdit()
        self._qCalendarWidget = QCalendarWidget()

        self._queryDisc = QLabel()

        self._buttonExe = QPushButton("Исполнить")
        self._buttonExe.clicked.connect(self.onButtonExe)

        self._combox = QComboBox()
        self._combox.currentTextChanged.connect(self.comboChanged)
        self._combox.addItems(self._dictexe.keys())

        self.initUi()

    def initUi(self):
        self.setGeometry(300, 300, 200, 200)
        self.setWindowTitle('Ресторан')
        #self.setWindowIcon(QIcon(''))

        w = QWidget()

        mainLayout = QVBoxLayout()
        w.setLayout(mainLayout)

        self.setCentralWidget(w)

        mainLayout.addWidget(self._view)

        tmpLayout = QHBoxLayout()
        mainLayout.addLayout(tmpLayout)
        tmpLayout.addWidget(QLabel("Добавления ингредиента"))
        tmpLayout.addWidget(self._addSpinBox)
        tmpLayout.addWidget(self._addComboBox)
        tmpLayout.addWidget(self._buttonAdd)

        mainLayout.addWidget(self._queryDisc)

        tmpLayout = QHBoxLayout()
        mainLayout.addLayout(tmpLayout)
        tmpLayout.addWidget(self._combox)
        tmpLayout.addLayout(self._layout)
        tmpLayout.addWidget(self._buttonExe)

        self.adjustSize()

    def comboChanged(self, text):
        self._dictexe[text][0]()

    def clearLayout(self):
        while self._layout.count() > 0:
            self._layout.itemAt(0).widget().setParent(None)

    #Названия и калорийность блюд по рецептам автора X
    def firstQuery(self):
        self._queryDisc.setText(
            "Названия и калорийность блюд по рецептам автора X")
        self.clearLayout()
        #self._qSpinBox.setValue(0)
        #self._layout.insertWidget(1,self._qSpinBox)
        self._qComboBox.clear()
        self._qComboBox.addItems(self._dataBase.authors())
        self._layout.insertWidget(1, self._qComboBox)

    def firstExe(self):
        model = self._dataBase.first(self._qComboBox.currentText())
        self.setModel(model)

    #Названия ресторанов, к которым относятся повара, готовящие блюда содержащие в
    #названии подстроку X (например, «картофельный»), отсортированные по алфавиту
    def secondQuery(self):
        self._queryDisc.setText(
            "Названия ресторанов, к которым относятся повара,\n готовящие блюда содержащие в названии подстроку X (например, «картофельный»),\n отсортированные по алфавиту"
        )
        self.clearLayout()
        self._qLineEdit.clear()
        self._layout.insertWidget(1, self._qLineEdit)

    def secondExe(self):
        model = self._dataBase.second(self._qLineEdit.text())
        self.setModel(model)

    #Названия и количества ингредиентов и названия мероприятий, на которых разливают
    #напитки в количестве меньше X после даты Y
    def thirdQuery(self):
        self._queryDisc.setText(
            "Названия и количества ингредиентов и названия мероприятий, на которых разливают\n напитки в количестве меньше X после даты Y"
        )
        self.clearLayout()
        self._layout.insertWidget(1, self._qCalendarWidget)
        self._qSpinBox.setMaximum(self._dataBase.maxDrinkCount() * 10)
        self._qSpinBox.setValue(0)
        self._layout.insertWidget(1, self._qSpinBox)

    def thirdExe(self):
        model = self._dataBase.third(
            self._qSpinBox.value(),
            self._qCalendarWidget.selectedDate().toPyDate())
        self.setModel(model)

    def setModel(self, model):
        if model is None:
            return
        self._view.setVisible(False)
        self._view.setModel(model)
        for i in range(model.columnCount()):
            self._view.resizeColumnToContents(i)
        self._view.setVisible(True)
        self.adjustSize()

    def onButtonExe(self):
        self._dictexe[self._combox.currentText()][1]()

    def getItems(self):
        name, ok = QInputDialog.getText(self, "Ингредиент", "Введите название")
        if not ok:
            return

        self._dataBase.add(self._addSpinBox.value(),
                           self._addComboBox.currentText(), name)
예제 #23
0
class DataSourceWidget(_AbstractCtrlWidget):
    """DataSourceWidget class.

    Widgets provide data source management and monitoring.
    """

    class AvailStateDelegate(QStyledItemDelegate):
        def __init__(self, parent=None):
            super().__init__(parent=parent)
            self._brush = FColor.mkBrush('g')

        def paint(self, painter, option, index):
            """Override."""
            v = index.data()
            painter.setPen(Qt.NoPen)
            if v:
                painter.setBrush(self._brush)
            else:
                painter.setBrush(Qt.NoBrush)

            rect = option.rect
            h = rect.height()
            painter.drawRect(rect.x() + 2, rect.y() + 2, h - 4, h - 4)

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

            self._c_brush = FColor.mkBrush('c')
            self._p_brush = FColor.mkBrush('p')

        def paint(self, painter, option, index):
            """Override."""
            v = index.data()
            if isinstance(v, int):
                painter.setPen(Qt.NoPen)

                if v == 0:
                    # control data
                    painter.setBrush(self._c_brush)
                elif v == 1:
                    # pipeline data
                    painter.setBrush(self._p_brush)
                else:
                    raise ValueError(f"Unknown data type: {v}")

                rect = option.rect
                h = rect.height()
                painter.drawRect(rect.x() + 2, rect.y() + 2, h - 4, h - 4)
            else:
                super().paint(painter, option, index)

    _source_types = {
        "Run directory": DataSource.FILE,
        "ZeroMQ bridge": DataSource.BRIDGE,
    }

    SPLITTER_HANDLE_WIDTH = 9

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._con_view = QTableView()
        self._con_model = ConnectionTableModel(self._source_types)
        self._con_view.setModel(self._con_model)
        self._con_src_type_delegate = ComboBoxDelegate(self._source_types)
        self._con_addr_delegate = LineEditItemDelegateN(self)
        self._con_port_delegate = LineEditItemDelegateN(
            self, validator=QIntValidator(0, 65535))
        self._con_view.setItemDelegateForColumn(1, self._con_src_type_delegate)
        self._con_view.setItemDelegateForColumn(2, self._con_addr_delegate)
        self._con_view.setItemDelegateForColumn(3, self._con_port_delegate)

        self._src_view = QTreeView()
        self._src_tree_model = DataSourceItemModel(self)
        self._src_avail_delegate = self.AvailStateDelegate(self)
        self._src_data_type_delegate = self.DataTypeDelegate(self)
        self._src_device_delegate = LineEditItemDelegate(self)
        self._src_ppt_delegate = LineEditItemDelegate(self)
        self._src_slicer_delegate = SliceItemDelegate(self)
        self._src_boundary_delegate = BoundaryItemDelegate(self)
        self._src_view.setModel(self._src_tree_model)
        self._src_view.setItemDelegateForColumn(0, self._src_avail_delegate)
        self._src_view.setItemDelegateForColumn(1, self._src_data_type_delegate)
        self._src_view.setItemDelegateForColumn(2, self._src_device_delegate)
        self._src_view.setItemDelegateForColumn(3, self._src_ppt_delegate)
        self._src_view.setItemDelegateForColumn(4, self._src_slicer_delegate)
        self._src_view.setItemDelegateForColumn(5, self._src_boundary_delegate)

        self._monitor_tb = QTabWidget()

        self._avail_src_view = QListView()
        self._avail_src_model = DataSourceListModel()
        self._avail_src_view.setModel(self._avail_src_model)

        self._process_mon_view = QTableView()
        self._process_mon_model = ProcessMonitorTableModel()
        self._process_mon_view.setModel(self._process_mon_model)
        self._process_mon_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

        self.initUI()
        self.initConnections()

        self._non_reconfigurable_widgets = [
            self._con_view,
        ]

        self._mon = MonProxy()

        self._raw_srcs = dict()
        self._matched_srcs = dict()
        self._avail_src_timer = QTimer()
        self._avail_src_timer.timeout.connect(self.updateAvailableSources)
        self._avail_src_timer.start(config["SOURCE_AVAIL_UPDATE_TIMER"])

        self._process_mon_timer = QTimer()
        self._process_mon_timer.timeout.connect(self.updateProcessInfo)
        self._process_mon_timer.start(config["PROCESS_MONITOR_UPDATE_TIMER"])

    def initUI(self):
        """Override."""
        self._monitor_tb.setTabPosition(QTabWidget.TabPosition.South)
        self._monitor_tb.addTab(self._avail_src_view, "Source monitor")
        self._monitor_tb.addTab(self._process_mon_view, "Process monitor")

        splitter = QSplitter(Qt.Vertical)
        splitter.setHandleWidth(self.SPLITTER_HANDLE_WIDTH)
        splitter.setChildrenCollapsible(False)
        splitter.addWidget(self._con_view)
        splitter.addWidget(self._src_view)
        splitter.addWidget(self._monitor_tb)
        splitter.setStretchFactor(0, 3)
        splitter.setStretchFactor(1, 1)
        h = splitter.sizeHint().height()
        splitter.setSizes([0.1 * h, 0.6 * h, 0.3 * h])

        layout = QVBoxLayout()
        layout.addWidget(splitter)
        self.setLayout(layout)

        self._con_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

        self._src_view.setIndentation(self._src_view.indentation()/2)
        self._src_view.expandToDepth(1)
        for i in range(4):
            self._src_view.resizeColumnToContents(i)
        for i in range(2):
            self._src_view.header().setSectionResizeMode(
                i, QHeaderView.Fixed)

    def initConnections(self):
        """Override."""
        mediator = self._mediator

        mediator.file_stream_initialized_sgn.connect(self.updateMetaData)

    def updateMetaData(self):
        """Override."""
        try:
            cons = self._con_model.connections()
            self._mediator.onBridgeConnectionsChange(cons)
        except ValueError as e:
            logger.error(e)
            return False

        return True

    def loadMetaData(self):
        """Override."""
        pass

    def updateAvailableSources(self):
        ret = self._mon.get_available_sources()
        if ret is not None:
            raw, matched = ret
            self._avail_src_model.setupModelData(raw)
            self._src_tree_model.updateMatchedSources(matched)

    def updateProcessInfo(self):
        info = []
        for p in list_foam_processes():
            info.append(list(p))
        self._process_mon_model.setupModelData(info)
예제 #24
0
class Widget(QWidget):
    def __init__(self, panel):
        super(Widget, self).__init__(panel)

        layout = QVBoxLayout()
        self.setLayout(layout)
        layout.setSpacing(0)

        self.searchEntry = SearchLineEdit()
        self.treeView = QTreeView(contextMenuPolicy=Qt.CustomContextMenu)
        self.textView = QTextBrowser()

        applyButton = QToolButton(autoRaise=True)
        editButton = QToolButton(autoRaise=True)
        addButton = QToolButton(autoRaise=True)
        self.menuButton = QPushButton(flat=True)
        menu = QMenu(self.menuButton)
        self.menuButton.setMenu(menu)

        splitter = QSplitter(Qt.Vertical)
        top = QHBoxLayout()
        layout.addLayout(top)
        splitter.addWidget(self.treeView)
        splitter.addWidget(self.textView)
        layout.addWidget(splitter)
        splitter.setSizes([200, 100])
        splitter.setCollapsible(0, False)

        top.addWidget(self.searchEntry)
        top.addWidget(applyButton)
        top.addSpacing(10)
        top.addWidget(addButton)
        top.addWidget(editButton)
        top.addWidget(self.menuButton)

        # action generator for actions added to search entry
        def act(slot, icon=None):
            a = QAction(self, triggered=slot)
            self.addAction(a)
            a.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            icon and a.setIcon(icons.get(icon))
            return a

        # hide if ESC pressed in lineedit
        a = act(self.slotEscapePressed)
        a.setShortcut(QKeySequence(Qt.Key_Escape))

        # import action
        a = self.importAction = act(self.slotImport, 'document-open')
        menu.addAction(a)

        # export action
        a = self.exportAction = act(self.slotExport, 'document-save-as')
        menu.addAction(a)

        # apply button
        a = self.applyAction = act(self.slotApply, 'edit-paste')
        applyButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)

        # add button
        a = self.addAction_ = act(self.slotAdd, 'list-add')
        a.setShortcut(QKeySequence(Qt.Key_Insert))
        addButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)

        # edit button
        a = self.editAction = act(self.slotEdit, 'document-edit')
        a.setShortcut(QKeySequence(Qt.Key_F2))
        editButton.setDefaultAction(a)
        menu.addAction(a)

        # set shortcut action
        a = self.shortcutAction = act(
            self.slotShortcut, 'preferences-desktop-keyboard-shortcuts')
        menu.addAction(a)

        # delete action
        a = self.deleteAction = act(self.slotDelete, 'list-remove')
        a.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Delete))
        menu.addAction(a)

        # restore action
        a = self.restoreAction = act(self.slotRestore)
        menu.addSeparator()
        menu.addAction(a)

        # help button
        a = self.helpAction = act(self.slotHelp, 'help-contents')
        menu.addSeparator()
        menu.addAction(a)

        self.treeView.setSelectionBehavior(QTreeView.SelectRows)
        self.treeView.setSelectionMode(QTreeView.ExtendedSelection)
        self.treeView.setRootIsDecorated(False)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setModel(model.model())
        self.treeView.setCurrentIndex(QModelIndex())

        # signals
        self.searchEntry.returnPressed.connect(self.slotReturnPressed)
        self.searchEntry.textChanged.connect(self.updateFilter)
        self.treeView.doubleClicked.connect(self.slotDoubleClicked)
        self.treeView.customContextMenuRequested.connect(self.showContextMenu)
        self.treeView.selectionModel().currentChanged.connect(self.updateText)
        self.treeView.model().dataChanged.connect(self.updateFilter)

        # highlight text
        self.highlighter = highlight.Highlighter(self.textView.document())

        # complete on snippet variables
        self.searchEntry.setCompleter(
            QCompleter([
                ':icon', ':indent', ':menu', ':name', ':python', ':selection',
                ':set', ':symbol', ':template', ':template-run'
            ], self.searchEntry))
        self.readSettings()
        app.settingsChanged.connect(self.readSettings)
        app.translateUI(self)
        self.updateColumnSizes()
        self.setAcceptDrops(True)

    def dropEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            filename = ev.mimeData().urls()[0].toLocalFile()
            if filename:
                ev.accept()
                from . import import_export
                import_export.load(filename, self)

    def dragEnterEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            ev.accept()

    def translateUI(self):
        try:
            self.searchEntry.setPlaceholderText(_("Search..."))
        except AttributeError:
            pass  # not in Qt 4.6
        shortcut = lambda a: a.shortcut().toString(QKeySequence.NativeText)
        self.menuButton.setText(_("&Menu"))
        self.addAction_.setText(_("&Add..."))
        self.addAction_.setToolTip(
            _("Add a new snippet. ({key})").format(
                key=shortcut(self.addAction_)))
        self.editAction.setText(_("&Edit..."))
        self.editAction.setToolTip(
            _("Edit the current snippet. ({key})").format(
                key=shortcut(self.editAction)))
        self.shortcutAction.setText(_("Configure Keyboard &Shortcut..."))
        self.deleteAction.setText(_("&Remove"))
        self.deleteAction.setToolTip(_("Remove the selected snippets."))
        self.applyAction.setText(_("A&pply"))
        self.applyAction.setToolTip(_("Apply the current snippet."))
        self.importAction.setText(_("&Import..."))
        self.importAction.setToolTip(_("Import snippets from a file."))
        self.exportAction.setText(_("E&xport..."))
        self.exportAction.setToolTip(_("Export snippets to a file."))
        self.restoreAction.setText(_("Restore &Built-in Snippets..."))
        self.restoreAction.setToolTip(
            _("Restore deleted or changed built-in snippets."))
        self.helpAction.setText(_("&Help"))
        self.searchEntry.setToolTip(
            _("Enter text to search in the snippets list.\n"
              "See \"What's This\" for more information."))
        self.searchEntry.setWhatsThis(''.join(
            map("<p>{0}</p>\n".format, (
                _("Enter text to search in the snippets list, and "
                  "press Enter to apply the currently selected snippet."),
                _("If the search text fully matches the value of the '{name}' variable "
                  "of a snippet, that snippet is selected.").format(
                      name="name"),
                _("If the search text starts with a colon ':', the rest of the "
                  "search text filters snippets that define the given variable. "
                  "After a space a value can also be entered, snippets will then "
                  "match if the value of the given variable contains the text after "
                  "the space."),
                _("E.g. entering {menu} will show all snippets that are displayed "
                  "in the insert menu.").format(menu="<code>:menu</code>"),
            ))))

    def sizeHint(self):
        return self.parent().mainwindow().size() / 4

    def readSettings(self):
        data = textformats.formatData('editor')
        self.textView.setFont(data.font)
        self.textView.setPalette(data.palette())

    def showContextMenu(self, pos):
        """Called when the user right-clicks the tree view."""
        self.menuButton.menu().popup(self.treeView.viewport().mapToGlobal(pos))

    def slotReturnPressed(self):
        """Called when the user presses Return in the search entry. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
            self.parent().hide()  # make configurable?
            view.setFocus()

    def slotEscapePressed(self):
        """Called when the user presses ESC in the search entry. Hides the panel."""
        self.parent().hide()
        self.parent().mainwindow().currentView().setFocus()

    def slotDoubleClicked(self, index):
        name = self.treeView.model().name(index)
        view = self.parent().mainwindow().currentView()
        insert.insert(name, view)

    def slotAdd(self):
        """Called when the user wants to add a new snippet."""
        edit.Edit(self, None)

    def slotEdit(self):
        """Called when the user wants to edit a snippet."""
        name = self.currentSnippet()
        if name:
            edit.Edit(self, name)

    def slotShortcut(self):
        """Called when the user selects the Configure Shortcut action."""
        from widgets import shortcuteditdialog
        name = self.currentSnippet()
        if name:
            collection = self.parent().snippetActions
            action = actions.action(name, None, collection)
            default = collection.defaults().get(name)
            mgr = actioncollectionmanager.manager(self.parent().mainwindow())
            cb = mgr.findShortcutConflict
            dlg = shortcuteditdialog.ShortcutEditDialog(
                self, cb, (collection, name))

            if dlg.editAction(action, default):
                mgr.removeShortcuts(action.shortcuts())
                collection.setShortcuts(name, action.shortcuts())
                self.treeView.update()

    def slotDelete(self):
        """Called when the user wants to delete the selected rows."""
        rows = sorted(set(i.row() for i in self.treeView.selectedIndexes()),
                      reverse=True)
        if rows:
            for row in rows:
                name = self.treeView.model().names()[row]
                self.parent().snippetActions.setShortcuts(name, [])
                self.treeView.model().removeRow(row)
            self.updateFilter()

    def slotApply(self):
        """Called when the user clicks the apply button. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)

    def slotImport(self):
        """Called when the user activates the import action."""
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"),
                                                  _("All Files"))
        caption = app.caption(_("dialog title", "Import Snippets"))
        filename = None
        filename = QFileDialog.getOpenFileName(self, caption, filename,
                                               filetypes)[0]
        if filename:
            from . import import_export
            import_export.load(filename, self)

    def slotExport(self):
        """Called when the user activates the export action."""
        allrows = [
            row for row in range(model.model().rowCount())
            if not self.treeView.isRowHidden(row, QModelIndex())
        ]
        selectedrows = [
            i.row() for i in self.treeView.selectedIndexes()
            if i.column() == 0 and i.row() in allrows
        ]
        names = self.treeView.model().names()
        names = [names[row] for row in selectedrows or allrows]

        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"),
                                                  _("All Files"))
        n = len(names)
        caption = app.caption(
            _("dialog title", "Export {num} Snippet", "Export {num} Snippets",
              n).format(num=n))
        filename = QFileDialog.getSaveFileName(self, caption, None,
                                               filetypes)[0]
        if filename:
            from . import import_export
            try:
                import_export.save(names, filename)
            except (IOError, OSError) as e:
                QMessageBox.critical(
                    self, _("Error"),
                    _("Can't write to destination:\n\n{url}\n\n{error}").
                    format(url=filename, error=e.strerror))

    def slotRestore(self):
        """Called when the user activates the Restore action."""
        from . import restore
        dlg = restore.RestoreDialog(self)
        dlg.setWindowModality(Qt.WindowModal)
        dlg.populate()
        dlg.show()
        dlg.finished.connect(dlg.deleteLater)

    def slotHelp(self):
        """Called when the user clicks the small help button."""
        userguide.show("snippets")

    def currentSnippet(self):
        """Returns the name of the current snippet if it is visible."""
        row = self.treeView.currentIndex().row()
        if row != -1 and not self.treeView.isRowHidden(row, QModelIndex()):
            return self.treeView.model().names()[row]

    def updateFilter(self):
        """Called when the text in the entry changes, updates search results."""
        text = self.searchEntry.text()
        ltext = text.lower()
        filterVars = text.startswith(':')
        if filterVars:
            try:
                fvar, fval = text[1:].split(None, 1)
                fhide = lambda v: v.get(fvar) in (True, None
                                                  ) or fval not in v.get(fvar)
            except ValueError:
                fvar = text[1:].strip()
                fhide = lambda v: not v.get(fvar)
        for row in range(self.treeView.model().rowCount()):
            name = self.treeView.model().names()[row]
            nameid = snippets.get(name).variables.get('name', '')
            if filterVars:
                hide = fhide(snippets.get(name).variables)
            elif nameid == text:
                i = self.treeView.model().createIndex(row, 0)
                self.treeView.selectionModel().setCurrentIndex(
                    i, QItemSelectionModel.SelectCurrent
                    | QItemSelectionModel.Rows)
                hide = False
            elif nameid.lower().startswith(ltext):
                hide = False
            elif ltext in snippets.title(name).lower():
                hide = False
            else:
                hide = True
            self.treeView.setRowHidden(row, QModelIndex(), hide)
        self.updateText()

    def updateText(self):
        """Called when the current snippet changes."""
        name = self.currentSnippet()
        self.textView.clear()
        if name:
            s = snippets.get(name)
            self.highlighter.setPython('python' in s.variables)
            self.textView.setPlainText(s.text)

    def updateColumnSizes(self):
        self.treeView.resizeColumnToContents(0)
        self.treeView.resizeColumnToContents(1)
예제 #25
0
class ChessClaimView(QMainWindow):
    """ The main window of the application.
    Attributes:
        rowCount(int): The number of the row the TreeView Table has.
        iconsSize(int): The recommended size of the icons.
        mac_notification: Notification for macOS
        win_notification: Notification for windows OS
    """
    def __init__(self):
        super().__init__()

        self.resize(720, 275)
        self.iconsSize = 16
        self.setWindowTitle('Chess Claim Tool')
        self.center()

        self.rowCount = 0

        if (platform.system() == "Darwin"):
            from MacNotification import Notification
            self.mac_notification = Notification()
        elif (platform.system() == "Windows"):
            from win10toast import ToastNotifier
            self.win_notification = ToastNotifier()

    def center(self):
        """ Centers the window on the screen """
        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2,
            (screen.height()-size.height())/2)

    def set_GUI(self):
        """ Initialize GUI components. """

        # Create the Menu
        self.livePgnOption = QAction('Live PGN',self)
        self.livePgnOption.setCheckable(True)
        aboutAction = QAction('About',self)

        menubar = self.menuBar()

        optionsMenu = menubar.addMenu('&Options')
        optionsMenu.addAction(self.livePgnOption)

        aboutMenu = menubar.addMenu('&Help')
        aboutMenu.addAction(aboutAction)

        aboutAction.triggered.connect(self.slots.on_about_clicked)

        # Create the Claims Table (TreeView)
        self.claimsTable = QTreeView()
        self.claimsTable.setFocusPolicy(Qt.NoFocus)
        self.claimsTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.claimsTable.header().setDefaultAlignment(Qt.AlignCenter)
        self.claimsTable.setSortingEnabled(True)
        self.claimsTable.doubleClicked.connect(self.open_game)

        # Create the Claims Model
        self.claimsTableModel = QStandardItemModel()
        labels = ["#","Timestamp","Type","Board","Players","Move"]
        self.claimsTableModel.setHorizontalHeaderLabels(labels)
        self.claimsTable.setModel(self.claimsTableModel)

        # Create the Scan & Stop Button Box
        self.buttonBox = ButtonBox(self)

        # Create the Sources Button
        sourcesButton = QPushButton("Add Sources")
        sourcesButton.setObjectName("sources")
        sourcesButton.clicked.connect(self.slots.on_sourcesButton_clicked)

        # Create the Status Bar
        self.pixmapCheck = QPixmap(resource_path("check_icon.png"))
        self.pixmapError = QPixmap(resource_path("error_icon.png"))

        self.sourceLabel = QLabel()
        self.sourceLabel.setObjectName("source-label")
        self.sourceImage = QLabel()
        self.sourceImage.setObjectName("source-image")
        self.downloadLabel = QLabel()
        self.downloadLabel.setObjectName("download-label")
        self.downloadImage = QLabel()
        self.downloadImage.setObjectName("download-image")
        self.scanningLabel = QLabel()
        self.scanningLabel.setObjectName("scanning")
        self.spinnerLabel = QLabel()
        self.spinnerLabel.setVisible(False)
        self.spinnerLabel.setObjectName("spinner")

        self.spinner = QMovie(resource_path("spinner.gif"))
        self.spinner.setScaledSize(QSize(self.iconsSize, self.iconsSize))
        self.spinnerLabel.setMovie(self.spinner)
        self.spinner.start()

        self.statusBar = QStatusBar()
        self.statusBar.setSizeGripEnabled(False)

        self.statusBar.addWidget(self.sourceLabel)
        self.statusBar.addWidget(self.sourceImage)
        self.statusBar.addWidget(self.downloadLabel)
        self.statusBar.addWidget(self.downloadImage)
        self.statusBar.addWidget(self.scanningLabel)
        self.statusBar.addWidget(self.spinnerLabel)
        self.statusBar.addPermanentWidget(sourcesButton)
        self.statusBar.setContentsMargins(10,5,9,5)

        # Container Layout for the Central Widget
        containerLayout = QVBoxLayout()
        containerLayout.setSpacing(0)
        containerLayout.addWidget(self.claimsTable)
        containerLayout.addWidget(self.buttonBox)

        # Central Widget
        containerWidget = QWidget()
        containerWidget.setLayout(containerLayout)

        self.setCentralWidget(containerWidget)
        self.setStatusBar(self.statusBar)

    def open_game(self):
        """ TODO: Double click should open a window to replay the game."""
        pass

    def resize_claimsTable(self):
        """ Resize the table (if needed) after the insertion of a new element"""
        for index in range(0,6):
            self.claimsTable.resizeColumnToContents(index)

    def set_slots(self, slots):
        """ Connect the Slots """
        self.slots = slots

    def add_to_table(self,type,bo_number,players,move):
        """ Add new row to the claimsTable
        Args:
            type: The type of the draw (3 Fold Repetition, 5 Fold Repetition,
                                        50 Moves Rule, 75 Moves Rule).
            bo_number: The number of the boards, if this information is available.
            players: The name of the players.
            move: With which move the draw is valid.
        """

        # Before insertion, remove rows as descripted in the remove_rows function
        self.remove_rows(type,players)

        timestamp = str(datetime.now().strftime('%H:%M:%S'))
        row = []
        items = [str(self.rowCount+1),timestamp,type,bo_number,players,move]

        """ Convert each item(str) to QStandardItem, make the necessary stylistic
        additions and append it to row."""

        for index in range(len(items)):
            standardItem = QStandardItem(items[index])
            standardItem.setTextAlignment(Qt.AlignCenter)

            if(index == 2):
                font = standardItem.font()
                font.setBold(True)
                standardItem.setFont(font)

            if (items[index] == "5 Fold Repetition" or items[index] == "75 Moves Rule"):
                standardItem.setData(QColor(255,0,0), Qt.ForegroundRole)

            row.append(standardItem)

        self.claimsTableModel.appendRow(row)
        self.rowCount = self.rowCount+1

        # After the insertion resize the table
        self.resize_claimsTable()

        # Always the last row(the bottom of the table) should be visible.
        self.claimsTable.scrollToBottom()

        #Send Notification
        self.notify(type,players,move)

    def notify(self,type,players,move):
        """ Send notification depending on the OS.
        Args:
            type: The type of the draw (3 Fold Repetition, 5 Fold Repetition,
                                        50 Moves Rule, 75 Moves Rule).
            players: The names of the players.
            move: With which move the draw is valid.
        """
        if (platform.system() == "Darwin"):
            self.mac_notification.clearNotifications()
            self.mac_notification.notify(type,players,move)
        elif(platform.system() == "Windows"):
                self.win_notification.show_toast(type,
                                   players+"\n"+move,
                                   icon_path=resource_path("logo.ico"),
                                   duration=5,
                                   threaded=True)

    def remove_from_table(self,index):
        """ Remove element from the claimsTable.
        Args:
            index: The index of the row we want to remove. First row has index=0.
        """
        self.claimsTableModel.removeRow(index)

    def remove_rows(self,type,players):
        """ Removes a existing row from the Claims Table when same players made
        the same type of draw with a new move - or they made 5 Fold Repetition
        over the 3 Fold or 75 Moves Rule over 50 moves Rule.

        Args:
            type: The type of the draw (3 Fold Repetition, 5 Fold Repetition,
                                        50 Moves Rule, 75 Moves Rule).
            players: The names of the players.
        """
        for index in range(self.rowCount):

            try:
                modelType = self.claimsTableModel.item(index,2).text()
                modelPlayers = self.claimsTableModel.item(index,4).text()
            except AttributeError:
                modelType = ""
                modelPlayers = ""

            if (modelType == type and modelPlayers == players):
                self.remove_from_table(index)
                self.rowCount = self.rowCount - 1
                break
            elif (type == "5 Fold Repetition" and modelType == "3 Fold Repetition" and modelPlayers == players) :
                self.remove_from_table(index)
                self.rowCount = self.rowCount - 1
                break
            elif (type == "75 Moves Rule" and modelType == "50 Moves Rule" and modelPlayers == players):
                self.remove_from_table(index)
                self.rowCount = self.rowCount - 1
                break

    def clear_table(self):
        """ Clear all the elements off the Claims Table and resets the rowCount. """
        for index in range(self.rowCount):
            self.claimsTableModel.removeRow(0)
        self.rowCount = 0

    def set_sources_status(self,status,validSources=None):
        """ Adds the sourcess in the statusBar.
        Args:
            status(str): The status of the validity of the sources.
                "ok": At least one source is valid.
                "error": None of the sources are valid.
            validSources(list): The list of valid sources, if there is any.
                This list is used here to display the ToolTip.
        """
        self.sourceLabel.setText("Sources:")

        # Set the ToolTip if there are sources.
        try:
            text = ""
            for index in range(len(validSources)):
                if (index == len(validSources) - 1):
                    number = str(index+1)
                    text = text+number+") "+validSources[index].get_value()
                else:
                    number = str(index+1)
                    text = text+number+") "+validSources[index].get_value()+"\n"
            self.sourceLabel.setToolTip(text)
        except TypeError:
            pass

        if (status == "ok"):
            self.sourceImage.setPixmap(self.pixmapCheck.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation))
        else:
            self.sourceImage.setPixmap(self.pixmapError.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation))

    def set_download_status(self,status):
        """ Adds download status in the statusBar.
        Args:
            status(str): The status of the download(s).
                "ok": The download of the sources is successful.
                "error": The download of the sources failed.
                "stop": The download process stopped.
        """
        timestamp = str(datetime.now().strftime('%H:%M:%S'))
        self.downloadLabel.setText(timestamp+" Download:")
        if (status == "ok"):
            self.downloadImage.setPixmap(self.pixmapCheck.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation))
        elif (status == "error"):
            self.downloadImage.setPixmap(self.pixmapError.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation))
        elif (status == "stop"):
            self.downloadImage.clear()
            self.downloadLabel.clear()

    def set_scan_status(self,status):
        """ Adds the scan status in the statusBar.
        Args:
            status(str): The status of the scan process.
                "active": The scan process is active.
                "error": The scan process waits for a new file.
                "stop": The scan process stopped.
        """
        if (status == "wait"):
            self.scanningLabel.setText("Scan: Waiting")
            self.spinnerLabel.setVisible(False)
        elif (status == "active"):
            self.scanningLabel.setText("Scanning...")
            self.spinnerLabel.setVisible(True)
        elif (status == "stop"):
            self.scanningLabel.clear()
            self.spinnerLabel.setVisible(False)

    def change_scanButton_text(self,status):
        """ Changes the text of the scanButton depending on the status of the application.
        Args:
            status(str): The status of the scan process.
                "active": The scan process is active.
                "wait": The scan process is being terminated
                "stop": The scan process stopped.
        """
        if (status == "active"):
            self.buttonBox.scanButton.setText("Scanning PGN...")
        elif (status == "stop"):
            self.buttonBox.scanButton.setText("Start Scan")
        elif(status == "wait"):
            self.buttonBox.scanButton.setText("Please Wait")

    def enable_buttons(self):
        self.buttonBox.scanButton.setEnabled(True)
        self.buttonBox.stopButton.setEnabled(True)

    def disable_buttons(self):
        self.buttonBox.scanButton.setEnabled(False)
        self.buttonBox.stopButton.setEnabled(False)

    def enable_statusBar(self):
        """ Show download and scan status messages - if they were previously
        hidden (by disable_statusBar) - from the statusBar."""
        self.downloadLabel.setVisible(True)
        self.scanningLabel.setVisible(True)
        self.downloadImage.setVisible(True)

    def disable_statusBar(self):
        """ Hide download and scan status messages from the statusBar. """
        self.downloadLabel.setVisible(False)
        self.downloadImage.setVisible(False)
        self.scanningLabel.setVisible(False)
        self.spinnerLabel.setVisible(False)

    def closeEvent(self,event):
        """ Reimplement the close button
        If the program is actively scanning a pgn a warning dialog shall be raised
        in order to make sure that the user didn't clicked the close Button accidentally.
        Args:
            event: The exit QEvent.
        """
        try:
            if (self.slots.scanWorker.isRunning):
                exitDialog = QMessageBox()
                exitDialog.setWindowTitle("Warning")
                exitDialog.setText("Scanning in Progress")
                exitDialog.setInformativeText("Do you want to quit?")
                exitDialog.setIcon(exitDialog.Warning)
                exitDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
                exitDialog.setDefaultButton(QMessageBox.Cancel)
                replay = exitDialog.exec()

                if replay == QMessageBox.Yes:
                    event.accept()
                else:
                    event.ignore()
        except:
            event.accept()

    def load_warning(self):
        """ Displays a Warning Dialog.
        trigger:
            User clicked the "Start Scanning" Button without any valid pgn source.
        """
        warningDialog = QMessageBox()
        warningDialog.setIcon(warningDialog.Warning)
        warningDialog.setWindowTitle("Warning")
        warningDialog.setText("PGN File(s) Not Found")
        warningDialog.setInformativeText("Please enter at least one valid PGN source.")
        warningDialog.exec()

    def load_about_dialog(self):
        """ Displays the About Dialog."""
        self.aboutDialog = AboutDialog()
        self.aboutDialog.set_GUI()
        self.aboutDialog.show()