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)
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)
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)
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_()
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
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()
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()
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)
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)
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
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)
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
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
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))
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
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
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()
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
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)
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)
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)
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)
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()