def updatePlotPersoButton(self): menu = QMenu(self.mw) menus = [] for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]: m = QMenu(i, menu) menus.append(m) menu.addMenu(m) mpr = QSignalMapper(menu) for i in range(self.mw.mdlCharacter.rowCount()): a = QAction(self.mw.mdlCharacter.name(i), menu) a.setIcon(self.mw.mdlCharacter.icon(i)) a.triggered.connect(mpr.map) mpr.setMapping(a, int(self.mw.mdlCharacter.ID(i))) imp = toInt(self.mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) # Disabling empty menus for m in menus: if not m.actions(): m.setEnabled(False) mpr.mapped.connect(self.addPlotPerso) self.mw.btnAddPlotPerso.setMenu(menu)
def updatePlotPersoButton(self): menu = QMenu(self.mw) menus = [] for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]: m = QMenu(i, menu) menus.append(m) menu.addMenu(m) mpr = QSignalMapper(menu) for i in range(self.mw.mdlCharacter.rowCount()): a = QAction(self.mw.mdlCharacter.name(i), menu) a.setIcon(self.mw.mdlCharacter.icon(i)) a.triggered.connect(mpr.map) mpr.setMapping(a, int(self.mw.mdlCharacter.ID(i))) imp = toInt(self.mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) # Disabling empty menus for m in menus: if not m.actions(): m.setEnabled(False) mpr.mapped.connect(self.addPlotPerso) self.mw.btnAddPlotPerso.setMenu(menu)
class Monitor(QObject): """File monitor This monitor can be used to track single files """ def __init__(self, **kwargs): super(Monitor, self).__init__(**kwargs) self.watched = WeakValueDictionary() self.delMapper = QSignalMapper(self) self.delMapper.mapped[str].connect(self.unmonitorFile) self.watcher = MonitorWithRename(parent=self) self.watcher.fileChanged.connect(self._onFileChanged) def monitorFile(self, path): """Monitor a file and return an object that tracks only `path` :rtype: SingleFileWatcher :return: an object tracking `path`, the same object is returned if the method is called with the same path. """ path = os.path.abspath(path) self.watcher.addPath(path) proxy = self.watched.get(path) if not proxy: proxy = SingleFileWatcher(path) proxy.destroyed.connect(self.delMapper.map) self.delMapper.setMapping(proxy, path) self.watched[path] = proxy return proxy @Slot(str) def unmonitorFile(self, path): """Stop monitoring a file Since there is only one :any:`SingleFileWatcher` object per path, all objects monitoring `path` will not receive notifications anymore. To let only one object stop monitoring the file, simply disconnect its `modified` signal. When the :any:`SingleFileWatcher` object returned by method :any:`monitorFile` is destroyed, the file is automatically un-monitored. """ path = os.path.abspath(path) self.watcher.removePath(path) self.watched.pop(path, None) @Slot(str) def _onFileChanged(self, path): proxy = self.watched.get(path) if proxy: proxy.modified.emit()
def refresh_accounts(self): self.menu_change_account.clear() signal_mapper = QSignalMapper(self) for account_name in sorted(self.app.accounts.keys()): action = QAction(account_name, self) self.menu_change_account.addAction(action) signal_mapper.setMapping(action, account_name) action.triggered.connect(signal_mapper.map) signal_mapper.mapped[str].connect(self.action_change_account)
class Monitor(QObject): """File monitor This monitor can be used to track single files """ def __init__(self, **kwargs): super(Monitor, self).__init__(**kwargs) self.watched = WeakValueDictionary() self.delMapper = QSignalMapper(self) self.delMapper.mapped[str].connect(self.unmonitorFile) self.watcher = MonitorWithRename(parent=self) self.watcher.fileChanged.connect(self._onFileChanged) def monitorFile(self, path): """Monitor a file and return an object that tracks only `path` :rtype: SingleFileWatcher :return: an object tracking `path`, the same object is returned if the method is called with the same path. """ path = os.path.abspath(path) self.watcher.addPath(path) proxy = self.watched.get(path) if not proxy: proxy = SingleFileWatcher(path) proxy.destroyed.connect(self.delMapper.map) self.delMapper.setMapping(proxy, path) self.watched[path] = proxy return proxy @Slot(str) def unmonitorFile(self, path): """Stop monitoring a file Since there is only one :any:`SingleFileWatcher` object per path, all objects monitoring `path` will not receive notifications anymore. To let only one object stop monitoring the file, simply disconnect its `modified` signal. When the :any:`SingleFileWatcher` object returned by method :any:`monitorFile` is destroyed, the file is automatically un-monitored. """ path = os.path.abspath(path) self.watcher.removePath(path) self.watched.pop(path, None) @Slot(str) def _onFileChanged(self, path): proxy = self.watched.get(path) if proxy: proxy.modified.emit()
def _add_widgets(self,vbox,apps): row=int(len(self.appsWidgets)/self.maxCol) col=(self.maxCol*(row+1))-len(self.appsWidgets) sigmap_run=QSignalMapper(self) sigmap_run.mapped[QString].connect(self._launch) for appName,data in apps.items(): appIcon=data['Icon'] appDesc=data['Name'] if QtGui.QIcon.hasThemeIcon(appIcon): icnApp=QtGui.QIcon.fromTheme(appIcon) elif os.path.isfile(appIcon): iconPixmap=QtGui.QPixmap(appIcon) scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2)) icnApp=QtGui.QIcon(scaledIcon) elif appIcon.startswith("http"): if not os.path.isdir("%s/icons"%self.cache): os.makedirs("%s/icons"%self.cache) tmpfile=os.path.join("%s/icons"%self.cache,appIcon.split("/")[2].split(".")[0]) if not os.path.isfile(tmpfile): try: urlretrieve(appIcon,tmpfile) except: tmpfile=QtGui.QIcon.fromTheme("shell") iconPixmap=QtGui.QPixmap(tmpfile) scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2)) icnApp=QtGui.QIcon(scaledIcon) appIcon=tmpfile else: continue if not appName: continue self.app_icons[appName]=appIcon self._debug("Adding %s"%appName) btnApp=navButton(self) btnApp.setIcon(icnApp) btnApp.setIconSize(QSize(BTN_SIZE,BTN_SIZE)) btnApp.setToolTip(appDesc) btnApp.setFocusPolicy(Qt.NoFocus) btnApp.keypress.connect(self._set_focus) btnApp.focusIn.connect(self._get_focus) self.focusWidgets.append(btnApp) self.appsWidgets.append(appName) sigmap_run.setMapping(btnApp,appName) btnApp.clicked.connect(sigmap_run.map) vbox.addWidget(btnApp,row,col,Qt.Alignment(-1)) col+=1 if col==self.maxCol: col=0 row+=1
def setup_board(self): gridLayout = QGridLayout() mapper = QSignalMapper(self) for row in range(3): for column in range(3): button = QPushButton() button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) button.setText(" ") gridLayout.addWidget(button, row, column) self.board.append(button) mapper.setMapping(button, len(self.board) - 1) button.clicked.connect(mapper.map) mapper.mapped.connect(self.handle_button_click) self.setLayout(gridLayout)
def onSelectionMenuClicked ( self ): signalMapper = QSignalMapper ( self ) acts = self.tabSelectionMenu.actions () activeAction = self.currentIndex () for i in range ( 0, acts.size () ): self.tabSelectionMenu.removeAction ( acts[ i ] ) for i in range ( 0, self.count () ): action = self.tabSelectionMenu.addAction ( self.tabText ( i ) ) action.triggered.connect ( signalMapper.map ) signalMapper.setMapping ( action, i ) if activeAction >= 0: acts = self.tabSelectionMenu.actions () acts[ activeAction ].setIcon ( QIcon ( "./resources/general_tick.ico" ) ) # TODO: CryIcon signalMapper.mapped.connect ( self.setCurrentIndex )
def __init__(self): super().__init__() grid = QGridLayout(self) mapper = QSignalMapper(self) btns = (('1', '2', '3', '+'), ('4', '5', '6', '-'), ('7', '8', '9', '*'), ('0', '.', '=', '/'), ) self.line = QLineEdit() self.flag = False grid.addWidget(self.line, 0, 0, 1, 4) for row in range(4): for col in range(4): button = QPushButton(btns[row][col]) grid.addWidget(button, row + 1, col) button.clicked.connect(mapper.map) mapper.setMapping(button, button.text()) mapper.mapped[str].connect(self.on_mapped)
def contextMenu(self, parent, index): menu = None row = index.row() if (row >= 0 and row < self.mCommands.size()): menu = QMenu(parent) if (row > 0): action = menu.addAction(self.tr("Move Up")) mapper = QSignalMapper(action) mapper.setMapping(action, row) action.triggered.connect(mapper.map) mapper.mapped.connect(self.moveUp) if (row+1 < self.mCommands.size()): action = menu.addAction(self.tr("Move Down")) mapper = QSignalMapper(action) mapper.setMapping(action, row + 1) action.triggered.connect(mapper.map) mapper.mapped.connect(self.moveUp) menu.addSeparator() action = menu.addAction(self.tr("Execute")) mapper = QSignalMapper(action) mapper.setMapping(action, row) action.triggered.connect(mapper.map) mapper.mapped.connect(self.execute) if sys.platform in ['linux', 'darwin']: action = menu.addAction(self.tr("Execute in Terminal")) mapper = QSignalMapper(action) mapper.setMapping(action, row) action.triggered.connect(mapper.map) mapper.mapped.connect(self.executeInTerminal) menu.addSeparator() action = menu.addAction(self.tr("Delete")) mapper = QSignalMapper(action) mapper.setMapping(action, row) action.triggered.connect(mapper.map) mapper.mapped.connect(self.remove) return menu
def contextMenu(self, parent, index): menu = None row = index.row() if (row >= 0 and row < self.mCommands.size()): menu = QMenu(parent) if (row > 0): action = menu.addAction(self.tr("Move Up")) mapper = QSignalMapper(action) mapper.setMapping(action, row) action.triggered.connect(mapper.map) mapper.mapped.connect(self.moveUp) if (row + 1 < self.mCommands.size()): action = menu.addAction(self.tr("Move Down")) mapper = QSignalMapper(action) mapper.setMapping(action, row + 1) action.triggered.connect(mapper.map) mapper.mapped.connect(self.moveUp) menu.addSeparator() action = menu.addAction(self.tr("Execute")) mapper = QSignalMapper(action) mapper.setMapping(action, row) action.triggered.connect(mapper.map) mapper.mapped.connect(self.execute) if sys.platform in ['linux', 'darwin']: action = menu.addAction(self.tr("Execute in Terminal")) mapper = QSignalMapper(action) mapper.setMapping(action, row) action.triggered.connect(mapper.map) mapper.mapped.connect(self.executeInTerminal) menu.addSeparator() action = menu.addAction(self.tr("Delete")) mapper = QSignalMapper(action) mapper.setMapping(action, row) action.triggered.connect(mapper.map) mapper.mapped.connect(self.remove) return menu
class MyWindow(QWidget): def __init__(self): super(MyWindow, self).__init__() self.setGeometry(100, 100, 600, 400) self.setWindowTitle("Send to Player") self.list = MyListWidget(self) self.uploader = Uploader(self.list, self) layout = QHBoxLayout(self) layout.addWidget(self.list) self.browserMapper = QSignalMapper() self.openSourceButton = QPushButton('&Open Music Archive') self.openSourceButton.released.connect(self.browserMapper.map) self.browserMapper.setMapping(self.openSourceButton, config.mediaSource) self.openTargetButton = QPushButton('Open &Player') self.openTargetButton.released.connect(self.browserMapper.map) self.browserMapper.setMapping(self.openTargetButton, config.mediaTarget) self.browserMapper.mapped[str].connect( lambda b: QDesktopServices.openUrl(QUrl(b))) self.submitButton = QPushButton('&Send to Player') self.submitButton.released.connect(self.uploader.uploadMedia) self.deleteSelectedButton = QPushButton('&Delete\nSelected') self.deleteSelectedButton.released.connect(self.list.deleteSelected) controls = QVBoxLayout() controls.addWidget(QLabel('Filesystem Browsers')) controls.addWidget(self.openSourceButton) controls.addWidget(self.openTargetButton) controls.addWidget(Separator(self)) controls.addWidget(self.deleteSelectedButton) controls.addWidget(Separator(self)) controls.addStretch() controls.addWidget(self.submitButton) layout.addLayout(controls) self.setLayout(layout)
def updatePlotPersoButton(self): menu = QMenu(self.mw) menus = [] for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]: m = QMenu(i, menu) menus.append(m) menu.addMenu(m) mpr = QSignalMapper(menu) for i in range(self.mw.mdlPersos.rowCount()): a = QAction(self.mw.mdlPersos.name(i), menu) a.setIcon(self.mw.mdlPersos.icon(i)) a.triggered.connect(mpr.map) mpr.setMapping(a, int(self.mw.mdlPersos.ID(i))) imp = toInt(self.mw.mdlPersos.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.addPlotPerso) self.mw.btnAddPlotPerso.setMenu(menu)
def __init__(self): QMainWindow.__init__(self) self.setMinimumSize(QSize(640, 480)) self.setWindowTitle("Hello world") centralWidget = QWidget(self) self.setCentralWidget(centralWidget) gridLayout = QGridLayout() centralWidget.setLayout(gridLayout) title = QLabel("Hello World from PyQt", self) title.setAlignment(QtCore.Qt.AlignCenter) gridLayout.addWidget(title, 0, 0, 1, 10) logging_text_edit = QPlainTextEdit() gridLayout.addWidget(logging_text_edit, 2, 0, 1, 10) mapper = QSignalMapper(self) mapper.mapped[int].connect(self.log_button_pressed) for x in range(10): button = QPushButton(self) button.setText("Print %d" % (x,)) gridLayout.addWidget(button, 3, x, QtCore.Qt.AlignHCenter) button.pressed.connect(mapper.map) mapper.setMapping(button, x) class QLogHandler(logging.Handler): def __init__(self): logging.Handler.__init__(self) def emit(self, record): record = self.format(record) logging_text_edit.appendPlainText(record) q_log_handler = QLogHandler() q_log_handler.setLevel(logging.DEBUG) q_log_handler.setFormatter(logging.Formatter('%(asctime)s\n%(name)s\n%(levelname)s:%(message)s\n')) logging.basicConfig(level=logging.DEBUG) logging.getLogger('').addHandler(q_log_handler)
def main(icon_spec): app = QApplication(sys.argv) main_window = QMainWindow() def sigint_handler(*args): main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler signal_timer = QTimer() signal_timer.start(100) signal_timer.timeout.connect(lambda: None) tool_bar = QToolBar() main_window.addToolBar(Qt.TopToolBarArea, tool_bar) table_view = QTableView() table_view.setSelectionBehavior(QAbstractItemView.SelectRows) table_view.setSelectionMode(QAbstractItemView.SingleSelection) table_view.setSortingEnabled(True) main_window.setCentralWidget(table_view) proxy_model = QSortFilterProxyModel() proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) proxy_model.setFilterKeyColumn(1) table_view.setModel(proxy_model) proxy_model.layoutChanged.connect(table_view.resizeRowsToContents) item_model = QStandardItemModel() proxy_model.setSourceModel(item_model) # get all icons and their available sizes icons = [] all_sizes = set([]) for context, icon_names in icon_spec: for icon_name in icon_names: icon = QIcon.fromTheme(icon_name) sizes = [] for size in icon.availableSizes(): size = (size.width(), size.height()) sizes.append(size) all_sizes.add(size) sizes.sort() icons.append({ 'context': context, 'icon_name': icon_name, 'icon': icon, 'sizes': sizes, }) all_sizes = list(all_sizes) all_sizes.sort() # input field for filter def filter_changed(value): proxy_model.setFilterRegExp(value) table_view.resizeRowsToContents() filter_line_edit = QLineEdit() filter_line_edit.setMaximumWidth(200) filter_line_edit.setPlaceholderText('Filter name') filter_line_edit.setToolTip('Filter name optionally using regular expressions (' + QKeySequence(QKeySequence.Find).toString() + ')') filter_line_edit.textChanged.connect(filter_changed) tool_bar.addWidget(filter_line_edit) # actions to toggle visibility of available sizes/columns def action_toggled(index): column = 2 + index table_view.setColumnHidden(column, not table_view.isColumnHidden(column)) table_view.resizeColumnsToContents() table_view.resizeRowsToContents() signal_mapper = QSignalMapper() for i, size in enumerate(all_sizes): action = QAction('%dx%d' % size, tool_bar) action.setCheckable(True) action.setChecked(True) tool_bar.addAction(action) action.toggled.connect(signal_mapper.map) signal_mapper.setMapping(action, i) # set tool tip and handle key sequence tool_tip = 'Toggle visibility of column' if i < 10: digit = ('%d' % (i + 1))[-1] tool_tip += ' (%s)' % QKeySequence('Ctrl+%s' % digit).toString() action.setToolTip(tool_tip) signal_mapper.mapped.connect(action_toggled) # label columns header_labels = ['context', 'name'] for width, height in all_sizes: header_labels.append('%dx%d' % (width, height)) item_model.setColumnCount(len(header_labels)) item_model.setHorizontalHeaderLabels(header_labels) # fill rows item_model.setRowCount(len(icons)) for row, icon_data in enumerate(icons): # context item = QStandardItem(icon_data['context']) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, 0, item) # icon name item = QStandardItem(icon_data['icon_name']) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, 1, item) for index_in_all_sizes, size in enumerate(all_sizes): column = 2 + index_in_all_sizes if size in icon_data['sizes']: # icon as pixmap to keep specific size item = QStandardItem('') pixmap = icon_data['icon'].pixmap(size[0], size[1]) item.setData(pixmap, Qt.DecorationRole) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, column, item) else: # single space to be sortable against icons item = QStandardItem(' ') item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, column, item) table_view.resizeColumnsToContents() # manually set row heights because resizeRowsToContents is not working properly for row, icon_data in enumerate(icons): if len(icon_data['sizes']) > 0: max_size = icon_data['sizes'][-1] table_view.setRowHeight(row, max_size[1]) # enable focus find (ctrl+f) and toggle columns (ctrl+NUM) def main_window_keyPressEvent(self, event, old_keyPressEvent=QMainWindow.keyPressEvent): if event.matches(QKeySequence.Find): filter_line_edit.setFocus() return if event.modifiers() == Qt.ControlModifier and event.key() >= Qt.Key_0 and event.key() <= Qt.Key_9: index = event.key() - Qt.Key_1 if event.key() == Qt.Key_0: index += 10 action = signal_mapper.mapping(index) if action: action.toggle() return old_keyPressEvent(self, event) main_window.keyPressEvent = MethodType(main_window_keyPressEvent, table_view) # enable copy (ctrl+c) name of icon to clipboard def table_view_keyPressEvent(self, event, old_keyPressEvent=QTableView.keyPressEvent): if event.matches(QKeySequence.Copy): selection_model = self.selectionModel() if selection_model.hasSelection(): index = selection_model.selectedRows()[0] source_index = self.model().mapToSource(index) item = self.model().sourceModel().item(source_index.row(), 1) icon_name = item.data(Qt.EditRole) app.clipboard().setText(icon_name.toString()) return old_keyPressEvent(self, event) table_view.keyPressEvent = MethodType(table_view_keyPressEvent, table_view) main_window.showMaximized() return app.exec_()
class UMLGraphicsView(E5GraphicsView): """ Class implementing a specialized E5GraphicsView for our diagrams. @signal relayout() emitted to indicate a relayout of the diagram is requested """ relayout = pyqtSignal() def __init__(self, scene, parent=None): """ Constructor @param scene reference to the scene object (QGraphicsScene) @param parent parent widget of the view (QWidget) """ E5GraphicsView.__init__(self, scene, parent) self.setObjectName("UMLGraphicsView") self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.diagramName = "Unnamed" self.__itemId = -1 self.border = 10 self.deltaSize = 100.0 self.__zoomWidget = E5ZoomWidget( UI.PixmapCache.getPixmap("zoomOut.png"), UI.PixmapCache.getPixmap("zoomIn.png"), UI.PixmapCache.getPixmap("zoomReset.png"), self) parent.statusBar().addPermanentWidget(self.__zoomWidget) self.__zoomWidget.setMapping( E5GraphicsView.ZoomLevels, E5GraphicsView.ZoomLevelDefault) self.__zoomWidget.valueChanged.connect(self.setZoom) self.zoomValueChanged.connect(self.__zoomWidget.setValue) self.__initActions() scene.changed.connect(self.__sceneChanged) self.grabGesture(Qt.PinchGesture) def __initActions(self): """ Private method to initialize the view actions. """ self.alignMapper = QSignalMapper(self) self.alignMapper.mapped[int].connect(self.__alignShapes) self.deleteShapeAct = \ QAction(UI.PixmapCache.getIcon("deleteShape.png"), self.tr("Delete shapes"), self) self.deleteShapeAct.triggered.connect(self.__deleteShape) self.incWidthAct = \ QAction(UI.PixmapCache.getIcon("sceneWidthInc.png"), self.tr("Increase width by {0} points").format( self.deltaSize), self) self.incWidthAct.triggered.connect(self.__incWidth) self.incHeightAct = \ QAction(UI.PixmapCache.getIcon("sceneHeightInc.png"), self.tr("Increase height by {0} points").format( self.deltaSize), self) self.incHeightAct.triggered.connect(self.__incHeight) self.decWidthAct = \ QAction(UI.PixmapCache.getIcon("sceneWidthDec.png"), self.tr("Decrease width by {0} points").format( self.deltaSize), self) self.decWidthAct.triggered.connect(self.__decWidth) self.decHeightAct = \ QAction(UI.PixmapCache.getIcon("sceneHeightDec.png"), self.tr("Decrease height by {0} points").format( self.deltaSize), self) self.decHeightAct.triggered.connect(self.__decHeight) self.setSizeAct = \ QAction(UI.PixmapCache.getIcon("sceneSize.png"), self.tr("Set size"), self) self.setSizeAct.triggered.connect(self.__setSize) self.rescanAct = \ QAction(UI.PixmapCache.getIcon("rescan.png"), self.tr("Re-Scan"), self) self.rescanAct.triggered.connect(self.__rescan) self.relayoutAct = \ QAction(UI.PixmapCache.getIcon("relayout.png"), self.tr("Re-Layout"), self) self.relayoutAct.triggered.connect(self.__relayout) self.alignLeftAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignLeft.png"), self.tr("Align Left"), self) self.alignMapper.setMapping(self.alignLeftAct, Qt.AlignLeft) self.alignLeftAct.triggered.connect(self.alignMapper.map) self.alignHCenterAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignHCenter.png"), self.tr("Align Center Horizontal"), self) self.alignMapper.setMapping(self.alignHCenterAct, Qt.AlignHCenter) self.alignHCenterAct.triggered.connect(self.alignMapper.map) self.alignRightAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignRight.png"), self.tr("Align Right"), self) self.alignMapper.setMapping(self.alignRightAct, Qt.AlignRight) self.alignRightAct.triggered.connect(self.alignMapper.map) self.alignTopAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignTop.png"), self.tr("Align Top"), self) self.alignMapper.setMapping(self.alignTopAct, Qt.AlignTop) self.alignTopAct.triggered.connect(self.alignMapper.map) self.alignVCenterAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignVCenter.png"), self.tr("Align Center Vertical"), self) self.alignMapper.setMapping(self.alignVCenterAct, Qt.AlignVCenter) self.alignVCenterAct.triggered.connect(self.alignMapper.map) self.alignBottomAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignBottom.png"), self.tr("Align Bottom"), self) self.alignMapper.setMapping(self.alignBottomAct, Qt.AlignBottom) self.alignBottomAct.triggered.connect(self.alignMapper.map) def __checkSizeActions(self): """ Private slot to set the enabled state of the size actions. """ diagramSize = self._getDiagramSize(10) sceneRect = self.scene().sceneRect() if (sceneRect.width() - self.deltaSize) < diagramSize.width(): self.decWidthAct.setEnabled(False) else: self.decWidthAct.setEnabled(True) if (sceneRect.height() - self.deltaSize) < diagramSize.height(): self.decHeightAct.setEnabled(False) else: self.decHeightAct.setEnabled(True) def __sceneChanged(self, areas): """ Private slot called when the scene changes. @param areas list of rectangles that contain changes (list of QRectF) """ if len(self.scene().selectedItems()) > 0: self.deleteShapeAct.setEnabled(True) else: self.deleteShapeAct.setEnabled(False) sceneRect = self.scene().sceneRect() newWidth = width = sceneRect.width() newHeight = height = sceneRect.height() rect = self.scene().itemsBoundingRect() # calculate with 10 pixel border on each side if sceneRect.right() - 10 < rect.right(): newWidth = rect.right() + 10 if sceneRect.bottom() - 10 < rect.bottom(): newHeight = rect.bottom() + 10 if newHeight != height or newWidth != width: self.setSceneSize(newWidth, newHeight) self.__checkSizeActions() def initToolBar(self): """ Public method to populate a toolbar with our actions. @return the populated toolBar (QToolBar) """ toolBar = QToolBar(self.tr("Graphics"), self) toolBar.setIconSize(UI.Config.ToolBarIconSize) toolBar.addAction(self.deleteShapeAct) toolBar.addSeparator() toolBar.addAction(self.alignLeftAct) toolBar.addAction(self.alignHCenterAct) toolBar.addAction(self.alignRightAct) toolBar.addAction(self.alignTopAct) toolBar.addAction(self.alignVCenterAct) toolBar.addAction(self.alignBottomAct) toolBar.addSeparator() toolBar.addAction(self.incWidthAct) toolBar.addAction(self.incHeightAct) toolBar.addAction(self.decWidthAct) toolBar.addAction(self.decHeightAct) toolBar.addAction(self.setSizeAct) toolBar.addSeparator() toolBar.addAction(self.rescanAct) toolBar.addAction(self.relayoutAct) return toolBar def filteredItems(self, items, itemType=UMLItem): """ Public method to filter a list of items. @param items list of items as returned by the scene object (QGraphicsItem) @param itemType type to be filtered (class) @return list of interesting collision items (QGraphicsItem) """ return [itm for itm in items if isinstance(itm, itemType)] def selectItems(self, items): """ Public method to select the given items. @param items list of items to be selected (list of QGraphicsItemItem) """ # step 1: deselect all items self.unselectItems() # step 2: select all given items for itm in items: if isinstance(itm, UMLItem): itm.setSelected(True) def selectItem(self, item): """ Public method to select an item. @param item item to be selected (QGraphicsItemItem) """ if isinstance(item, UMLItem): item.setSelected(not item.isSelected()) def __deleteShape(self): """ Private method to delete the selected shapes from the display. """ for item in self.scene().selectedItems(): item.removeAssociations() item.setSelected(False) self.scene().removeItem(item) del item def __incWidth(self): """ Private method to handle the increase width context menu entry. """ self.resizeScene(self.deltaSize, True) self.__checkSizeActions() def __incHeight(self): """ Private method to handle the increase height context menu entry. """ self.resizeScene(self.deltaSize, False) self.__checkSizeActions() def __decWidth(self): """ Private method to handle the decrease width context menu entry. """ self.resizeScene(-self.deltaSize, True) self.__checkSizeActions() def __decHeight(self): """ Private method to handle the decrease height context menu entry. """ self.resizeScene(-self.deltaSize, False) self.__checkSizeActions() def __setSize(self): """ Private method to handle the set size context menu entry. """ from .UMLSceneSizeDialog import UMLSceneSizeDialog rect = self._getDiagramRect(10) sceneRect = self.scene().sceneRect() dlg = UMLSceneSizeDialog(sceneRect.width(), sceneRect.height(), rect.width(), rect.height(), self) if dlg.exec_() == QDialog.Accepted: width, height = dlg.getData() self.setSceneSize(width, height) self.__checkSizeActions() def autoAdjustSceneSize(self, limit=False): """ Public method to adjust the scene size to the diagram size. @param limit flag indicating to limit the scene to the initial size (boolean) """ super(UMLGraphicsView, self).autoAdjustSceneSize(limit=limit) self.__checkSizeActions() def saveImage(self): """ Public method to handle the save context menu entry. """ fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, self.tr("Save Diagram"), "", self.tr("Portable Network Graphics (*.png);;" "Scalable Vector Graphics (*.svg)"), "", E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if fname: ext = QFileInfo(fname).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fname += ex if QFileInfo(fname).exists(): res = E5MessageBox.yesNo( self, self.tr("Save Diagram"), self.tr("<p>The file <b>{0}</b> already exists." " Overwrite it?</p>").format(fname), icon=E5MessageBox.Warning) if not res: return success = super(UMLGraphicsView, self).saveImage( fname, QFileInfo(fname).suffix().upper()) if not success: E5MessageBox.critical( self, self.tr("Save Diagram"), self.tr( """<p>The file <b>{0}</b> could not be saved.</p>""") .format(fname)) def __relayout(self): """ Private slot to handle the re-layout context menu entry. """ self.__itemId = -1 self.scene().clear() self.relayout.emit() def __rescan(self): """ Private slot to handle the re-scan context menu entry. """ # 1. save positions of all items and names of selected items itemPositions = {} selectedItems = [] for item in self.filteredItems(self.scene().items(), UMLItem): name = item.getName() if name: itemPositions[name] = (item.x(), item.y()) if item.isSelected(): selectedItems.append(name) # 2. save # 2. re-layout the diagram self.__relayout() # 3. move known items to the saved positions for item in self.filteredItems(self.scene().items(), UMLItem): name = item.getName() if name in itemPositions: item.setPos(*itemPositions[name]) if name in selectedItems: item.setSelected(True) def printDiagram(self): """ Public slot called to print the diagram. """ printer = QPrinter(mode=QPrinter.ScreenResolution) printer.setFullPage(True) if Preferences.getPrinter("ColorMode"): printer.setColorMode(QPrinter.Color) else: printer.setColorMode(QPrinter.GrayScale) if Preferences.getPrinter("FirstPageFirst"): printer.setPageOrder(QPrinter.FirstPageFirst) else: printer.setPageOrder(QPrinter.LastPageFirst) printer.setPageMargins( Preferences.getPrinter("LeftMargin") * 10, Preferences.getPrinter("TopMargin") * 10, Preferences.getPrinter("RightMargin") * 10, Preferences.getPrinter("BottomMargin") * 10, QPrinter.Millimeter ) printerName = Preferences.getPrinter("PrinterName") if printerName: printer.setPrinterName(printerName) printDialog = QPrintDialog(printer, self) if printDialog.exec_(): super(UMLGraphicsView, self).printDiagram( printer, self.diagramName) def printPreviewDiagram(self): """ Public slot called to show a print preview of the diagram. """ from PyQt5.QtPrintSupport import QPrintPreviewDialog printer = QPrinter(mode=QPrinter.ScreenResolution) printer.setFullPage(True) if Preferences.getPrinter("ColorMode"): printer.setColorMode(QPrinter.Color) else: printer.setColorMode(QPrinter.GrayScale) if Preferences.getPrinter("FirstPageFirst"): printer.setPageOrder(QPrinter.FirstPageFirst) else: printer.setPageOrder(QPrinter.LastPageFirst) printer.setPageMargins( Preferences.getPrinter("LeftMargin") * 10, Preferences.getPrinter("TopMargin") * 10, Preferences.getPrinter("RightMargin") * 10, Preferences.getPrinter("BottomMargin") * 10, QPrinter.Millimeter ) printerName = Preferences.getPrinter("PrinterName") if printerName: printer.setPrinterName(printerName) preview = QPrintPreviewDialog(printer, self) preview.paintRequested[QPrinter].connect(self.__printPreviewPrint) preview.exec_() def __printPreviewPrint(self, printer): """ Private slot to generate a print preview. @param printer reference to the printer object (QPrinter) """ super(UMLGraphicsView, self).printDiagram(printer, self.diagramName) def setDiagramName(self, name): """ Public slot to set the diagram name. @param name diagram name (string) """ self.diagramName = name def __alignShapes(self, alignment): """ Private slot to align the selected shapes. @param alignment alignment type (Qt.AlignmentFlag) """ # step 1: get all selected items items = self.scene().selectedItems() if len(items) <= 1: return # step 2: find the index of the item to align in relation to amount = None for i, item in enumerate(items): rect = item.sceneBoundingRect() if alignment == Qt.AlignLeft: if amount is None or rect.x() < amount: amount = rect.x() index = i elif alignment == Qt.AlignRight: if amount is None or rect.x() + rect.width() > amount: amount = rect.x() + rect.width() index = i elif alignment == Qt.AlignHCenter: if amount is None or rect.width() > amount: amount = rect.width() index = i elif alignment == Qt.AlignTop: if amount is None or rect.y() < amount: amount = rect.y() index = i elif alignment == Qt.AlignBottom: if amount is None or rect.y() + rect.height() > amount: amount = rect.y() + rect.height() index = i elif alignment == Qt.AlignVCenter: if amount is None or rect.height() > amount: amount = rect.height() index = i rect = items[index].sceneBoundingRect() # step 3: move the other items for i, item in enumerate(items): if i == index: continue itemrect = item.sceneBoundingRect() xOffset = yOffset = 0 if alignment == Qt.AlignLeft: xOffset = rect.x() - itemrect.x() elif alignment == Qt.AlignRight: xOffset = (rect.x() + rect.width()) - \ (itemrect.x() + itemrect.width()) elif alignment == Qt.AlignHCenter: xOffset = (rect.x() + rect.width() // 2) - \ (itemrect.x() + itemrect.width() // 2) elif alignment == Qt.AlignTop: yOffset = rect.y() - itemrect.y() elif alignment == Qt.AlignBottom: yOffset = (rect.y() + rect.height()) - \ (itemrect.y() + itemrect.height()) elif alignment == Qt.AlignVCenter: yOffset = (rect.y() + rect.height() // 2) - \ (itemrect.y() + itemrect.height() // 2) item.moveBy(xOffset, yOffset) self.scene().update() def __itemsBoundingRect(self, items): """ Private method to calculate the bounding rectangle of the given items. @param items list of items to operate on (list of UMLItem) @return bounding rectangle (QRectF) """ rect = self.scene().sceneRect() right = rect.left() bottom = rect.top() left = rect.right() top = rect.bottom() for item in items: rect = item.sceneBoundingRect() left = min(rect.left(), left) right = max(rect.right(), right) top = min(rect.top(), top) bottom = max(rect.bottom(), bottom) return QRectF(left, top, right - left, bottom - top) def keyPressEvent(self, evt): """ Protected method handling key press events. @param evt reference to the key event (QKeyEvent) """ key = evt.key() if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]: items = self.filteredItems(self.scene().selectedItems()) if items: if evt.modifiers() & Qt.ControlModifier: stepSize = 50 else: stepSize = 5 if key == Qt.Key_Up: dx = 0 dy = -stepSize elif key == Qt.Key_Down: dx = 0 dy = stepSize elif key == Qt.Key_Left: dx = -stepSize dy = 0 else: dx = stepSize dy = 0 for item in items: item.moveBy(dx, dy) evt.accept() return super(UMLGraphicsView, self).keyPressEvent(evt) def wheelEvent(self, evt): """ Protected method to handle wheel events. @param evt reference to the wheel event (QWheelEvent) """ if evt.modifiers() & Qt.ControlModifier: if qVersion() >= "5.0.0": delta = evt.angleDelta().y() else: delta = evt.delta() if delta < 0: self.zoomOut() else: self.zoomIn() evt.accept() return super(UMLGraphicsView, self).wheelEvent(evt) def event(self, evt): """ Public method handling events. @param evt reference to the event (QEvent) @return flag indicating, if the event was handled (boolean) """ if evt.type() == QEvent.Gesture: self.gestureEvent(evt) return True return super(UMLGraphicsView, self).event(evt) def gestureEvent(self, evt): """ Protected method handling gesture events. @param evt reference to the gesture event (QGestureEvent """ pinch = evt.gesture(Qt.PinchGesture) if pinch: if pinch.state() == Qt.GestureStarted: pinch.setScaleFactor(self.zoom() / 100.0) else: self.setZoom(int(pinch.scaleFactor() * 100)) evt.accept() def getItemId(self): """ Public method to get the ID to be assigned to an item. @return item ID (integer) """ self.__itemId += 1 return self.__itemId def findItem(self, id): """ Public method to find an UML item based on the ID. @param id of the item to search for (integer) @return item found (UMLItem) or None """ for item in self.scene().items(): try: if item.getId() == id: return item except AttributeError: continue return None def findItemByName(self, name): """ Public method to find an UML item based on its name. @param name name to look for (string) @return item found (UMLItem) or None """ for item in self.scene().items(): try: if item.getName() == name: return item except AttributeError: continue return None def getPersistenceData(self): """ Public method to get a list of data to be persisted. @return list of data to be persisted (list of strings) """ lines = [ "diagram_name: {0}".format(self.diagramName), ] for item in self.filteredItems(self.scene().items(), UMLItem): lines.append("item: id={0}, x={1}, y={2}, item_type={3}{4}".format( item.getId(), item.x(), item.y(), item.getItemType(), item.buildItemDataString())) from .AssociationItem import AssociationItem for item in self.filteredItems(self.scene().items(), AssociationItem): lines.append("association: {0}".format( item.buildAssociationItemDataString())) return lines def parsePersistenceData(self, version, data): """ Public method to parse persisted data. @param version version of the data (string) @param data persisted data to be parsed (list of string) @return tuple of flag indicating success (boolean) and faulty line number (integer) """ umlItems = {} if not data[0].startswith("diagram_name:"): return False, 0 self.diagramName = data[0].split(": ", 1)[1].strip() from .ClassItem import ClassItem from .ModuleItem import ModuleItem from .PackageItem import PackageItem from .AssociationItem import AssociationItem linenum = 0 for line in data[1:]: linenum += 1 if not line.startswith(("item:", "association:")): return False, linenum key, value = line.split(": ", 1) if key == "item": id, x, y, itemType, itemData = value.split(", ", 4) try: id = int(id.split("=", 1)[1].strip()) x = float(x.split("=", 1)[1].strip()) y = float(y.split("=", 1)[1].strip()) itemType = itemType.split("=", 1)[1].strip() if itemType == ClassItem.ItemType: itm = ClassItem(x=x, y=y, scene=self.scene()) elif itemType == ModuleItem.ItemType: itm = ModuleItem(x=x, y=y, scene=self.scene()) elif itemType == PackageItem.ItemType: itm = PackageItem(x=x, y=y, scene=self.scene()) itm.setId(id) umlItems[id] = itm if not itm.parseItemDataString(version, itemData): return False, linenum except ValueError: return False, linenum elif key == "association": srcId, dstId, assocType, topToBottom = \ AssociationItem.parseAssociationItemDataString( value.strip()) assoc = AssociationItem(umlItems[srcId], umlItems[dstId], assocType, topToBottom) self.scene().addItem(assoc) return True, -1
class MainWindow(QMainWindow): """This create the main window of the application""" def __init__(self): super(MainWindow, self).__init__() # remove close & maximize window buttons #self.setWindowFlags(Qt.CustomizeWindowHint|Qt.WindowMinimizeButtonHint) self.setMinimumSize(500, 666) #self.setMaximumSize(1000,666) self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.mdiArea.setViewMode(QMdiArea.TabbedView) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.child = None self.createActions() self.createMenus() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("LEKTURE") mytoolbar = QToolBar() #self.toolbar = self.addToolBar() mytoolbar.addAction(self.newAct) mytoolbar.addAction(self.openAct) mytoolbar.addAction(self.saveAct) mytoolbar.addAction(self.saveAsAct) mytoolbar.addSeparator() mytoolbar.addAction(self.outputsAct) mytoolbar.addAction(self.scenarioAct) self.scenarioAct.setVisible(False) mytoolbar.setMovable(False) mytoolbar.setFixedWidth(60) self.addToolBar(Qt.LeftToolBarArea, mytoolbar) def closeEvent(self, scenario): """method called when the main window wants to be closed""" self.mdiArea.closeAllSubWindows() if self.mdiArea.currentSubWindow(): scenario.ignore() else: self.writeSettings() scenario.accept() def newFile(self): """creates a new project""" child = self.createProjekt() child.newFile() child.show() self.child = child def open(self): """open a project""" fileName, _ = QFileDialog.getOpenFileName(self) if fileName: existing = self.findProjekt(fileName) if existing: self.mdiArea.setActiveSubWindow(existing) return child = self.createProjekt() if child.loadFile(fileName): self.statusBar().showMessage("File loaded", 2000) child.show() else: child.close() def save(self): """called when user save a project""" if self.activeProjekt() and self.activeProjekt().save(): self.statusBar().showMessage("File saved", 2000) else: self.statusBar().showMessage("Error when trying to save the file") def saveAs(self): """called when user save AS a project""" if self.activeProjekt() and self.activeProjekt().saveAs(): self.statusBar().showMessage("File saved", 2000) else: self.statusBar().showMessage("Error when trying to save the file") def openFolder(self): """called when user calls 'reveal in finder' function""" if self.activeProjekt() and self.activeProjekt().openFolder(): self.statusBar().showMessage("File revealed in Finder", 2000) def about(self): """called when user wants to know a bit more on the app""" import sys python_version = str(sys.version_info[0]) python_version_temp = sys.version_info[1:5] for item in python_version_temp: python_version = python_version + "." + str(item) QMessageBox.about(self, "About Lekture", "pylekture build " + str(pylekture.__version__ + "\n" + \ "python version " + str(python_version))) def updateMenus(self): """update menus""" hasProjekt = (self.activeProjekt() is not None) self.saveAct.setEnabled(hasProjekt) self.saveAsAct.setEnabled(hasProjekt) self.outputsAct.setEnabled(hasProjekt) self.scenarioAct.setEnabled(hasProjekt) self.openFolderAct.setEnabled(hasProjekt) self.closeAct.setEnabled(hasProjekt) self.closeAllAct.setEnabled(hasProjekt) self.nextAct.setEnabled(hasProjekt) self.previousAct.setEnabled(hasProjekt) self.separatorAct.setVisible(hasProjekt) def updateWindowMenu(self): """unpates menus on the window toolbar""" self.windowMenu.clear() self.windowMenu.addAction(self.closeAct) self.windowMenu.addAction(self.closeAllAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.nextAct) self.windowMenu.addAction(self.previousAct) self.windowMenu.addAction(self.separatorAct) windows = self.mdiArea.subWindowList() self.separatorAct.setVisible(len(windows) != 0) for i, window in enumerate(windows): child = window.widget() text = "%d %s" % (i + 1, child.userFriendlyCurrentFile()) if i < 9: text = '&' + text action = self.windowMenu.addAction(text) action.setCheckable(True) action.setChecked(child is self.activeProjekt()) action.triggered.connect(self.windowMapper.map) self.windowMapper.setMapping(action, window) def createProjekt(self): """create a new project""" child = Projekt() self.mdiArea.addSubWindow(child) self.child = child return child def createActions(self): """create all actions""" self.newAct = QAction("&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) self.openAct = QAction("&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) self.saveAct = QAction("&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) self.saveAsAct = QAction("Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) self.openFolderAct = QAction("Open Project Folder", self, statusTip="Reveal Project in Finder", triggered=self.openFolder) self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) self.closeAct = QAction("Cl&ose", self, statusTip="Close the active window", triggered=self.mdiArea.closeActiveSubWindow) self.outputsAct = QAction("Outputs", self, statusTip="Open the outputs panel", triggered=self.openOutputsPanel) self.scenarioAct = QAction("Scenario", self, statusTip="Open the scenario panel", triggered=self.openScenarioPanel) self.closeAllAct = QAction("Close &All", self, statusTip="Close all the windows", triggered=self.mdiArea.closeAllSubWindows) self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, statusTip="Move the focus to the next window", triggered=self.mdiArea.activateNextSubWindow) self.previousAct = QAction("Pre&vious", self, shortcut=QKeySequence.PreviousChild, statusTip="Move the focus to the previous window", triggered=self.mdiArea.activatePreviousSubWindow) self.separatorAct = QAction(self) self.separatorAct.setSeparator(True) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) def createMenus(self): """create all menus""" self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.saveAsAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.openFolderAct) self.fileMenu.addAction(self.exitAct) self.viewMenu = self.menuBar().addMenu("&View") self.viewMenu.addAction(self.outputsAct) self.viewMenu.addAction(self.scenarioAct) self.windowMenu = self.menuBar().addMenu("&Window") self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) def createStatusBar(self): """create the status bar""" self.statusBar().showMessage("Ready") def readSettings(self): """read the settings""" settings = QSettings('Pixel Stereo', 'lekture') pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(1000, 650)) self.move(pos) self.resize(size) def writeSettings(self): """write settings""" settings = QSettings('Pixel Stereo', 'lekture') settings.setValue('pos', self.pos()) settings.setValue('size', self.size()) def activeProjekt(self): """return the active project object""" activeSubWindow = self.mdiArea.activeSubWindow() if activeSubWindow: return activeSubWindow.widget() else: return None def findProjekt(self, fileName): """return the project""" canonicalFilePath = QFileInfo(fileName).canonicalFilePath() for window in self.mdiArea.subWindowList(): if window.widget().currentFile() == canonicalFilePath: return window return None def setActiveSubWindow(self, window): """set the active sub window""" if window: self.mdiArea.setActiveSubWindow(window) def openOutputsPanel(self): """switch to the outputs editor""" if self.child: project = self.activeProjekt() project.scenario_events_group.setVisible(False) project.outputs_group.setVisible(True) self.scenarioAct.setVisible(True) self.outputsAct.setVisible(False) def openScenarioPanel(self): """switch to the scenario editors""" if self.child: project = self.activeProjekt() project.outputs_group.setVisible(False) project.scenario_events_group.setVisible(True) self.scenarioAct.setVisible(False) self.outputsAct.setVisible(True)
class UMLGraphicsView(E5GraphicsView): """ Class implementing a specialized E5GraphicsView for our diagrams. @signal relayout() emitted to indicate a relayout of the diagram is requested """ relayout = pyqtSignal() def __init__(self, scene, parent=None): """ Constructor @param scene reference to the scene object (QGraphicsScene) @param parent parent widget of the view (QWidget) """ E5GraphicsView.__init__(self, scene, parent) self.setObjectName("UMLGraphicsView") self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.diagramName = "Unnamed" self.__itemId = -1 self.border = 10 self.deltaSize = 100.0 self.__zoomWidget = E5ZoomWidget( UI.PixmapCache.getPixmap("zoomOut.png"), UI.PixmapCache.getPixmap("zoomIn.png"), UI.PixmapCache.getPixmap("zoomReset.png"), self) parent.statusBar().addPermanentWidget(self.__zoomWidget) self.__zoomWidget.setMapping(E5GraphicsView.ZoomLevels, E5GraphicsView.ZoomLevelDefault) self.__zoomWidget.valueChanged.connect(self.setZoom) self.zoomValueChanged.connect(self.__zoomWidget.setValue) self.__initActions() scene.changed.connect(self.__sceneChanged) self.grabGesture(Qt.PinchGesture) def __initActions(self): """ Private method to initialize the view actions. """ self.alignMapper = QSignalMapper(self) self.alignMapper.mapped[int].connect(self.__alignShapes) self.deleteShapeAct = \ QAction(UI.PixmapCache.getIcon("deleteShape.png"), self.tr("Delete shapes"), self) self.deleteShapeAct.triggered.connect(self.__deleteShape) self.incWidthAct = \ QAction(UI.PixmapCache.getIcon("sceneWidthInc.png"), self.tr("Increase width by {0} points").format( self.deltaSize), self) self.incWidthAct.triggered.connect(self.__incWidth) self.incHeightAct = \ QAction(UI.PixmapCache.getIcon("sceneHeightInc.png"), self.tr("Increase height by {0} points").format( self.deltaSize), self) self.incHeightAct.triggered.connect(self.__incHeight) self.decWidthAct = \ QAction(UI.PixmapCache.getIcon("sceneWidthDec.png"), self.tr("Decrease width by {0} points").format( self.deltaSize), self) self.decWidthAct.triggered.connect(self.__decWidth) self.decHeightAct = \ QAction(UI.PixmapCache.getIcon("sceneHeightDec.png"), self.tr("Decrease height by {0} points").format( self.deltaSize), self) self.decHeightAct.triggered.connect(self.__decHeight) self.setSizeAct = \ QAction(UI.PixmapCache.getIcon("sceneSize.png"), self.tr("Set size"), self) self.setSizeAct.triggered.connect(self.__setSize) self.rescanAct = \ QAction(UI.PixmapCache.getIcon("rescan.png"), self.tr("Re-Scan"), self) self.rescanAct.triggered.connect(self.__rescan) self.relayoutAct = \ QAction(UI.PixmapCache.getIcon("relayout.png"), self.tr("Re-Layout"), self) self.relayoutAct.triggered.connect(self.__relayout) self.alignLeftAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignLeft.png"), self.tr("Align Left"), self) self.alignMapper.setMapping(self.alignLeftAct, Qt.AlignLeft) self.alignLeftAct.triggered.connect(self.alignMapper.map) self.alignHCenterAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignHCenter.png"), self.tr("Align Center Horizontal"), self) self.alignMapper.setMapping(self.alignHCenterAct, Qt.AlignHCenter) self.alignHCenterAct.triggered.connect(self.alignMapper.map) self.alignRightAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignRight.png"), self.tr("Align Right"), self) self.alignMapper.setMapping(self.alignRightAct, Qt.AlignRight) self.alignRightAct.triggered.connect(self.alignMapper.map) self.alignTopAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignTop.png"), self.tr("Align Top"), self) self.alignMapper.setMapping(self.alignTopAct, Qt.AlignTop) self.alignTopAct.triggered.connect(self.alignMapper.map) self.alignVCenterAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignVCenter.png"), self.tr("Align Center Vertical"), self) self.alignMapper.setMapping(self.alignVCenterAct, Qt.AlignVCenter) self.alignVCenterAct.triggered.connect(self.alignMapper.map) self.alignBottomAct = \ QAction(UI.PixmapCache.getIcon("shapesAlignBottom.png"), self.tr("Align Bottom"), self) self.alignMapper.setMapping(self.alignBottomAct, Qt.AlignBottom) self.alignBottomAct.triggered.connect(self.alignMapper.map) def __checkSizeActions(self): """ Private slot to set the enabled state of the size actions. """ diagramSize = self._getDiagramSize(10) sceneRect = self.scene().sceneRect() if (sceneRect.width() - self.deltaSize) < diagramSize.width(): self.decWidthAct.setEnabled(False) else: self.decWidthAct.setEnabled(True) if (sceneRect.height() - self.deltaSize) < diagramSize.height(): self.decHeightAct.setEnabled(False) else: self.decHeightAct.setEnabled(True) def __sceneChanged(self, areas): """ Private slot called when the scene changes. @param areas list of rectangles that contain changes (list of QRectF) """ if len(self.scene().selectedItems()) > 0: self.deleteShapeAct.setEnabled(True) else: self.deleteShapeAct.setEnabled(False) sceneRect = self.scene().sceneRect() newWidth = width = sceneRect.width() newHeight = height = sceneRect.height() rect = self.scene().itemsBoundingRect() # calculate with 10 pixel border on each side if sceneRect.right() - 10 < rect.right(): newWidth = rect.right() + 10 if sceneRect.bottom() - 10 < rect.bottom(): newHeight = rect.bottom() + 10 if newHeight != height or newWidth != width: self.setSceneSize(newWidth, newHeight) self.__checkSizeActions() def initToolBar(self): """ Public method to populate a toolbar with our actions. @return the populated toolBar (QToolBar) """ toolBar = QToolBar(self.tr("Graphics"), self) toolBar.setIconSize(UI.Config.ToolBarIconSize) toolBar.addAction(self.deleteShapeAct) toolBar.addSeparator() toolBar.addAction(self.alignLeftAct) toolBar.addAction(self.alignHCenterAct) toolBar.addAction(self.alignRightAct) toolBar.addAction(self.alignTopAct) toolBar.addAction(self.alignVCenterAct) toolBar.addAction(self.alignBottomAct) toolBar.addSeparator() toolBar.addAction(self.incWidthAct) toolBar.addAction(self.incHeightAct) toolBar.addAction(self.decWidthAct) toolBar.addAction(self.decHeightAct) toolBar.addAction(self.setSizeAct) toolBar.addSeparator() toolBar.addAction(self.rescanAct) toolBar.addAction(self.relayoutAct) return toolBar def filteredItems(self, items, itemType=UMLItem): """ Public method to filter a list of items. @param items list of items as returned by the scene object (QGraphicsItem) @param itemType type to be filtered (class) @return list of interesting collision items (QGraphicsItem) """ return [itm for itm in items if isinstance(itm, itemType)] def selectItems(self, items): """ Public method to select the given items. @param items list of items to be selected (list of QGraphicsItemItem) """ # step 1: deselect all items self.unselectItems() # step 2: select all given items for itm in items: if isinstance(itm, UMLItem): itm.setSelected(True) def selectItem(self, item): """ Public method to select an item. @param item item to be selected (QGraphicsItemItem) """ if isinstance(item, UMLItem): item.setSelected(not item.isSelected()) def __deleteShape(self): """ Private method to delete the selected shapes from the display. """ for item in self.scene().selectedItems(): item.removeAssociations() item.setSelected(False) self.scene().removeItem(item) del item def __incWidth(self): """ Private method to handle the increase width context menu entry. """ self.resizeScene(self.deltaSize, True) self.__checkSizeActions() def __incHeight(self): """ Private method to handle the increase height context menu entry. """ self.resizeScene(self.deltaSize, False) self.__checkSizeActions() def __decWidth(self): """ Private method to handle the decrease width context menu entry. """ self.resizeScene(-self.deltaSize, True) self.__checkSizeActions() def __decHeight(self): """ Private method to handle the decrease height context menu entry. """ self.resizeScene(-self.deltaSize, False) self.__checkSizeActions() def __setSize(self): """ Private method to handle the set size context menu entry. """ from .UMLSceneSizeDialog import UMLSceneSizeDialog rect = self._getDiagramRect(10) sceneRect = self.scene().sceneRect() dlg = UMLSceneSizeDialog(sceneRect.width(), sceneRect.height(), rect.width(), rect.height(), self) if dlg.exec_() == QDialog.Accepted: width, height = dlg.getData() self.setSceneSize(width, height) self.__checkSizeActions() def autoAdjustSceneSize(self, limit=False): """ Public method to adjust the scene size to the diagram size. @param limit flag indicating to limit the scene to the initial size (boolean) """ super(UMLGraphicsView, self).autoAdjustSceneSize(limit=limit) self.__checkSizeActions() def saveImage(self): """ Public method to handle the save context menu entry. """ fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, self.tr("Save Diagram"), "", self.tr("Portable Network Graphics (*.png);;" "Scalable Vector Graphics (*.svg)"), "", E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if fname: ext = QFileInfo(fname).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fname += ex if QFileInfo(fname).exists(): res = E5MessageBox.yesNo( self, self.tr("Save Diagram"), self.tr("<p>The file <b>{0}</b> already exists." " Overwrite it?</p>").format(fname), icon=E5MessageBox.Warning) if not res: return success = super(UMLGraphicsView, self).saveImage(fname, QFileInfo(fname).suffix().upper()) if not success: E5MessageBox.critical( self, self.tr("Save Diagram"), self.tr( """<p>The file <b>{0}</b> could not be saved.</p>"""). format(fname)) def __relayout(self): """ Private slot to handle the re-layout context menu entry. """ self.__itemId = -1 self.scene().clear() self.relayout.emit() def __rescan(self): """ Private slot to handle the re-scan context menu entry. """ # 1. save positions of all items and names of selected items itemPositions = {} selectedItems = [] for item in self.filteredItems(self.scene().items(), UMLItem): name = item.getName() if name: itemPositions[name] = (item.x(), item.y()) if item.isSelected(): selectedItems.append(name) # 2. save # 2. re-layout the diagram self.__relayout() # 3. move known items to the saved positions for item in self.filteredItems(self.scene().items(), UMLItem): name = item.getName() if name in itemPositions: item.setPos(*itemPositions[name]) if name in selectedItems: item.setSelected(True) def printDiagram(self): """ Public slot called to print the diagram. """ printer = QPrinter(mode=QPrinter.ScreenResolution) printer.setFullPage(True) if Preferences.getPrinter("ColorMode"): printer.setColorMode(QPrinter.Color) else: printer.setColorMode(QPrinter.GrayScale) if Preferences.getPrinter("FirstPageFirst"): printer.setPageOrder(QPrinter.FirstPageFirst) else: printer.setPageOrder(QPrinter.LastPageFirst) printer.setPageMargins( Preferences.getPrinter("LeftMargin") * 10, Preferences.getPrinter("TopMargin") * 10, Preferences.getPrinter("RightMargin") * 10, Preferences.getPrinter("BottomMargin") * 10, QPrinter.Millimeter) printerName = Preferences.getPrinter("PrinterName") if printerName: printer.setPrinterName(printerName) printDialog = QPrintDialog(printer, self) if printDialog.exec_(): super(UMLGraphicsView, self).printDiagram(printer, self.diagramName) def printPreviewDiagram(self): """ Public slot called to show a print preview of the diagram. """ from PyQt5.QtPrintSupport import QPrintPreviewDialog printer = QPrinter(mode=QPrinter.ScreenResolution) printer.setFullPage(True) if Preferences.getPrinter("ColorMode"): printer.setColorMode(QPrinter.Color) else: printer.setColorMode(QPrinter.GrayScale) if Preferences.getPrinter("FirstPageFirst"): printer.setPageOrder(QPrinter.FirstPageFirst) else: printer.setPageOrder(QPrinter.LastPageFirst) printer.setPageMargins( Preferences.getPrinter("LeftMargin") * 10, Preferences.getPrinter("TopMargin") * 10, Preferences.getPrinter("RightMargin") * 10, Preferences.getPrinter("BottomMargin") * 10, QPrinter.Millimeter) printerName = Preferences.getPrinter("PrinterName") if printerName: printer.setPrinterName(printerName) preview = QPrintPreviewDialog(printer, self) preview.paintRequested[QPrinter].connect(self.__printPreviewPrint) preview.exec_() def __printPreviewPrint(self, printer): """ Private slot to generate a print preview. @param printer reference to the printer object (QPrinter) """ super(UMLGraphicsView, self).printDiagram(printer, self.diagramName) def setDiagramName(self, name): """ Public slot to set the diagram name. @param name diagram name (string) """ self.diagramName = name def __alignShapes(self, alignment): """ Private slot to align the selected shapes. @param alignment alignment type (Qt.AlignmentFlag) """ # step 1: get all selected items items = self.scene().selectedItems() if len(items) <= 1: return # step 2: find the index of the item to align in relation to amount = None for i, item in enumerate(items): rect = item.sceneBoundingRect() if alignment == Qt.AlignLeft: if amount is None or rect.x() < amount: amount = rect.x() index = i elif alignment == Qt.AlignRight: if amount is None or rect.x() + rect.width() > amount: amount = rect.x() + rect.width() index = i elif alignment == Qt.AlignHCenter: if amount is None or rect.width() > amount: amount = rect.width() index = i elif alignment == Qt.AlignTop: if amount is None or rect.y() < amount: amount = rect.y() index = i elif alignment == Qt.AlignBottom: if amount is None or rect.y() + rect.height() > amount: amount = rect.y() + rect.height() index = i elif alignment == Qt.AlignVCenter: if amount is None or rect.height() > amount: amount = rect.height() index = i rect = items[index].sceneBoundingRect() # step 3: move the other items for i, item in enumerate(items): if i == index: continue itemrect = item.sceneBoundingRect() xOffset = yOffset = 0 if alignment == Qt.AlignLeft: xOffset = rect.x() - itemrect.x() elif alignment == Qt.AlignRight: xOffset = (rect.x() + rect.width()) - \ (itemrect.x() + itemrect.width()) elif alignment == Qt.AlignHCenter: xOffset = (rect.x() + rect.width() // 2) - \ (itemrect.x() + itemrect.width() // 2) elif alignment == Qt.AlignTop: yOffset = rect.y() - itemrect.y() elif alignment == Qt.AlignBottom: yOffset = (rect.y() + rect.height()) - \ (itemrect.y() + itemrect.height()) elif alignment == Qt.AlignVCenter: yOffset = (rect.y() + rect.height() // 2) - \ (itemrect.y() + itemrect.height() // 2) item.moveBy(xOffset, yOffset) self.scene().update() def __itemsBoundingRect(self, items): """ Private method to calculate the bounding rectangle of the given items. @param items list of items to operate on (list of UMLItem) @return bounding rectangle (QRectF) """ rect = self.scene().sceneRect() right = rect.left() bottom = rect.top() left = rect.right() top = rect.bottom() for item in items: rect = item.sceneBoundingRect() left = min(rect.left(), left) right = max(rect.right(), right) top = min(rect.top(), top) bottom = max(rect.bottom(), bottom) return QRectF(left, top, right - left, bottom - top) def keyPressEvent(self, evt): """ Protected method handling key press events. @param evt reference to the key event (QKeyEvent) """ key = evt.key() if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]: items = self.filteredItems(self.scene().selectedItems()) if items: if evt.modifiers() & Qt.ControlModifier: stepSize = 50 else: stepSize = 5 if key == Qt.Key_Up: dx = 0 dy = -stepSize elif key == Qt.Key_Down: dx = 0 dy = stepSize elif key == Qt.Key_Left: dx = -stepSize dy = 0 else: dx = stepSize dy = 0 for item in items: item.moveBy(dx, dy) evt.accept() return super(UMLGraphicsView, self).keyPressEvent(evt) def wheelEvent(self, evt): """ Protected method to handle wheel events. @param evt reference to the wheel event (QWheelEvent) """ if evt.modifiers() & Qt.ControlModifier: if qVersion() >= "5.0.0": delta = evt.angleDelta().y() else: delta = evt.delta() if delta < 0: self.zoomOut() else: self.zoomIn() evt.accept() return super(UMLGraphicsView, self).wheelEvent(evt) def event(self, evt): """ Public method handling events. @param evt reference to the event (QEvent) @return flag indicating, if the event was handled (boolean) """ if evt.type() == QEvent.Gesture: self.gestureEvent(evt) return True return super(UMLGraphicsView, self).event(evt) def gestureEvent(self, evt): """ Protected method handling gesture events. @param evt reference to the gesture event (QGestureEvent """ pinch = evt.gesture(Qt.PinchGesture) if pinch: if pinch.state() == Qt.GestureStarted: pinch.setScaleFactor(self.zoom() / 100.0) else: self.setZoom(int(pinch.scaleFactor() * 100)) evt.accept() def getItemId(self): """ Public method to get the ID to be assigned to an item. @return item ID (integer) """ self.__itemId += 1 return self.__itemId def findItem(self, id): """ Public method to find an UML item based on the ID. @param id of the item to search for (integer) @return item found (UMLItem) or None """ for item in self.scene().items(): try: if item.getId() == id: return item except AttributeError: continue return None def findItemByName(self, name): """ Public method to find an UML item based on its name. @param name name to look for (string) @return item found (UMLItem) or None """ for item in self.scene().items(): try: if item.getName() == name: return item except AttributeError: continue return None def getPersistenceData(self): """ Public method to get a list of data to be persisted. @return list of data to be persisted (list of strings) """ lines = [ "diagram_name: {0}".format(self.diagramName), ] for item in self.filteredItems(self.scene().items(), UMLItem): lines.append("item: id={0}, x={1}, y={2}, item_type={3}{4}".format( item.getId(), item.x(), item.y(), item.getItemType(), item.buildItemDataString())) from .AssociationItem import AssociationItem for item in self.filteredItems(self.scene().items(), AssociationItem): lines.append("association: {0}".format( item.buildAssociationItemDataString())) return lines def parsePersistenceData(self, version, data): """ Public method to parse persisted data. @param version version of the data (string) @param data persisted data to be parsed (list of string) @return tuple of flag indicating success (boolean) and faulty line number (integer) """ umlItems = {} if not data[0].startswith("diagram_name:"): return False, 0 self.diagramName = data[0].split(": ", 1)[1].strip() from .ClassItem import ClassItem from .ModuleItem import ModuleItem from .PackageItem import PackageItem from .AssociationItem import AssociationItem linenum = 0 for line in data[1:]: linenum += 1 if not line.startswith(("item:", "association:")): return False, linenum key, value = line.split(": ", 1) if key == "item": id, x, y, itemType, itemData = value.split(", ", 4) try: id = int(id.split("=", 1)[1].strip()) x = float(x.split("=", 1)[1].strip()) y = float(y.split("=", 1)[1].strip()) itemType = itemType.split("=", 1)[1].strip() if itemType == ClassItem.ItemType: itm = ClassItem(x=x, y=y, scene=self.scene()) elif itemType == ModuleItem.ItemType: itm = ModuleItem(x=x, y=y, scene=self.scene()) elif itemType == PackageItem.ItemType: itm = PackageItem(x=x, y=y, scene=self.scene()) itm.setId(id) umlItems[id] = itm if not itm.parseItemDataString(version, itemData): return False, linenum except ValueError: return False, linenum elif key == "association": srcId, dstId, assocType, topToBottom = \ AssociationItem.parseAssociationItemDataString( value.strip()) assoc = AssociationItem(umlItems[srcId], umlItems[dstId], assocType, topToBottom) self.scene().addItem(assoc) return True, -1
def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Add / remove items self.actOpen = QAction(QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open Item"), menu) self.actOpen.triggered.connect(self.openItem) menu.addAction(self.actOpen) menu.addSeparator() # Add / remove items self.actAddFolder = QAction( QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) menu.addSeparator() # Copy, cut, paste self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "Cut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [ qApp.translate("outlineBasics", "Main"), qApp.translate("outlineBasics", "Secondary"), qApp.translate("outlineBasics", "Minor") ]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlCharacter.rowCount()): a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction( mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) menu.addSeparator() # Custom icons self.menuCustomIcons = QMenu( qApp.translate("outlineBasics", "Set Custom Icon"), menu) a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons) a.triggered.connect(lambda: self.setCustomIcon("")) self.menuCustomIcons.addAction(a) self.menuCustomIcons.addSeparator() txt = QLineEdit() txt.textChanged.connect(self.filterLstIcons) txt.setPlaceholderText("Filter icons") txt.setStyleSheet("background: transparent; border: none;") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(txt) self.menuCustomIcons.addAction(act) self.lstIcons = QListWidget() for i in customIcons(): item = QListWidgetItem() item.setIcon(QIcon.fromTheme(i)) item.setData(Qt.UserRole, i) item.setToolTip(i) self.lstIcons.addItem(item) self.lstIcons.itemClicked.connect(self.setCustomIconFromItem) self.lstIcons.setViewMode(self.lstIcons.IconMode) self.lstIcons.setUniformItemSizes(True) self.lstIcons.setResizeMode(self.lstIcons.Adjust) self.lstIcons.setMovement(self.lstIcons.Static) self.lstIcons.setStyleSheet( "background: transparent; background: none;") self.filterLstIcons("") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(self.lstIcons) self.menuCustomIcons.addAction(act) menu.addMenu(self.menuCustomIcons) # Disabling stuff if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder() \ or not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) > 0 and index.isValid( ) and not index.internalPointer().isFolder(): self.actAddFolder.setEnabled(False) self.actAddText.setEnabled(False) if len(sel) == 0: self.actOpen.setEnabled(False) self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) self.menuCustomIcons.setEnabled(False) return menu
class AddSeqTool(AbstractPathTool): """Summary Attributes: apply_button (TYPE): Description buttons (list): Description dialog (TYPE): Description highlighter (TYPE): Description seq_box (TYPE): Description sequence_radio_button_id (dict): Description signal_mapper (TYPE): Description use_abstract_sequence (bool): Description validated_sequence_to_apply (TYPE): Description """ def __init__(self, manager): """Summary Args: manager (TYPE): Description """ AbstractPathTool.__init__(self, manager) self.dialog = QDialog() self.buttons = [] self.seq_box = None self.sequence_radio_button_id = {} self.use_abstract_sequence = True self.validated_sequence_to_apply = None self.initDialog() def __repr__(self): """Summary Returns: TYPE: Description """ return "add_seq_tool" # first letter should be lowercase def methodPrefix(self): """Summary Returns: TYPE: Description """ return "addSeqTool" # first letter should be lowercase def initDialog(self): """Creates buttons for each sequence option and add them to the dialog. Maps the clicked signal of those buttons to keep track of what sequence gets selected. """ ui_dlg = Ui_AddSeqDialog() ui_dlg.setupUi(self.dialog) self.signal_mapper = QSignalMapper(self) # set up the radio buttons for i, name in enumerate(['Abstract', 'Custom'] + sorted(sequences.keys())): radio_button = QRadioButton(ui_dlg.group_box) radio_button.setObjectName(name + "Button") radio_button.setText(name) self.buttons.append(radio_button) ui_dlg.horizontalLayout.addWidget(radio_button) self.signal_mapper.setMapping(radio_button, i) radio_button.clicked.connect(self.signal_mapper.map) if name in sequences: self.sequence_radio_button_id[sequences[name]] = i self.signal_mapper.mapped.connect(self.sequenceOptionChangedSlot) # disable apply until valid option or custom sequence is chosen self.apply_button = ui_dlg.custom_button_box.button(QDialogButtonBox.Apply) self.apply_button.setEnabled(False) # watch sequence textedit box to validate custom sequences self.seq_box = ui_dlg.seq_text_edit self.seq_box.textChanged.connect(self.validateCustomSequence) self.highlighter = DNAHighlighter(self.seq_box) # finally, pre-click the first radio button self.buttons[0].click() def sequenceOptionChangedSlot(self, option_chosen): """ Connects to signal_mapper to receive a signal whenever user selects a sequence option. Args: option_chosen (TYPE): Description """ option_name = self.buttons[option_chosen].text() if option_name == 'Abstract': self.use_abstract_sequence = True elif option_name == 'Custom': self.use_abstract_sequence = False else: self.use_abstract_sequence = False user_sequence = sequences.get(option_name, None) if self.seq_box.toPlainText() != user_sequence: self.seq_box.setText(user_sequence) def validateCustomSequence(self): """ Called when user changes sequence (seq_box emits textChanged signal) If sequence is valid, make the apply_button active to click. Select an appropriate sequence option radio button, if necessary. """ user_sequence = self.seq_box.toPlainText() # Validate the sequence and activate the button if it checks out. if re.search(RE_DNA_PATTERN, user_sequence) is None: self.apply_button.setEnabled(True) else: self.apply_button.setEnabled(False) if len(user_sequence) == 0: # A zero-length custom sequence defaults to Abstract type. if not self.buttons[0].isChecked(): self.buttons[0].click() else: # Does this match a known sequence? if user_sequence in self.sequence_radio_button_id: # Handles case where the user might copy & paste in a known sequence i = self.sequence_radio_button_id[user_sequence] if not self.buttons[i].isChecked(): # Select the corresponding radio button for known sequence self.buttons[i].click() else: # Unrecognized, Custom type if not self.buttons[1].isChecked(): self.buttons[1].click() def applySequence(self, oligo): """Summary Args: oligo (TYPE): Description Returns: TYPE: Description """ self.dialog.setFocus() if self.dialog.exec_(): # apply the sequence if accept was clicked if self.use_abstract_sequence: oligo.applySequence(None) return (oligo.length(), None) else: self.validated_sequence_to_apply = self.seq_box.toPlainText().upper() oligo.applySequence(self.validated_sequence_to_apply) return oligo.length(), len(self.validated_sequence_to_apply) return (None, None)
class MainWindow(QMainWindow): """This create the main window of the application""" def __init__(self): super(MainWindow, self).__init__() self.ports = serial.listports() self.port = None # remove close & maximize window buttons #self.setWindowFlags(Qt.CustomizeWindowHint|Qt.WindowMinimizeButtonHint) self.setMinimumSize(850, 450) self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.mdiArea.setViewMode(QMdiArea.TabbedView) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.child = None self.createActions() self.createMenus() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("VISCAM") mytoolbar = QToolBar() ports_menu = QComboBox() ports_menu.addItem('Output Port') ports_menu.insertSeparator(1) for port in self.ports: ports_menu.addItem(port) self.ports_menu = ports_menu ports_menu.currentTextChanged.connect(self.setActivePort) mytoolbar.addWidget(ports_menu) mytoolbar.addSeparator() mytoolbar.setMovable(False) mytoolbar.setFixedHeight(60) self.addToolBar(Qt.TopToolBarArea, mytoolbar) def closeEvent(self, scenario): self.mdiArea.closeAllSubWindows() if self.mdiArea.currentSubWindow(): scenario.ignore() else: self.writeSettings() scenario.accept() def about(self): QMessageBox.about( self, "About Viscam", "<b>Viscam</b> controls and manage your video camera through VISCA protocol." "This release is an alpha version. Don't use it in production !!") def updateMenus(self): hasCamera = (self.activeCamera() is not None) self.nextAct.setEnabled(hasCamera) self.previousAct.setEnabled(hasCamera) self.separatorAct.setVisible(hasCamera) def updatePortMenu(self): self.PortMenu.clear() for i, port in enumerate(self.ports): text = "%d %s" % (i + 1, port) if i < 9: text = '&' + text action = self.PortMenu.addAction(text) action.setCheckable(True) if port == self.port: action.setChecked(True) action.triggered.connect(self.setActivePort) def updateWindowMenu(self): self.windowMenu.clear() self.windowMenu.addAction(self.nextAct) self.windowMenu.addAction(self.previousAct) self.windowMenu.addAction(self.separatorAct) windows = self.mdiArea.subWindowList() self.separatorAct.setVisible(len(windows) != 0) for i, window in enumerate(windows): child = window.widget() text = "%d %s" % (i + 1, child.userFriendlyCurrentFile()) if i < 9: text = '&' + text action = self.windowMenu.addAction(text) action.setCheckable(True) action.setChecked(child is self.activeCamera()) action.triggered.connect(self.windowMapper.map) self.windowMapper.setMapping(action, window) def createCamera(self): child = Camera(serial) self.mdiArea.addSubWindow(child) child.newFile() self.child = child return child def createActions(self): self.exitAct = QAction( "E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) self.closeAct = QAction("Cl&ose", self, statusTip="Close the active window", triggered=self.mdiArea.closeActiveSubWindow) self.closeAllAct = QAction("Close &All", self, statusTip="Close all the windows", triggered=self.mdiArea.closeAllSubWindows) self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, statusTip="Move the focus to the next window", triggered=self.mdiArea.activateNextSubWindow) self.previousAct = QAction( "Pre&vious", self, shortcut=QKeySequence.PreviousChild, statusTip="Move the focus to the previous window", triggered=self.mdiArea.activatePreviousSubWindow) self.separatorAct = QAction(self) self.separatorAct.setSeparator(True) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.exitAct) self.PortMenu = self.menuBar().addMenu("&Ports") self.updatePortMenu() self.PortMenu.aboutToShow.connect(self.updatePortMenu) self.windowMenu = self.menuBar().addMenu("&Window") self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) def createStatusBar(self): self.statusBar().showMessage("Ready") def readSettings(self): settings = QSettings('Pixel Stereo', 'viscam') port = settings.value('port') pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(1000, 650)) self.move(pos) self.resize(size) def writeSettings(self): settings = QSettings('Pixel Stereo', 'viscam') settings.setValue('port', self.port) settings.setValue('pos', self.pos()) settings.setValue('size', self.size()) def activeCamera(self): activeSubWindow = self.mdiArea.activeSubWindow() if activeSubWindow: return activeSubWindow.widget() else: return None def findCamera(self, fileName): canonicalFilePath = QFileInfo(fileName).canonicalFilePath() for window in self.mdiArea.subWindowList(): if window.widget().currentFile() == canonicalFilePath: return window return None def setActiveSubWindow(self, window): if window: self.mdiArea.setActiveSubWindow(window) def setActivePort(self): self.port = self.ports_menu.currentText().encode('utf-8') self.updatePortMenu() serial.open(portname=self.port) viscams = _cmd_adress_set(serial) _if_clear(serial) for v in viscams: v = self.createCamera()
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.createActions() self.createMenus() self.createToolBars() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("MDI") def closeEvent(self, event): self.mdiArea.closeAllSubWindows() if self.mdiArea.currentSubWindow(): event.ignore() else: self.writeSettings() event.accept() def newFile(self): child = self.createMdiChild() child.newFile() child.show() def open(self): fileName, _ = QFileDialog.getOpenFileName(self) if fileName: existing = self.findMdiChild(fileName) if existing: self.mdiArea.setActiveSubWindow(existing) return child = self.createMdiChild() if child.loadFile(fileName): self.statusBar().showMessage("File loaded", 2000) child.show() else: child.close() def save(self): if self.activeMdiChild() and self.activeMdiChild().save(): self.statusBar().showMessage("File saved", 2000) def saveAs(self): if self.activeMdiChild() and self.activeMdiChild().saveAs(): self.statusBar().showMessage("File saved", 2000) def cut(self): if self.activeMdiChild(): self.activeMdiChild().cut() def copy(self): if self.activeMdiChild(): self.activeMdiChild().copy() def paste(self): if self.activeMdiChild(): self.activeMdiChild().paste() def about(self): QMessageBox.about( self, "About MDI", "The <b>MDI</b> example demonstrates how to write multiple " "document interface applications using Qt.") def updateMenus(self): hasMdiChild = (self.activeMdiChild() is not None) self.saveAct.setEnabled(hasMdiChild) self.saveAsAct.setEnabled(hasMdiChild) self.pasteAct.setEnabled(hasMdiChild) self.closeAct.setEnabled(hasMdiChild) self.closeAllAct.setEnabled(hasMdiChild) self.tileAct.setEnabled(hasMdiChild) self.cascadeAct.setEnabled(hasMdiChild) self.nextAct.setEnabled(hasMdiChild) self.previousAct.setEnabled(hasMdiChild) self.separatorAct.setVisible(hasMdiChild) hasSelection = (self.activeMdiChild() is not None and self.activeMdiChild().textCursor().hasSelection()) self.cutAct.setEnabled(hasSelection) self.copyAct.setEnabled(hasSelection) def updateWindowMenu(self): self.windowMenu.clear() self.windowMenu.addAction(self.closeAct) self.windowMenu.addAction(self.closeAllAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.tileAct) self.windowMenu.addAction(self.cascadeAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.nextAct) self.windowMenu.addAction(self.previousAct) self.windowMenu.addAction(self.separatorAct) windows = self.mdiArea.subWindowList() self.separatorAct.setVisible(len(windows) != 0) for i, window in enumerate(windows): child = window.widget() text = "%d %s" % (i + 1, child.userFriendlyCurrentFile()) if i < 9: text = '&' + text action = self.windowMenu.addAction(text) action.setCheckable(True) action.setChecked(child is self.activeMdiChild()) action.triggered.connect(self.windowMapper.map) self.windowMapper.setMapping(action, window) def createMdiChild(self): child = MdiChild() self.mdiArea.addSubWindow(child) child.copyAvailable.connect(self.cutAct.setEnabled) child.copyAvailable.connect(self.copyAct.setEnabled) return child def createActions(self): self.newAct = QAction(QIcon(':/images/new.png'), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) self.openAct = QAction(QIcon(':/images/open.png'), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) self.saveAct = QAction(QIcon(':/images/save.png'), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) self.saveAsAct = QAction( "Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) self.exitAct = QAction( "E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) self.cutAct = QAction( QIcon(':/images/cut.png'), "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) self.suggestAct = QAction( "Suggest", self, shortcut=QKeySequence.Copy, statusTip="Cut the current selection's contents to the clipboard", triggered=self.copy) self.copyAct = QAction( QIcon(':/images/copy.png'), "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) self.pasteAct = QAction( QIcon(':/images/paste.png'), "&Paste", self, shortcut=QKeySequence.Paste, statusTip= "Paste the clipboard's contents into the current selection", triggered=self.paste) self.closeAct = QAction("Cl&ose", self, statusTip="Close the active window", triggered=self.mdiArea.closeActiveSubWindow) self.closeAllAct = QAction("Close &All", self, statusTip="Close all the windows", triggered=self.mdiArea.closeAllSubWindows) self.tileAct = QAction("&Tile", self, statusTip="Tile the windows", triggered=self.mdiArea.tileSubWindows) self.cascadeAct = QAction("&Cascade", self, statusTip="Cascade the windows", triggered=self.mdiArea.cascadeSubWindows) self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, statusTip="Move the focus to the next window", triggered=self.mdiArea.activateNextSubWindow) self.previousAct = QAction( "Pre&vious", self, shortcut=QKeySequence.PreviousChild, statusTip="Move the focus to the previous window", triggered=self.mdiArea.activatePreviousSubWindow) self.separatorAct = QAction(self) self.separatorAct.setSeparator(True) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, statusTip="Show the Qt library's About box", triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.saveAsAct) self.fileMenu.addSeparator() action = self.fileMenu.addAction("Switch layout direction") action.triggered.connect(self.switchLayoutDirection) self.fileMenu.addAction(self.exitAct) self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.cutAct) self.editMenu.addAction(self.copyAct) self.editMenu.addAction(self.pasteAct) self.windowMenu = self.menuBar().addMenu("&Window") self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def createToolBars(self): self.fileToolBar = self.addToolBar("File") self.fileToolBar.addAction(self.newAct) self.fileToolBar.addAction(self.openAct) self.fileToolBar.addAction(self.saveAct) self.editToolBar = self.addToolBar("Edit") self.editToolBar.addAction(self.cutAct) self.editToolBar.addAction(self.copyAct) self.editToolBar.addAction(self.pasteAct) def createStatusBar(self): self.statusBar().showMessage("Ready") def readSettings(self): settings = QSettings('Trolltech', 'MDI Example') pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(400, 400)) self.move(pos) self.resize(size) def writeSettings(self): settings = QSettings('Trolltech', 'MDI Example') settings.setValue('pos', self.pos()) settings.setValue('size', self.size()) def activeMdiChild(self): activeSubWindow = self.mdiArea.activeSubWindow() if activeSubWindow: return activeSubWindow.widget() return None def findMdiChild(self, fileName): canonicalFilePath = QFileInfo(fileName).canonicalFilePath() for window in self.mdiArea.subWindowList(): if window.widget().currentFile() == canonicalFilePath: return window return None def switchLayoutDirection(self): if self.layoutDirection() == Qt.LeftToRight: QApplication.setLayoutDirection(Qt.RightToLeft) else: QApplication.setLayoutDirection(Qt.LeftToRight) def setActiveSubWindow(self, window): if window: self.mdiArea.setActiveSubWindow(window) def contextMenuEvent(self, event): menu = QMenu(self) menu.addAction(self.cutAct) menu.addAction(self.copyAct) menu.addAction(self.pasteAct) menu.addAction(self.suggestAct) menu.exec_(event.globalPos())
class MainWindow(QMainWindow, Ui_MainWindow): dictChanged = pyqtSignal(str) # Tab indexes TabInfos = 0 TabSummary = 1 TabPersos = 2 TabPlots = 3 TabWorld = 4 TabOutline = 5 TabRedac = 6 def __init__(self): QMainWindow.__init__(self) self.setupUi(self) self.currentProject = None self.readSettings() # UI self.setupMoreUi() # Welcome self.welcome.updateValues() # self.welcome.btnCreate.clicked.connect self.stack.setCurrentIndex(0) # Word count self.mprWordCount = QSignalMapper(self) for t, i in [ (self.txtSummarySentence, 0), (self.txtSummaryPara, 1), (self.txtSummaryPage, 2), (self.txtSummaryFull, 3) ]: t.textChanged.connect(self.mprWordCount.map) self.mprWordCount.setMapping(t, i) self.mprWordCount.mapped.connect(self.wordCount) # Snowflake Method Cycle self.mapperCycle = QSignalMapper(self) for t, i in [ (self.btnStepTwo, 0), (self.btnStepThree, 1), (self.btnStepFour, 2), (self.btnStepFive, 3), (self.btnStepSix, 4), (self.btnStepSeven, 5), (self.btnStepEight, 6) ]: t.clicked.connect(self.mapperCycle.map) self.mapperCycle.setMapping(t, i) self.mapperCycle.mapped.connect(self.clickCycle) self.cmbSummary.currentIndexChanged.connect(self.summaryPageChanged) self.cmbSummary.setCurrentIndex(0) self.cmbSummary.currentIndexChanged.emit(0) # Main Menu for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuMode, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) self.actSave.triggered.connect(self.saveDatas) self.actSaveAs.triggered.connect(self.welcome.saveAsFile) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) self.actSettings.triggered.connect(self.settingsWindow) self.actCloseProject.triggered.connect(self.closeProject) self.actQuit.triggered.connect(self.close) self.actToolFrequency.triggered.connect(self.frequencyAnalyzer) self.generateViewMenu() self.makeUIConnections() # self.loadProject(os.path.join(appPath(), "test_project.zip")) ############################################################################### # SUMMARY ############################################################################### def summaryPageChanged(self, index): fractalButtons = [ self.btnStepTwo, self.btnStepThree, self.btnStepFive, self.btnStepSeven, ] for b in fractalButtons: b.setVisible(fractalButtons.index(b) == index) ############################################################################### # OUTLINE ############################################################################### def outlineRemoveItemsRedac(self): self.treeRedacOutline.delete() def outlineRemoveItemsOutline(self): self.treeOutlineOutline.delete() ############################################################################### # PERSOS ############################################################################### def changeCurrentPerso(self, trash=None): index = self.lstPersos.currentPersoIndex() if not index.isValid(): self.tabPlot.setEnabled(False) return self.tabPersos.setEnabled(True) for w in [ self.txtPersoName, self.sldPersoImportance, self.txtPersoMotivation, self.txtPersoGoal, self.txtPersoConflict, self.txtPersoEpiphany, self.txtPersoSummarySentence, self.txtPersoSummaryPara, self.txtPersoSummaryFull, self.txtPersoNotes, ]: w.setCurrentModelIndex(index) # Button color self.mdlPersos.updatePersoColor(index) # Perso Infos self.tblPersoInfos.setRootIndex(index) if self.mdlPersos.rowCount(index): self.updatePersoInfoView() def updatePersoInfoView(self): # Hide columns for i in range(self.mdlPersos.columnCount()): self.tblPersoInfos.hideColumn(i) self.tblPersoInfos.showColumn(Perso.infoName.value) self.tblPersoInfos.showColumn(Perso.infoData.value) self.tblPersoInfos.horizontalHeader().setSectionResizeMode( Perso.infoName.value, QHeaderView.ResizeToContents) self.tblPersoInfos.horizontalHeader().setSectionResizeMode( Perso.infoData.value, QHeaderView.Stretch) self.tblPersoInfos.verticalHeader().hide() ############################################################################### # PLOTS ############################################################################### def changeCurrentPlot(self): index = self.lstPlots.currentPlotIndex() if not index.isValid(): self.tabPlot.setEnabled(False) return self.tabPlot.setEnabled(True) self.txtPlotName.setCurrentModelIndex(index) self.txtPlotDescription.setCurrentModelIndex(index) self.txtPlotResult.setCurrentModelIndex(index) self.sldPlotImportance.setCurrentModelIndex(index) self.lstPlotPerso.setRootIndex(index.sibling(index.row(), Plot.persos.value)) subplotindex = index.sibling(index.row(), Plot.subplots.value) self.lstSubPlots.setRootIndex(subplotindex) if self.mdlPlots.rowCount(subplotindex): self.updateSubPlotView() # self.txtSubPlotSummary.setCurrentModelIndex(QModelIndex()) self.txtSubPlotSummary.setEnabled(False) self._updatingSubPlot = True self.txtSubPlotSummary.setPlainText("") self._updatingSubPlot = False self.lstPlotPerso.selectionModel().clear() def updateSubPlotView(self): # Hide columns for i in range(self.mdlPlots.columnCount()): self.lstSubPlots.hideColumn(i) self.lstSubPlots.showColumn(Subplot.name.value) self.lstSubPlots.showColumn(Subplot.meta.value) self.lstSubPlots.horizontalHeader().setSectionResizeMode( Subplot.name.value, QHeaderView.Stretch) self.lstSubPlots.horizontalHeader().setSectionResizeMode( Subplot.meta.value, QHeaderView.ResizeToContents) self.lstSubPlots.verticalHeader().hide() def changeCurrentSubPlot(self, index): # Got segfaults when using textEditView model system, so ad hoc stuff. index = index.sibling(index.row(), Subplot.summary.value) item = self.mdlPlots.itemFromIndex(index) if not item: self.txtSubPlotSummary.setEnabled(False) return self.txtSubPlotSummary.setEnabled(True) txt = item.text() self._updatingSubPlot = True self.txtSubPlotSummary.setPlainText(txt) self._updatingSubPlot = False def updateSubPlotSummary(self): if self._updatingSubPlot: return index = self.lstSubPlots.currentIndex() if not index.isValid(): return index = index.sibling(index.row(), Subplot.summary.value) item = self.mdlPlots.itemFromIndex(index) self._updatingSubPlot = True item.setText(self.txtSubPlotSummary.toPlainText()) self._updatingSubPlot = False def plotPersoSelectionChanged(self): "Enables or disables remove plot perso button." self.btnRmPlotPerso.setEnabled( len(self.lstPlotPerso.selectedIndexes()) != 0) ############################################################################### # WORLD ############################################################################### def changeCurrentWorld(self): index = self.mdlWorld.selectedIndex() if not index.isValid(): self.tabWorld.setEnabled(False) return self.tabWorld.setEnabled(True) self.txtWorldName.setCurrentModelIndex(index) self.txtWorldDescription.setCurrentModelIndex(index) self.txtWorldPassion.setCurrentModelIndex(index) self.txtWorldConflict.setCurrentModelIndex(index) ############################################################################### # LOAD AND SAVE ############################################################################### def loadProject(self, project, loadFromFile=True): """Loads the project ``project``. If ``loadFromFile`` is False, then it does not load datas from file. It assumes that the datas have been populated in a different way.""" if loadFromFile and not os.path.exists(project): print(self.tr("The file {} does not exist. Try again.").format(project)) self.statusBar().showMessage( self.tr("The file {} does not exist. Try again.").format(project), 5000) return if loadFromFile: # Load empty settings imp.reload(settings) # Load data self.loadEmptyDatas() self.loadDatas(project) self.makeConnections() # Load settings for i in settings.openIndexes: idx = self.mdlOutline.indexFromPath(i) self.mainEditor.setCurrentModelIndex(idx, newTab=True) self.generateViewMenu() self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) self.actSpellcheck.setChecked(settings.spellcheck) self.toggleSpellcheck(settings.spellcheck) self.updateMenuDict() self.setDictionary() self.mainEditor.setFolderView(settings.folderView) self.mainEditor.updateFolderViewButtons(settings.folderView) self.tabMain.setCurrentIndex(settings.lastTab) # We force to emit even if it opens on the current tab self.tabMain.currentChanged.emit(settings.lastTab) self.mainEditor.updateCorkBackground() # Set autosave self.saveTimer = QTimer() self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000) self.saveTimer.setSingleShot(False) self.saveTimer.timeout.connect(self.saveDatas) if settings.autoSave: self.saveTimer.start() # Set autosave if no changes self.saveTimerNoChanges = QTimer() self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000) self.saveTimerNoChanges.setSingleShot(True) self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges) self.mdlOutline.dataChanged.connect(self.startTimerNoChanges) self.mdlPersos.dataChanged.connect(self.startTimerNoChanges) self.mdlPlots.dataChanged.connect(self.startTimerNoChanges) self.mdlWorld.dataChanged.connect(self.startTimerNoChanges) # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges) self.mdlStatus.dataChanged.connect(self.startTimerNoChanges) self.mdlLabels.dataChanged.connect(self.startTimerNoChanges) self.saveTimerNoChanges.timeout.connect(self.saveDatas) self.saveTimerNoChanges.stop() # UI for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuMode, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(True) # FIXME: set Window's name: project name # Stuff # self.checkPersosID() # Should'n be necessary any longer self.currentProject = project QSettings().setValue("lastProject", project) # Show main Window self.stack.setCurrentIndex(1) def closeProject(self): # Save datas self.saveDatas() self.currentProject = None QSettings().setValue("lastProject", "") # FIXME: close all opened tabs in mainEditor # Clear datas self.loadEmptyDatas() self.saveTimer.stop() # UI for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuMode, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) # Reload recent files self.welcome.updateValues() # Show welcome dialog self.stack.setCurrentIndex(0) def readSettings(self): # Load State and geometry sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) if sttgns.contains("geometry"): self.restoreGeometry(sttgns.value("geometry")) if sttgns.contains("windowState"): self.restoreState(sttgns.value("windowState")) else: self.dckCheatSheet.hide() self.dckSearch.hide() if sttgns.contains("metadataState"): state = [False if v == "false" else True for v in sttgns.value("metadataState")] self.redacMetadata.restoreState(state) if sttgns.contains("revisionsState"): state = [False if v == "false" else True for v in sttgns.value("revisionsState")] self.redacMetadata.revisions.restoreState(state) if sttgns.contains("splitterRedacH"): self.splitterRedacH.restoreState(sttgns.value("splitterRedacH")) if sttgns.contains("splitterRedacV"): self.splitterRedacV.restoreState(sttgns.value("splitterRedacV")) if sttgns.contains("toolbar"): # self.toolbar is not initialized yet, so we just store balue self._toolbarState = sttgns.value("toolbar") else: self._toolbarState = "" def closeEvent(self, event): # Save State and geometry and other things sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) sttgns.setValue("geometry", self.saveGeometry()) sttgns.setValue("windowState", self.saveState()) sttgns.setValue("metadataState", self.redacMetadata.saveState()) sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState()) sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState()) sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState()) sttgns.setValue("toolbar", self.toolbar.saveState()) # Specific settings to save before quitting settings.lastTab = self.tabMain.currentIndex() if self.currentProject: # Remembering the current items sel = [] for i in range(self.mainEditor.tab.count()): sel.append(self.mdlOutline.pathToIndex(self.mainEditor.tab.widget(i).currentIndex)) settings.openIndexes = sel # Save data from models if self.currentProject and settings.saveOnQuit: self.saveDatas() # closeEvent # QMainWindow.closeEvent(self, event) # Causin segfaults? def startTimerNoChanges(self): if settings.autoSaveNoChanges: self.saveTimerNoChanges.start() def saveDatas(self, projectName=None): """Saves the current project (in self.currentProject). If ``projectName`` is given, currentProject becomes projectName. In other words, it "saves as...". """ if projectName: self.currentProject = projectName QSettings().setValue("lastProject", projectName) # Saving files = [] files.append((saveStandardItemModelXML(self.mdlFlatData), "flatModel.xml")) files.append((saveStandardItemModelXML(self.mdlPersos), "perso.xml")) files.append((saveStandardItemModelXML(self.mdlWorld), "world.xml")) files.append((saveStandardItemModelXML(self.mdlLabels), "labels.xml")) files.append((saveStandardItemModelXML(self.mdlStatus), "status.xml")) files.append((saveStandardItemModelXML(self.mdlPlots), "plots.xml")) files.append((self.mdlOutline.saveToXML(), "outline.xml")) files.append((settings.save(), "settings.pickle")) saveFilesToZip(files, self.currentProject) # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) self.statusBar().showMessage( self.tr("Project {} saved.").format(self.currentProject), 5000) def loadEmptyDatas(self): self.mdlFlatData = QStandardItemModel(self) self.mdlPersos = persosModel(self) # self.mdlPersosProxy = persosProxyModel(self) # self.mdlPersosInfos = QStandardItemModel(self) self.mdlLabels = QStandardItemModel(self) self.mdlStatus = QStandardItemModel(self) self.mdlPlots = plotModel(self) self.mdlOutline = outlineModel(self) self.mdlWorld = worldModel(self) def loadDatas(self, project): # Loading files = loadFilesFromZip(project) errors = [] if "flatModel.xml" in files: loadStandardItemModelXML(self.mdlFlatData, files["flatModel.xml"], fromString=True) else: errors.append("flatModel.xml") if "perso.xml" in files: loadStandardItemModelXML(self.mdlPersos, files["perso.xml"], fromString=True) else: errors.append("perso.xml") if "world.xml" in files: loadStandardItemModelXML(self.mdlWorld, files["world.xml"], fromString=True) else: errors.append("world.xml") if "labels.xml" in files: loadStandardItemModelXML(self.mdlLabels, files["labels.xml"], fromString=True) else: errors.append("perso.xml") if "status.xml" in files: loadStandardItemModelXML(self.mdlStatus, files["status.xml"], fromString=True) else: errors.append("perso.xml") if "plots.xml" in files: loadStandardItemModelXML(self.mdlPlots, files["plots.xml"], fromString=True) else: errors.append("perso.xml") if "outline.xml" in files: self.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) else: errors.append("perso.xml") if "settings.pickle" in files: settings.load(files["settings.pickle"], fromString=True) else: errors.append("perso.xml") # Giving some feedback if not errors: print(self.tr("Project {} loaded.").format(project)) self.statusBar().showMessage( self.tr("Project {} loaded.").format(project), 5000) else: print(self.tr("Project {} loaded with some errors:").format(project)) for e in errors: print(self.tr(" * {} wasn't found in project file.").format(e)) self.statusBar().showMessage( self.tr("Project {} loaded with some errors.").format(project), 5000) ############################################################################### # MAIN CONNECTIONS ############################################################################### def makeUIConnections(self): "Connections that have to be made once only, event when new project is loaded." self.lstPersos.currentItemChanged.connect(self.changeCurrentPerso, AUC) self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC) self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC) self.txtSubPlotSummary.document().contentsChanged.connect( self.updateSubPlotSummary, AUC) self.lstSubPlots.activated.connect(self.changeCurrentSubPlot, AUC) self.btnRedacAddFolder.clicked.connect(self.treeRedacOutline.addFolder, AUC) self.btnOutlineAddFolder.clicked.connect(self.treeOutlineOutline.addFolder, AUC) self.btnRedacAddText.clicked.connect(self.treeRedacOutline.addText, AUC) self.btnOutlineAddText.clicked.connect(self.treeOutlineOutline.addText, AUC) self.btnRedacRemoveItem.clicked.connect(self.outlineRemoveItemsRedac, AUC) self.btnOutlineRemoveItem.clicked.connect(self.outlineRemoveItemsOutline, AUC) self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup) def makeConnections(self): # Flat datas (Summary and general infos) for widget, col in [ (self.txtSummarySituation, 0), (self.txtSummarySentence, 1), (self.txtSummarySentence_2, 1), (self.txtSummaryPara, 2), (self.txtSummaryPara_2, 2), (self.txtPlotSummaryPara, 2), (self.txtSummaryPage, 3), (self.txtSummaryPage_2, 3), (self.txtPlotSummaryPage, 3), (self.txtSummaryFull, 4), (self.txtPlotSummaryFull, 4), ]: widget.setModel(self.mdlFlatData) widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(1, col)) for widget, col in [ (self.txtGeneralTitle, 0), (self.txtGeneralSubtitle, 1), (self.txtGeneralSerie, 2), (self.txtGeneralVolume, 3), (self.txtGeneralGenre, 4), (self.txtGeneralLicense, 5), (self.txtGeneralAuthor, 6), (self.txtGeneralEmail, 7), ]: widget.setModel(self.mdlFlatData) widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(0, col)) # Persos self.lstPersos.setPersosModel(self.mdlPersos) self.tblPersoInfos.setModel(self.mdlPersos) self.btnAddPerso.clicked.connect(self.mdlPersos.addPerso, AUC) self.btnRmPerso.clicked.connect(self.mdlPersos.removePerso, AUC) self.btnPersoColor.clicked.connect(self.mdlPersos.chosePersoColor, AUC) self.btnPersoAddInfo.clicked.connect(self.mdlPersos.addPersoInfo, AUC) self.btnPersoRmInfo.clicked.connect(self.mdlPersos.removePersoInfo, AUC) for w, c in [ (self.txtPersoName, Perso.name.value), (self.sldPersoImportance, Perso.importance.value), (self.txtPersoMotivation, Perso.motivation.value), (self.txtPersoGoal, Perso.goal.value), (self.txtPersoConflict, Perso.conflict.value), (self.txtPersoEpiphany, Perso.epiphany.value), (self.txtPersoSummarySentence, Perso.summarySentence.value), (self.txtPersoSummaryPara, Perso.summaryPara.value), (self.txtPersoSummaryFull, Perso.summaryFull.value), (self.txtPersoNotes, Perso.notes.value) ]: w.setModel(self.mdlPersos) w.setColumn(c) self.tabPersos.setEnabled(False) # Plots self.lstPlots.setPlotModel(self.mdlPlots) self.lstPlotPerso.setModel(self.mdlPlots) self.lstSubPlots.setModel(self.mdlPlots) self._updatingSubPlot = False self.btnAddPlot.clicked.connect(self.mdlPlots.addPlot, AUC) self.btnRmPlot.clicked.connect(lambda: self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()), AUC) self.btnAddSubPlot.clicked.connect(self.mdlPlots.addSubPlot, AUC) self.btnRmSubPlot.clicked.connect(self.mdlPlots.removeSubPlot, AUC) self.lstPlotPerso.selectionModel().selectionChanged.connect(self.plotPersoSelectionChanged) self.btnRmPlotPerso.clicked.connect(self.mdlPlots.removePlotPerso, AUC) for w, c in [ (self.txtPlotName, Plot.name.value), (self.txtPlotDescription, Plot.description.value), (self.txtPlotResult, Plot.result.value), (self.sldPlotImportance, Plot.importance.value), ]: w.setModel(self.mdlPlots) w.setColumn(c) self.tabPlot.setEnabled(False) self.mdlPlots.updatePlotPersoButton() self.mdlPersos.dataChanged.connect(self.mdlPlots.updatePlotPersoButton) self.lstOutlinePlots.setPlotModel(self.mdlPlots) self.lstOutlinePlots.setShowSubPlot(True) self.plotPersoDelegate = outlinePersoDelegate(self.mdlPersos, self) self.lstPlotPerso.setItemDelegate(self.plotPersoDelegate) self.plotDelegate = plotDelegate(self) self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate) # World self.treeWorld.setModel(self.mdlWorld) for i in range(self.mdlWorld.columnCount()): self.treeWorld.hideColumn(i) self.treeWorld.showColumn(0) self.btnWorldEmptyData.setMenu(self.mdlWorld.emptyDataMenu()) self.treeWorld.selectionModel().selectionChanged.connect(self.changeCurrentWorld, AUC) self.btnAddWorld.clicked.connect(self.mdlWorld.addItem, AUC) self.btnRmWorld.clicked.connect(self.mdlWorld.removeItem, AUC) for w, c in [ (self.txtWorldName, World.name.value), (self.txtWorldDescription, World.description.value), (self.txtWorldPassion, World.passion.value), (self.txtWorldConflict, World.conflict.value), ]: w.setModel(self.mdlWorld) w.setColumn(c) self.tabWorld.setEnabled(False) self.treeWorld.expandAll() # Outline self.treeRedacOutline.setModel(self.mdlOutline) self.treeOutlineOutline.setModelPersos(self.mdlPersos) self.treeOutlineOutline.setModelLabels(self.mdlLabels) self.treeOutlineOutline.setModelStatus(self.mdlStatus) self.redacMetadata.setModels(self.mdlOutline, self.mdlPersos, self.mdlLabels, self.mdlStatus) self.outlineItemEditor.setModels(self.mdlOutline, self.mdlPersos, self.mdlLabels, self.mdlStatus) self.treeOutlineOutline.setModel(self.mdlOutline) # self.redacEditor.setModel(self.mdlOutline) self.storylineView.setModels(self.mdlOutline, self.mdlPersos, self.mdlPlots) self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda: self.outlineItemEditor.selectionChanged( self.treeOutlineOutline), AUC) self.treeOutlineOutline.clicked.connect(lambda: self.outlineItemEditor.selectionChanged(self.treeOutlineOutline), AUC) # Sync selection self.treeRedacOutline.selectionModel().selectionChanged.connect( lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC) self.treeRedacOutline.clicked.connect( lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC) self.treeRedacOutline.selectionModel().selectionChanged.connect(self.mainEditor.selectionChanged, AUC) # Cheat Sheet self.cheatSheet.setModels() # Debug self.mdlFlatData.setVerticalHeaderLabels(["Infos générales", "Summary"]) self.tblDebugFlatData.setModel(self.mdlFlatData) self.tblDebugPersos.setModel(self.mdlPersos) self.tblDebugPersosInfos.setModel(self.mdlPersos) self.tblDebugPersos.selectionModel().currentChanged.connect( lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlPersos.index( self.tblDebugPersos.selectionModel().currentIndex().row(), Perso.name.value)), AUC) self.tblDebugPlots.setModel(self.mdlPlots) self.tblDebugPlotsPersos.setModel(self.mdlPlots) self.tblDebugSubPlots.setModel(self.mdlPlots) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugPlotsPersos.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), Plot.persos.value)), AUC) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), Plot.subplots.value)), AUC) self.treeDebugWorld.setModel(self.mdlWorld) self.treeDebugOutline.setModel(self.mdlOutline) self.lstDebugLabels.setModel(self.mdlLabels) self.lstDebugStatus.setModel(self.mdlStatus) ############################################################################### # GENERAL AKA UNSORTED ############################################################################### def clickCycle(self, i): if i == 0: # step 2 - paragraph summary self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(1) if i == 1: # step 3 - characters summary self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(0) if i == 2: # step 4 - page summary self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(2) if i == 3: # step 5 - characters description self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(1) if i == 4: # step 6 - four page synopsis self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(3) if i == 5: # step 7 - full character charts self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(2) if i == 6: # step 8 - scene list self.tabMain.setCurrentIndex(self.TabPlots) def wordCount(self, i): src = { 0: self.txtSummarySentence, 1: self.txtSummaryPara, 2: self.txtSummaryPage, 3: self.txtSummaryFull }[i] lbl = { 0: self.lblSummaryWCSentence, 1: self.lblSummaryWCPara, 2: self.lblSummaryWCPage, 3: self.lblSummaryWCFull }[i] wc = wordCount(src.toPlainText()) if i in [2, 3]: pages = self.tr(" (~{} pages)").format(int(wc / 25) / 10.) else: pages = "" lbl.setText(self.tr("Words: {}{}").format(wc, pages)) def setupMoreUi(self): # Tool bar on the right self.toolbar = collapsibleDockWidgets(Qt.RightDockWidgetArea, self) self.toolbar.addCustomWidget(self.tr("Book summary"), self.grpPlotSummary, self.TabPlots) self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac) if self._toolbarState: self.toolbar.restoreState(self._toolbarState) # Custom "tab" bar on the left self.lstTabs.setIconSize(QSize(48, 48)) for i in range(self.tabMain.count()): icons = ["general-128px.png", "summary-128px.png", "characters-128px.png", "plot-128px.png", "world-128px.png", "outline-128px.png", "redaction-128px.png", "" ] self.tabMain.setTabIcon(i, QIcon(appPath("icons/Custom/Tabs/{}".format(icons[i])))) item = QListWidgetItem(self.tabMain.tabIcon(i), self.tabMain.tabText(i)) item.setSizeHint(QSize(item.sizeHint().width(), 64)) item.setTextAlignment(Qt.AlignCenter) self.lstTabs.addItem(item) self.tabMain.tabBar().hide() self.lstTabs.currentRowChanged.connect(self.tabMain.setCurrentIndex) self.tabMain.currentChanged.connect(self.lstTabs.setCurrentRow) # Splitters self.splitterPersos.setStretchFactor(0, 25) self.splitterPersos.setStretchFactor(1, 75) self.splitterPlot.setStretchFactor(0, 20) self.splitterPlot.setStretchFactor(1, 60) self.splitterPlot.setStretchFactor(2, 30) self.splitterWorld.setStretchFactor(0, 25) self.splitterWorld.setStretchFactor(1, 75) self.splitterOutlineH.setStretchFactor(0, 25) self.splitterOutlineH.setStretchFactor(1, 75) self.splitterOutlineV.setStretchFactor(0, 75) self.splitterOutlineV.setStretchFactor(1, 25) self.splitterRedacV.setStretchFactor(0, 75) self.splitterRedacV.setStretchFactor(1, 25) self.splitterRedacH.setStretchFactor(0, 30) self.splitterRedacH.setStretchFactor(1, 40) self.splitterRedacH.setStretchFactor(2, 30) # QFormLayout stretch for w in [self.txtWorldDescription, self.txtWorldPassion, self.txtWorldConflict]: s = w.sizePolicy() s.setVerticalStretch(1) w.setSizePolicy(s) # Help box references = [ (self.lytTabOverview, self.tr("Enter infos about your book, and yourself."), 0), (self.lytSituation, self.tr( """The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous evil wizard could wasn't abled to kill a baby?' (Harry Potter)"""), 1), (self.lytSummary, self.tr( """Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary."""), 1), (self.lytTabPersos, self.tr("Create your characters."), 0), (self.lytTabPlot, self.tr("Develop plots."), 0), (self.lytTabOutline, self.tr("Create the outline of your masterpiece."), 0), (self.lytTabRedac, self.tr("Write."), 0), (self.lytTabDebug, self.tr("Debug infos. Sometimes useful."), 0) ] for widget, text, pos in references: label = helpLabel(text, self) self.actShowHelp.toggled.connect(label.setVisible, AUC) widget.layout().insertWidget(pos, label) self.actShowHelp.setChecked(False) # Spellcheck if enchant: self.menuDict = QMenu(self.tr("Dictionary")) self.menuDictGroup = QActionGroup(self) self.updateMenuDict() self.menuTools.addMenu(self.menuDict) self.actSpellcheck.toggled.connect(self.toggleSpellcheck, AUC) self.dictChanged.connect(self.mainEditor.setDict, AUC) self.dictChanged.connect(self.redacMetadata.setDict, AUC) self.dictChanged.connect(self.outlineItemEditor.setDict, AUC) else: # No Spell check support self.actSpellcheck.setVisible(False) a = QAction(self.tr("Install PyEnchant to use spellcheck"), self) a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning)) a.triggered.connect(self.openPyEnchantWebPage, AUC) self.menuTools.addAction(a) ############################################################################### # SPELLCHECK ############################################################################### def updateMenuDict(self): if not enchant: return self.menuDict.clear() for i in enchant.list_dicts(): a = QAction(str(i[0]), self) a.setCheckable(True) if settings.dict is None: settings.dict = enchant.get_default_language() if str(i[0]) == settings.dict: a.setChecked(True) a.triggered.connect(self.setDictionary, AUC) self.menuDictGroup.addAction(a) self.menuDict.addAction(a) def setDictionary(self): if not enchant: return for i in self.menuDictGroup.actions(): if i.isChecked(): # self.dictChanged.emit(i.text().replace("&", "")) settings.dict = i.text().replace("&", "") # Find all textEditView from self, and toggle spellcheck for w in self.findChildren(textEditView, QRegExp(".*"), Qt.FindChildrenRecursively): w.setDict(settings.dict) def openPyEnchantWebPage(self): from PyQt5.QtGui import QDesktopServices QDesktopServices.openUrl(QUrl("http://pythonhosted.org/pyenchant/")) def toggleSpellcheck(self, val): settings.spellcheck = val # Find all textEditView from self, and toggle spellcheck for w in self.findChildren(textEditView, QRegExp(".*"), Qt.FindChildrenRecursively): w.toggleSpellcheck(val) ############################################################################### # SETTINGS ############################################################################### def settingsLabel(self): self.settingsWindow(3) def settingsStatus(self): self.settingsWindow(4) def settingsWindow(self, tab=None): self.sw = settingsWindow(self) self.sw.hide() self.sw.setWindowModality(Qt.ApplicationModal) self.sw.setWindowFlags(Qt.Dialog) r = self.sw.geometry() r2 = self.geometry() self.sw.move(r2.center() - r.center()) if tab: self.sw.setTab(tab) self.sw.show() ############################################################################### # TOOLS ############################################################################### def frequencyAnalyzer(self): self.fw = frequencyAnalyzer(self) self.fw.show() ############################################################################### # VIEW MENU ############################################################################### def generateViewMenu(self): values = [ (self.tr("Nothing"), "Nothing"), (self.tr("POV"), "POV"), (self.tr("Label"), "Label"), (self.tr("Progress"), "Progress"), (self.tr("Compile"), "Compile"), ] menus = [ (self.tr("Tree"), "Tree"), (self.tr("Index cards"), "Cork"), (self.tr("Outline"), "Outline") ] submenus = { "Tree": [ (self.tr("Icon color"), "Icon"), (self.tr("Text color"), "Text"), (self.tr("Background color"), "Background"), ], "Cork": [ (self.tr("Icon"), "Icon"), (self.tr("Text"), "Text"), (self.tr("Background"), "Background"), (self.tr("Border"), "Border"), (self.tr("Corner"), "Corner"), ], "Outline": [ (self.tr("Icon color"), "Icon"), (self.tr("Text color"), "Text"), (self.tr("Background color"), "Background"), ], } self.menuView.clear() # print("Generating menus with", settings.viewSettings) for mnu, mnud in menus: m = QMenu(mnu, self.menuView) for s, sd in submenus[mnud]: m2 = QMenu(s, m) agp = QActionGroup(m2) for v, vd in values: a = QAction(v, m) a.setCheckable(True) a.setData("{},{},{}".format(mnud, sd, vd)) if settings.viewSettings[mnud][sd] == vd: a.setChecked(True) a.triggered.connect(self.setViewSettingsAction, AUC) agp.addAction(a) m2.addAction(a) m.addMenu(m2) self.menuView.addMenu(m) def setViewSettingsAction(self): action = self.sender() item, part, element = action.data().split(",") self.setViewSettings(item, part, element) def setViewSettings(self, item, part, element): settings.viewSettings[item][part] = element if item == "Cork": self.mainEditor.updateCorkView() if item == "Outline": self.mainEditor.updateTreeView() self.treeOutlineOutline.viewport().update() if item == "Tree": self.treeRedacOutline.viewport().update() ############################################################################### # COMPILE ############################################################################### def doCompile(self): self.compileDialog = compileDialog() self.compileDialog.show()
class AddSeqTool(AbstractPathTool): def __init__(self, controller, parent=None): AbstractPathTool.__init__(self, controller, parent) self.dialog = QDialog() self.buttons = [] self.seqBox = None self.chosenStandardSequence = None # state for tab switching self.customSequenceIsValid = False # state for tab switching self.useCustomSequence = False # for applying sequence self.validatedSequenceToApply = None self.initDialog() def __repr__(self): return "add_seq_tool" # first letter should be lowercase def methodPrefix(self): return "addSeqTool" # first letter should be lowercase def initDialog(self): """ 1. Create buttons according to available scaffold sequences and add them to the dialog. 2. Map the clicked signal of those buttons to keep track of what sequence gets selected. 3. Watch the tabWidget change signal to determine whether a standard or custom sequence should be applied. """ uiDlg = Ui_AddSeqDialog() uiDlg.setupUi(self.dialog) self.signalMapper = QSignalMapper(self) # set up the radio buttons for i, name in enumerate(sorted(sequences.keys())): radioButton = QRadioButton(uiDlg.groupBox) radioButton.setObjectName(name + "Button") radioButton.setText(name) self.buttons.append(radioButton) uiDlg.verticalLayout.addWidget(radioButton) self.signalMapper.setMapping(radioButton, i) radioButton.clicked.connect(self.signalMapper.map) self.signalMapper.mapped.connect(self.standardSequenceChangedSlot) uiDlg.tabWidget.currentChanged.connect(self.tabWidgetChangedSlot) # disable apply until valid option or custom sequence is chosen self.applyButton = uiDlg.customButtonBox.button(QDialogButtonBox.Apply) self.applyButton.setEnabled(False) # watch sequence textedit box to validate custom sequences self.seqBox = uiDlg.seqTextEdit self.seqBox.textChanged.connect(self.validateCustomSequence) self.highlighter = DNAHighlighter(self.seqBox) # finally, pre-click the M13mp18 radio button self.buttons[0].click() buttons = self.buttons self.dialog.setFocusProxy(uiDlg.groupBox) self.dialog.setFocusPolicy(Qt.TabFocus) uiDlg.groupBox.setFocusPolicy(Qt.TabFocus) for i in range(len(buttons)-1): uiDlg.groupBox.setTabOrder(buttons[i], buttons[i+1]) def tabWidgetChangedSlot(self, index): applyEnabled = False if index == 1: # Custom Sequence self.validateCustomSequence() if self.customSequenceIsValid: applyEnabled = True else: # Standard Sequence self.useCustomSequence = False if self.chosenStandardSequence != None: # Overwrite sequence in case custom has been applied activeButton = self.buttons[self.chosenStandardSequence] sequenceName = str(activeButton.text()) self.validatedSequenceToApply = sequences.get(sequenceName, None) applyEnabled = True self.applyButton.setEnabled(applyEnabled) def standardSequenceChangedSlot(self, optionChosen): """ Connected to signalMapper to receive a signal whenever user selects a different sequence in the standard tab. """ sequenceName = str(self.buttons[optionChosen].text()) self.validatedSequenceToApply = sequences.get(sequenceName, None) self.chosenStandardSequence = optionChosen self.applyButton.setEnabled(True) def validateCustomSequence(self): """ Called when: 1. User enters custom sequence (i.e. seqBox emits textChanged signal) 2. tabWidgetChangedSlot sees the user has switched to custom tab. When the sequence is valid, make the applyButton active for clicking. Otherwise """ userSequence = self.seqBox.toPlainText() if len(userSequence) == 0: self.customSequenceIsValid = False return # tabWidgetChangedSlot will disable applyButton if dnapattern.indexIn(userSequence) == -1: # no invalid characters self.useCustomSequence = True self.customSequenceIsValid = True self.applyButton.setEnabled(True) else: self.customSequenceIsValid = False self.applyButton.setEnabled(False) def applySequence(self, oligo): self.dialog.setFocus() if self.dialog.exec_(): # apply the sequence if accept was clicked if self.useCustomSequence: self.validatedSequenceToApply = str(self.seqBox.toPlainText().toUpper()) oligo.applySequence(self.validatedSequenceToApply) return oligo.length(), len(self.validatedSequenceToApply) return (None, None)
class MainWindow(QMainWindow, Ui_MainWindow): """docstring for MainWindow.""" def __init__(self, parent=None): super(MainWindow, self).__init__() self._csvFilePath = "" self.serialport = serial.Serial() self.receiver_thread = readerThread(self) self.receiver_thread.setPort(self.serialport) self._localEcho = None self._viewMode = None self._quickSendOptRow = 1 self.setupUi(self) self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) font = QtGui.QFont() font.setFamily(EDITOR_FONT) font.setPointSize(9) self.txtEdtOutput.setFont(font) self.txtEdtInput.setFont(font) #self.quickSendTable.setFont(font) if UI_FONT is not None: font = QtGui.QFont() font.setFamily(UI_FONT) font.setPointSize(9) self.dockWidget_PortConfig.setFont(font) self.dockWidget_SendHex.setFont(font) self.dockWidget_QuickSend.setFont(font) self.setupMenu() self.setupFlatUi() self.onEnumPorts() icon = QtGui.QIcon(":/MyTerm.ico") self.setWindowIcon(icon) self.actionAbout.setIcon(icon) self.defaultStyleWidget = QWidget() self.defaultStyleWidget.setWindowIcon(icon) icon = QtGui.QIcon(":/qt_logo_16.ico") self.actionAbout_Qt.setIcon(icon) self._viewGroup = QActionGroup(self) self._viewGroup.addAction(self.actionAscii) self._viewGroup.addAction(self.actionHex_lowercase) self._viewGroup.addAction(self.actionHEX_UPPERCASE) self._viewGroup.setExclusive(True) # bind events self.actionOpen_Cmd_File.triggered.connect(self.openQuickSend) self.actionSave_Log.triggered.connect(self.onSaveLog) self.actionExit.triggered.connect(self.onExit) self.actionOpen.triggered.connect(self.openPort) self.actionClose.triggered.connect(self.closePort) self.actionPort_Config_Panel.triggered.connect(self.onTogglePrtCfgPnl) self.actionQuick_Send_Panel.triggered.connect(self.onToggleQckSndPnl) self.actionSend_Hex_Panel.triggered.connect(self.onToggleHexPnl) self.dockWidget_PortConfig.visibilityChanged.connect(self.onVisiblePrtCfgPnl) self.dockWidget_QuickSend.visibilityChanged.connect(self.onVisibleQckSndPnl) self.dockWidget_SendHex.visibilityChanged.connect(self.onVisibleHexPnl) self.actionLocal_Echo.triggered.connect(self.onLocalEcho) self.actionAlways_On_Top.triggered.connect(self.onAlwaysOnTop) self.actionAscii.triggered.connect(self.onViewChanged) self.actionHex_lowercase.triggered.connect(self.onViewChanged) self.actionHEX_UPPERCASE.triggered.connect(self.onViewChanged) self.actionAbout.triggered.connect(self.onAbout) self.actionAbout_Qt.triggered.connect(self.onAboutQt) self.btnOpen.clicked.connect(self.onOpen) self.btnClear.clicked.connect(self.onClear) self.btnSaveLog.clicked.connect(self.onSaveLog) self.btnEnumPorts.clicked.connect(self.onEnumPorts) self.btnSendHex.clicked.connect(self.onSend) self.receiver_thread.read.connect(self.onReceive) self.receiver_thread.exception.connect(self.onReaderExcept) self._signalMapQuickSendOpt = QSignalMapper(self) self._signalMapQuickSendOpt.mapped[int].connect(self.onQuickSendOptions) self._signalMapQuickSend = QSignalMapper(self) self._signalMapQuickSend.mapped[int].connect(self.onQuickSend) # initial action self.actionHEX_UPPERCASE.setChecked(True) self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE) self.initQuickSend() self.restoreLayout() self.moveScreenCenter() self.syncMenu() if self.isMaximized(): self.setMaximizeButton("restore") else: self.setMaximizeButton("maximize") self.loadSettings() def setupMenu(self): self.menuMenu = QtWidgets.QMenu() self.menuMenu.setTitle("&File") self.menuMenu.setObjectName("menuMenu") self.menuView = QtWidgets.QMenu(self.menuMenu) self.menuView.setTitle("&View") self.menuView.setObjectName("menuView") self.menuView.addAction(self.actionAscii) self.menuView.addAction(self.actionHex_lowercase) self.menuView.addAction(self.actionHEX_UPPERCASE) self.menuMenu.addAction(self.actionOpen_Cmd_File) self.menuMenu.addAction(self.actionSave_Log) self.menuMenu.addSeparator() self.menuMenu.addAction(self.actionPort_Config_Panel) self.menuMenu.addAction(self.actionQuick_Send_Panel) self.menuMenu.addAction(self.actionSend_Hex_Panel) self.menuMenu.addAction(self.menuView.menuAction()) self.menuMenu.addAction(self.actionLocal_Echo) self.menuMenu.addAction(self.actionAlways_On_Top) self.menuMenu.addSeparator() self.menuMenu.addAction(self.actionAbout) self.menuMenu.addAction(self.actionAbout_Qt) self.menuMenu.addSeparator() self.menuMenu.addAction(self.actionExit) self.sendOptMenu = QtWidgets.QMenu() self.actionSend_Hex = QtWidgets.QAction(self) self.actionSend_Hex.setText("Send &Hex") self.actionSend_Hex.setStatusTip("Send Hex (e.g. 31 32 FF)") self.actionSend_Asc = QtWidgets.QAction(self) self.actionSend_Asc.setText("Send &Asc") self.actionSend_Asc.setStatusTip("Send Asc (e.g. abc123)") self.actionSend_TFH = QtWidgets.QAction(self) self.actionSend_TFH.setText("Send &Text file as hex") self.actionSend_TFH.setStatusTip('Send text file as hex (e.g. strings "31 32 FF" in the file)') self.actionSend_TFA = QtWidgets.QAction(self) self.actionSend_TFA.setText("Send t&Ext file as asc") self.actionSend_TFA.setStatusTip('Send text file as asc (e.g. strings "abc123" in the file)') self.actionSend_FB = QtWidgets.QAction(self) self.actionSend_FB.setText("Send &Bin/Hex file") self.actionSend_FB.setStatusTip("Send a bin file or a hex file") self.sendOptMenu.addAction(self.actionSend_Hex) self.sendOptMenu.addAction(self.actionSend_Asc) self.sendOptMenu.addAction(self.actionSend_TFH) self.sendOptMenu.addAction(self.actionSend_TFA) self.sendOptMenu.addAction(self.actionSend_FB) self.actionSend_Hex.triggered.connect(self.onSetSendHex) self.actionSend_Asc.triggered.connect(self.onSetSendAsc) self.actionSend_TFH.triggered.connect(self.onSetSendTFH) self.actionSend_TFA.triggered.connect(self.onSetSendTFA) self.actionSend_FB.triggered.connect(self.onSetSendFB) def setupFlatUi(self): self._dragPos = self.pos() self._isDragging = False self.setMouseTracking(True) self.setWindowFlags(Qt.FramelessWindowHint) self.setStyleSheet(""" QWidget { background-color: %(BackgroundColor)s; /*background-image: url(:/background.png);*/ outline: none; } QLabel { color:%(TextColor)s; font-size:12px; /*font-family:Century;*/ } QComboBox { color:%(TextColor)s; font-size:12px; /*font-family:Century;*/ } QComboBox { border: none; padding: 1px 1px 1px 3px; } QComboBox:editable { background: white; } QComboBox:!editable, QComboBox::drop-down:editable { background: #62c7e0; } QComboBox:!editable:hover, QComboBox::drop-down:editable:hover { background: #c7eaf3; } QComboBox:!editable:pressed, QComboBox::drop-down:editable:pressed { background: #35b6d7; } QComboBox:on { padding-top: 3px; padding-left: 4px; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 16px; border: none; } QComboBox::down-arrow { image: url(:/downarrow.png); } QComboBox::down-arrow:on { image: url(:/uparrow.png); } QGroupBox { color:%(TextColor)s; font-size:12px; /*font-family:Century;*/ border: 1px solid gray; margin-top: 15px; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; left:5px; top:3px; } QCheckBox { color:%(TextColor)s; spacing: 5px; font-size:12px; /*font-family:Century;*/ } QCheckBox::indicator:unchecked { image: url(:/checkbox_unchecked.png); } QCheckBox::indicator:unchecked:hover { image: url(:/checkbox_unchecked_hover.png); } QCheckBox::indicator:unchecked:pressed { image: url(:/checkbox_unchecked_pressed.png); } QCheckBox::indicator:checked { image: url(:/checkbox_checked.png); } QCheckBox::indicator:checked:hover { image: url(:/checkbox_checked_hover.png); } QCheckBox::indicator:checked:pressed { image: url(:/checkbox_checked_pressed.png); } QScrollBar:horizontal { background-color:%(BackgroundColor)s; border: none; height: 15px; margin: 0px 20px 0 20px; } QScrollBar::handle:horizontal { background: %(ScrollBar_Handle)s; min-width: 20px; } QScrollBar::add-line:horizontal { image: url(:/rightarrow.png); border: none; background: %(ScrollBar_Line)s; width: 20px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { image: url(:/leftarrow.png); border: none; background: %(ScrollBar_Line)s; width: 20px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar:vertical { background-color:%(BackgroundColor)s; border: none; width: 15px; margin: 20px 0px 20px 0px; } QScrollBar::handle::vertical { background: %(ScrollBar_Handle)s; min-height: 20px; } QScrollBar::add-line::vertical { image: url(:/downarrow.png); border: none; background: %(ScrollBar_Line)s; height: 20px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line::vertical { image: url(:/uparrow.png); border: none; background: %(ScrollBar_Line)s; height: 20px; subcontrol-position: top; subcontrol-origin: margin; } QTableView { background-color: white; /*selection-background-color: #FF92BB;*/ border: 1px solid %(TableView_Border)s; color: %(TextColor)s; } QTableView::focus { /*border: 1px solid #2a7fff;*/ } QTableView QTableCornerButton::section { border: none; border-right: 1px solid %(TableView_Border)s; border-bottom: 1px solid %(TableView_Border)s; background-color: %(TableView_Corner)s; } QTableView QWidget { background-color: white; } QTableView::item:focus { border: 1px red; background-color: transparent; color: %(TextColor)s; } QHeaderView::section { border: none; border-right: 1px solid %(TableView_Border)s; border-bottom: 1px solid %(TableView_Border)s; padding-left: 2px; padding-right: 2px; color: #444444; background-color: %(TableView_Header)s; } QTextEdit { background-color:white; color:%(TextColor)s; border-top: none; border-bottom: none; border-left: 2px solid %(BackgroundColor)s; border-right: 2px solid %(BackgroundColor)s; } QTextEdit::focus { } QToolButton, QPushButton { background-color:#30a7b8; border:none; color:#ffffff; font-size:12px; /*font-family:Century;*/ } QToolButton:hover, QPushButton:hover { background-color:#51c0d1; } QToolButton:pressed, QPushButton:pressed { background-color:#3a9ecc; } QMenuBar { color: %(TextColor)s; height: 24px; } QMenuBar::item { background-color: transparent; margin: 8px 0px 0px 0px; padding: 1px 8px 1px 8px; height: 15px; } QMenuBar::item:selected { background: #51c0d1; } QMenuBar::item:pressed { } /* QMenu { color: %(TextColor)s; background: #ffffff; } QMenu { margin: 2px; } QMenu::item { padding: 2px 25px 2px 21px; border: 1px solid transparent; } QMenu::item:selected { background: #51c0d1; } QMenu::icon { background: transparent; border: 2px inset transparent; }*/ QDockWidget { font-size:12px; /*font-family:Century;*/ color: %(TextColor)s; titlebar-close-icon: none; titlebar-normal-icon: none; } QDockWidget::title { margin: 0; padding: 2px; subcontrol-origin: content; subcontrol-position: right top; text-align: left; background: #67baed; } QDockWidget::float-button { max-width: 12px; max-height: 12px; background-color:transparent; border:none; image: url(:/restore_inactive.png); } QDockWidget::float-button:hover { background-color:#227582; image: url(:/restore_active.png); } QDockWidget::float-button:pressed { padding: 0; background-color:#14464e; image: url(:/restore_active.png); } QDockWidget::close-button { max-width: 12px; max-height: 12px; background-color:transparent; border:none; image: url(:/close_inactive.png); } QDockWidget::close-button:hover { background-color:#ea5e00; image: url(:/close_active.png); } QDockWidget::close-button:pressed { background-color:#994005; image: url(:/close_active.png); padding: 0; } """ % dict( BackgroundColor = '#99d9ea', TextColor = '#202020', ScrollBar_Handle = '#61b9e1', ScrollBar_Line = '#7ecfe4', TableView_Corner = '#8ae6d2', TableView_Header = '#8ae6d2', TableView_Border = '#eeeeee' )) self.dockWidgetContents.setStyleSheet(""" QPushButton { min-height:23px; } """) self.dockWidget_QuickSend.setStyleSheet(""" QToolButton, QPushButton { background-color:#27b798; /*font-family:Consolas;*/ /*font-size:12px;*/ /*min-width:46px;*/ } QToolButton:hover, QPushButton:hover { background-color:#3bd5b4; } QToolButton:pressed, QPushButton:pressed { background-color:#1d8770; } """) self.dockWidgetContents_2.setStyleSheet(""" QPushButton { min-height:23px; min-width:50px; } """) w = self.frameGeometry().width() self._minBtn = QPushButton(self) self._minBtn.setGeometry(w-103,0,28,24) self._minBtn.clicked.connect(self.onMinimize) self._minBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/minimize_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/minimize_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/minimize_active.png); } """) self._maxBtn = QPushButton(self) self._maxBtn.setGeometry(w-74,0,28,24) self._maxBtn.clicked.connect(self.onMaximize) self.setMaximizeButton("maximize") self._closeBtn = QPushButton(self) self._closeBtn.setGeometry(w-45,0,36,24) self._closeBtn.clicked.connect(self.onExit) self._closeBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/close_inactive.png); } QPushButton:hover { background-color:#ea5e00; image: url(:/close_active.png); } QPushButton:pressed { background-color:#994005; image: url(:/close_active.png); } """) self.btnMenu = QtWidgets.QToolButton(self) self.btnMenu.setEnabled(True) self.btnMenu.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) self.btnMenu.setIcon(QtGui.QIcon(':/MyTerm.ico')) self.btnMenu.setText('Myterm ') self.btnMenu.setGeometry(3,3,80,18) self.btnMenu.setMenu(self.menuMenu) self.btnMenu.setPopupMode(QtWidgets.QToolButton.InstantPopup) self.btnRefresh = QtWidgets.QToolButton(self) self.btnRefresh.setEnabled(True) self.btnRefresh.setIcon(QtGui.QIcon(':/refresh.ico')) self.btnRefresh.setGeometry(110,3,18,18) self.btnRefresh.clicked.connect(self.onEnumPorts) self.verticalLayout_1.removeWidget(self.cmbPort) self.cmbPort.setParent(self) self.cmbPort.setGeometry(128,3,60,18) self.verticalLayout_1.removeWidget(self.btnOpen) self.btnOpen.setParent(self) self.btnOpen.setGeometry(210,3,60,18) def resizeEvent(self, event): w = event.size().width() self._minBtn.move(w-103,0) self._maxBtn.move(w-74,0) self._closeBtn.move(w-45,0) def onMinimize(self): self.showMinimized() def isMaximized(self): return ((self.windowState() == Qt.WindowMaximized)) def onMaximize(self): if self.isMaximized(): self.showNormal() self.setMaximizeButton("maximize") else: self.showMaximized() self.setMaximizeButton("restore") def setMaximizeButton(self, style): if "maximize" == style: self._maxBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/maximize_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/maximize_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/maximize_active.png); } """) elif "restore" == style: self._maxBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/restore_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/restore_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/restore_active.png); } """) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._isDragging = True self._dragPos = event.globalPos() - self.pos() event.accept() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton and self._isDragging and not self.isMaximized(): self.move(event.globalPos() - self._dragPos) event.accept() def mouseReleaseEvent(self, event): self._isDragging = False event.accept() def saveSettings(self): root = ET.Element("MyTerm") GUISettings = ET.SubElement(root, "GUISettings") PortCfg = ET.SubElement(GUISettings, "PortConfig") ET.SubElement(PortCfg, "port").text = self.cmbPort.currentText() ET.SubElement(PortCfg, "baudrate").text = self.cmbBaudRate.currentText() ET.SubElement(PortCfg, "databits").text = self.cmbDataBits.currentText() ET.SubElement(PortCfg, "parity").text = self.cmbParity.currentText() ET.SubElement(PortCfg, "stopbits").text = self.cmbStopBits.currentText() ET.SubElement(PortCfg, "rtscts").text = self.chkRTSCTS.isChecked() and "on" or "off" ET.SubElement(PortCfg, "xonxoff").text = self.chkXonXoff.isChecked() and "on" or "off" View = ET.SubElement(GUISettings, "View") ET.SubElement(View, "LocalEcho").text = self.actionLocal_Echo.isChecked() and "on" or "off" ET.SubElement(View, "ReceiveView").text = self._viewGroup.checkedAction().text() with open(get_config_path('MyTerm.xml'), 'w') as f: f.write('<?xml version="1.0" encoding="UTF-8"?>\n') f.write(ET.tostring(root, encoding='utf-8', pretty_print=True).decode("utf-8")) def loadSettings(self): if os.path.isfile(get_config_path("MyTerm.xml")): with open(get_config_path("MyTerm.xml"), 'r') as f: tree = safeET.parse(f) port = tree.findtext('GUISettings/PortConfig/port', default='') if port != '': self.cmbPort.setCurrentText(port) baudrate = tree.findtext('GUISettings/PortConfig/baudrate', default='38400') if baudrate != '': self.cmbBaudRate.setCurrentText(baudrate) databits = tree.findtext('GUISettings/PortConfig/databits', default='8') id = self.cmbDataBits.findText(databits) if id >= 0: self.cmbDataBits.setCurrentIndex(id) parity = tree.findtext('GUISettings/PortConfig/parity', default='None') id = self.cmbParity.findText(parity) if id >= 0: self.cmbParity.setCurrentIndex(id) stopbits = tree.findtext('GUISettings/PortConfig/stopbits', default='1') id = self.cmbStopBits.findText(stopbits) if id >= 0: self.cmbStopBits.setCurrentIndex(id) rtscts = tree.findtext('GUISettings/PortConfig/rtscts', default='off') if 'on' == rtscts: self.chkRTSCTS.setChecked(True) else: self.chkRTSCTS.setChecked(False) xonxoff = tree.findtext('GUISettings/PortConfig/xonxoff', default='off') if 'on' == xonxoff: self.chkXonXoff.setChecked(True) else: self.chkXonXoff.setChecked(False) LocalEcho = tree.findtext('GUISettings/View/LocalEcho', default='off') if 'on' == LocalEcho: self.actionLocal_Echo.setChecked(True) self._localEcho = True else: self.actionLocal_Echo.setChecked(False) self._localEcho = False ReceiveView = tree.findtext('GUISettings/View/ReceiveView', default='HEX(UPPERCASE)') if 'Ascii' in ReceiveView: self.actionAscii.setChecked(True) self._viewMode = VIEWMODE_ASCII elif 'lowercase' in ReceiveView: self.actionHex_lowercase.setChecked(True) self._viewMode = VIEWMODE_HEX_LOWERCASE elif 'UPPERCASE' in ReceiveView: self.actionHEX_UPPERCASE.setChecked(True) self._viewMode = VIEWMODE_HEX_UPPERCASE self.receiver_thread.setViewMode(self._viewMode) def closeEvent(self, event): self.saveLayout() self.saveQuickSend() self.saveSettings() event.accept() def initQuickSend(self): #self.quickSendTable.horizontalHeader().setDefaultSectionSize(40) #self.quickSendTable.horizontalHeader().setMinimumSectionSize(25) self.quickSendTable.setRowCount(50) self.quickSendTable.setColumnCount(3) self.quickSendTable.verticalHeader().setSectionsClickable(True) for row in range(50): self.initQuickSendButton(row) if os.path.isfile(get_config_path('QuickSend.csv')): self.loadQuickSend(get_config_path('QuickSend.csv')) self.quickSendTable.resizeColumnsToContents() def initQuickSendButton(self, row, cmd = 'cmd', opt = 'H', dat = ''): if self.quickSendTable.cellWidget(row, 0) is None: item = QToolButton(self) item.setText(cmd) item.clicked.connect(self._signalMapQuickSend.map) self._signalMapQuickSend.setMapping(item, row) self.quickSendTable.setCellWidget(row, 0, item) else: self.quickSendTable.cellWidget(row, 0).setText(cmd) if self.quickSendTable.cellWidget(row, 1) is None: item = QToolButton(self) item.setText(opt) #item.setMaximumSize(QtCore.QSize(16, 16)) item.clicked.connect(self._signalMapQuickSendOpt.map) self._signalMapQuickSendOpt.setMapping(item, row) self.quickSendTable.setCellWidget(row, 1, item) else: self.quickSendTable.cellWidget(row, 1).setText(opt) if self.quickSendTable.item(row, 2) is None: self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat)) else: self.quickSendTable.item(row, 2).setText(dat) self.quickSendTable.setRowHeight(row, 16) def onSetSendHex(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('H') def onSetSendAsc(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('A') def onSetSendTFH(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('FH') def onSetSendTFA(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('FA') def onSetSendFB(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('FB') def onQuickSendOptions(self, row): self._quickSendOptRow = row item = self.quickSendTable.cellWidget(row, 1) self.sendOptMenu.popup(item.mapToGlobal(QPoint(item.size().width(), item.size().height()))) def openQuickSend(self): fileName = QFileDialog.getOpenFileName(self, "Select a file", os.getcwd(), "CSV Files (*.csv)")[0] if fileName: self.loadQuickSend(fileName, notifyExcept = True) def saveQuickSend(self): # scan table rows = self.quickSendTable.rowCount() #cols = self.quickSendTable.columnCount() save_data = [[self.quickSendTable.cellWidget(row, 0).text(), self.quickSendTable.cellWidget(row, 1).text(), self.quickSendTable.item(row, 2) is not None and self.quickSendTable.item(row, 2).text() or ''] for row in range(rows)] #import pprint #pprint.pprint(save_data, width=120, compact=True) # write to file with open(get_config_path('QuickSend.csv'), 'w') as csvfile: csvwriter = csv.writer(csvfile, delimiter=',', lineterminator='\n') csvwriter.writerows(save_data) def loadQuickSend(self, path, notifyExcept = False): data = [] set_rows = 0 set_cols = 0 try: with open(path) as csvfile: csvData = csv.reader(csvfile) for row in csvData: data.append(row) set_rows = set_rows + 1 if len(row) > set_cols: set_cols = len(row) except IOError as e: print("({})".format(e)) if notifyExcept: QMessageBox.critical(self.defaultStyleWidget, "Open failed", str(e), QMessageBox.Close) return rows = self.quickSendTable.rowCount() cols = self.quickSendTable.columnCount() if rows < set_rows: rows = set_rows + 10 self.quickSendTable.setRowCount(rows) for row, rowdat in enumerate(data): if len(rowdat) >= 3: cmd, opt, dat = rowdat[0:3] self.initQuickSendButton(row, cmd, opt, dat) # self.quickSendTable.cellWidget(row, 0).setText(cmd) # self.quickSendTable.cellWidget(row, 1).setText(opt) # self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat)) self.quickSendTable.resizeColumnsToContents() #self.quickSendTable.resizeRowsToContents() def onQuickSend(self, row): if self.quickSendTable.item(row, 2) is not None: tablestring = self.quickSendTable.item(row, 2).text() format = self.quickSendTable.cellWidget(row, 1).text() if 'H' == format: self.transmitHex(tablestring) elif 'A' == format: self.transmitAsc(tablestring) elif 'FB' == format: try: with open(tablestring, 'rb') as f: bytes = f.read() self.transmitBytearray(bytes) except IOError as e: print("({})".format(e)) QMessageBox.critical(self.defaultStyleWidget, "Open failed", str(e), QMessageBox.Close) else: try: with open(tablestring, 'rt') as f: filestring = f.read() if 'FH' == format: self.transmitHex(filestring) elif 'FA' == format: self.transmitAsc(filestring) except IOError as e: print("({})".format(e)) QMessageBox.critical(self.defaultStyleWidget, "Open failed", str(e), QMessageBox.Close) def onSend(self): sendstring = self.txtEdtInput.toPlainText() self.transmitHex(sendstring) def transmitHex(self, hexstring): if len(hexstring) > 0: hexarray = [] _hexstring = hexstring.replace(' ', '') _hexstring = _hexstring.replace('\r', '') _hexstring = _hexstring.replace('\n', '') for i in range(0, len(_hexstring), 2): word = _hexstring[i:i+2] if is_hex(word): hexarray.append(int(word, 16)) else: QMessageBox.critical(self.defaultStyleWidget, "Error", "'%s' is not hexadecimal." % (word), QMessageBox.Close) return self.transmitBytearray(bytearray(hexarray)) def transmitAsc(self, text): if len(text) > 0: byteArray = [ord(char) for char in text] self.transmitBytearray(bytearray(byteArray)) def transmitBytearray(self, byteArray): if self.serialport.isOpen(): try: self.serialport.write(byteArray) except Exception as e: QMessageBox.critical(self.defaultStyleWidget, "Exception in transmit", str(e), QMessageBox.Close) print("Exception in transmitBytearray(%s)" % text) else: if self._viewMode == VIEWMODE_ASCII: text = byteArray.decode('unicode_escape') elif self._viewMode == VIEWMODE_HEX_LOWERCASE: text = ''.join('%02x ' % t for t in byteArray) elif self._viewMode == VIEWMODE_HEX_UPPERCASE: text = ''.join('%02X ' % t for t in byteArray) self.appendOutputText("\n%s T->:%s" % (self.timestamp(), text), Qt.blue) def onReaderExcept(self, e): self.closePort() QMessageBox.critical(self.defaultStyleWidget, "Read failed", str(e), QMessageBox.Close) def timestamp(self): return datetime.datetime.now().time().isoformat()[:-3] def onReceive(self, data): self.appendOutputText("\n%s R<-:%s" % (self.timestamp(), data)) def appendOutputText(self, data, color=Qt.black): # the qEditText's "append" methon will add a unnecessary newline. # self.txtEdtOutput.append(data.decode('utf-8')) tc=self.txtEdtOutput.textColor() self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End) self.txtEdtOutput.setTextColor(QtGui.QColor(color)) self.txtEdtOutput.insertPlainText(data) self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End) self.txtEdtOutput.setTextColor(tc) def getPort(self): return self.cmbPort.currentText() def getDataBits(self): return {'5':serial.FIVEBITS, '6':serial.SIXBITS, '7':serial.SEVENBITS, '8':serial.EIGHTBITS}[self.cmbDataBits.currentText()] def getParity(self): return {'None' :serial.PARITY_NONE, 'Even' :serial.PARITY_EVEN, 'Odd' :serial.PARITY_ODD, 'Mark' :serial.PARITY_MARK, 'Space':serial.PARITY_SPACE}[self.cmbParity.currentText()] def getStopBits(self): return {'1' :serial.STOPBITS_ONE, '1.5':serial.STOPBITS_ONE_POINT_FIVE, '2' :serial.STOPBITS_TWO}[self.cmbStopBits.currentText()] def openPort(self): if self.serialport.isOpen(): return _port = self.getPort() if '' == _port: QMessageBox.information(self.defaultStyleWidget, "Invalid parameters", "Port is empty.") return _baudrate = self.cmbBaudRate.currentText() if '' == _baudrate: QMessageBox.information(self.defaultStyleWidget, "Invalid parameters", "Baudrate is empty.") return self.serialport.port = _port self.serialport.baudrate = _baudrate self.serialport.bytesize = self.getDataBits() self.serialport.stopbits = self.getStopBits() self.serialport.parity = self.getParity() self.serialport.rtscts = self.chkRTSCTS.isChecked() self.serialport.xonxoff = self.chkXonXoff.isChecked() # self.serialport.timeout = THREAD_TIMEOUT # self.serialport.writeTimeout = SERIAL_WRITE_TIMEOUT try: self.serialport.open() except Exception as e: QMessageBox.critical(self.defaultStyleWidget, "Could not open serial port", str(e), QMessageBox.Close) else: self._start_reader() self.setWindowTitle("%s on %s [%s, %s%s%s%s%s]" % ( appInfo.title, self.serialport.portstr, self.serialport.baudrate, self.serialport.bytesize, self.serialport.parity, self.serialport.stopbits, self.serialport.rtscts and ' RTS/CTS' or '', self.serialport.xonxoff and ' Xon/Xoff' or '', ) ) pal = self.btnOpen.palette() pal.setColor(QtGui.QPalette.Button, QtGui.QColor(0,0xff,0x7f)) self.btnOpen.setAutoFillBackground(True) self.btnOpen.setPalette(pal) self.btnOpen.setText('Close') self.btnOpen.update() def closePort(self): if self.serialport.isOpen(): self._stop_reader() self.serialport.close() self.setWindowTitle(appInfo.title) pal = self.btnOpen.style().standardPalette() self.btnOpen.setAutoFillBackground(True) self.btnOpen.setPalette(pal) self.btnOpen.setText('Open') self.btnOpen.update() def _start_reader(self): """Start reader thread""" self.receiver_thread.start() def _stop_reader(self): """Stop reader thread only, wait for clean exit of thread""" self.receiver_thread.join() def onTogglePrtCfgPnl(self): if self.actionPort_Config_Panel.isChecked(): self.dockWidget_PortConfig.show() else: self.dockWidget_PortConfig.hide() def onToggleQckSndPnl(self): if self.actionQuick_Send_Panel.isChecked(): self.dockWidget_QuickSend.show() else: self.dockWidget_QuickSend.hide() def onToggleHexPnl(self): if self.actionSend_Hex_Panel.isChecked(): self.dockWidget_SendHex.show() else: self.dockWidget_SendHex.hide() def onVisiblePrtCfgPnl(self, visible): self.actionPort_Config_Panel.setChecked(visible) def onVisibleQckSndPnl(self, visible): self.actionQuick_Send_Panel.setChecked(visible) def onVisibleHexPnl(self, visible): self.actionSend_Hex_Panel.setChecked(visible) def onLocalEcho(self): self._localEcho = self.actionLocal_Echo.isChecked() def onAlwaysOnTop(self): if self.actionAlways_On_Top.isChecked(): style = self.windowFlags() self.setWindowFlags(style|Qt.WindowStaysOnTopHint) self.show() else: style = self.windowFlags() self.setWindowFlags(style & ~Qt.WindowStaysOnTopHint) self.show() def onOpen(self): if self.serialport.isOpen(): self.closePort() else: self.openPort() def onClear(self): self.txtEdtOutput.clear() def onSaveLog(self): fileName = QFileDialog.getSaveFileName(self, "Save as", os.getcwd(), "Log files (*.log);;Text files (*.txt);;All files (*.*)")[0] if fileName: import codecs with codecs.open(fileName, 'w', 'utf-8') as f: f.write(self.txtEdtOutput.toPlainText()) def moveScreenCenter(self): w = self.frameGeometry().width() h = self.frameGeometry().height() desktop = QDesktopWidget() screenW = desktop.screen().width() screenH = desktop.screen().height() self.setGeometry((screenW-w)/2, (screenH-h)/2, w, h) def onEnumPorts(self): self.cmbPort.clear() for p in enum_ports(): self.cmbPort.addItem(p) def onAbout(self): QMessageBox.about(self.defaultStyleWidget, "About MyTerm", appInfo.aboutme) def onAboutQt(self): QMessageBox.aboutQt(self.defaultStyleWidget) def onExit(self): if self.serialport.isOpen(): self.closePort() self.close() def restoreLayout(self): if os.path.isfile(get_config_path("UILayout.dat")): try: f=open(get_config_path("UILayout.dat"), 'rb') geometry, state=pickle.load(f) self.restoreGeometry(geometry) self.restoreState(state) except Exception as e: print("Exception on restoreLayout, {}".format(e)) else: try: f=QFile(':/default_layout_qt5.dat') f.open(QIODevice.ReadOnly) geometry, state=pickle.loads(f.readAll()) self.restoreGeometry(geometry) self.restoreState(state) except Exception as e: print("Exception on restoreLayout, {}".format(e)) def saveLayout(self): with open(get_config_path("UILayout.dat"), 'wb') as f: pickle.dump((self.saveGeometry(), self.saveState()), f) def syncMenu(self): self.actionPort_Config_Panel.setChecked(not self.dockWidget_PortConfig.isHidden()) self.actionQuick_Send_Panel.setChecked(not self.dockWidget_QuickSend.isHidden()) self.actionSend_Hex_Panel.setChecked(not self.dockWidget_SendHex.isHidden()) def onViewChanged(self): checked = self._viewGroup.checkedAction() if checked is None: self._viewMode = VIEWMODE_HEX_UPPERCASE self.actionHEX_UPPERCASE.setChecked(True) else: if 'Ascii' in checked.text(): self._viewMode = VIEWMODE_ASCII elif 'lowercase' in checked.text(): self._viewMode = VIEWMODE_HEX_LOWERCASE elif 'UPPERCASE' in checked.text(): self._viewMode = VIEWMODE_HEX_UPPERCASE self.receiver_thread.setViewMode(self._viewMode)
def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Add / remove items self.actAddFolder = QAction(QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) menu.addSeparator() # Copy, cut, paste self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "Cut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlPersos.rowCount()): a = QAction(mw.mdlPersos.icon(i), mw.mdlPersos.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlPersos.ID(i))) imp = toInt(mw.mdlPersos.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction(mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder() \ or not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder(): self.actAddFolder.setEnabled(False) self.actAddText.setEnabled(False) if len(sel) == 0: self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) return menu
class NumPad(QDialog, Ui_numPad): """Numpad for user input. Enables the user to insert numbers on a touchscreen. """ def __init__(self, parent: QWidget=None, text: str="") -> None: super(NumPad, self).__init__(parent) self.setupUi(self) # type: ignore try: suffixIndex = text.index(" ") except ValueError: self.outputLineEdit.setText(text) else: self.outputLineEdit.setText(text[:suffixIndex]) self.outputLineEdit.setSelection(0, len(self.outputLineEdit.text())) self.outputLineEdit.setFocus() self.signal_mapper = QSignalMapper(self) self.signal_mapper.setMapping(self.button0, "0") self.signal_mapper.setMapping(self.button1, "1") self.signal_mapper.setMapping(self.button2, "2") self.signal_mapper.setMapping(self.button3, "3") self.signal_mapper.setMapping(self.button4, "4") self.signal_mapper.setMapping(self.button5, "5") self.signal_mapper.setMapping(self.button6, "6") self.signal_mapper.setMapping(self.button7, "7") self.signal_mapper.setMapping(self.button8, "8") self.signal_mapper.setMapping(self.button9, "9") self.signal_mapper.setMapping(self.buttonDecimal, DECIMAL_SEPARATOR) self.signal_mapper.setMapping(self.buttonDel, "DEL") self.button0.pressed.connect(self.signal_mapper.map) self.button1.pressed.connect(self.signal_mapper.map) self.button2.pressed.connect(self.signal_mapper.map) self.button3.pressed.connect(self.signal_mapper.map) self.button4.pressed.connect(self.signal_mapper.map) self.button5.pressed.connect(self.signal_mapper.map) self.button6.pressed.connect(self.signal_mapper.map) self.button7.pressed.connect(self.signal_mapper.map) self.button8.pressed.connect(self.signal_mapper.map) self.button9.pressed.connect(self.signal_mapper.map) self.buttonDecimal.pressed.connect(self.signal_mapper.map) self.buttonDel.pressed.connect(self.signal_mapper.map) self.signal_mapper.mapped[str].connect(self.button_pressed) # type: ignore self.buttonOK.pressed.connect(self.accept) self.buttonCancel.pressed.connect(self.close) self.outputLineEdit.focusOutEvent = self.focusOutEvent doubleValidator = QDoubleValidator() self.outputLineEdit.setValidator(doubleValidator) @pyqtSlot(str) def button_pressed(self, value: str) -> None: """Handle a pressed button.""" cursor_position = self.outputLineEdit.cursorPosition() # Bei markierten Zeichen diese löschen if len(self.outputLineEdit.selectedText()) > 0: cursor_position = self.outputLineEdit.selectionStart() selection_length = len(self.outputLineEdit.selectedText()) self.outputLineEdit.setText( string_remove_by_index( self.outputLineEdit.text(), start_index=cursor_position, length=selection_length ) ) # Bei DEL letztes Zeichen löschen elif value == "DEL": # self.outputLineEdit.setText(self.outputLineEdit.text().remove( # self.outputLineEdit.cursorPosition() - 1, 1)) self.outputLineEdit.setText( string_remove_by_index( self.outputLineEdit.text(), start_index=self.outputLineEdit.cursorPosition() - 1, length=1) ) # Wenn nicht DEL, dann Zeichen schreiben if not value == "DEL": if value == DECIMAL_SEPARATOR: if DECIMAL_SEPARATOR not in self.outputLineEdit.text(): self.outputLineEdit.setText( string_insert(self.outputLineEdit.text(), str(value), cursor_position) ) else: self.outputLineEdit.setText( string_insert(self.outputLineEdit.text(), str(value), cursor_position) )
class DataLoader(QWidget): dataset_changed = pyqtSignal() new_file_in_dataset = pyqtSignal(str) deembedding_changed = pyqtSignal() dut_folder = None dut_files = None dummy_raw = None dummy_deem = None dummy = None dummy_file = None dummy_toggle_status = True thru = None thru_file = None thru_toggle_status = True ra = None duts = {} def __init__(self, parent=None): super(QWidget, self).__init__(parent) file_icon = QIcon('../icons/file.png') folder_icon = QIcon('../icons/folder.png') plot_icon = QIcon('../icons/plot.png') self.toggleon_icon = QIcon('../icons/on.png') self.toggleoff_icon = QIcon('../icons/off.png') self.txt_dut = QLineEdit() self.btn_browsedut_file = QPushButton(file_icon, '') self.btn_browsedut_folder = QPushButton(folder_icon, '') self.txt_thru = QLineEdit() self.btn_browsethru_file = QPushButton(file_icon, '') self.btn_browsethru_folder = QPushButton(folder_icon, '') self.btn_togglethru = QPushButton(self.toggleon_icon, '') self.btn_plotthru = QPushButton(plot_icon, '') self.txt_dummy = QLineEdit() self.btn_browsedummy_file = QPushButton(file_icon, '') self.btn_browsedummy_folder = QPushButton(folder_icon, '') self.btn_toggledummy = QPushButton(self.toggleon_icon, '') self.btn_plotdummy = QPushButton(plot_icon, '') for w in [ self.btn_browsedut_file, self.btn_browsethru_file, self.btn_browsedummy_file ]: w.setToolTip('Browse file') for w in [ self.btn_browsedut_folder, self.btn_browsethru_folder, self.btn_browsedummy_folder ]: w.setToolTip('Browse folder') for w in [self.btn_togglethru, self.btn_toggledummy]: w.setEnabled(False) for w in [self.btn_plotthru, self.btn_plotdummy]: w.setToolTip('Plot') w.setEnabled(False) self.btn_load = QPushButton('Load dataset') self.txt_ra = QLineEdit() l = QVBoxLayout() for field in [[ QLabel('DUT:'), self.txt_dut, self.btn_browsedut_file, self.btn_browsedut_folder ], [ QLabel('Thru:'), self.txt_thru, self.btn_browsethru_file, self.btn_browsethru_folder, self.btn_togglethru, self.btn_plotthru ], [ QLabel('Dummy:'), self.txt_dummy, self.btn_browsedummy_file, self.btn_browsedummy_folder, self.btn_toggledummy, self.btn_plotdummy ]]: hl = QHBoxLayout() for w in field: hl.addWidget(w) l.addLayout(hl) hl = QHBoxLayout() for w in [QLabel('Contact resistance:'), self.txt_ra, self.btn_load]: hl.addWidget(w) l.addLayout(hl) self.setLayout(l) # initialise data loader self.clear() # set up folder watcher self.timer = QTimer() self.timer.setInterval(1000) # check for changes every second self.timer.timeout.connect(self.watch_folder) # make connections self.map_browse = QSignalMapper(self) for x in ['dut', 'thru', 'dummy']: self.__dict__['btn_browse' + x + '_folder'].clicked.connect( self.map_browse.map) self.__dict__['btn_browse' + x + '_file'].clicked.connect( self.map_browse.map) self.map_browse.setMapping( self.__dict__['btn_browse' + x + '_folder'], x + '_folder') self.map_browse.setMapping( self.__dict__['btn_browse' + x + '_file'], x + '_file') self.map_browse.mapped[str].connect(self.browse) self.btn_togglethru.clicked.connect(self.toggle_thru) self.btn_toggledummy.clicked.connect(self.toggle_dummy) self.btn_plotdummy.clicked.connect(self.plot_dummy) self.btn_plotthru.clicked.connect(self.plot_thru) self.btn_load.clicked.connect(self.load_dataset) def browse(self, x): # open browser and update the text field field, type = x.split('_') if type == 'folder': folder = QFileDialog.getExistingDirectory(self, 'Choose folder') if folder: self.__dict__['txt_' + field].setText(folder) elif type == 'file': filename, filter = QFileDialog.getOpenFileName( self, 'Choose file', filter='*.txt *.s2p *.dat') if filename: self.__dict__['txt_' + field].setText(filename) def get_spectra_files(self, path, tellmeifitsafolder=False): supported_exts = ['.txt', '.s2p', '.dat'] folder = None files = [] itsafolder = False if os.path.isdir(path): folder = path for ext in supported_exts: files += [ os.path.basename(x) for x in sorted(glob(os.path.join(folder, '*' + ext))) ] itsafolder = True elif os.path.isfile(path): basename, ext = os.path.splitext(path) if ext.lower() in supported_exts: folder = os.path.dirname(path) files += [path] else: pass if tellmeifitsafolder: return folder, files, itsafolder else: return folder, files @pyqtSlot() def load_dataset(self, dut=None, thru=None, dummy=None, ra=None): # This function inspects the provided folders and will try to load the dummy and thru spectra for de-embedding. # It does not load the DUT spectra, since this might take a long time, they can be accessed via the get_spectrum # function. # tidy up first self.empty_cache() self.dut_folder = None self.dut_files = None self.thru_file = None self.thru = None self.dummy_file = None self.dummy_raw = None self.dummy_deem = None self.dummy = None self.btn_toggledummy.setEnabled(False) self.btn_togglethru.setEnabled(False) self.btn_plotdummy.setEnabled(False) self.btn_plotthru.setEnabled(False) # take what you can from the function parameters, the rest from the text fields dut = str(self.txt_dut.text()) if not dut else dut thru = str(self.txt_thru.text()) if not thru else thru dummy = str(self.txt_dummy.text()) if not dummy else dummy ra = str(self.txt_ra.text()) if not ra else ra # update the text fields self.txt_dut.setText(dut) self.txt_thru.setText(thru) self.txt_dummy.setText(dummy) self.txt_ra.setText(str(ra) if ra else '0') # check provided files self.dut_folder, self.dut_files, itsafolder = self.get_spectra_files( dut, tellmeifitsafolder=True) if not self.dut_files: QMessageBox.warning(self, 'Warning', 'Please select a valid DUT folder or file') # if the user loaded a folder, switch on the folder watcher if itsafolder: self.timer.start() else: self.timer.stop() thru_folder, thru_files = self.get_spectra_files(thru) if len(thru_files) != 1: self.txt_thru.setText('Please select a valid thru folder or file') else: self.thru_file = os.path.join(thru_folder, thru_files[0]) dummy_folder, dummy_files = self.get_spectra_files(dummy) if len(dummy_files) != 1: self.txt_dummy.setText( 'Please select a valid dummy folder or file') else: self.dummy_file = os.path.join(dummy_folder, dummy_files[0]) # load the provided files if self.dummy_file: try: self.dummy_raw = Network(self.dummy_file) assert self.dummy_raw.number_of_ports == 2 self.btn_toggledummy.setEnabled(True) self.btn_plotdummy.setEnabled(True) except Exception as e: QMessageBox.warning( self, 'Warning', 'File: ' + self.dummy_file + ' is not a valid 2-port RF spectrum file.') self.dummy_raw = None if self.thru_file: try: self.thru = Network(self.thru_file) assert self.thru.number_of_ports == 2 self.btn_togglethru.setEnabled(True) self.btn_plotthru.setEnabled(True) except Exception as e: QMessageBox.warning( self, 'Warning', 'File: ' + self.thru_file + ' is not a valid 2-port RF spectrum file.') self.thru = None self.dummy_deem = None if self.dummy_raw and self.thru: # check for mHz deviation, since sometimes the frequency value saved is # not 100% equal when importing from different file formats... if not check_deembedding_compatibility(self.dummy_raw, self.thru): QMessageBox.warning(self, 'Warning', 'Dummy and thru are not compatible') self.dummy_raw = None self.thru = None for w in [ self.btn_toggledummy, self.btn_togglethru, self.btn_plotdummy, self.btn_plotthru ]: w.setEnabled(False) else: self.dummy_deem = self.dummy_raw.deembed_thru(self.thru) self.dummy = self.dummy_deem if (self.thru_toggle_status and self.thru) else self.dummy_raw self.dataset_changed.emit() def get_spectrum(self, index): # check if the spectrum is already prepared filename = self.dut_files[index] if filename in self.duts: return self.duts[filename] else: # try to load spectrum and check its compatibility try: dut = Network(os.path.join(self.dut_folder, filename)) except Exception: QMessageBox.warning( self, 'Warning', 'File: ' + filename + ' is not a valid RF spectrum file.') return None if self.thru and self.thru_toggle_status: if check_deembedding_compatibility(dut, self.thru): dut = dut.deembed_thru(self.thru) else: QMessageBox.warning(self, 'Warning', 'Could not deembed thru.') self.thru_toggle_status = False self.btn_togglethru.setIcon(self.toggleoff_icon) if self.dummy and self.dummy_toggle_status: if check_deembedding_compatibility(dut, self.dummy): dut.y -= self.dummy.y else: QMessageBox.warning(self, 'Warning', 'Could not deembed dummy.') self.dummy_toggle_status = False self.btn_toggledummy.setIcon(self.toggleoff_icon) try: ra = float(self.txt_ra.text()) except: QMessageBox.warning( self, 'Warning', 'Invalid value for contact resistance. Using zero.') ra = 0. if not ra == 0: y = np.zeros(dut.y.shape, dtype=complex) y[:, 0, 0] = 1. / (1. / dut.y[:, 0, 0] - ra) y[:, 0, 1] = 1. / (1. / dut.y[:, 0, 1] + ra) y[:, 1, 0] = 1. / (1. / dut.y[:, 1, 0] + ra) y[:, 1, 1] = 1. / (1. / dut.y[:, 1, 1] - ra) dut.y = y self.duts[filename] = dut return dut def empty_cache(self): self.duts = {} # empty the DUT dictionary def toggle_thru(self): self.empty_cache() self.thru_toggle_status = not self.thru_toggle_status self.btn_togglethru.setIcon( self.toggleon_icon if self.thru_toggle_status else self. toggleoff_icon) self.dummy = self.dummy_deem if (self.thru_toggle_status and self.thru) else self.dummy_raw self.deembedding_changed.emit() def toggle_dummy(self): self.empty_cache() self.dummy_toggle_status = not self.dummy_toggle_status self.btn_toggledummy.setIcon( self.toggleon_icon if self.dummy_toggle_status else self. toggleoff_icon) self.deembedding_changed.emit() def plot_dummy(self): dialog = QDialog(self) dialog.setWindowTitle('Dummy' + (' (deembedded thru)' if self.thru and self.thru_toggle_status else '')) dialog.setModal(True) figure = plt.figure() canvas = FigureCanvas(figure) toolbar = NavigationToolbar(canvas, dialog) l = QVBoxLayout() for w in [toolbar, canvas]: l.addWidget(w) dialog.setLayout(l) self.dummy.plot_mat('y', ylim=1e-3, fig=figure) figure.suptitle(self.dummy.name) figure.subplots_adjust(top=0.9) canvas.draw() dialog.show() def plot_thru(self): dialog = QDialog(self) dialog.setWindowTitle('Thru') dialog.setModal(True) figure = plt.figure() ax = figure.add_subplot(111) canvas = FigureCanvas(figure) toolbar = NavigationToolbar(canvas, dialog) l = QVBoxLayout() for w in [toolbar, canvas]: l.addWidget(w) dialog.setLayout(l) name = self.thru.name self.thru.name = None # workaround to avoid cluttering the legend self.thru.plot_s_deg(ax=ax) ax.set_title(name) self.thru.name = name canvas.draw() dialog.show() def clear(self): self.txt_dut.setText('Path to DUT...') self.txt_thru.setText('Path to thru...') self.txt_dummy.setText('Path to dummy...') self.txt_ra.setText('0') def watch_folder(self): folder, files = self.get_spectra_files(self.dut_folder) for f in files: if f not in self.dut_files: self.dut_files.append(f) self.new_file_in_dataset.emit(f)
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.createActions() self.createMenus() self.createToolBars() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("MDI Test") def closeEvent(self, event): self.mdiArea.closeAllSubWindows() if self.mdiArea.currentSubWindow(): event.ignore() else: self.writeSettings() event.accept() def newFile(self): child = self.createMdiChild() child.newFile() child.show() def open(self): fileName, _ = QFileDialog.getOpenFileName(self) if fileName: existing = self.findMdiChild(fileName) if existing: self.mdiArea.setActiveSubWindow(existing) return child = self.createMdiChild() if child.loadFile(fileName): self.statusBar().showMessage("File loaded", 2000) child.show() else: child.close() def save(self): if self.activeMdiChild() and self.activeMdiChild().save(): self.statusBar().showMessage("File saved", 2000) def saveAs(self): if self.activeMdiChild() and self.activeMdiChild().saveAs(): self.statusBar().showMessage("File saved", 2000) def cut(self): if self.activeMdiChild(): self.activeMdiChild().cut() def copy(self): if self.activeMdiChild(): self.activeMdiChild().copy() def paste(self): if self.activeMdiChild(): self.activeMdiChild().paste() def about(self): QMessageBox.about( self, "About MDI", "The <b>MDI</b> example demonstrates how to write multiple " "document interface applications using Qt.") def updateMenus(self): hasMdiChild = (self.activeMdiChild() is not None) #self.saveAct.setEnabled(hasMdiChild) #self.saveAsAct.setEnabled(hasMdiChild) self.pasteAct.setEnabled(hasMdiChild) self.closeAct.setEnabled(hasMdiChild) self.closeAllAct.setEnabled(hasMdiChild) self.tileAct.setEnabled(hasMdiChild) self.cascadeAct.setEnabled(hasMdiChild) self.nextAct.setEnabled(hasMdiChild) self.previousAct.setEnabled(hasMdiChild) self.separatorAct.setVisible(hasMdiChild) hasSelection = (self.activeMdiChild() is not None and self.activeMdiChild().textCursor().hasSelection()) self.cutAct.setEnabled(hasSelection) self.copyAct.setEnabled(hasSelection) def updateWindowMenu(self): self.windowMenu.clear() self.windowMenu.addAction(self.closeAct) self.windowMenu.addAction(self.closeAllAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.tileAct) self.windowMenu.addAction(self.cascadeAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.nextAct) self.windowMenu.addAction(self.previousAct) self.windowMenu.addAction(self.separatorAct) windows = self.mdiArea.subWindowList() self.separatorAct.setVisible(len(windows) != 0) for i, window in enumerate(windows): child = window.widget() text = "%d %s" % (i + 1, child.userFriendlyCurrentFile()) if i < 9: text = '&' + text action = self.windowMenu.addAction(text) action.setCheckable(True) action.setChecked(child is self.activeMdiChild()) action.triggered.connect(self.windowMapper.map) self.windowMapper.setMapping(action, window) def createMdiChild(self): child = MdiChild() self.mdiArea.addSubWindow(child) child.copyAvailable.connect(self.cutAct.setEnabled) child.copyAvailable.connect(self.copyAct.setEnabled) return child # showntell def createMdiChild15(self): child = MdiChild_ShowNTell() self.mdiArea.addSubWindow(child) return child #MNIST def show_n_tell(self): print('show_n_tell....') child = self.createMdiChild15() print('self.createMdiChild15') #child.resize(830,480) #print('self.createMdiChild15') child.show print('child.show()') def createActions(self): self.cutAct = QAction( QIcon(':/images/cut.png'), "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) self.copyAct = QAction( QIcon(':/images/copy.png'), "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) self.pasteAct = QAction( QIcon(':/images/paste.png'), "&Paste", self, shortcut=QKeySequence.Paste, statusTip= "Paste the clipboard's contents into the current selection", triggered=self.paste) self.closeAct = QAction("Cl&ose", self, statusTip="Close the active window", triggered=self.mdiArea.closeActiveSubWindow) self.closeAllAct = QAction("Close &All", self, statusTip="Close all the windows", triggered=self.mdiArea.closeAllSubWindows) self.tileAct = QAction("&Tile", self, statusTip="Tile the windows", triggered=self.mdiArea.tileSubWindows) self.cascadeAct = QAction("&Cascade", self, statusTip="Cascade the windows", triggered=self.mdiArea.cascadeSubWindows) self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, statusTip="Move the focus to the next window", triggered=self.mdiArea.activateNextSubWindow) self.previousAct = QAction( "Pre&vious", self, shortcut=QKeySequence.PreviousChild, statusTip="Move the focus to the previous window", triggered=self.mdiArea.activatePreviousSubWindow) self.separatorAct = QAction(self) self.separatorAct.setSeparator(True) # 메뉴 ACTION을 다이나믹하게 연결해준다 self.MenuActRef = { 'NewFileAct': 0, 'OpnFileAct': 0, 'SavFileAct': 0, 'SavASFileAct': 0, 'AboutAct': 0, 'AboutQTAct': 0, 'ExitAct': 0, 'SwitchLayout': 0, 'ShowNTell': 0 } # ******* Create the File Menu ******* self.NewFileAct = QAction(QIcon(':/images/new.png'), '&New File', self) self.NewFileAct.setShortcut("Ctrl+N") self.NewFileAct.setStatusTip('Create a New File') self.NewFileAct.triggered.connect(self.newFile) self.MenuActRef['NewFileAct'] = self.NewFileAct #self.newAct = QAction(QIcon(':/images/new.png'), "&New", self, # shortcut=QKeySequence.New, statusTip="Create a new file", # triggered=self.newFile) # ******* Open File Menu Items ******* self.OpnFileAct = QAction(QIcon(':/images/open.png'), '&Open File', self) self.OpnFileAct.setShortcut("Ctrl+O") self.OpnFileAct.setStatusTip('Open an Existing File') self.OpnFileAct.triggered.connect(self.open) self.MenuActRef['OpnFileAct'] = self.OpnFileAct #self.openAct = QAction(QIcon(':/images/open.png'), "&Open...", self, # shortcut=QKeySequence.Open, statusTip="Open an existing file", # triggered=self.open) # ******* Save File Menu Items ******* self.SavFileAct = QAction(QIcon(':/images/save.png'), '&Save File', self) self.SavFileAct.setShortcut("Ctrl+S") self.SavFileAct.setStatusTip('Save Current File') self.SavFileAct.triggered.connect(self.save) self.MenuActRef['SavFileAct'] = self.SavFileAct #self.saveAct = QAction(QIcon(':/images/save.png'), "&Save", self, # shortcut=QKeySequence.Save, # statusTip="Save the document to disk", triggered=self.save) # ******* SaveAS File Menu Items ******* self.SavASFileAct = QAction('Save &As File', self) self.SavASFileAct.setShortcut("Ctrl+A") self.SavASFileAct.setStatusTip('Save Current File under a new name ') self.SavASFileAct.triggered.connect(self.saveAs) self.MenuActRef['SavASFileAct'] = self.SavASFileAct #self.saveAsAct = QAction("Save &As...", self, # shortcut=QKeySequence.SaveAs, # statusTip="Save the document under a new name", # triggered=self.saveAs) # ******* About Menu Items ******* self.aboutAct = QAction("&About", self) self.aboutAct.setStatusTip("Show the application's About box") self.aboutAct.triggered.connect(self.about) self.MenuActRef['AboutAct'] = self.aboutAct # ******* About QT Menu Items ******* self.aboutAct = QAction("About &Qt", self) self.aboutAct.setStatusTip("Show the Qt library's About box") self.aboutAct.triggered.connect(QApplication.instance().aboutQt) self.MenuActRef['AboutQTAct'] = self.aboutAct # ******* Exit Menu Items ******* self.exitAct = QAction("E&xit", self) self.exitAct.setStatusTip("Exit the application") self.exitAct.triggered.connect(QApplication.instance().closeAllWindows) self.MenuActRef['ExitAct'] = self.exitAct #self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit, # statusTip="Exit the application", # triggered=QApplication.instance().closeAllWindows) # ******* Switch layout Items ******* self.SwitchLayout = QAction("&Switch layout direction", self) self.SwitchLayout.setStatusTip("Switch layout direction") self.SwitchLayout.triggered.connect(self.switchLayoutDirection) self.MenuActRef['SwitchLayout'] = self.SwitchLayout #self.SwitchLayout = QAction("&Switch layout direction", self, # statusTip="Switch layout direction", # triggered=self.switchLayoutDirection) # ******* Switch layout Items ******* self.ShowNTell = QAction("&ShowNTell", self) self.ShowNTell.setStatusTip("&ShowNTell") self.ShowNTell.triggered.connect(self.show_n_tell) self.MenuActRef['ShowNTell'] = self.ShowNTell #self.ShowNTell = QAction("&show_n_tell", self, # statusTip="show_n_tell", # triggered=self.show_n_tell) def createMenus(self): # 메뉴를 다이나믹하게 생성하고 연결함 self.MenuLayout = { 0: { 'addMenu': '&File', 'addToolMenu': 'File' }, 1: { 'addDynamic': 'NewFileAct', 'addToolbar': 'NewFileAct' }, 2: { 'addDynamic': 'OpnFileAct', 'addToolbar': 'OpnFileAct' }, 3: { 'addDynamic': 'SavFileAct', 'addToolbar': 'SavFileAct' }, 4: { 'addDynamic': 'SavASFileAct' }, 5: { 'addSeparator': '' }, 6: { 'addDynamic': 'SwitchLayout' }, 7: { 'addDynamic': 'ExitAct' }, 8: { 'addMenu': '&Edit' }, 9: { 'addAction': self.cutAct }, 10: { 'addAction': self.copyAct }, 11: { 'addAction': self.pasteAct }, 12: { 'addMenu': '&Window' }, 13: { 'updateMenu': '' }, 14: { 'addSeparator': '' }, 15: { 'addMenu': '&Help' }, 16: { 'addDynamic': 'AboutAct' }, 17: { 'addDynamic': 'AboutQTAct' }, 18: { 'addMenu': '&MNIST' }, 19: { 'addDynamic': 'ShowNTell' } } for idx in self.MenuLayout: item = self.MenuLayout[idx] if 'addMenu' in item.keys(): self.windowMenu = self.menuBar().addMenu(item['addMenu']) elif 'addAction' in item.keys(): self.windowMenu.addAction(item['addAction']) elif 'addSeparator' in item.keys(): self.windowMenu.addSeparator() elif 'updateMenu' in item.keys(): self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) # 메뉴 ACTION을 다이나믹하게 elif 'addDynamic' in item.keys(): self.windowMenu.addAction(self.MenuActRef[item['addDynamic']]) def createToolBars(self): for idx in self.MenuLayout: item = self.MenuLayout[idx] if 'addToolMenu' in item.keys(): self.fileToolBar = self.addToolBar(item['addToolMenu']) elif 'addToolbar' in item.keys(): self.fileToolBar.addAction(self.MenuActRef[item['addDynamic']]) #self.fileToolBar = self.addToolBar("File") #self.fileToolBar.addAction(self.MenuActRef['NewFileAct']) #self.fileToolBar.addAction(self.MenuActRef['OpnFileAct']) #self.fileToolBar.addAction(self.MenuActRef['SavFileAct']) #self.fileToolBar.addAction(self.newAct) #self.fileToolBar.addAction(self.openAct) #self.fileToolBar.addAction(self.saveAct) self.editToolBar = self.addToolBar("Edit") self.editToolBar.addAction(self.cutAct) self.editToolBar.addAction(self.copyAct) self.editToolBar.addAction(self.pasteAct) def createStatusBar(self): self.statusBar().showMessage("Ready") def readSettings(self): settings = QSettings('NH-Soft', 'MDI Example') pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(400, 400)) self.move(pos) self.resize(size) def writeSettings(self): settings = QSettings('NH-Soft', 'MDI Example') settings.setValue('pos', self.pos()) settings.setValue('size', self.size()) def activeMdiChild(self): activeSubWindow = self.mdiArea.activeSubWindow() if activeSubWindow: return activeSubWindow.widget() return None def findMdiChild(self, fileName): canonicalFilePath = QFileInfo(fileName).canonicalFilePath() for window in self.mdiArea.subWindowList(): if window.widget().currentFile() == canonicalFilePath: return window return None def switchLayoutDirection(self): if self.layoutDirection() == Qt.LeftToRight: QApplication.setLayoutDirection(Qt.RightToLeft) else: QApplication.setLayoutDirection(Qt.LeftToRight) def setActiveSubWindow(self, window): if window: self.mdiArea.setActiveSubWindow(window)
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.mdi = QMdiArea() self.setCentralWidget(self.mdi) fileNewAction = self.createAction("&New", self.fileNew, QKeySequence.New, "filenew", "Create a text file") fileOpenAction = self.createAction("&Open...", self.fileOpen, QKeySequence.Open, "fileopen", "Open an existing text file") fileSaveAction = self.createAction("&Save", self.fileSave, QKeySequence.Save, "filesave", "Save the text") fileSaveAsAction = self.createAction( "Save &As...", self.fileSaveAs, icon="filesaveas", tip="Save the text using a new filename") fileSaveAllAction = self.createAction("Save A&ll", self.fileSaveAll, "filesave", tip="Save all the files") fileQuitAction = self.createAction("&Quit", self.close, "Ctrl+Q", "filequit", "Close the application") editCopyAction = self.createAction("&Copy", self.editCopy, QKeySequence.Copy, "editcopy", "Copy text to the clipboard") editCutAction = self.createAction("Cu&t", self.editCut, QKeySequence.Cut, "editcut", "Cut text to the clipboard") editPasteAction = self.createAction("&Paste", self.editPaste, QKeySequence.Paste, "editpaste", "Paste in the clipboard's text") self.windowNextAction = self.createAction( "&Next", self.mdi.activateNextSubWindow, QKeySequence.NextChild) self.windowPrevAction = self.createAction( "&Previous", self.mdi.activatePreviousSubWindow, QKeySequence.PreviousChild) self.windowCascadeAction = self.createAction( "Casca&de", self.mdi.cascadeSubWindows) self.windowTileAction = self.createAction("&Tile", self.mdi.tileSubWindows) self.windowRestoreAction = self.createAction("&Restore All", self.windowRestoreAll) self.windowMinimizeAction = self.createAction("&Iconize All", self.windowMinimizeAll) #self.windowArrangeIconsAction = self.createAction( # "&Arrange Icons", self.mdi.arrangeIcons) self.windowArrangeIconsAction = self.createAction( "&Arrange Icons", self.windowMinimizeAll) self.windowCloseAction = self.createAction( "&Close", self.mdi.closeActiveSubWindow, QKeySequence.Close) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.mdi.setActiveSubWindow) fileMenu = self.menuBar().addMenu("&File") self.addActions( fileMenu, (fileNewAction, fileOpenAction, fileSaveAction, fileSaveAsAction, fileSaveAllAction, None, fileQuitAction)) editMenu = self.menuBar().addMenu("&Edit") self.addActions(editMenu, (editCopyAction, editCutAction, editPasteAction)) self.windowMenu = self.menuBar().addMenu("&Window") self.windowMenu.aboutToShow.connect(self.updateWindowMenu) fileToolbar = self.addToolBar("File") fileToolbar.setObjectName("FileToolbar") self.addActions(fileToolbar, (fileNewAction, fileOpenAction, fileSaveAction)) editToolbar = self.addToolBar("Edit") editToolbar.setObjectName("EditToolbar") self.addActions(editToolbar, (editCopyAction, editCutAction, editPasteAction)) settings = QSettings() if settings.value("MainWindow/Geometry") or settings.value( "MainWindow/State"): self.restoreGeometry( QByteArray(settings.value("MainWindow/Geometry"))) self.restoreState(QByteArray(settings.value("MainWindow/State"))) status = self.statusBar() status.setSizeGripEnabled(False) status.showMessage("Ready", 5000) self.updateWindowMenu() self.setWindowTitle("Text Editor") QTimer.singleShot(0, self.loadFiles) def createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): action = QAction(text, self) if icon is not None: action.setIcon(QIcon(":/{0}.png".format(icon))) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: action.triggered.connect(slot) if checkable: action.setCheckable(True) return action def addActions(self, target, actions): for action in actions: if action is None: target.addSeparator() else: target.addAction(action) def closeEvent(self, event): failures = [] for textEdit in self.mdi.subWindowList(): textEdit = textEdit.widget() if textEdit.isModified(): try: textEdit.save() except IOError as e: failures.append(str(e)) if (failures and QMessageBox.warning( self, "Text Editor -- Save Error", "Failed to save{0}\nQuit anyway?".format( "\n\t".join(failures)), QMessageBox.Yes | QMessageBox.No) == QMessageBox.No): event.ignore() return settings = QSettings() settings.setValue("MainWindow/Geometry", self.saveGeometry()) settings.setValue("MainWindow/State", self.saveState()) files = [] for textEdit in self.mdi.subWindowList(): textEdit = textEdit.widget() if not textEdit.filename.startswith("Unnamed"): files.append(textEdit.filename) settings.setValue("CurrentFiles", files) self.mdi.closeAllSubWindows() def loadFiles(self): if len(sys.argv) > 1: for filename in sys.argv[1:31]: # Load at most 30 files filename = filename if QFileInfo(filename).isFile(): self.loadFile(filename) QApplication.processEvents() else: settings = QSettings() #files = settings.value("CurrentFiles").toStringList() if settings.value("CurrentFiles"): files = settings.value("CurrentFiles") for filename in files: filename = filename if QFile.exists(filename): self.loadFile(filename) QApplication.processEvents() def fileNew(self): textEdit = textedit.TextEdit() self.mdi.addSubWindow(textEdit) textEdit.show() def fileOpen(self): filename, filetype = QFileDialog.getOpenFileName( self, "Text Editor -- Open File") if filename: for textEdit_MSW in self.mdi.subWindowList(): textEdit = textEdit_MSW.widget() if textEdit.filename == filename: self.mdi.setActiveSubWindow(textEdit_MSW) break else: self.loadFile(filename) def loadFile(self, filename): textEdit = textedit.TextEdit(filename) try: textEdit.load() except EnvironmentError as e: QMessageBox.warning(self, "Text Editor -- Load Error", "Failed to load {0}: {1}".format(filename, e)) textEdit.close() del textEdit else: self.mdi.addSubWindow(textEdit) textEdit.show() def fileSave(self): textEdit = self.mdi.activeSubWindow() textEdit = textEdit.widget() if textEdit is None or not isinstance(textEdit, QTextEdit): return True try: textEdit.save() return True except EnvironmentError as e: QMessageBox.warning( self, "Text Editor -- Save Error", "Failed to save {0}: {1}".format(textEdit.filename, e)) return False def fileSaveAs(self): textEdit = self.mdi.activeSubWindow() textEdit = textEdit.widget() if textEdit is None or not isinstance(textEdit, QTextEdit): return filename, filetype = QFileDialog.getSaveFileName( self, "Text Editor -- Save File As", textEdit.filename, "Text files (*.txt *.*)") if filename: textEdit.filename = filename return self.fileSave() return True def fileSaveAll(self): errors = [] for textEdit in self.mdi.subWindowList(): textEdit = textEdit.widget() if textEdit.isModified(): try: textEdit.save() except EnvironmentError as e: errors.append("{0}: {1}".format(textEdit.filename, e)) if errors: QMessageBox.warning( self, "Text Editor -- Save All Error", "Failed to save\n{0}".format("\n".join(errors))) def editCopy(self): textEdit = self.mdi.activeSubWindow() textEdit = textEdit.widget() if textEdit is None or not isinstance(textEdit, QTextEdit): return cursor = textEdit.textCursor() text = cursor.selectedText() if text: clipboard = QApplication.clipboard() clipboard.setText(text) def editCut(self): textEdit = self.mdi.activeSubWindow() textEdit = textEdit.widget() if textEdit is None or not isinstance(textEdit, QTextEdit): return cursor = textEdit.textCursor() text = cursor.selectedText() if text: cursor.removeSelectedText() clipboard = QApplication.clipboard() clipboard.setText(text) def editPaste(self): textEdit = self.mdi.activeSubWindow() textEdit = textEdit.widget() if textEdit is None or not isinstance(textEdit, QTextEdit): return clipboard = QApplication.clipboard() textEdit.insertPlainText(clipboard.text()) def windowRestoreAll(self): for textEdit in self.mdi.subWindowList(): textEdit = textEdit.widget() textEdit.showNormal() def windowMinimizeAll(self): for textEdit in self.mdi.subWindowList(): textEdit = textEdit.widget() textEdit.showMinimized() def updateWindowMenu(self): self.windowMenu.clear() self.addActions( self.windowMenu, (self.windowNextAction, self.windowPrevAction, self.windowCascadeAction, self.windowTileAction, self.windowRestoreAction, self.windowMinimizeAction, self.windowArrangeIconsAction, None, self.windowCloseAction)) textEdits = self.mdi.subWindowList() if not textEdits: return self.windowMenu.addSeparator() i = 1 menu = self.windowMenu for textEdit_MSW in textEdits: textEdit = textEdit_MSW.widget() title = textEdit.windowTitle() if i == 10: self.windowMenu.addSeparator() menu = menu.addMenu("&More") accel = "" if i < 10: accel = "&{0} ".format(i) elif i < 36: accel = "&{0} ".format(chr(i + ord("@") - 9)) action = menu.addAction("{0}{1}".format(accel, title)) self.windowMapper.setMapping(action, textEdit_MSW) action.triggered.connect(self.windowMapper.map) i += 1
def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Add / remove items self.actAddFolder = QAction( QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) menu.addSeparator() # Copy, cut, paste self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "Cut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlCharacter.rowCount()): a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction( mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder() \ or not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) > 0 and index.isValid( ) and not index.internalPointer().isFolder(): self.actAddFolder.setEnabled(False) self.actAddText.setEnabled(False) if len(sel) == 0: self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) return menu
class KnobTurner(QObject, Ui_Dialog): _frameCount = 0 # running total frames shown _frameRate_RunningAvg = -1.0 # decaying average frame rate (displayed on video) _alpha = 0.01 # how fast to update the screen refresh frame rate display variable _showHist = False _histogramWindowName = "Color Histogram" class Mapper(QObject): def __init__(self, caller, called, action=None): QObject.__init__(self) self._caller = caller self._called = called self._action = action @property def caller(self): return self._caller @property def called(self): return self._called @property def action(self): return self._action def __init__(self): print(">>>>>>>>>>>>>>>TODO: Restore the latest set of options??") Ui_Dialog.__init__(self) QObject.__init__(self) self._filters = [ BlurFilter(), SimpleMotionDetection(), ActivityFilter(), HistogramFilter(), BlockNumber(), EdgeDetector() ] @pyqtSlot() def resetFilterListModel(self): # if we get here then the filter list has been drag-rearranged # reset the filter list based off the filterList order self._filters = [] prev = None for indx in range(0, self.filterList.count()): fltr = self.filterList.item(indx).data(Qt.UserRole) self._filters.append(fltr) for indx in range(0, self.filterList.count()): print("Index: %d Name: %s" % (indx, self.filterList.item(indx).data(Qt.UserRole).name)) def getFilterProperties(self, fltr): attribs = [ funct.replace(FrameProcessor.propStartsWith, "") for funct in dir(fltr) if callable(getattr(fltr, funct)) and funct.startswith(FrameProcessor.propStartsWith) ] setters = [ ftn.replace(FrameProcessor.propEndsWithSetter, "") for ftn in attribs if ftn.endswith(FrameProcessor.propEndsWithSetter) ] getters = [ ftn.replace(FrameProcessor.propEndsWithGetter, "") for ftn in attribs if ftn.endswith(FrameProcessor.propEndsWithGetter) ] return getters, setters def check_state(self, caller): validator = caller.validator() state = validator.validate(caller.text(), 0)[0] if state == QValidator.Acceptable: color = '#c4df9b' # green elif state == QValidator.Intermediate: color = '#fff79a' # yellow else: color = '#f6989d' # red caller.setStyleSheet('QLineEdit { background-color: %s }' % color) return (state == QValidator.Acceptable) @pyqtSlot(QObject) def saveFilterValue(self, obj): assert (isinstance(obj.caller, QSpinBox) or isinstance(obj.caller, QCheckBox) or isinstance(obj.caller, QLineEdit)) if isinstance(obj.caller, QSpinBox): newVal = obj.caller.value() elif isinstance(obj.caller, QCheckBox): newVal = obj.caller.isChecked() elif isinstance(obj.caller, QLineEdit): if self.check_state(obj.caller) != True: return newVal = [int(t) for t in obj.caller.text().split(",")] try: func = getattr(obj.called, obj.action) except AttributeError: print("Whoops, for some reason the callback isn't valid.") else: result = func(newVal) # reset the frame rate self._frameRate_RunningAvg = -1 def setupUi(self, dlg): Ui_Dialog.setupUi(self, dlg) self.btnShowHist.clicked.connect(self.showHistogram) self._filterListOrderMapper = FilterListOrderMapper() self._filterListOrderMapper.listChanged.connect( self.resetFilterListModel) self.filterList.installEventFilter(self._filterListOrderMapper) self.filterList.setDragDropMode(QAbstractItemView.InternalMove) self._optionMapper = QSignalMapper(dlg) self._optionMapper.mapped[QObject].connect(self.saveFilterValue) self._optionSave = [ ] # this is only for making sure the Mapper object doesn't get garbage collected #regx = QRegExp( "([0-9]{1,3}[\,][ ]*){2}[0-9]{1,3}" ) regx = QRegExp("([0-9]{1,3}[\,][ ]*){1,2}[0-9]{1,3}") for fltr in self._filters: print("Filter added: %s" % fltr.name) fltr.loadConfig(None) self.label_Status.setText("Loading filters") getters, setters = self.getFilterProperties(fltr) print("\tAvailable GETable options: ", getters) print("\tAvailable SETable options: ", setters) group = QGroupBox() vbl = QVBoxLayout(group) # Row ------ fm = QFrame() vb = QVBoxLayout(fm) vb.setSizeConstraint(QLayout.SetFixedSize) # add the enabled checkbox first always cb2 = QCheckBox(fltr.name) cb2.setCheckState( getattr( fltr, FrameProcessor.propStartsWith + "Enabled" + FrameProcessor.propEndsWithGetter)()) cb2.setStyleSheet('background-color:#%02x%02x%02x' % (fltr.color()[::-1])) vb.addWidget(cb2) # watch for values changing cb2.stateChanged.connect(self._optionMapper.map) self._optionSave.append( KnobTurner.Mapper( cb2, fltr, FrameProcessor.propStartsWith + "Enabled" + FrameProcessor.propEndsWithSetter)) self._optionMapper.setMapping(cb2, self._optionSave[-1]) for item in setters: prop_type = getattr(fltr, FrameProcessor.propTypeStartsWith + item)() if prop_type is int: hz = QHBoxLayout() lb1 = QLabel("%s:" % item) sp1 = QSpinBox() sp1.setRange( 0, 10000000) # TODO: add this as a query to the filter sp1.setValue( getattr( fltr, FrameProcessor.propStartsWith + item + FrameProcessor.propEndsWithGetter)()) vb.addWidget(lb1) vb.addWidget(sp1) # watch for values changing sp1.valueChanged.connect(self._optionMapper.map) self._optionSave.append( KnobTurner.Mapper( sp1, fltr, FrameProcessor.propStartsWith + item + FrameProcessor.propEndsWithSetter)) self._optionMapper.setMapping(sp1, self._optionSave[-1]) elif prop_type is bool: if item == "Enabled": continue else: cb2 = QCheckBox(item) cb2.setCheckState( getattr( fltr, FrameProcessor.propStartsWith + item + FrameProcessor.propEndsWithGetter)()) vb.addWidget(cb2) # watch for values changing cb2.stateChanged.connect(self._optionMapper.map) self._optionSave.append( KnobTurner.Mapper( cb2, fltr, FrameProcessor.propStartsWith + item + FrameProcessor.propEndsWithSetter)) self._optionMapper.setMapping(cb2, self._optionSave[-1]) elif prop_type is tuple: hz = QHBoxLayout() lb1 = QLabel("%s:" % item) val = getattr( fltr, FrameProcessor.propStartsWith + item + FrameProcessor.propEndsWithGetter)() le = QLineEdit("{0}".format(val).replace("(", "").replace( ")", "").replace("[", "").replace("]", "")) le.setValidator(QRegExpValidator(regx)) le.textChanged.connect(self._optionMapper.map) le.textChanged.emit(le.text()) self._optionSave.append( KnobTurner.Mapper( le, fltr, FrameProcessor.propStartsWith + item + FrameProcessor.propEndsWithSetter)) self._optionMapper.setMapping(le, self._optionSave[-1]) vb.addWidget(lb1) vb.addWidget(le) # end Row 2 ----- vbl.addWidget(fm) vbl.setSizeConstraint(QLayout.SetFixedSize) lwi = QListWidgetItem() lwi.setSizeHint(vbl.sizeHint()) lwi.setData( Qt.UserRole, QVariant(fltr)) # attach the filter to this item in the list self.filterList.addItem(lwi) self.filterList.setItemWidget(lwi, group) self.label_Status.setText("Loading filters... Done") self.label_Status.setText("Starting cameras...") self.startCamera() self.label_Status.setText("Starting cameras... Done") self.showWindows() self.label_Status.setText("Ready") def startCamera(self): print("Starting camera") #vid = "drop.avi" # use video instead of camera vid = None self._cameraDevice = CameraDevice(vid) self.videoLive.setCamera(self._cameraDevice) self.videoLive.newFrame.connect(self.processPreviewFrame) self.videoFiltered.setCamera(self._cameraDevice) self.videoFiltered.newFrame.connect(self.processFilteredFrame) def showHistogram(self): self._showHist = not self._showHist if not self._showHist: cv2.destroyWindow(self._histogramWindowName) def updateHistogram(self): bin_count = self.hist.shape[0] bin_w = 40 img = np.zeros((256, bin_count * bin_w, 3), np.uint8) for i in range(0, bin_count): h = int(self.hist[i]) cv2.rectangle( img, (i * bin_w + 2, 255), # top left corner ((i + 1) * bin_w - 2, 255 - h), # bottom right corner (int(180.0 * i / bin_count), 255, 255), # color -1) # -1 is fill shape img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR) cv2.imshow(self._histogramWindowName, img) def showWindows(self): self.videoLive.show() self.videoFiltered.show() print("Live video size: {}", self.videoLive.sizeHint()) @pyqtSlot(np.ndarray) def processPreviewFrame(self, frame): # update histogram every 15th frame (picked arbitrarily) if self._showHist == True and self._frameCount % 15 == 0: hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) hist = cv2.calcHist([hsv], [0], None, [16], [0, 180]) cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX) self.hist = hist.reshape(-1) self.updateHistogram() intersect_box = None colors = [] boxes = [] for fltr in self._filters: if fltr.prop_Enabled_get(): box = fltr.getBoundingBox( ) # CAREFUL!!: returns (x1, y1, x2, y2) NOT x, y, w, h **** if len(box) != 0: colors.append(fltr.color()) boxes.append(box) if intersect_box == None: intersect_box = box else: intersect_box = intersection_rect(intersect_box, box) # fade the color for all but the intersection of active areas if intersect_box != None: rectW = intersect_box[2] - intersect_box[0] rectH = intersect_box[3] - intersect_box[1] saverect = frame[intersect_box[1]:intersect_box[1] + rectH, intersect_box[0]:intersect_box[0] + rectW] frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) ## TODO: this crashes now with 'TypeError: No loop matching the specified signature and casting was found for ufunc true_divide' - new numpy # frame /= 2 # cut the overal luminocity of the preview video by half to highlight the in-frame portion frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR) # restore the color to only the intersection if intersect_box != None: frame[intersect_box[1]:intersect_box[1] + rectH, intersect_box[0]:intersect_box[0] + rectW] = saverect # paint the boxes back on top of the gray image for index, box in enumerate(boxes): cv2.rectangle(frame, (box[0], box[1]), (box[2], box[3]), colors[index], 2) self.videoLive.setNewFrame(frame) @pyqtSlot(np.ndarray) def processFilteredFrame(self, frame): t = clock() for fltr in self._filters: if fltr.prop_Enabled_get( ) == True: # I'm cheating here because I know this property exists for all filters frame = fltr.processFrame(frame) # show time delta_t = clock() - t if self._frameRate_RunningAvg == -1: self._frameRate_RunningAvg = delta_t self._frameRate_RunningAvg = (self._alpha * delta_t) + ( 1.0 - self._alpha) * self._frameRate_RunningAvg draw_str(frame, 20, 20, 'time: %.1f ms' % (self._frameRate_RunningAvg * 1000)) # last guy puts the frame up self.videoFiltered.setNewFrame(frame) @pyqtSlot() def shutDown(self): print( ">>>>>>>>>>>>>>>TODO: Print and/or save the latest set of options!!" ) for fltr in self._filters: fltr.saveConfig(None) print("Shutting down")
class Panel(QDialog): # A list of two-sized tuples (QWidget's name, model field name). FIELDS = [] # Name to use for serialization of persistent data about this panel (geometry). # XXX At the time of this writing (ticket #364), there's already a separate system in Cocoa # to persist dialog frames. A "clean" implementation would do like we do with the main window # and implement frame save/restore in core, but I fear that I'll needlessly complicate things # doing so, so for now, I limit myself to a qt-only solution. Later, we should re-evaluate # whether it could be a good idea to push this implementation to the core. PERSISTENT_NAME = None def __init__(self, mainwindow): # The flags we pass are that so we don't get the "What's this" button in the title bar QDialog.__init__(self, mainwindow, Qt.WindowTitleHint | Qt.WindowSystemMenuHint) self._widget2ModelAttr = {} self.mainwindow = mainwindow def _changeComboBoxItems(self, comboBox, newItems): # When a combo box's items are changed, its currentIndex changed with a currentIndexChanged # signal, and if that signal results in the model being updated, it messes the model. # We thus have to disconnect the combo box's signal before changing the items. if comboBox in self._widget2ModelAttr: comboBox.currentIndexChanged.disconnect(self.comboBoxCurrentIndexChanged) index = comboBox.currentIndex() comboBox.clear() comboBox.addItems(newItems) comboBox.setCurrentIndex(index) if comboBox in self._widget2ModelAttr: comboBox.currentIndexChanged.connect(self.comboBoxCurrentIndexChanged) def _connectSignals(self): self._signalMapper = QSignalMapper() for widgetName, modelAttr in self.FIELDS: widget = getattr(self, widgetName) self._widget2ModelAttr[widget] = modelAttr self._signalMapper.setMapping(widget, widget) if isinstance(widget, QComboBox): widget.currentIndexChanged.connect(self._signalMapper.map) elif isinstance(widget, QSpinBox): widget.valueChanged.connect(self._signalMapper.map) elif isinstance(widget, QLineEdit): widget.editingFinished.connect(self._signalMapper.map) elif isinstance(widget, QPlainTextEdit): widget.textChanged.connect(self._signalMapper.map) elif isinstance(widget, QCheckBox): widget.stateChanged.connect(self._signalMapper.map) self._signalMapper.mapped[QWidget].connect(self.widgetChanged) def _loadFields(self): for widgetName, modelAttr in self.FIELDS: widget = getattr(self, widgetName) value = getattr(self.model, modelAttr) if isinstance(widget, QComboBox): widget.setCurrentIndex(value) elif isinstance(widget, QSpinBox): widget.setValue(value) elif isinstance(widget, QLineEdit): widget.setText(value) elif isinstance(widget, QPlainTextEdit): widget.setPlainText(value) elif isinstance(widget, QCheckBox): widget.setChecked(value) def _saveFields(self): pass def _loadGeometry(self): if self.PERSISTENT_NAME: self.mainwindow.app.prefs.restoreGeometry('%sGeometry' % self.PERSISTENT_NAME, self) def _saveGeometry(self): if self.PERSISTENT_NAME: self.mainwindow.app.prefs.saveGeometry('%sGeometry' % self.PERSISTENT_NAME, self) def accept(self): # The setFocus() call is to force the last edited field to "commit". When the save button # is clicked, accept() is called before the last field to have focus has a chance to emit # its edition signal. self.setFocus() self.model.save() self._saveGeometry() QDialog.accept(self) def reject(self): self._saveGeometry() super().reject() #--- Event Handlers def widgetChanged(self, sender): modelAttr = self._widget2ModelAttr[sender] if isinstance(sender, QComboBox): newvalue = sender.currentIndex() elif isinstance(sender, QSpinBox): newvalue = sender.value() elif isinstance(sender, QLineEdit): newvalue = sender.text() elif isinstance(sender, QPlainTextEdit): newvalue = sender.toPlainText() elif isinstance(sender, QCheckBox): newvalue = sender.isChecked() setattr(self.model, modelAttr, newvalue) #--- model --> view def pre_load(self): self._loadGeometry() def pre_save(self): self._saveFields() def post_load(self): if not self._widget2ModelAttr: # signal not connected yet self._connectSignals() self._loadFields() self.show() # For initial text edits to have their text selected, we *have to* first select the dialog, # then setFocus on it with qt.TabFocusReason. Don't ask, I don't know why either... self.setFocus() focus = self.nextInFocusChain() while focus.focusPolicy() == Qt.NoFocus: focus = focus.nextInFocusChain() focus.setFocus(Qt.TabFocusReason)
class AddSeqTool(AbstractPathTool): """Summary Attributes: apply_button (TYPE): Description buttons (list): Description dialog (TYPE): Description highlighter (TYPE): Description seq_box (TYPE): Description sequence_radio_button_id (dict): Description signal_mapper (TYPE): Description use_abstract_sequence (bool): Description validated_sequence_to_apply (TYPE): Description """ def __init__(self, manager): """Summary Args: manager (TYPE): Description """ AbstractPathTool.__init__(self, manager) self.dialog = QDialog() self.buttons = [] self.seq_box = None self.sequence_radio_button_id = {} self.use_abstract_sequence = True self.validated_sequence_to_apply = None self.initDialog() def __repr__(self): """Summary Returns: TYPE: Description """ return "add_seq_tool" # first letter should be lowercase def methodPrefix(self): """Summary Returns: TYPE: Description """ return "addSeqTool" # first letter should be lowercase def initDialog(self): """Creates buttons for each sequence option and add them to the dialog. Maps the clicked signal of those buttons to keep track of what sequence gets selected. """ ui_dlg = Ui_AddSeqDialog() ui_dlg.setupUi(self.dialog) self.signal_mapper = QSignalMapper(self) # set up the radio buttons for i, name in enumerate(['Abstract', 'Custom'] + sorted(sequences.keys())): radio_button = QRadioButton(ui_dlg.group_box) radio_button.setObjectName(name + "Button") radio_button.setText(name) self.buttons.append(radio_button) ui_dlg.horizontalLayout.addWidget(radio_button) self.signal_mapper.setMapping(radio_button, i) radio_button.clicked.connect(self.signal_mapper.map) if name in sequences: self.sequence_radio_button_id[sequences[name]] = i self.signal_mapper.mapped.connect(self.sequenceOptionChangedSlot) # disable apply until valid option or custom sequence is chosen self.apply_button = ui_dlg.custom_button_box.button( QDialogButtonBox.Apply) self.apply_button.setEnabled(False) # watch sequence textedit box to validate custom sequences self.seq_box = ui_dlg.seq_text_edit self.seq_box.textChanged.connect(self.validateCustomSequence) self.highlighter = DNAHighlighter(self.seq_box) # finally, pre-click the first radio button self.buttons[0].click() def sequenceOptionChangedSlot(self, option_chosen): """ Connects to signal_mapper to receive a signal whenever user selects a sequence option. Args: option_chosen (TYPE): Description """ option_name = self.buttons[option_chosen].text() if option_name == 'Abstract': self.use_abstract_sequence = True elif option_name == 'Custom': self.use_abstract_sequence = False else: self.use_abstract_sequence = False user_sequence = sequences.get(option_name, None) if self.seq_box.toPlainText() != user_sequence: self.seq_box.setText(user_sequence) def validateCustomSequence(self): """ Called when user changes sequence (seq_box emits textChanged signal) If sequence is valid, make the apply_button active to click. Select an appropriate sequence option radio button, if necessary. """ user_sequence = self.seq_box.toPlainText() # Validate the sequence and activate the button if it checks out. if re.search(RE_DNA_PATTERN, user_sequence) is None: self.apply_button.setEnabled(True) else: self.apply_button.setEnabled(False) if len(user_sequence) == 0: # A zero-length custom sequence defaults to Abstract type. if not self.buttons[0].isChecked(): self.buttons[0].click() else: # Does this match a known sequence? if user_sequence in self.sequence_radio_button_id: # Handles case where the user might copy & paste in a known sequence i = self.sequence_radio_button_id[user_sequence] if not self.buttons[i].isChecked(): # Select the corresponding radio button for known sequence self.buttons[i].click() else: # Unrecognized, Custom type if not self.buttons[1].isChecked(): self.buttons[1].click() def applySequence(self, oligo): """Summary Args: oligo (TYPE): Description Returns: TYPE: Description """ self.dialog.setFocus() if self.dialog.exec_(): # apply the sequence if accept was clicked if self.use_abstract_sequence: oligo.applySequence(None) return (oligo.length(), None) else: self.validated_sequence_to_apply = self.seq_box.toPlainText( ).upper() oligo.applySequence(self.validated_sequence_to_apply) return oligo.length(), len(self.validated_sequence_to_apply) return (None, None)
def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Get index under cursor pos = self.viewport().mapFromGlobal(QCursor.pos()) mouseIndex = self.indexAt(pos) # Get index's title if mouseIndex.isValid(): title = mouseIndex.internalPointer().title() elif self.rootIndex().parent().isValid(): # mouseIndex is the background of an item, so we check the parent mouseIndex = self.rootIndex().parent() title = mouseIndex.internalPointer().title() else: title = qApp.translate("outlineBasics", "Root") if len(title) > 25: title = title[:25] + "…" # Open Item action self.actOpen = QAction(QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open {}".format(title)), menu) self.actOpen.triggered.connect(self.openItem) menu.addAction(self.actOpen) # Open item(s) in new tab if mouseIndex in sel and len(sel) > 1: actionTitle = qApp.translate("outlineBasics", "Open {} items in new tabs").format(len(sel)) self._indexesToOpen = sel else: actionTitle = qApp.translate("outlineBasics", "Open {} in a new tab").format(title) self._indexesToOpen = [mouseIndex] self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu) self.actNewTab.triggered.connect(self.openItemsInNewTabs) menu.addAction(self.actNewTab) menu.addSeparator() # Add text / folder self.actAddFolder = QAction(QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New &Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New &Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) menu.addSeparator() # Copy, cut, paste, duplicate self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "C&ut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "&Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "&Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) # Rename / duplicate / remove items self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "&Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) self.actRename = QAction(QIcon.fromTheme("edit-rename"), qApp.translate("outlineBasics", "&Rename"), menu) self.actRename.triggered.connect(self.rename) menu.addAction(self.actRename) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [qApp.translate("outlineBasics", "Main"), qApp.translate("outlineBasics", "Secondary"), qApp.translate("outlineBasics", "Minor")]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlCharacter.rowCount()): a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction(mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) menu.addSeparator() # Custom icons if self.menuCustomIcons: menu.addMenu(self.menuCustomIcons) else: self.menuCustomIcons = QMenu(qApp.translate("outlineBasics", "Set Custom Icon"), menu) a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons) a.triggered.connect(lambda: self.setCustomIcon("")) self.menuCustomIcons.addAction(a) self.menuCustomIcons.addSeparator() txt = QLineEdit() txt.textChanged.connect(self.filterLstIcons) txt.setPlaceholderText("Filter icons") txt.setStyleSheet("QLineEdit { background: transparent; border: none; }") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(txt) self.menuCustomIcons.addAction(act) self.lstIcons = QListWidget() for i in customIcons(): item = QListWidgetItem() item.setIcon(QIcon.fromTheme(i)) item.setData(Qt.UserRole, i) item.setToolTip(i) self.lstIcons.addItem(item) self.lstIcons.itemClicked.connect(self.setCustomIconFromItem) self.lstIcons.setViewMode(self.lstIcons.IconMode) self.lstIcons.setUniformItemSizes(True) self.lstIcons.setResizeMode(self.lstIcons.Adjust) self.lstIcons.setMovement(self.lstIcons.Static) self.lstIcons.setStyleSheet("background: transparent; background: none;") self.filterLstIcons("") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(self.lstIcons) self.menuCustomIcons.addAction(act) menu.addMenu(self.menuCustomIcons) # Disabling stuff if not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) == 0: self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actRename.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) self.menuCustomIcons.setEnabled(False) if len(sel) > 1: self.actRename.setEnabled(False) return menu
class MainWindow(object): def __init__(self): self.main_window_tab_table = QTabWidget() self.hbox = QHBoxLayout() self.but = QPushButton('Add') self.file_menu = None self.help_menu = None self.about_qt = None self.exit_action = None self.choice_interface = None def setup_ui(self, main_window): self.main_window = main_window main_window.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) red, green, blue = 11, 7, 11 main_window.setPalette(QPalette(QColor(red, green, blue))) self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.create_menu(main_window) self.create_tool_bars(main_window) main_window.setCentralWidget(self.mdiArea) self.windowMapper = QSignalMapper(main_window) def file_menu_action(self): self.exit_action = QAction(QIcon('../resources/icons/fileclose.png'), "&Exit", self.file_menu) self.exit_action.setShortcut('Ctrl+Q') self.exit_action.setStatusTip('Exit application') # self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit, # statusTip="Exit the application", # triggered=QApplication.instance().closeAllWindows) self.choice_interface = QAction(QIcon('../resources/icons/ethernet_card_vista.png'), '&New Table', self.file_menu) self.choice_interface.setShortcut('Ctrl+A') self.choice_interface.setStatusTip('Exit application') # create new table self.new_act = QAction(QIcon(':/images/new.png'), "&New", self.file_menu) self.new_act.setShortcut('Ctrl+Q') self.new_act.setStatusTip('Create a new file') # triggered=self.open self.open_act = QAction(QIcon(':/images/open.png'), "&Open", self.file_menu) self.open_act.setShortcut(QKeySequence.Open) self.open_act.setStatusTip('Open an existing file') # triggered=self.save self.save_act = QAction(QIcon(':/images/save.png'), "&Save", self.file_menu) self.save_act.setShortcut(QKeySequence.Save) self.save_act.setStatusTip('Save the table to disk') # triggered=self.saveAs self.save_as_act = QAction(QIcon(':/images/save.png'), "&Save &As", self.file_menu) self.save_as_act.setShortcut(QKeySequence.SaveAs) self.save_as_act.setStatusTip('Save the document under a new name') # triggered=self.cut self.cut_act = QAction(QIcon(':/images/save.png'), "&Cut", self.file_menu) self.cut_act.setShortcut(QKeySequence.Cut) self.cut_act.setStatusTip('Cut the current selections contents to the clipboard') # triggered=self.copy self.copy_act = QAction(QIcon(':/images/save.png'), "&Copy", self.file_menu) self.copy_act.setShortcut(QKeySequence.Copy) self.copy_act.setStatusTip('Copy the current selections contents to the clipboard') # triggered=self.paste self.paste_act = QAction(QIcon(':/images/save.png'), "&Paste", self.file_menu) self.paste_act.setShortcut(QKeySequence.Paste) self.paste_act.setStatusTip('Paste the clipboards contents into the current selection') self.close_act = QAction(QIcon(':/images/save.png'), "&Close", self.file_menu) self.close_act.setStatusTip('Close the active window') self.close_act.triggered.connect(self.mdiArea.closeActiveSubWindow) self.close_all_act = QAction(QIcon(':/images/save.png'), "&Close &All", self.file_menu) self.close_all_act.setStatusTip('Close all the windows') self.close_all_act.triggered.connect(self.mdiArea.closeAllSubWindows) self.tile_act = QAction(QIcon(':/images/save.png'), "&Tile", self.file_menu) self.tile_act.setStatusTip('Tile the windows') self.tile_act.triggered.connect(self.mdiArea.tileSubWindows) self.cascade_act = QAction(QIcon(':/images/save.png'), "&Cascade", self.file_menu) self.cascade_act.setStatusTip('Cascade the windows') self.cascade_act.triggered.connect(self.mdiArea.cascadeSubWindows) self.next_act = QAction(QIcon(':/images/save.png'), "&Next", self.file_menu) self.next_act.setShortcut(QKeySequence.NextChild) self.next_act.setStatusTip('Move the focus to the next window') self.next_act.triggered.connect(self.mdiArea.activateNextSubWindow) self.previous_act = QAction(QIcon(':/images/save.png'), "&Previous", self.file_menu) self.previous_act.setShortcut(QKeySequence.PreviousChild) self.previous_act.setStatusTip('Move the focus to the previous window') self.previous_act.triggered.connect(self.mdiArea.activatePreviousSubWindow) # triggered=self.about # self.separatorAct = QAction(self) # self.separatorAct.setSeparator(True) def help_menu_action(self): self.about_qt = QAction(QIcon('../resources/icons/info.png'), '&About Qt', self.help_menu) self.about_qt.setShortcut('Ctrl+L') self.about_qt.setStatusTip('Show the Qt librarys About box') self.about_qt.triggered.connect(qApp.aboutQt) # self.aboutQtAct = QAction("About &Qt", self, # statusTip="Show the Qt library's About box", # triggered=QApplication.instance().aboutQt) self.about_program_act = QAction(QIcon(':/images/save.png'), "&About Program", self.file_menu) self.about_program_act.setStatusTip('Show the applications About box') def create_menu(self, main_window): self.file_menu = main_window.menuBar().addMenu("&File") self.file_menu_action() self.file_menu.addAction(self.choice_interface) self.file_menu.addAction(self.exit_action) self.file_menu.addAction(self.new_act) self.file_menu.addAction(self.open_act) self.file_menu.addAction(self.save_act) self.file_menu.addAction(self.save_as_act) # self.fileMenu.addSeparator() self.help_menu = main_window.menuBar().addMenu("&Help") self.help_menu_action() self.help_menu.addAction(self.about_qt) self.help_menu.addAction(self.about_program_act) self.editMenu = main_window.menuBar().addMenu("&Edit") self.editMenu.addAction(self.cut_act) self.editMenu.addAction(self.copy_act) self.editMenu.addAction(self.paste_act) self.windowMenu = main_window.menuBar().addMenu("&Window") self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) # self.menuBar().addSeparator() def about(self): QMessageBox.about(self, "About MDI", "The <b>MDI</b> example demonstrates how to write multiple " "document interface applications using Qt.") def updateMenus(self): hasMdiChild = (self.activeMdiChild() is not None) self.save_act.setEnabled(hasMdiChild) self.save_as_act.setEnabled(hasMdiChild) self.paste_act.setEnabled(hasMdiChild) self.close_act.setEnabled(hasMdiChild) self.close_all_act.setEnabled(hasMdiChild) self.tile_act.setEnabled(hasMdiChild) self.cascade_act.setEnabled(hasMdiChild) self.next_act.setEnabled(hasMdiChild) self.previous_act.setEnabled(hasMdiChild) # self.separator_act.setVisible(hasMdiChild) hasSelection = (self.activeMdiChild() is not None and self.activeMdiChild().textCursor().hasSelection()) self.cut_act.setEnabled(hasSelection) self.copy_act.setEnabled(hasSelection) def updateWindowMenu(self): self.windowMenu.clear() self.windowMenu.addAction(self.close_act) self.windowMenu.addAction(self.close_all_act) self.windowMenu.addSeparator() self.windowMenu.addAction(self.tile_act) self.windowMenu.addAction(self.cascade_act) self.windowMenu.addSeparator() self.windowMenu.addAction(self.next_act) self.windowMenu.addAction(self.previous_act) # self.windowMenu.addAction(self.separator_act) windows = self.mdiArea.subWindowList() # self.separator_act.setVisible(len(windows) != 0) for i, window in enumerate(windows): child = window.widget() text = "%d %s" % (i + 1, child.userFriendlyCurrentFile()) if i < 9: text = '&' + text action = self.windowMenu.addAction(text) action.setCheckable(True) action.setChecked(child is self.main_window.activeMdiChild()) action.triggered.connect(self.windowMapper.map) self.windowMapper.setMapping(action, window) # def switchLayoutDirection(self): # if self.layoutDirection() == Qt.LeftToRight: # QApplication.setLayoutDirection(Qt.RightToLeft) # else: # QApplication.setLayoutDirection(Qt.LeftToRight) ''' def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.saveAsAct) self.fileMenu.addSeparator() action = self.fileMenu.addAction("Switch layout direction") action.triggered.connect(self.switchLayoutDirection) self.fileMenu.addAction(self.exitAct) self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.cutAct) self.editMenu.addAction(self.copyAct) self.editMenu.addAction(self.pasteAct) self.windowMenu = self.menuBar().addMenu("&Window") self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) ''' def create_tool_bars(self, main_window): self.file_tool_bar = main_window.addToolBar("File") self.file_tool_bar.addAction(self.new_act) self.file_tool_bar.addAction(self.open_act) self.file_tool_bar.addAction(self.save_act) self.edit_tool_bar = main_window.addToolBar("Edit") self.edit_tool_bar.addAction(self.cut_act) self.edit_tool_bar.addAction(self.copy_act) self.edit_tool_bar.addAction(self.paste_act)
class MainWindow(QMainWindow, Ui_MainWindow): """docstring for MainWindow.""" def __init__(self, parent=None): super(MainWindow, self).__init__() self._csvFilePath = "" self.serialport = serial.Serial() self.receiver_thread = readerThread(self) self.receiver_thread.setPort(self.serialport) self._localEcho = None self._viewMode = None self._quickSendOptRow = 1 self.setupUi(self) self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) font = QtGui.QFont() font.setFamily(EDITOR_FONT) font.setPointSize(9) self.txtEdtOutput.setFont(font) self.txtEdtInput.setFont(font) #self.quickSendTable.setFont(font) if UI_FONT is not None: font = QtGui.QFont() font.setFamily(UI_FONT) font.setPointSize(9) self.dockWidget_PortConfig.setFont(font) self.dockWidget_SendHex.setFont(font) self.dockWidget_QuickSend.setFont(font) self.setupMenu() self.setupFlatUi() self.onEnumPorts() icon = QtGui.QIcon(":/MyTerm.ico") self.setWindowIcon(icon) self.actionAbout.setIcon(icon) self.defaultStyleWidget = QWidget() self.defaultStyleWidget.setWindowIcon(icon) icon = QtGui.QIcon(":/qt_logo_16.ico") self.actionAbout_Qt.setIcon(icon) self._viewGroup = QActionGroup(self) self._viewGroup.addAction(self.actionAscii) self._viewGroup.addAction(self.actionHex_lowercase) self._viewGroup.addAction(self.actionHEX_UPPERCASE) self._viewGroup.setExclusive(True) # bind events self.actionOpen_Cmd_File.triggered.connect(self.openQuickSend) self.actionSave_Log.triggered.connect(self.onSaveLog) self.actionExit.triggered.connect(self.onExit) self.actionOpen.triggered.connect(self.openPort) self.actionClose.triggered.connect(self.closePort) self.actionPort_Config_Panel.triggered.connect(self.onTogglePrtCfgPnl) self.actionQuick_Send_Panel.triggered.connect(self.onToggleQckSndPnl) self.actionSend_Hex_Panel.triggered.connect(self.onToggleHexPnl) self.dockWidget_PortConfig.visibilityChanged.connect( self.onVisiblePrtCfgPnl) self.dockWidget_QuickSend.visibilityChanged.connect( self.onVisibleQckSndPnl) self.dockWidget_SendHex.visibilityChanged.connect(self.onVisibleHexPnl) self.actionLocal_Echo.triggered.connect(self.onLocalEcho) self.actionAlways_On_Top.triggered.connect(self.onAlwaysOnTop) self.actionAscii.triggered.connect(self.onViewChanged) self.actionHex_lowercase.triggered.connect(self.onViewChanged) self.actionHEX_UPPERCASE.triggered.connect(self.onViewChanged) self.actionAbout.triggered.connect(self.onAbout) self.actionAbout_Qt.triggered.connect(self.onAboutQt) self.btnOpen.clicked.connect(self.onOpen) self.btnClear.clicked.connect(self.onClear) self.btnSaveLog.clicked.connect(self.onSaveLog) self.btnEnumPorts.clicked.connect(self.onEnumPorts) self.btnSendHex.clicked.connect(self.onSend) self.receiver_thread.read.connect(self.onReceive) self.receiver_thread.exception.connect(self.onReaderExcept) self._signalMapQuickSendOpt = QSignalMapper(self) self._signalMapQuickSendOpt.mapped[int].connect( self.onQuickSendOptions) self._signalMapQuickSend = QSignalMapper(self) self._signalMapQuickSend.mapped[int].connect(self.onQuickSend) # initial action self.actionHEX_UPPERCASE.setChecked(True) self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE) self.initQuickSend() self.restoreLayout() self.moveScreenCenter() self.syncMenu() if self.isMaximized(): self.setMaximizeButton("restore") else: self.setMaximizeButton("maximize") self.loadSettings() def setupMenu(self): self.menuMenu = QtWidgets.QMenu() self.menuMenu.setTitle("&File") self.menuMenu.setObjectName("menuMenu") self.menuView = QtWidgets.QMenu(self.menuMenu) self.menuView.setTitle("&View") self.menuView.setObjectName("menuView") self.menuView.addAction(self.actionAscii) self.menuView.addAction(self.actionHex_lowercase) self.menuView.addAction(self.actionHEX_UPPERCASE) self.menuMenu.addAction(self.actionOpen_Cmd_File) self.menuMenu.addAction(self.actionSave_Log) self.menuMenu.addSeparator() self.menuMenu.addAction(self.actionPort_Config_Panel) self.menuMenu.addAction(self.actionQuick_Send_Panel) self.menuMenu.addAction(self.actionSend_Hex_Panel) self.menuMenu.addAction(self.menuView.menuAction()) self.menuMenu.addAction(self.actionLocal_Echo) self.menuMenu.addAction(self.actionAlways_On_Top) self.menuMenu.addSeparator() self.menuMenu.addAction(self.actionAbout) self.menuMenu.addAction(self.actionAbout_Qt) self.menuMenu.addSeparator() self.menuMenu.addAction(self.actionExit) self.sendOptMenu = QtWidgets.QMenu() self.actionSend_Hex = QtWidgets.QAction(self) self.actionSend_Hex.setText("Send &Hex") self.actionSend_Hex.setStatusTip("Send Hex (e.g. 31 32 FF)") self.actionSend_Asc = QtWidgets.QAction(self) self.actionSend_Asc.setText("Send &Asc") self.actionSend_Asc.setStatusTip("Send Asc (e.g. abc123)") self.actionSend_TFH = QtWidgets.QAction(self) self.actionSend_TFH.setText("Send &Text file as hex") self.actionSend_TFH.setStatusTip( 'Send text file as hex (e.g. strings "31 32 FF" in the file)') self.actionSend_TFA = QtWidgets.QAction(self) self.actionSend_TFA.setText("Send t&Ext file as asc") self.actionSend_TFA.setStatusTip( 'Send text file as asc (e.g. strings "abc123" in the file)') self.actionSend_FB = QtWidgets.QAction(self) self.actionSend_FB.setText("Send &Bin/Hex file") self.actionSend_FB.setStatusTip("Send a bin file or a hex file") self.sendOptMenu.addAction(self.actionSend_Hex) self.sendOptMenu.addAction(self.actionSend_Asc) self.sendOptMenu.addAction(self.actionSend_TFH) self.sendOptMenu.addAction(self.actionSend_TFA) self.sendOptMenu.addAction(self.actionSend_FB) self.actionSend_Hex.triggered.connect(self.onSetSendHex) self.actionSend_Asc.triggered.connect(self.onSetSendAsc) self.actionSend_TFH.triggered.connect(self.onSetSendTFH) self.actionSend_TFA.triggered.connect(self.onSetSendTFA) self.actionSend_FB.triggered.connect(self.onSetSendFB) def setupFlatUi(self): self._dragPos = self.pos() self._isDragging = False self.setMouseTracking(True) self.setWindowFlags(Qt.FramelessWindowHint) self.setStyleSheet(""" QWidget { background-color: %(BackgroundColor)s; /*background-image: url(:/background.png);*/ outline: none; } QLabel { color:%(TextColor)s; font-size:12px; /*font-family:Century;*/ } QComboBox { color:%(TextColor)s; font-size:12px; /*font-family:Century;*/ } QComboBox { border: none; padding: 1px 1px 1px 3px; } QComboBox:editable { background: white; } QComboBox:!editable, QComboBox::drop-down:editable { background: #62c7e0; } QComboBox:!editable:hover, QComboBox::drop-down:editable:hover { background: #c7eaf3; } QComboBox:!editable:pressed, QComboBox::drop-down:editable:pressed { background: #35b6d7; } QComboBox:on { padding-top: 3px; padding-left: 4px; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 16px; border: none; } QComboBox::down-arrow { image: url(:/downarrow.png); } QComboBox::down-arrow:on { image: url(:/uparrow.png); } QGroupBox { color:%(TextColor)s; font-size:12px; /*font-family:Century;*/ border: 1px solid gray; margin-top: 15px; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; left:5px; top:3px; } QCheckBox { color:%(TextColor)s; spacing: 5px; font-size:12px; /*font-family:Century;*/ } QCheckBox::indicator:unchecked { image: url(:/checkbox_unchecked.png); } QCheckBox::indicator:unchecked:hover { image: url(:/checkbox_unchecked_hover.png); } QCheckBox::indicator:unchecked:pressed { image: url(:/checkbox_unchecked_pressed.png); } QCheckBox::indicator:checked { image: url(:/checkbox_checked.png); } QCheckBox::indicator:checked:hover { image: url(:/checkbox_checked_hover.png); } QCheckBox::indicator:checked:pressed { image: url(:/checkbox_checked_pressed.png); } QScrollBar:horizontal { background-color:%(BackgroundColor)s; border: none; height: 15px; margin: 0px 20px 0 20px; } QScrollBar::handle:horizontal { background: %(ScrollBar_Handle)s; min-width: 20px; } QScrollBar::add-line:horizontal { image: url(:/rightarrow.png); border: none; background: %(ScrollBar_Line)s; width: 20px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { image: url(:/leftarrow.png); border: none; background: %(ScrollBar_Line)s; width: 20px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar:vertical { background-color:%(BackgroundColor)s; border: none; width: 15px; margin: 20px 0px 20px 0px; } QScrollBar::handle::vertical { background: %(ScrollBar_Handle)s; min-height: 20px; } QScrollBar::add-line::vertical { image: url(:/downarrow.png); border: none; background: %(ScrollBar_Line)s; height: 20px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line::vertical { image: url(:/uparrow.png); border: none; background: %(ScrollBar_Line)s; height: 20px; subcontrol-position: top; subcontrol-origin: margin; } QTableView { background-color: white; /*selection-background-color: #FF92BB;*/ border: 1px solid %(TableView_Border)s; color: %(TextColor)s; } QTableView::focus { /*border: 1px solid #2a7fff;*/ } QTableView QTableCornerButton::section { border: none; border-right: 1px solid %(TableView_Border)s; border-bottom: 1px solid %(TableView_Border)s; background-color: %(TableView_Corner)s; } QTableView QWidget { background-color: white; } QTableView::item:focus { border: 1px red; background-color: transparent; color: %(TextColor)s; } QHeaderView::section { border: none; border-right: 1px solid %(TableView_Border)s; border-bottom: 1px solid %(TableView_Border)s; padding-left: 2px; padding-right: 2px; color: #444444; background-color: %(TableView_Header)s; } QTextEdit { background-color:white; color:%(TextColor)s; border-top: none; border-bottom: none; border-left: 2px solid %(BackgroundColor)s; border-right: 2px solid %(BackgroundColor)s; } QTextEdit::focus { } QToolButton, QPushButton { background-color:#30a7b8; border:none; color:#ffffff; font-size:12px; /*font-family:Century;*/ } QToolButton:hover, QPushButton:hover { background-color:#51c0d1; } QToolButton:pressed, QPushButton:pressed { background-color:#3a9ecc; } QMenuBar { color: %(TextColor)s; height: 24px; } QMenuBar::item { background-color: transparent; margin: 8px 0px 0px 0px; padding: 1px 8px 1px 8px; height: 15px; } QMenuBar::item:selected { background: #51c0d1; } QMenuBar::item:pressed { } /* QMenu { color: %(TextColor)s; background: #ffffff; } QMenu { margin: 2px; } QMenu::item { padding: 2px 25px 2px 21px; border: 1px solid transparent; } QMenu::item:selected { background: #51c0d1; } QMenu::icon { background: transparent; border: 2px inset transparent; }*/ QDockWidget { font-size:12px; /*font-family:Century;*/ color: %(TextColor)s; titlebar-close-icon: none; titlebar-normal-icon: none; } QDockWidget::title { margin: 0; padding: 2px; subcontrol-origin: content; subcontrol-position: right top; text-align: left; background: #67baed; } QDockWidget::float-button { max-width: 12px; max-height: 12px; background-color:transparent; border:none; image: url(:/restore_inactive.png); } QDockWidget::float-button:hover { background-color:#227582; image: url(:/restore_active.png); } QDockWidget::float-button:pressed { padding: 0; background-color:#14464e; image: url(:/restore_active.png); } QDockWidget::close-button { max-width: 12px; max-height: 12px; background-color:transparent; border:none; image: url(:/close_inactive.png); } QDockWidget::close-button:hover { background-color:#ea5e00; image: url(:/close_active.png); } QDockWidget::close-button:pressed { background-color:#994005; image: url(:/close_active.png); padding: 0; } """ % dict(BackgroundColor='#99d9ea', TextColor='#202020', ScrollBar_Handle='#61b9e1', ScrollBar_Line='#7ecfe4', TableView_Corner='#8ae6d2', TableView_Header='#8ae6d2', TableView_Border='#eeeeee')) self.dockWidgetContents.setStyleSheet(""" QPushButton { min-height:23px; } """) self.dockWidget_QuickSend.setStyleSheet(""" QToolButton, QPushButton { background-color:#27b798; /*font-family:Consolas;*/ /*font-size:12px;*/ /*min-width:46px;*/ } QToolButton:hover, QPushButton:hover { background-color:#3bd5b4; } QToolButton:pressed, QPushButton:pressed { background-color:#1d8770; } """) self.dockWidgetContents_2.setStyleSheet(""" QPushButton { min-height:23px; min-width:50px; } """) w = self.frameGeometry().width() self._minBtn = QPushButton(self) self._minBtn.setGeometry(w - 103, 0, 28, 24) self._minBtn.clicked.connect(self.onMinimize) self._minBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/minimize_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/minimize_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/minimize_active.png); } """) self._maxBtn = QPushButton(self) self._maxBtn.setGeometry(w - 74, 0, 28, 24) self._maxBtn.clicked.connect(self.onMaximize) self.setMaximizeButton("maximize") self._closeBtn = QPushButton(self) self._closeBtn.setGeometry(w - 45, 0, 36, 24) self._closeBtn.clicked.connect(self.onExit) self._closeBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/close_inactive.png); } QPushButton:hover { background-color:#ea5e00; image: url(:/close_active.png); } QPushButton:pressed { background-color:#994005; image: url(:/close_active.png); } """) self.btnMenu = QtWidgets.QToolButton(self) self.btnMenu.setEnabled(True) self.btnMenu.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) self.btnMenu.setIcon(QtGui.QIcon(':/MyTerm.ico')) self.btnMenu.setText('Myterm ') self.btnMenu.setGeometry(3, 3, 80, 18) self.btnMenu.setMenu(self.menuMenu) self.btnMenu.setPopupMode(QtWidgets.QToolButton.InstantPopup) self.btnRefresh = QtWidgets.QToolButton(self) self.btnRefresh.setEnabled(True) self.btnRefresh.setIcon(QtGui.QIcon(':/refresh.ico')) self.btnRefresh.setGeometry(110, 3, 18, 18) self.btnRefresh.clicked.connect(self.onEnumPorts) self.verticalLayout_1.removeWidget(self.cmbPort) self.cmbPort.setParent(self) self.cmbPort.setGeometry(128, 3, 60, 18) self.verticalLayout_1.removeWidget(self.btnOpen) self.btnOpen.setParent(self) self.btnOpen.setGeometry(210, 3, 60, 18) def resizeEvent(self, event): w = event.size().width() self._minBtn.move(w - 103, 0) self._maxBtn.move(w - 74, 0) self._closeBtn.move(w - 45, 0) def onMinimize(self): self.showMinimized() def isMaximized(self): return ((self.windowState() == Qt.WindowMaximized)) def onMaximize(self): if self.isMaximized(): self.showNormal() self.setMaximizeButton("maximize") else: self.showMaximized() self.setMaximizeButton("restore") def setMaximizeButton(self, style): if "maximize" == style: self._maxBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/maximize_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/maximize_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/maximize_active.png); } """) elif "restore" == style: self._maxBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/restore_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/restore_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/restore_active.png); } """) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._isDragging = True self._dragPos = event.globalPos() - self.pos() event.accept() def mouseMoveEvent(self, event): if event.buttons( ) == Qt.LeftButton and self._isDragging and not self.isMaximized(): self.move(event.globalPos() - self._dragPos) event.accept() def mouseReleaseEvent(self, event): self._isDragging = False event.accept() def saveSettings(self): root = ET.Element("MyTerm") GUISettings = ET.SubElement(root, "GUISettings") PortCfg = ET.SubElement(GUISettings, "PortConfig") ET.SubElement(PortCfg, "port").text = self.cmbPort.currentText() ET.SubElement(PortCfg, "baudrate").text = self.cmbBaudRate.currentText() ET.SubElement(PortCfg, "databits").text = self.cmbDataBits.currentText() ET.SubElement(PortCfg, "parity").text = self.cmbParity.currentText() ET.SubElement(PortCfg, "stopbits").text = self.cmbStopBits.currentText() ET.SubElement( PortCfg, "rtscts").text = self.chkRTSCTS.isChecked() and "on" or "off" ET.SubElement( PortCfg, "xonxoff").text = self.chkXonXoff.isChecked() and "on" or "off" View = ET.SubElement(GUISettings, "View") ET.SubElement( View, "LocalEcho" ).text = self.actionLocal_Echo.isChecked() and "on" or "off" ET.SubElement( View, "ReceiveView").text = self._viewGroup.checkedAction().text() with open(get_config_path('MyTerm.xml'), 'w') as f: f.write('<?xml version="1.0" encoding="UTF-8"?>\n') f.write( ET.tostring(root, encoding='utf-8', pretty_print=True).decode("utf-8")) def loadSettings(self): if os.path.isfile(get_config_path("MyTerm.xml")): with open(get_config_path("MyTerm.xml"), 'r') as f: tree = safeET.parse(f) port = tree.findtext('GUISettings/PortConfig/port', default='') if port != '': self.cmbPort.setCurrentText(port) baudrate = tree.findtext('GUISettings/PortConfig/baudrate', default='38400') if baudrate != '': self.cmbBaudRate.setCurrentText(baudrate) databits = tree.findtext('GUISettings/PortConfig/databits', default='8') id = self.cmbDataBits.findText(databits) if id >= 0: self.cmbDataBits.setCurrentIndex(id) parity = tree.findtext('GUISettings/PortConfig/parity', default='None') id = self.cmbParity.findText(parity) if id >= 0: self.cmbParity.setCurrentIndex(id) stopbits = tree.findtext('GUISettings/PortConfig/stopbits', default='1') id = self.cmbStopBits.findText(stopbits) if id >= 0: self.cmbStopBits.setCurrentIndex(id) rtscts = tree.findtext('GUISettings/PortConfig/rtscts', default='off') if 'on' == rtscts: self.chkRTSCTS.setChecked(True) else: self.chkRTSCTS.setChecked(False) xonxoff = tree.findtext('GUISettings/PortConfig/xonxoff', default='off') if 'on' == xonxoff: self.chkXonXoff.setChecked(True) else: self.chkXonXoff.setChecked(False) LocalEcho = tree.findtext('GUISettings/View/LocalEcho', default='off') if 'on' == LocalEcho: self.actionLocal_Echo.setChecked(True) self._localEcho = True else: self.actionLocal_Echo.setChecked(False) self._localEcho = False ReceiveView = tree.findtext('GUISettings/View/ReceiveView', default='HEX(UPPERCASE)') if 'Ascii' in ReceiveView: self.actionAscii.setChecked(True) self._viewMode = VIEWMODE_ASCII elif 'lowercase' in ReceiveView: self.actionHex_lowercase.setChecked(True) self._viewMode = VIEWMODE_HEX_LOWERCASE elif 'UPPERCASE' in ReceiveView: self.actionHEX_UPPERCASE.setChecked(True) self._viewMode = VIEWMODE_HEX_UPPERCASE self.receiver_thread.setViewMode(self._viewMode) def closeEvent(self, event): self.saveLayout() self.saveQuickSend() self.saveSettings() event.accept() def initQuickSend(self): #self.quickSendTable.horizontalHeader().setDefaultSectionSize(40) #self.quickSendTable.horizontalHeader().setMinimumSectionSize(25) self.quickSendTable.setRowCount(50) self.quickSendTable.setColumnCount(3) self.quickSendTable.verticalHeader().setSectionsClickable(True) for row in range(50): self.initQuickSendButton(row) if os.path.isfile(get_config_path('QuickSend.csv')): self.loadQuickSend(get_config_path('QuickSend.csv')) self.quickSendTable.resizeColumnsToContents() def initQuickSendButton(self, row, cmd='cmd', opt='H', dat=''): if self.quickSendTable.cellWidget(row, 0) is None: item = QToolButton(self) item.setText(cmd) item.clicked.connect(self._signalMapQuickSend.map) self._signalMapQuickSend.setMapping(item, row) self.quickSendTable.setCellWidget(row, 0, item) else: self.quickSendTable.cellWidget(row, 0).setText(cmd) if self.quickSendTable.cellWidget(row, 1) is None: item = QToolButton(self) item.setText(opt) #item.setMaximumSize(QtCore.QSize(16, 16)) item.clicked.connect(self._signalMapQuickSendOpt.map) self._signalMapQuickSendOpt.setMapping(item, row) self.quickSendTable.setCellWidget(row, 1, item) else: self.quickSendTable.cellWidget(row, 1).setText(opt) if self.quickSendTable.item(row, 2) is None: self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat)) else: self.quickSendTable.item(row, 2).setText(dat) self.quickSendTable.setRowHeight(row, 16) def onSetSendHex(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('H') def onSetSendAsc(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('A') def onSetSendTFH(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('FH') def onSetSendTFA(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('FA') def onSetSendFB(self): item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1) item.setText('FB') def onQuickSendOptions(self, row): self._quickSendOptRow = row item = self.quickSendTable.cellWidget(row, 1) self.sendOptMenu.popup( item.mapToGlobal(QPoint(item.size().width(), item.size().height()))) def openQuickSend(self): fileName = QFileDialog.getOpenFileName(self, "Select a file", os.getcwd(), "CSV Files (*.csv)")[0] if fileName: self.loadQuickSend(fileName, notifyExcept=True) def saveQuickSend(self): # scan table rows = self.quickSendTable.rowCount() #cols = self.quickSendTable.columnCount() save_data = [[ self.quickSendTable.cellWidget(row, 0).text(), self.quickSendTable.cellWidget(row, 1).text(), self.quickSendTable.item(row, 2) is not None and self.quickSendTable.item(row, 2).text() or '' ] for row in range(rows)] #import pprint #pprint.pprint(save_data, width=120, compact=True) # write to file with open(get_config_path('QuickSend.csv'), 'w') as csvfile: csvwriter = csv.writer(csvfile, delimiter=',', lineterminator='\n') csvwriter.writerows(save_data) def loadQuickSend(self, path, notifyExcept=False): data = [] set_rows = 0 set_cols = 0 try: with open(path) as csvfile: csvData = csv.reader(csvfile) for row in csvData: data.append(row) set_rows = set_rows + 1 if len(row) > set_cols: set_cols = len(row) except IOError as e: print("({})".format(e)) if notifyExcept: QMessageBox.critical(self.defaultStyleWidget, "Open failed", str(e), QMessageBox.Close) return rows = self.quickSendTable.rowCount() cols = self.quickSendTable.columnCount() if rows < set_rows: rows = set_rows + 10 self.quickSendTable.setRowCount(rows) for row, rowdat in enumerate(data): if len(rowdat) >= 3: cmd, opt, dat = rowdat[0:3] self.initQuickSendButton(row, cmd, opt, dat) # self.quickSendTable.cellWidget(row, 0).setText(cmd) # self.quickSendTable.cellWidget(row, 1).setText(opt) # self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat)) self.quickSendTable.resizeColumnsToContents() #self.quickSendTable.resizeRowsToContents() def onQuickSend(self, row): if self.quickSendTable.item(row, 2) is not None: tablestring = self.quickSendTable.item(row, 2).text() format = self.quickSendTable.cellWidget(row, 1).text() if 'H' == format: self.transmitHex(tablestring) elif 'A' == format: self.transmitAsc(tablestring) elif 'FB' == format: try: with open(tablestring, 'rb') as f: bytes = f.read() self.transmitBytearray(bytes) except IOError as e: print("({})".format(e)) QMessageBox.critical(self.defaultStyleWidget, "Open failed", str(e), QMessageBox.Close) else: try: with open(tablestring, 'rt') as f: filestring = f.read() if 'FH' == format: self.transmitHex(filestring) elif 'FA' == format: self.transmitAsc(filestring) except IOError as e: print("({})".format(e)) QMessageBox.critical(self.defaultStyleWidget, "Open failed", str(e), QMessageBox.Close) def onSend(self): sendstring = self.txtEdtInput.toPlainText() self.transmitHex(sendstring) def transmitHex(self, hexstring): if len(hexstring) > 0: hexarray = [] _hexstring = hexstring.replace(' ', '') _hexstring = _hexstring.replace('\r', '') _hexstring = _hexstring.replace('\n', '') for i in range(0, len(_hexstring), 2): word = _hexstring[i:i + 2] if is_hex(word): hexarray.append(int(word, 16)) else: QMessageBox.critical(self.defaultStyleWidget, "Error", "'%s' is not hexadecimal." % (word), QMessageBox.Close) return self.transmitBytearray(bytearray(hexarray)) def transmitAsc(self, text): if len(text) > 0: byteArray = [ord(char) for char in text] self.transmitBytearray(bytearray(byteArray)) def transmitBytearray(self, byteArray): if self.serialport.isOpen(): try: self.serialport.write(byteArray) except Exception as e: QMessageBox.critical(self.defaultStyleWidget, "Exception in transmit", str(e), QMessageBox.Close) print("Exception in transmitBytearray(%s)" % text) else: if self._viewMode == VIEWMODE_ASCII: text = byteArray.decode('unicode_escape') elif self._viewMode == VIEWMODE_HEX_LOWERCASE: text = ''.join('%02x ' % t for t in byteArray) elif self._viewMode == VIEWMODE_HEX_UPPERCASE: text = ''.join('%02X ' % t for t in byteArray) self.appendOutputText("\n%s T->:%s" % (self.timestamp(), text), Qt.blue) def onReaderExcept(self, e): self.closePort() QMessageBox.critical(self.defaultStyleWidget, "Read failed", str(e), QMessageBox.Close) def timestamp(self): return datetime.datetime.now().time().isoformat()[:-3] def onReceive(self, data): self.appendOutputText("\n%s R<-:%s" % (self.timestamp(), data)) def appendOutputText(self, data, color=Qt.black): # the qEditText's "append" methon will add a unnecessary newline. # self.txtEdtOutput.append(data.decode('utf-8')) tc = self.txtEdtOutput.textColor() self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End) self.txtEdtOutput.setTextColor(QtGui.QColor(color)) self.txtEdtOutput.insertPlainText(data) self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End) self.txtEdtOutput.setTextColor(tc) def getPort(self): return self.cmbPort.currentText() def getDataBits(self): return { '5': serial.FIVEBITS, '6': serial.SIXBITS, '7': serial.SEVENBITS, '8': serial.EIGHTBITS }[self.cmbDataBits.currentText()] def getParity(self): return { 'None': serial.PARITY_NONE, 'Even': serial.PARITY_EVEN, 'Odd': serial.PARITY_ODD, 'Mark': serial.PARITY_MARK, 'Space': serial.PARITY_SPACE }[self.cmbParity.currentText()] def getStopBits(self): return { '1': serial.STOPBITS_ONE, '1.5': serial.STOPBITS_ONE_POINT_FIVE, '2': serial.STOPBITS_TWO }[self.cmbStopBits.currentText()] def openPort(self): if self.serialport.isOpen(): return _port = self.getPort() if '' == _port: QMessageBox.information(self.defaultStyleWidget, "Invalid parameters", "Port is empty.") return _baudrate = self.cmbBaudRate.currentText() if '' == _baudrate: QMessageBox.information(self.defaultStyleWidget, "Invalid parameters", "Baudrate is empty.") return self.serialport.port = _port self.serialport.baudrate = _baudrate self.serialport.bytesize = self.getDataBits() self.serialport.stopbits = self.getStopBits() self.serialport.parity = self.getParity() self.serialport.rtscts = self.chkRTSCTS.isChecked() self.serialport.xonxoff = self.chkXonXoff.isChecked() # self.serialport.timeout = THREAD_TIMEOUT # self.serialport.writeTimeout = SERIAL_WRITE_TIMEOUT try: self.serialport.open() except Exception as e: QMessageBox.critical(self.defaultStyleWidget, "Could not open serial port", str(e), QMessageBox.Close) else: self._start_reader() self.setWindowTitle("%s on %s [%s, %s%s%s%s%s]" % ( appInfo.title, self.serialport.portstr, self.serialport.baudrate, self.serialport.bytesize, self.serialport.parity, self.serialport.stopbits, self.serialport.rtscts and ' RTS/CTS' or '', self.serialport.xonxoff and ' Xon/Xoff' or '', )) pal = self.btnOpen.palette() pal.setColor(QtGui.QPalette.Button, QtGui.QColor(0, 0xff, 0x7f)) self.btnOpen.setAutoFillBackground(True) self.btnOpen.setPalette(pal) self.btnOpen.setText('Close') self.btnOpen.update() def closePort(self): if self.serialport.isOpen(): self._stop_reader() self.serialport.close() self.setWindowTitle(appInfo.title) pal = self.btnOpen.style().standardPalette() self.btnOpen.setAutoFillBackground(True) self.btnOpen.setPalette(pal) self.btnOpen.setText('Open') self.btnOpen.update() def _start_reader(self): """Start reader thread""" self.receiver_thread.start() def _stop_reader(self): """Stop reader thread only, wait for clean exit of thread""" self.receiver_thread.join() def onTogglePrtCfgPnl(self): if self.actionPort_Config_Panel.isChecked(): self.dockWidget_PortConfig.show() else: self.dockWidget_PortConfig.hide() def onToggleQckSndPnl(self): if self.actionQuick_Send_Panel.isChecked(): self.dockWidget_QuickSend.show() else: self.dockWidget_QuickSend.hide() def onToggleHexPnl(self): if self.actionSend_Hex_Panel.isChecked(): self.dockWidget_SendHex.show() else: self.dockWidget_SendHex.hide() def onVisiblePrtCfgPnl(self, visible): self.actionPort_Config_Panel.setChecked(visible) def onVisibleQckSndPnl(self, visible): self.actionQuick_Send_Panel.setChecked(visible) def onVisibleHexPnl(self, visible): self.actionSend_Hex_Panel.setChecked(visible) def onLocalEcho(self): self._localEcho = self.actionLocal_Echo.isChecked() def onAlwaysOnTop(self): if self.actionAlways_On_Top.isChecked(): style = self.windowFlags() self.setWindowFlags(style | Qt.WindowStaysOnTopHint) self.show() else: style = self.windowFlags() self.setWindowFlags(style & ~Qt.WindowStaysOnTopHint) self.show() def onOpen(self): if self.serialport.isOpen(): self.closePort() else: self.openPort() def onClear(self): self.txtEdtOutput.clear() def onSaveLog(self): fileName = QFileDialog.getSaveFileName( self, "Save as", os.getcwd(), "Log files (*.log);;Text files (*.txt);;All files (*.*)")[0] if fileName: import codecs with codecs.open(fileName, 'w', 'utf-8') as f: f.write(self.txtEdtOutput.toPlainText()) def moveScreenCenter(self): w = self.frameGeometry().width() h = self.frameGeometry().height() desktop = QDesktopWidget() screenW = desktop.screen().width() screenH = desktop.screen().height() self.setGeometry((screenW - w) / 2, (screenH - h) / 2, w, h) def onEnumPorts(self): self.cmbPort.clear() for p in enum_ports(): self.cmbPort.addItem(p) def onAbout(self): QMessageBox.about(self.defaultStyleWidget, "About MyTerm", appInfo.aboutme) def onAboutQt(self): QMessageBox.aboutQt(self.defaultStyleWidget) def onExit(self): if self.serialport.isOpen(): self.closePort() self.close() def restoreLayout(self): if os.path.isfile(get_config_path("UILayout.dat")): try: f = open(get_config_path("UILayout.dat"), 'rb') geometry, state = pickle.load(f) self.restoreGeometry(geometry) self.restoreState(state) except Exception as e: print("Exception on restoreLayout, {}".format(e)) else: try: f = QFile(':/default_layout_qt5.dat') f.open(QIODevice.ReadOnly) geometry, state = pickle.loads(f.readAll()) self.restoreGeometry(geometry) self.restoreState(state) except Exception as e: print("Exception on restoreLayout, {}".format(e)) def saveLayout(self): with open(get_config_path("UILayout.dat"), 'wb') as f: pickle.dump((self.saveGeometry(), self.saveState()), f) def syncMenu(self): self.actionPort_Config_Panel.setChecked( not self.dockWidget_PortConfig.isHidden()) self.actionQuick_Send_Panel.setChecked( not self.dockWidget_QuickSend.isHidden()) self.actionSend_Hex_Panel.setChecked( not self.dockWidget_SendHex.isHidden()) def onViewChanged(self): checked = self._viewGroup.checkedAction() if checked is None: self._viewMode = VIEWMODE_HEX_UPPERCASE self.actionHEX_UPPERCASE.setChecked(True) else: if 'Ascii' in checked.text(): self._viewMode = VIEWMODE_ASCII elif 'lowercase' in checked.text(): self._viewMode = VIEWMODE_HEX_LOWERCASE elif 'UPPERCASE' in checked.text(): self._viewMode = VIEWMODE_HEX_UPPERCASE self.receiver_thread.setViewMode(self._viewMode)
class MainWindow(QMainWindow): #Main window of the Serie Browser def __init__(self, series_list, nameWindow): # series_list = list of series to be displayed and retrieved from the API, # nameWindow = name to be displayed on top of the indow super().__init__() self.__nDisplay = len(series_list) # Number of series displayed self.__seriesList = series_list # List of series displayed at first # Load .ui designed on Qt self.__UI = uic.loadUi('main.ui', self) # Show window on desktop self.showMaximized() # Name of the window self.__textLabel = QLabel(nameWindow) self.__textLabel.setText(nameWindow) self.__textLabel.setTextFormat(QtCore.Qt.RichText) self.__textLabel.setText( "<span style=' font-size:16pt; font-weight:600; color:#aa0000;'>" + nameWindow + "</span>") self.__UI.horizontalLayout.addWidget(self.__textLabel) # Define a Scroll area for serie display self.__serieWind = QWidget() # Create a widget for the scroll area self.__scrollArea = QScrollArea() # I Create a Scroll Area self.__scrollArea.setWidgetResizable(True) self.__scrollArea.setWidget( self.__serieWind) # Insert the scroll area in the widget self.__gridLayout = QGridLayout( ) # Create a grid layout for the scroll area self.__serieWind.setLayout( self.__gridLayout) # Insert the grid layout in the scroll area self.__UI.horizontalLayout_2.addWidget( self.__scrollArea ) # Insert the scroll area in the main horizontal layout from .ui self.__serieWind.setObjectName("serieWind") self.setStyleSheet("#serieWind{background-color: black;}" ) # Define color of the scroll area # Add research bar self.__searchWidget = QLineEdit() self.__searchWidget.setFixedSize(150, 40) self.__searchWidget.returnPressed.connect( self.slot_research ) # Connect the signal return pressed to slot_research self.__UI.horizontalLayout.addWidget( self.__searchWidget) # Insert research bar in layout from .ui # Add research button self.__researchButton = QPushButton("Search") self.__researchButton.setFixedSize(100, 40) self.__researchButton.pressed.connect( self.slot_research) # Connect the signal pressed to slot_research self.__UI.horizontalLayout.addWidget( self.__researchButton ) # Insert research button in layout from .ui # Add favourites title # Favourites Layout self.__favLayout = QVBoxLayout() self.__UI.horizontalLayout_2.addLayout(self.__favLayout) # Favourites Title self.__favouritesTitle = QLabel("Favourites") self.__favouritesTitle.setText( "<span style=' font-size:16pt; font-weight:600; color:#aa0000;'> Favourites </span>" ) self.__favLayout.addWidget(self.__favouritesTitle) # Add favourites list to the layout with a QListWidget self.__favouritesWidget = QListWidget() self.__favouritesWidget.setMaximumWidth(250) self.__favLayout.addWidget(self.__favouritesWidget) self.__favButtonsTopLayout = QHBoxLayout() self.__favButtonsBottomLayout = QHBoxLayout() self.__favLayout.addLayout(self.__favButtonsTopLayout) self.__favLayout.addLayout(self.__favButtonsBottomLayout) # Add More Info button for favourites list self.__favMoreInfoButton = QPushButton("More Info") self.__favButtonsTopLayout.addWidget(self.__favMoreInfoButton) self.__favMoreInfoButton.clicked.connect( self.slot_open_serie_window ) # Connect clicked signal of MoreInfo button to slot_open_serie_window # Add Remove favourite button for favourites list self.__removeFavButton = QPushButton("Remove Favourite") self.__favButtonsTopLayout.addWidget(self.__removeFavButton) self.__removeFavButton.clicked.connect( self.slot_remove_favourite ) # Connect clicked signal to Remove button to slot_remove_favourites # Magic Button : finds a serie that has an episode that is going to be aired soon self.__magicButton = QPushButton("MAGIC BUTTON") self.__magicButton.clicked.connect(self.slot_magic_add_to_favourites) self.__favButtonsBottomLayout.addWidget(self.__magicButton) # Clear Favourites Button self.__clearFavButton = QPushButton("Clear Favourites") self.__favButtonsBottomLayout.addWidget(self.__clearFavButton) self.__clearFavButton.clicked.connect(self.slot_clear_favourites) # Notifications checkbox self.__enableNotifications = QCheckBox() self.__enableNotifications.setText("Enable Notifications") self.__enableNotifications.setChecked(True) self.__enableNotifications.stateChanged.connect( self.slot_change_notifications_state) self.__favLayout.addWidget(self.__enableNotifications) #Create and load favourites list self.__favouritesIDList = [] # Creation of a ID list of favourites self.__favouriteSeries = [ ] # Creation of list of favourites of class Serie self.__fileName = "favourites" #Creation of a file for pickler if (os.path.exists(self.__fileName)) and (os.path.getsize( self.__fileName) > 0): #Check if the file exists with open(self.__fileName, "rb") as favFile: depickler = pickle.Unpickler(favFile) self.__favouriteSeries = depickler.load( ) #Loading previously saved favourites from file for i in range( len(self.__favouriteSeries) ): #Loop to add favourites series to favourite QWidgetList and create favouritesIDList favItem = self.__favouriteSeries[i].name self.__favouritesWidget.addItem(favItem) self.__favouritesIDList += [self.__favouriteSeries[i].id] #Alert self.__alertWindow = Afficher(self.__favouriteSeries, self.__enableNotifications.isChecked(), self) self.__alertWindow.start() # Signal Mapper to connect slot_add_to_favourites to class MainWidget self.__sigMapper = QSignalMapper(self) self.__sigMapper.mapped.connect(self.slot_add_to_favourites) # Display series MainWidgets on MainWindow self.__numberSeriesWidgetLines = ceil(self.__nDisplay / 5) # 5 widgets per line maximum self.__positions = [(i + 1, j) for i in range(self.__numberSeriesWidgetLines) for j in range(5) ] # Define positions for the grid layout self.__seriesWidgetList = [] for i in range(len(self.__seriesList) ): #Loop for creation and display of serie MainWidgets currentWidget = MainWidget(i, self.__seriesList[i]) self.__seriesWidgetList += [currentWidget] self.__gridLayout.addWidget(currentWidget, *self.__positions[i]) i += 1 self.__sigMapper.setMapping(currentWidget.favButton, currentWidget.id) currentWidget.favButton.clicked.connect( self.__sigMapper.map ) #Connect add to favourite button of MainWidget to signal mapper # Getters and setters for seriesList @property def seriesList(self): return self.__seriesList @seriesList.setter def seriesList(self, newSeriesList): self.__seriesList = newSeriesList # Methods # Slot to add a favourite to the QListWidget def slot_add_to_favourites(self, id): #id = id of the serie to add to favourites if (id not in self.__favouritesIDList ): #Check if the serie is already in the favourite self.__favouritesIDList += [id] serie = searchSerie(id) nm = serie.name self.__favouriteSeries += [serie] self.__favouritesWidget.addItem(nm) self.__alertWindow.quit() self.__alertWindow = Afficher( self.__favouriteSeries, self.__enableNotifications.isChecked(), self) self.__alertWindow.start() with open(self.__fileName, "wb") as favFile: pickler = pickle.Pickler(favFile) pickler.dump( self.__favouriteSeries) # Saving refreshed favourites list else: # If the serie is already in the favourites, displaying an error message error_dialog = QMessageBox.information(None, "Error", "Favourite already added.", QMessageBox.Cancel) # The slot that adds a serie that has a soon-to-be-aired episode to favourites def slot_magic_add_to_favourites(self): serie = findForthcomingSerie(self.__favouritesIDList) id = serie.id self.__favouritesIDList += [id] nm = serie.name self.__favouriteSeries += [serie] self.__favouritesWidget.addItem(nm) self.__alertWindow.quit() self.__alertWindow = Afficher(self.__favouriteSeries, self.__enableNotifications.isChecked(), self) self.__alertWindow.start() with open(self.__fileName, "wb") as favFile: pickler = pickle.Pickler(favFile) pickler.dump( self.__favouriteSeries) # Saving refreshed favourites list # Slot to remove a serie from user's favourites list def slot_remove_favourite(self): idx = self.__favouritesWidget.currentRow( ) # The index of the selected row if ( idx == -1 ): # Exception if the user didn't select a favourite before clicking "remove favourite" button QMessageBox.information(None, "Error", "You didn't select a favourite.", QMessageBox.Ok) else: del self.__favouriteSeries[ idx] # The favourites widget list and the inner user's favourites list are sorted in the same order del self.__favouritesIDList[idx] self.__favouritesWidget.takeItem(idx) self.__alertWindow.quit() self.__alertWindow = Afficher( self.__favouriteSeries, self.__enableNotifications.isChecked(), self) self.__alertWindow.start() with open(self.__fileName, "wb") as favFile: pickler = pickle.Pickler(favFile) pickler.dump( self.__favouriteSeries) # Saving refreshed favourites list # Slot that clears user's favourites list def slot_clear_favourites(self): self.__favouriteSeries = [] self.__favouritesIDList = [] self.__favouritesWidget.clear() self.__alertWindow.quit() self.__alertWindow = Afficher(self.__favouriteSeries, self.__enableNotifications.isChecked(), self) self.__alertWindow.start() # Clear favourites file with open(self.__fileName, "wb") as favFile: pickler = pickle.Pickler(favFile) pickler.dump( self.__favouriteSeries) # Saving refreshed favourites list # Slot to open window with more information for favourites def slot_open_serie_window(self): idx = self.__favouritesWidget.currentRow() try: if idx == -1: raise ValueError("No row is selected in favourites widget.") else: ser = self.__favouriteSeries[idx] self.__newWindow = NewWindow(ser, self) self.__newWindow.exec_() except ValueError: # If the user hasn't selected an element from the list, currentRow returns -1 and an error dialog is displayed QMessageBox.information(None, "Error", "You didn't select a serie in your list.", QMessageBox.Cancel) # Slot to do the research def slot_research(self): self.__searchText = self.__searchWidget.text() # Delete all the widgets displayed in scroll area for i in reversed(range(self.__gridLayout.count())): self.__gridLayout.itemAt(i).widget().setParent(None) # Research on the API self.__seriesList = searchSeries(self.__searchText) # Add results of the research to the scroll area for i in range(len(self.__seriesList)): currentWidget = MainWidget(i, self.__seriesList[i]) self.__gridLayout.addWidget(currentWidget, *self.__positions[i]) self.__sigMapper.setMapping(currentWidget.favButton, currentWidget.id) currentWidget.favButton.clicked.connect(self.__sigMapper.map) # A slot that is activated when the notifications checkbox is checked or unchecked. It starts and stops the notifications thread def slot_change_notifications_state(self): if (self.__alertWindow.notificationsEnabled): self.__alertWindow.notificationsEnabled = False self.__alertWindow.quit() else: self.__alertWindow.notificationsEnabled = True self.__alertWindow = Afficher( self.__favouriteSeries, self.__enableNotifications.isChecked(), self) self.__alertWindow.start()
class processManagerDialog(QDialog): def __init__(self, dataTab=None, parent=None): super(processManagerDialog, self).__init__(parent=parent) self.installDir = GENERAL.get_value('INSTALL_DIR') self.mainLayout = QVBoxLayout(self) self.upperLayout = QVBoxLayout(self) self.lowerLayout = QVBoxLayout(self) self.menuBar = QMenuBar(self) self.toolBar = QToolBar(self) self.processTable = QTableView(self) self.tableModel = functionModel(self) self.processTablePopMenu = QMenu(self) self.importMenu = QMenu(self) self.signalMapper = QSignalMapper(self) # self.MLProject = MLProject self.openedData = GENERAL.get_value('OPENED_DATA') self.MainTabWindow = GENERAL.get_value('TABWINDOW') # tool bar self.addProcessAction = None self.importProcessAction = None # data self.processList = [] self.processDict = set() self.dataTab = dataTab self.targetDataset = dataTab.filename if dataTab else None # self.curDir = GENERAL.get_value('INSTALL_DIR') self.initUI() self.initRightClickMenu() def initUI(self): if self.targetDataset: print(self.targetDataset) self.setLayout(self.mainLayout) self.setMinimumSize(800, 600) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.upperLayout.addWidget(self.menuBar) self.upperLayout.addWidget(self.toolBar) self.lowerLayout.addWidget(self.processTable) self.lowerLayout.setContentsMargins(10, 0, 10, 10) self.mainLayout.addLayout(self.upperLayout) self.mainLayout.addLayout(self.lowerLayout) # menu fileMenu = QMenu('File', self) f1 = QAction('add process', self) f1.triggered.connect(self.addProcess) fileMenu.addActions([f1]) self.menuBar.addMenu(fileMenu) # toolbar self.addProcessAction = QAction(QIcon('./res/add_red_small.ico'), 'add process', self) self.addProcessAction.setStatusTip('add process') self.addProcessAction.triggered.connect(self.addProcess) self.importProcessAction = QAction(QIcon('./res/import.ico'), 'import process', self) self.importProcessAction.setStatusTip('import process') self.importProcessAction.triggered.connect(self.importProcess) self.importProcessAction.setEnabled(False) self.toolBar.addActions([self.addProcessAction, self.importProcessAction]) # table self.processTable.setModel(self.tableModel) self.processTable.autoScrollMargin() self.processTable.setSelectionBehavior(QTableView.SelectRows) self.processTable.selectionModel().selectionChanged.connect(self.itemSelected) # table double click self.processTable.doubleClicked.connect(self.editDescribe) self.tableModel.notEmpty.connect(lambda: self.setTableHeaderStyle()) self.loadProcessList() # init mapper self.signalMapper.mapped[int].connect(self.addProcess) def loadProcessList(self): localProcessFile = os.path.join(self.installDir, 'local', 'localProcess.ml') if os.path.exists(localProcessFile): with open(localProcessFile, 'rb') as f: self.processDict, self.processList = pickle.load(f) self.tableModel.loadProcessList(self.processList) else: print('local process file not exist') def saveProcessList(self): localProcessFile = os.path.join(self.installDir, 'local', 'localProcess.ml') with open(localProcessFile, 'wb') as f: pickle.dump((self.processDict, self.processList), f) def addProcess(self): dialog = addProcessDialog(self) r = dialog.exec_() index = len(self.processList) if r == QDialog.Accepted: for i, f in enumerate(dialog.functionList): f['describe'] = '' f['ID'] = hashlib.new('md5', ((f['name'] + f['path']).encode('UTF-8'))).hexdigest() if f['ID'] not in self.processDict: self.processList.insert(index + i, f) self.processDict.add(f['ID']) self.tableModel.addProcessList(self.processList) self.saveProcessList() def importProcess(self): # import process to process list if self.dataTab: pass else: self.importMenu.clear() tabList = [] for index, tab in enumerate(self.openedData): t = QAction(os.path.basename(tab.filename), self) t.triggered.connect(self.signalMapper.map) self.signalMapper.setMapping(t, index) tabList.append(t) self.importMenu.addActions(tabList) # pop menu self.importMenu.exec(QCursor.pos()) def addProcess(self, index): tab = self.openedData[index] print(tab.filename, index) def lightUpDataTab(self, index): """ future feature: light up tab in the main tab window when mouse hover on the button """ pass def itemSelected(self, selected: QItemSelection, deselected: QItemSelection): print(selected, deselected) if selected.count() == 0 or not self.openedData or len(self.openedData) == 0: self.importProcessAction.setEnabled(False) else: self.importProcessAction.setEnabled(True) def delProcess(self, point: QPoint): if point: modelIndexList = self.processTable.selectionModel().selectedRows() modelIndex = [modelIndexList[i].row() for i in range(len(modelIndexList))] self.tableModel.beginResetModel() for row in modelIndex: p = self.processList.pop(row) self.processDict.remove(p['ID']) self.saveProcessList() self.tableModel.rows -= len(modelIndex) self.tableModel.endResetModel() def setTableHeaderStyle(self): self.processTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents) self.processTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents) def initRightClickMenu(self): self.processTable.setContextMenuPolicy(Qt.CustomContextMenu) self.processTable.customContextMenuRequested.connect(self.onProcessTableRightClicked) def onProcessTableRightClicked(self, point: QPoint): self.processTablePopMenu.clear() # numeric m1 = QAction('delete process', self) m1.triggered.connect(lambda: self.delProcess(point)) m2 = QAction('edit describe', self) m2.triggered.connect(lambda: self.editDescribe(point)) m3 = QAction('edit source file', self) m3.triggered.connect(lambda: self.editSource(point)) processTableActionList = [m1, m2, m3] self.processTablePopMenu.addActions(processTableActionList) # test action t1 = QAction('test', self) # t1.triggered.connect() self.processTablePopMenu.addAction(t1) # pop menu self.processTablePopMenu.exec(QCursor.pos()) def editDescribe(self, point: QPoint): if isinstance(point, QPoint): row = self.processTable.indexAt(point).row() elif isinstance(point, QModelIndex): row = point.row() dialog = describeEditDialog(self.processList[row]['describe'], self) r = dialog.exec_() if r == QDialog.Accepted: self.processList[row]['describe'] = dialog.describe self.tableModel.update() def editSource(self, point: QPoint): index = self.processTable.indexAt(point) print(index.row(), index.column()) pass def closeEvent(self, event: QCloseEvent): self.saveProcessList() event.accept()
def main(icon_spec): app = QApplication(sys.argv) main_window = QMainWindow() def sigint_handler(*args): main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler signal_timer = QTimer() signal_timer.start(100) signal_timer.timeout.connect(lambda: None) tool_bar = QToolBar() main_window.addToolBar(Qt.TopToolBarArea, tool_bar) table_view = QTableView() table_view.setSelectionBehavior(QAbstractItemView.SelectRows) table_view.setSelectionMode(QAbstractItemView.SingleSelection) table_view.setSortingEnabled(True) main_window.setCentralWidget(table_view) proxy_model = QSortFilterProxyModel() proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) proxy_model.setFilterKeyColumn(1) table_view.setModel(proxy_model) proxy_model.layoutChanged.connect(table_view.resizeRowsToContents) item_model = QStandardItemModel() proxy_model.setSourceModel(item_model) # get all icons and their available sizes icons = [] all_sizes = set([]) QIcon.setThemeName('Tango') for context, icon_names in icon_spec: for icon_name in icon_names: icon = QIcon.fromTheme(icon_name) sizes = [] for size in icon.availableSizes(): size = (size.width(), size.height()) sizes.append(size) all_sizes.add(size) sizes.sort() icons.append({ 'context': context, 'icon_name': icon_name, 'icon': icon, 'sizes': sizes, }) all_sizes = list(all_sizes) all_sizes.sort() # input field for filter def filter_changed(value): proxy_model.setFilterRegExp(value) table_view.resizeRowsToContents() filter_line_edit = QLineEdit() filter_line_edit.setMaximumWidth(200) filter_line_edit.setPlaceholderText('Filter name') filter_line_edit.setToolTip( 'Filter name optionally using regular expressions (' + QKeySequence(QKeySequence.Find).toString() + ')') filter_line_edit.textChanged.connect(filter_changed) tool_bar.addWidget(filter_line_edit) # actions to toggle visibility of available sizes/columns def action_toggled(index): column = 2 + index table_view.setColumnHidden(column, not table_view.isColumnHidden(column)) table_view.resizeColumnsToContents() table_view.resizeRowsToContents() signal_mapper = QSignalMapper() for i, size in enumerate(all_sizes): action = QAction('%dx%d' % size, tool_bar) action.setCheckable(True) action.setChecked(True) tool_bar.addAction(action) action.toggled.connect(signal_mapper.map) signal_mapper.setMapping(action, i) # set tool tip and handle key sequence tool_tip = 'Toggle visibility of column' if i < 10: digit = ('%d' % (i + 1))[-1] tool_tip += ' (%s)' % QKeySequence('Ctrl+%s' % digit).toString() action.setToolTip(tool_tip) signal_mapper.mapped.connect(action_toggled) # label columns header_labels = ['context', 'name'] for width, height in all_sizes: header_labels.append('%dx%d' % (width, height)) item_model.setColumnCount(len(header_labels)) item_model.setHorizontalHeaderLabels(header_labels) # fill rows item_model.setRowCount(len(icons)) for row, icon_data in enumerate(icons): # context item = QStandardItem(icon_data['context']) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, 0, item) # icon name item = QStandardItem(icon_data['icon_name']) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, 1, item) for index_in_all_sizes, size in enumerate(all_sizes): column = 2 + index_in_all_sizes if size in icon_data['sizes']: # icon as pixmap to keep specific size item = QStandardItem('') pixmap = icon_data['icon'].pixmap(size[0], size[1]) item.setData(pixmap, Qt.DecorationRole) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, column, item) else: # single space to be sortable against icons item = QStandardItem(' ') item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, column, item) table_view.resizeColumnsToContents() # manually set row heights because resizeRowsToContents is not working properly for row, icon_data in enumerate(icons): if len(icon_data['sizes']) > 0: max_size = icon_data['sizes'][-1] table_view.setRowHeight(row, max_size[1]) # enable focus find (ctrl+f) and toggle columns (ctrl+NUM) def main_window_keyPressEvent(self, event, old_keyPressEvent=QMainWindow.keyPressEvent): if event.matches(QKeySequence.Find): filter_line_edit.setFocus() return if event.modifiers() == Qt.ControlModifier and event.key( ) >= Qt.Key_0 and event.key() <= Qt.Key_9: index = event.key() - Qt.Key_1 if event.key() == Qt.Key_0: index += 10 action = signal_mapper.mapping(index) if action: action.toggle() return old_keyPressEvent(self, event) main_window.keyPressEvent = MethodType(main_window_keyPressEvent, table_view) # enable copy (ctrl+c) name of icon to clipboard def table_view_keyPressEvent(self, event, old_keyPressEvent=QTableView.keyPressEvent): if event.matches(QKeySequence.Copy): selection_model = self.selectionModel() if selection_model.hasSelection(): index = selection_model.selectedRows()[0] source_index = self.model().mapToSource(index) item = self.model().sourceModel().item(source_index.row(), 1) icon_name = item.data(Qt.EditRole) app.clipboard().setText(icon_name.toString()) return old_keyPressEvent(self, event) table_view.keyPressEvent = MethodType(table_view_keyPressEvent, table_view) main_window.showMaximized() return app.exec_()
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.createActions() self.createMenus() self.createToolBars() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("MDI") def closeEvent(self, event): self.mdiArea.closeAllSubWindows() if self.mdiArea.currentSubWindow(): event.ignore() else: self.writeSettings() event.accept() def newFile(self): child = self.createMdiChild() child.newFile() child.show() def open(self): fileName, _ = QFileDialog.getOpenFileName(self) if fileName: existing = self.findMdiChild(fileName) if existing: self.mdiArea.setActiveSubWindow(existing) return child = self.createMdiChild() if child.loadFile(fileName): self.statusBar().showMessage("File loaded", 2000) child.show() else: child.close() def save(self): if self.activeMdiChild() and self.activeMdiChild().save(): self.statusBar().showMessage("File saved", 2000) def saveAs(self): if self.activeMdiChild() and self.activeMdiChild().saveAs(): self.statusBar().showMessage("File saved", 2000) def cut(self): if self.activeMdiChild(): self.activeMdiChild().cut() def copy(self): if self.activeMdiChild(): self.activeMdiChild().copy() def paste(self): if self.activeMdiChild(): self.activeMdiChild().paste() def about(self): QMessageBox.about(self, "About MDI", "The <b>MDI</b> example demonstrates how to write multiple " "document interface applications using Qt.") def updateMenus(self): hasMdiChild = (self.activeMdiChild() is not None) self.saveAct.setEnabled(hasMdiChild) self.saveAsAct.setEnabled(hasMdiChild) self.pasteAct.setEnabled(hasMdiChild) self.closeAct.setEnabled(hasMdiChild) self.closeAllAct.setEnabled(hasMdiChild) self.tileAct.setEnabled(hasMdiChild) self.cascadeAct.setEnabled(hasMdiChild) self.nextAct.setEnabled(hasMdiChild) self.previousAct.setEnabled(hasMdiChild) self.separatorAct.setVisible(hasMdiChild) hasSelection = (self.activeMdiChild() is not None and self.activeMdiChild().textCursor().hasSelection()) self.cutAct.setEnabled(hasSelection) self.copyAct.setEnabled(hasSelection) def updateWindowMenu(self): self.windowMenu.clear() self.windowMenu.addAction(self.closeAct) self.windowMenu.addAction(self.closeAllAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.tileAct) self.windowMenu.addAction(self.cascadeAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.nextAct) self.windowMenu.addAction(self.previousAct) self.windowMenu.addAction(self.separatorAct) windows = self.mdiArea.subWindowList() self.separatorAct.setVisible(len(windows) != 0) for i, window in enumerate(windows): child = window.widget() text = "%d %s" % (i + 1, child.userFriendlyCurrentFile()) if i < 9: text = '&' + text action = self.windowMenu.addAction(text) action.setCheckable(True) action.setChecked(child is self.activeMdiChild()) action.triggered.connect(self.windowMapper.map) self.windowMapper.setMapping(action, window) def createMdiChild(self): child = MdiChild() self.mdiArea.addSubWindow(child) child.copyAvailable.connect(self.cutAct.setEnabled) child.copyAvailable.connect(self.copyAct.setEnabled) return child def createActions(self): self.newAct = QAction(QIcon(':/images/new.png'), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) self.openAct = QAction(QIcon(':/images/open.png'), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) self.saveAct = QAction(QIcon(':/images/save.png'), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) self.saveAsAct = QAction("Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) self.cutAct = QAction(QIcon(':/images/cut.png'), "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) self.copyAct = QAction(QIcon(':/images/copy.png'), "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) self.pasteAct = QAction(QIcon(':/images/paste.png'), "&Paste", self, shortcut=QKeySequence.Paste, statusTip="Paste the clipboard's contents into the current selection", triggered=self.paste) self.closeAct = QAction("Cl&ose", self, statusTip="Close the active window", triggered=self.mdiArea.closeActiveSubWindow) self.closeAllAct = QAction("Close &All", self, statusTip="Close all the windows", triggered=self.mdiArea.closeAllSubWindows) self.tileAct = QAction("&Tile", self, statusTip="Tile the windows", triggered=self.mdiArea.tileSubWindows) self.cascadeAct = QAction("&Cascade", self, statusTip="Cascade the windows", triggered=self.mdiArea.cascadeSubWindows) self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, statusTip="Move the focus to the next window", triggered=self.mdiArea.activateNextSubWindow) self.previousAct = QAction("Pre&vious", self, shortcut=QKeySequence.PreviousChild, statusTip="Move the focus to the previous window", triggered=self.mdiArea.activatePreviousSubWindow) self.separatorAct = QAction(self) self.separatorAct.setSeparator(True) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, statusTip="Show the Qt library's About box", triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.saveAsAct) self.fileMenu.addSeparator() action = self.fileMenu.addAction("Switch layout direction") action.triggered.connect(self.switchLayoutDirection) self.fileMenu.addAction(self.exitAct) self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.cutAct) self.editMenu.addAction(self.copyAct) self.editMenu.addAction(self.pasteAct) self.windowMenu = self.menuBar().addMenu("&Window") self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def createToolBars(self): self.fileToolBar = self.addToolBar("File") self.fileToolBar.addAction(self.newAct) self.fileToolBar.addAction(self.openAct) self.fileToolBar.addAction(self.saveAct) self.editToolBar = self.addToolBar("Edit") self.editToolBar.addAction(self.cutAct) self.editToolBar.addAction(self.copyAct) self.editToolBar.addAction(self.pasteAct) def createStatusBar(self): self.statusBar().showMessage("Ready") def readSettings(self): settings = QSettings('Trolltech', 'MDI Example') pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(400, 400)) self.move(pos) self.resize(size) def writeSettings(self): settings = QSettings('Trolltech', 'MDI Example') settings.setValue('pos', self.pos()) settings.setValue('size', self.size()) def activeMdiChild(self): activeSubWindow = self.mdiArea.activeSubWindow() if activeSubWindow: return activeSubWindow.widget() return None def findMdiChild(self, fileName): canonicalFilePath = QFileInfo(fileName).canonicalFilePath() for window in self.mdiArea.subWindowList(): if window.widget().currentFile() == canonicalFilePath: return window return None def switchLayoutDirection(self): if self.layoutDirection() == Qt.LeftToRight: QApplication.setLayoutDirection(Qt.RightToLeft) else: QApplication.setLayoutDirection(Qt.LeftToRight) def setActiveSubWindow(self, window): if window: self.mdiArea.setActiveSubWindow(window)
class ToolBox(QFrame): """ A tool box widget. """ # Emitted when a tab is toggled. tabToogled = Signal(int, bool) def setExclusive(self, exclusive): """ Set exclusive tabs (only one tab can be open at a time). """ if self.__exclusive != exclusive: self.__exclusive = exclusive self.__tabActionGroup.setExclusive(exclusive) checked = self.__tabActionGroup.checkedAction() if checked is None: # The action group can be out of sync with the actions state # when switching between exclusive states. actions_checked = [ page.action for page in self.__pages if page.action.isChecked() ] if actions_checked: checked = actions_checked[0] # Trigger/toggle remaining open pages if exclusive and checked is not None: for page in self.__pages: if checked != page.action and page.action.isChecked(): page.action.trigger() def exclusive(self): """ Are the tabs in the toolbox exclusive. """ return self.__exclusive exclusive_ = Property(bool, fget=exclusive, fset=setExclusive, designable=True, doc="Exclusive tabs") def __init__(self, parent=None, **kwargs): QFrame.__init__(self, parent, **kwargs) self.__pages = [] self.__tabButtonHeight = -1 self.__tabIconSize = QSize() self.__exclusive = False self.__setupUi() def __setupUi(self): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # Scroll area for the contents. self.__scrollArea = \ _ToolBoxScrollArea(self, objectName="toolbox-scroll-area") self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.__scrollArea.setFrameStyle(QScrollArea.NoFrame) self.__scrollArea.setWidgetResizable(True) # A widget with all of the contents. # The tabs/contents are placed in the layout inside this widget self.__contents = QWidget(self.__scrollArea, objectName="toolbox-contents") self.__contentsLayout = _ToolBoxLayout( sizeConstraint=_ToolBoxLayout.SetMinAndMaxSize, spacing=0) self.__contentsLayout.setContentsMargins(0, 0, 0, 0) self.__contents.setLayout(self.__contentsLayout) self.__scrollArea.setWidget(self.__contents) layout.addWidget(self.__scrollArea) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) self.__tabActionGroup = \ QActionGroup(self, objectName="toolbox-tab-action-group") self.__tabActionGroup.setExclusive(self.__exclusive) self.__actionMapper = QSignalMapper(self) self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled) def setTabButtonHeight(self, height): """ Set the tab button height. """ if self.__tabButtonHeight != height: self.__tabButtonHeight = height for page in self.__pages: page.button.setFixedHeight(height) def tabButtonHeight(self): """ Return the tab button height. """ return self.__tabButtonHeight def setTabIconSize(self, size): """ Set the tab button icon size. """ if self.__tabIconSize != size: self.__tabIconSize = size for page in self.__pages: page.button.setIconSize(size) def tabIconSize(self): """ Return the tab icon size. """ return self.__tabIconSize def tabButton(self, index): """ Return the tab button at `index` """ return self.__pages[index].button def tabAction(self, index): """ Return open/close action for the tab at `index`. """ return self.__pages[index].action def addItem(self, widget, text, icon=None, toolTip=None): """ Append the `widget` in a new tab and return its index. Parameters ---------- widget : :class:`QWidget` A widget to be inserted. The toolbox takes ownership of the widget. text : str Name/title of the new tab. icon : :class:`QIcon`, optional An icon for the tab button. toolTip : str, optional Tool tip for the tab button. """ return self.insertItem(self.count(), widget, text, icon, toolTip) def insertItem(self, index, widget, text, icon=None, toolTip=None): """ Insert the `widget` in a new tab at position `index`. See also -------- ToolBox.addItem """ button = self.createTabButton(widget, text, icon, toolTip) self.__contentsLayout.insertWidget(index * 2, button) self.__contentsLayout.insertWidget(index * 2 + 1, widget) widget.hide() page = _ToolBoxPage(index, widget, button.defaultAction(), button) self.__pages.insert(index, page) for i in range(index + 1, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) self.__updatePositions() # Show (open) the first tab. if self.count() == 1 and index == 0: page.action.trigger() self.__updateSelected() self.updateGeometry() return index def removeItem(self, index): """ Remove the widget at `index`. .. note:: The widget hidden but is is not deleted. """ self.__contentsLayout.takeAt(2 * index + 1) self.__contentsLayout.takeAt(2 * index) page = self.__pages.pop(index) # Update the page indexes for i in range(index, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) page.button.deleteLater() # Hide the widget and reparent to self # This follows QToolBox.removeItem page.widget.hide() page.widget.setParent(self) self.__updatePositions() self.__updateSelected() self.updateGeometry() def count(self): """ Return the number of widgets inserted in the toolbox. """ return len(self.__pages) def widget(self, index): """ Return the widget at `index`. """ return self.__pages[index].widget def createTabButton(self, widget, text, icon=None, toolTip=None): """ Create the tab button for `widget`. """ action = QAction(text, self) action.setCheckable(True) if icon: action.setIcon(icon) if toolTip: action.setToolTip(toolTip) self.__tabActionGroup.addAction(action) self.__actionMapper.setMapping(action, action) action.toggled.connect(self.__actionMapper.map) button = ToolBoxTabButton(self, objectName="toolbox-tab-button") button.setDefaultAction(action) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) if self.__tabIconSize.isValid(): button.setIconSize(self.__tabIconSize) if self.__tabButtonHeight > 0: button.setFixedHeight(self.__tabButtonHeight) return button def ensureWidgetVisible(self, child, xmargin=50, ymargin=50): """ Scroll the contents so child widget instance is visible inside the viewport. """ self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin) def sizeHint(self): hint = self.__contentsLayout.sizeHint() if self.count(): # Compute max width of hidden widgets also. scroll = self.__scrollArea scroll_w = scroll.verticalScrollBar().sizeHint().width() frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2 max_w = max([p.widget.sizeHint().width() for p in self.__pages]) hint = QSize( max(max_w, hint.width()) + scroll_w + frame_w, hint.height()) return QSize(200, 200).expandedTo(hint) def __onTabActionToogled(self, action): page = find(self.__pages, action, key=attrgetter("action")) on = action.isChecked() page.widget.setVisible(on) index = page.index if index > 0: # Update the `previous` tab buttons style hints previous = self.__pages[index - 1].button flag = QStyleOptionToolBox.NextIsSelected if on: previous.selected |= flag else: previous.selected &= ~flag previous.update() if index < self.count() - 1: next = self.__pages[index + 1].button flag = QStyleOptionToolBox.PreviousIsSelected if on: next.selected |= flag else: next.selected &= ~flag next.update() self.tabToogled.emit(index, on) self.__contentsLayout.invalidate() def __updateSelected(self): """Update the tab buttons selected style flags. """ if self.count() == 0: return opt = QStyleOptionToolBox def update(button, next_sel, prev_sel): if next_sel: button.selected |= opt.NextIsSelected else: button.selected &= ~opt.NextIsSelected if prev_sel: button.selected |= opt.PreviousIsSelected else: button.selected &= ~opt.PreviousIsSelected button.update() if self.count() == 1: update(self.__pages[0].button, False, False) elif self.count() >= 2: pages = self.__pages for i in range(1, self.count() - 1): update(pages[i].button, pages[i + 1].action.isChecked(), pages[i - 1].action.isChecked()) def __updatePositions(self): """Update the tab buttons position style flags. """ if self.count() == 0: return elif self.count() == 1: self.__pages[0].button.position = QStyleOptionToolBox.OnlyOneTab else: self.__pages[0].button.position = QStyleOptionToolBox.Beginning self.__pages[-1].button.position = QStyleOptionToolBox.End for p in self.__pages[1:-1]: p.button.position = QStyleOptionToolBox.Middle for p in self.__pages: p.button.update()
def makePopupMenu(self): index = self.currentIndex() sel = self.getSelection() clipboard = qApp.clipboard() menu = QMenu(self) # Get index under cursor pos = self.viewport().mapFromGlobal(QCursor.pos()) mouseIndex = self.indexAt(pos) # Get index's title if mouseIndex.isValid(): title = mouseIndex.internalPointer().title() elif self.rootIndex().parent().isValid(): # mouseIndex is the background of an item, so we check the parent mouseIndex = self.rootIndex().parent() title = mouseIndex.internalPointer().title() else: title = qApp.translate("outlineBasics", "Root") if len(title) > 25: title = title[:25] + "…" # Open Item action self.actOpen = QAction( QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open {}".format(title)), menu) self.actOpen.triggered.connect(self.openItem) menu.addAction(self.actOpen) # Open item(s) in new tab if mouseIndex in sel and len(sel) > 1: actionTitle = qApp.translate("outlineBasics", "Open {} items in new tabs").format( len(sel)) self._indexesToOpen = sel else: actionTitle = qApp.translate("outlineBasics", "Open {} in a new tab").format(title) self._indexesToOpen = [mouseIndex] self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu) self.actNewTab.triggered.connect(self.openItemsInNewTabs) menu.addAction(self.actNewTab) menu.addSeparator() # Add text / folder self.actAddFolder = QAction( QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New &Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New &Text"), menu) self.actAddText.triggered.connect(self.addText) menu.addAction(self.actAddText) menu.addSeparator() # Copy, cut, paste, duplicate self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "C&ut"), menu) self.actCut.triggered.connect(self.cut) menu.addAction(self.actCut) self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "&Copy"), menu) self.actCopy.triggered.connect(self.copy) menu.addAction(self.actCopy) self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "&Paste"), menu) self.actPaste.triggered.connect(self.paste) menu.addAction(self.actPaste) # Rename / duplicate / remove items self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "&Delete"), menu) self.actDelete.triggered.connect(self.delete) menu.addAction(self.actDelete) self.actRename = QAction(QIcon.fromTheme("edit-rename"), qApp.translate("outlineBasics", "&Rename"), menu) self.actRename.triggered.connect(self.rename) menu.addAction(self.actRename) menu.addSeparator() # POV self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu) mw = mainWindow() a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV) a.triggered.connect(lambda: self.setPOV("")) self.menuPOV.addAction(a) self.menuPOV.addSeparator() menus = [] for i in [ qApp.translate("outlineBasics", "Main"), qApp.translate("outlineBasics", "Secondary"), qApp.translate("outlineBasics", "Minor") ]: m = QMenu(i, self.menuPOV) menus.append(m) self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) for i in range(mw.mdlCharacter.rowCount()): a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) mpr.mapped.connect(self.setPOV) menu.addMenu(self.menuPOV) # Status self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu) # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus) # a.triggered.connect(lambda: self.setStatus("")) # self.menuStatus.addAction(a) # self.menuStatus.addSeparator() mpr = QSignalMapper(self.menuStatus) for i in range(mw.mdlStatus.rowCount()): a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuStatus.addAction(a) mpr.mapped.connect(self.setStatus) menu.addMenu(self.menuStatus) # Labels self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu) mpr = QSignalMapper(self.menuLabel) for i in range(mw.mdlLabels.rowCount()): a = QAction( mw.mdlLabels.item(i, 0).icon(), mw.mdlLabels.item(i, 0).text(), self.menuLabel) a.triggered.connect(mpr.map) mpr.setMapping(a, i) self.menuLabel.addAction(a) mpr.mapped.connect(self.setLabel) menu.addMenu(self.menuLabel) menu.addSeparator() # Custom icons if self.menuCustomIcons: menu.addMenu(self.menuCustomIcons) else: self.menuCustomIcons = QMenu( qApp.translate("outlineBasics", "Set Custom Icon"), menu) a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons) a.triggered.connect(lambda: self.setCustomIcon("")) self.menuCustomIcons.addAction(a) self.menuCustomIcons.addSeparator() txt = QLineEdit() txt.textChanged.connect(self.filterLstIcons) txt.setPlaceholderText("Filter icons") txt.setStyleSheet("background: transparent; border: none;") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(txt) self.menuCustomIcons.addAction(act) self.lstIcons = QListWidget() for i in customIcons(): item = QListWidgetItem() item.setIcon(QIcon.fromTheme(i)) item.setData(Qt.UserRole, i) item.setToolTip(i) self.lstIcons.addItem(item) self.lstIcons.itemClicked.connect(self.setCustomIconFromItem) self.lstIcons.setViewMode(self.lstIcons.IconMode) self.lstIcons.setUniformItemSizes(True) self.lstIcons.setResizeMode(self.lstIcons.Adjust) self.lstIcons.setMovement(self.lstIcons.Static) self.lstIcons.setStyleSheet( "background: transparent; background: none;") self.filterLstIcons("") act = QWidgetAction(self.menuCustomIcons) act.setDefaultWidget(self.lstIcons) self.menuCustomIcons.addAction(act) menu.addMenu(self.menuCustomIcons) # Disabling stuff if not clipboard.mimeData().hasFormat("application/xml"): self.actPaste.setEnabled(False) if len(sel) == 0: self.actCopy.setEnabled(False) self.actCut.setEnabled(False) self.actRename.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) self.menuCustomIcons.setEnabled(False) if len(sel) > 1: self.actRename.setEnabled(False) return menu
class runomatic(QWidget): update_signal=pyqtSignal("PyQt_PyObject") def __init__(self): super().__init__() self._plasmaMetaHotkey(enable=False,reconfigure=True) self.dbg=False exePath=sys.argv[0] if os.path.islink(sys.argv[0]): exePath=os.path.realpath(sys.argv[0]) self.baseDir=os.path.abspath(os.path.dirname(exePath)) signal.signal(signal.SIGUSR1,self._end_process) signal.signal(signal.SIGUSR2,self._fail_process) self.runoapps="/usr/share/runomatic/applications" self.launched=[] self.blocked=[] self.procMon=[] self.categories={} self.desktops={} self.pid=0 self.app_icons={} self.tab_icons={} self.tab_id={} self.focusWidgets=[] self.appsWidgets=[] self.launchErr=False self.id=0 self.firstLaunch=True self.currentTab=0 self.currentBtn=0 self.closeKey=False self.confKey=False self.keymap={} self.display=os.environ['DISPLAY'] self.cache="%s/.cache/runomatic/"%os.environ['HOME'] self.defaultBg="/usr/share/runomatic/rsrc/background2.png" self.username=getpass.getuser() self.runner=appRun() self._set_keymapping() self._read_config() self._plasmaMetaHotkey(enable=True) self._render_gui() #def init def _plasmaMetaHotkey(self,enable=None,reconfigure=None): env=os.environ.copy() cmd='kwriteconfig5 --file ~/.config/kwinrc --group ModifierOnlyShortcuts --key Meta "org.kde.plasmashell,/PlasmaShell,org.kde.PlasmaShell,activateLauncherMenu"' if enable==False: cmd='kwriteconfig5 --file ~/.config/kwinrc --group ModifierOnlyShortcuts --key Meta ""' try: a=subprocess.Popen(cmd,stdin=None,stdout=None,stderr=None,shell=True,env=env) a.wait() except Exception as e: self._debug("kwriteconfig: %s"%e) if reconfigure==True: try: cmd='qdbus org.kde.KWin /KWin reconfigure' a=subprocess.Popen(cmd,stdin=None,stdout=None,stderr=None,shell=True,env=env) a.wait() except Exception as e: self._debug("qdbus: %s"%e) def _fail_process(self,*args): self.launchErr=True self._debug("PROCESS %s FAILED TO LAUNCH"%args[0]) #def _fail_process def _end_process(self,*args): for thread in self.runner.getDeadProcesses(): idx=self._get_tabId_from_thread(thread) if idx and idx>0: if self.launchErr: self.blocked.append(self.tab_id[idx]['app']) self._on_tabRemove(idx) self.focusWidgets[self.currentBtn].index=None self.focusWidgets[self.currentBtn].statusBar.setText("") if self.launchErr: self.launchErr=False self.focusWidgets[self.currentBtn].statusBar.label.setStyleSheet(self.focusWidgets[self.currentBtn].cssError) self.focusWidgets[self.currentBtn].statusBar.show(state='error') # self._on_tabSelect(0) self.focusWidgets[self.currentBtn].setFocus() #def _end_process def _debug(self,msg): if self.dbg: print("runomatic: %s"%msg) #def _debug def _set_keymapping(self): #Disable meta association within plasma. I've no eggs to capture and block this event in kde for key,value in vars(Qt).items(): if isinstance(value, Qt.Key): self.keymap[value]=key.partition('_')[2] self.modmap={ Qt.ControlModifier: self.keymap[Qt.Key_Control], Qt.AltModifier: self.keymap[Qt.Key_Alt], Qt.ShiftModifier: self.keymap[Qt.Key_Shift], Qt.MetaModifier: self.keymap[Qt.Key_Meta], Qt.GroupSwitchModifier: self.keymap[Qt.Key_AltGr], Qt.KeypadModifier: self.keymap[Qt.Key_NumLock] } self.sigmap_tabSelect=QSignalMapper(self) self.sigmap_tabSelect.mapped[QInt].connect(self._on_tabSelect) self.sigmap_tabRemove=QSignalMapper(self) self.sigmap_tabRemove.mapped[QInt].connect(self._on_tabRemove) #Shortcut for super_keys #def _set_keymapping def _read_config(self): data=self.runner.get_apps() self.categories=data.get('categories') self.desktops=data.get('desktops') self.keybinds=data.get('keybinds',{}) self.password=data.get('password') self.close_on_exit=data.get('close',False) self.bg=data.get('background',self.defaultBg) if (not(os.path.isfile(self.bg))): self.bg=self.defaultBg self.runner.setBg(self.bg) #def _read_config(self): def _init_gui(self): self.setWindowFlags(Qt.FramelessWindowHint) self.setWindowFlags(Qt.X11BypassWindowManagerHint) self.setWindowState(Qt.WindowFullScreen) self.setWindowFlags(Qt.WindowStaysOnTopHint) self.setWindowModality(Qt.WindowModal) self.bg="/usr/share/runomatic/rsrc/background2.png" self.previousIcon=QtGui.QIcon.fromTheme("go-previous") self.previousIcon=QtGui.QIcon.fromTheme("go-home") btnPrevious=QPushButton() btnPrevious.setObjectName("PushButton") btnPrevious.setIcon(self.previousIcon) btnPrevious.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE)) self.sigmap_tabSelect.setMapping(btnPrevious,0) btnPrevious.clicked.connect(self.sigmap_tabSelect.map) self.homeIcon=QtGui.QIcon.fromTheme("go-home") btnHome=QPushButton() btnHome.setObjectName("PushButton") btnHome.setIcon(self.homeIcon) btnHome.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE)) self.tab_id[0]={'index':self.id,'thread':0,'xephyr':None,'wid':0,'show':btnHome,'close':btnPrevious,'display':"%s"%os.environ['DISPLAY']} self.closeIcon=QtGui.QIcon.fromTheme("window-close") self.grab=False self.setStyleSheet(self._define_css()) monitor=QDesktopWidget().screenGeometry(1) self.move(monitor.left(),monitor.top()) cursor=QtGui.QCursor(Qt.PointingHandCursor) self.setCursor(cursor) self.showFullScreen() #self.show() #def _init_gui(self): def _render_gui(self): #Enable transparent window # self.setStyleSheet('background:transparent') # self.setAttribute(Qt.WA_TranslucentBackground) #### def launchConf(): try: if os.path.isfile("%s/runoconfig.py"%self.baseDir): if self.close(): os.execv("%s/runoconfig.py"%self.baseDir,["1"]) else: self.showMessage(_("runoconfig not found at %s"%self.baseDir),"error2",20) except: print(_("runoconfig not found")) self._init_gui() self.box=QGridLayout() self.statusBar=QAnimatedStatusBar.QAnimatedStatusBar() self.statusBar.setStateCss("error","background-color:qlineargradient(x1:0 y1:0,x2:0 y2:1,stop:0 rgba(255,255,0,1), stop:1 rgba(255,0,0,0.6));text-decoration:none;color:white;text-align:center;font-size:128px;height:256px") self.statusBar.setStateCss("error2","background-color:qlineargradient(x1:0 y1:0,x2:0 y2:1,stop:0 rgba(255,0,0,1), stop:1 rgba(255,0,0,0.6));color:white;text-align:center;text-decoration:none;font-size:12px;height:20px") self.statusBar.height_=152 self.box.addWidget(self.statusBar,0,0,1,2) self.tabBar=self._tabBar() self.setObjectName("tabbar") # self.tabBar.setAttribute(Qt.WA_TranslucentBackground) self.tabBar.currentChanged.connect(lambda:self._on_tabChanged(False)) self.box.addWidget(self.tabBar,0,0,1,1) self.setLayout(self.box) self.tabBar.setFocusPolicy(Qt.NoFocus) if self.focusWidgets: self._set_focus("") else: wdg=QWidget() lyt=QVBoxLayout() wdg.setLayout(lyt) lbl=QLabel(_("There's no launchers to show.\nDid you run the configure app?")) lyt.addWidget(lbl) btn=QPushButton(_("Launch configuration app")) btn.clicked.connect(launchConf) lyt.addWidget(btn) self.box.addWidget(wdg,0,0,1,1,Qt.AlignCenter) #def _render_gui def closeEvent(self,event): if self.password: text=_("Insert the password") if self.close_on_exit==True: text=_("Insert the password. Current session will also be closed.") pwd,resp=QInputDialog.getText(self,_("Confirm close"),text,QLineEdit.Password) if resp: if not hashpwd.verify(pwd,self.password): event.ignore() else: event.ignore() self._plasmaMetaHotkey(reconfigure=True) for index in self.tab_id.keys(): if index: th=self.tab_id[index].get('thread',None) if th: self.runner.send_signal_to_thread("kill",th) xe=self.tab_id[index].get('xephyr',None) if xe: self.runner.send_signal_to_thread("kill",xe) dsp=self.tab_id[index].get('display',None) if dsp: self.runner.stop_display(self.tab_id[index].get('wid',''),dsp) xlockFile=os.path.join("/tmp",".X%s-lock"%dsp.replace(":","")) if os.path.isfile(xlockFile): os.remove(xlockFile) if str(self.close_on_exit).lower()=='true': print("Closing session...") subprocess.run(["loginctl","terminate-user","%s"%self.username]) #def closeEvent def keyPressEvent(self,event): key=self.keymap.get(event.key(),event.text()) if key in ("Alt" ,"Super_L"): self.grab=True self.grabKeyboard() #def eventFilter def keyReleaseEvent(self,event): key=self.keymap.get(event.key(),event.text()) confKey='' if self.keybinds: confKey=self.keybinds.get('conf',None) if key not in ('Tab','Super_L'): if key=='F4' and self.grab: self.closeKey=True elif key==confKey: if os.path.isfile("%s/runoconfig.py"%self.baseDir): if self.close(): os.execv("%s/runoconfig.py"%self.baseDir,["1"]) else: event.ignore() self.showMessage(_("runoconfig not found"),"error2",20) elif key.isdigit(): if int(key)<=self.tabBar.count(): self._on_tabSelect(int(key),True) cont=0 for btn in self.focusWidgets: if btn.statusBar.label.text()==key: self.currentBtn=cont self.focusWidgets[cont].setFocus() cont+=1 else: event.ignore() self.releaseKeyboard() if key in ('Alt','Control','Super_L'): if key!='Super_L': self.releaseKeyboard() else: event.setAccepted(True) self._on_tabSelect(0,True) event.accept() self.grab=False if key=='Alt': if self.closeKey: self.closeKey=False self.close() if self.confKey: self.confKey=False #def keyReleaseEvent def _set_focus(self,key): cursor=QtGui.QCursor(Qt.PointingHandCursor) self.setCursor(cursor) self.grabMouse() #cursor.setPos(50,50) self.releaseMouse() if key=="Space" or key=="NumLock+Enter" or key=="Return": self.focusWidgets[self.currentBtn].clicked.emit() elif key=="Tab": tabCount=self.tabBar.count() newTab=self.tabBar.currentIndex()+1 if newTab>tabCount: newTab=0 self.tabBar.setCurrentIndex(newTab) if key=="Alt": return(True) else: self.focusWidgets[self.currentBtn].resize(QSize(BTN_SIZE,BTN_SIZE)) self.focusWidgets[self.currentBtn].setIconSize(QSize(BTN_SIZE,BTN_SIZE)) if key=="Left": self.currentBtn+=1 if self.currentBtn>=len(self.focusWidgets): self.currentBtn=0 elif key=="Right": self.currentBtn-=1 if self.currentBtn<0: self.currentBtn=len(self.focusWidgets)-1 elif key=="Up": currentBtn=self.currentBtn currentBtn-=self.maxCol if currentBtn<0: currentBtn=self.currentBtn self.currentBtn=currentBtn elif key=="Down": currentBtn=self.currentBtn currentBtn+=self.maxCol if currentBtn>=len(self.focusWidgets): currentBtn=self.currentBtn self.currentBtn=currentBtn self.focusWidgets[self.currentBtn].setFocus() self._debug("Focus to %s"%self.focusWidgets[self.currentBtn]) self.focusWidgets[self.currentBtn].resize(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2)) self.focusWidgets[self.currentBtn].setIconSize(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2)) #def _set_focus(self,key): def _get_focus(self,widget): self.currentBtn=self.focusWidgets.index(widget) #def _get_focus(self,widget) def _add_widgets(self,vbox,apps): row=int(len(self.appsWidgets)/self.maxCol) col=(self.maxCol*(row+1))-len(self.appsWidgets) sigmap_run=QSignalMapper(self) sigmap_run.mapped[QString].connect(self._launch) for appName,data in apps.items(): appIcon=data['Icon'] appDesc=data['Name'] if QtGui.QIcon.hasThemeIcon(appIcon): icnApp=QtGui.QIcon.fromTheme(appIcon) elif os.path.isfile(appIcon): iconPixmap=QtGui.QPixmap(appIcon) scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2)) icnApp=QtGui.QIcon(scaledIcon) elif appIcon.startswith("http"): if not os.path.isdir("%s/icons"%self.cache): os.makedirs("%s/icons"%self.cache) tmpfile=os.path.join("%s/icons"%self.cache,appIcon.split("/")[2].split(".")[0]) if not os.path.isfile(tmpfile): try: urlretrieve(appIcon,tmpfile) except: tmpfile=QtGui.QIcon.fromTheme("shell") iconPixmap=QtGui.QPixmap(tmpfile) scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2)) icnApp=QtGui.QIcon(scaledIcon) appIcon=tmpfile else: continue if not appName: continue self.app_icons[appName]=appIcon self._debug("Adding %s"%appName) btnApp=navButton(self) btnApp.setIcon(icnApp) btnApp.setIconSize(QSize(BTN_SIZE,BTN_SIZE)) btnApp.setToolTip(appDesc) btnApp.setFocusPolicy(Qt.NoFocus) btnApp.keypress.connect(self._set_focus) btnApp.focusIn.connect(self._get_focus) self.focusWidgets.append(btnApp) self.appsWidgets.append(appName) sigmap_run.setMapping(btnApp,appName) btnApp.clicked.connect(sigmap_run.map) vbox.addWidget(btnApp,row,col,Qt.Alignment(-1)) col+=1 if col==self.maxCol: col=0 row+=1 #def _add_widgets def _tabBar(self): tabBar=QTabWidget() tabScroll=QWidget() tabScroll.setObjectName("scroll") tabScroll.setStyleSheet("#scroll{background-image:url('%s');}"%self.bg) tabScroll.setFocusPolicy(Qt.NoFocus) scrollArea=QScrollArea(tabScroll) scrollArea.setFocusPolicy(Qt.NoFocus) tabContent=QWidget() vbox=QGridLayout() scr=app.primaryScreen() w=scr.size().width()-BTN_SIZE h=scr.size().height()-(2*BTN_SIZE) self.maxCol=int(w/BTN_SIZE)-3 self._debug("Size: %s\nCols: %s"%(self.width(),self.maxCol)) for desktop in self.desktops: apps=self._get_desktop_apps(desktop) self._add_widgets(vbox,apps) tabContent.setLayout(vbox) scrollArea.setWidget(tabContent) scrollArea.setObjectName("launcher") scrollArea.alignment() tabBar.addTab(tabScroll,"") tabBar.tabBar().setTabButton(0,QTabBar.LeftSide,self.tab_id[0]['show']) tabBar.tabBar().tabButton(0,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus) scrollArea.setGeometry(QRect(0,0,w,h)) tabBar.setObjectName("tabbar") return (tabBar) #def _tabBar def _on_tabChanged(self,remove=True): self._debug("From Tab: %s"%self.currentTab) index=self._get_tabId_from_index(self.currentTab) self._debug("Tab Index: %s"%index) key='show' if self.currentTab==0: index=0 key='close' self.tabBar.tabBar().setTabButton(self.currentTab,QTabBar.LeftSide,self.tab_id[index][key]) self._debug("Pressed: %s"%self.tab_id[index][key]) try: self.tabBar.tabBar().tabButton(self.currentTab,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus) except: pass self.runner.send_signal_to_thread("stop",self.tab_id[index]['thread']) index=self._get_tabId_from_index(self.tabBar.currentIndex()) self.currentTab=self.tabBar.currentIndex() key='close' if self.currentTab==0: self.focusWidgets[self.currentBtn].setFocus() index=0 key='show' self._debug("New Tab Index: %s"%self.tab_id[index]) self._debug("New index: %s"%index) self._debug("Btn: %s"%self.tab_id[index][key]) self.tabBar.tabBar().setTabButton(self.currentTab,QTabBar.LeftSide,self.tab_id[index][key]) self.tabBar.tabBar().tabButton(self.currentTab,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus) self.runner.send_signal_to_thread("cont",self.tab_id[index]['thread']) #os.environ['DISPLAY']=self.tab_id[index]['display'] self._debug("New Current Tab: %s Icon:%s"%(self.currentTab,key)) #def _on_tabChanged def _on_tabSelect(self,index,btn=None): self._debug("Select tab: %s"%index) if btn: index=self._get_tabId_from_index(index) self.tabBar.setCurrentIndex(self.tab_id[index]['index']) cursor=QtGui.QCursor() self.setCursor(cursor) cursor.setPos(300,300) #def _on_tabSelect def _on_tabRemove(self,index): self._debug("Remove tab: %s"%index) self.tabBar.blockSignals(True) self.tabBar.removeTab(self.tab_id[index]['index']) xlockFile=os.path.join("/tmp",".X%s-lock"%self.tab_id[index]['display'].replace(":","")) if os.path.isfile(xlockFile): os.remove(xlockFile) if (self.tab_id[index].get('app',"") in self.launched): self.launched.remove(self.tab_id[index]['app']) for idx in range(index+1,len(self.tab_id)): if idx in self.tab_id.keys(): self._debug("Tab Array: %s"%self.tab_id) if 'index' in self.tab_id[idx].keys(): self._debug("Reasign %s"%(self.tab_id[idx]['index'])) self.tab_id[idx]['index']=self.tab_id[idx]['index']-1 self.focusWidgets[self.appsWidgets.index(self.tab_id[idx]['app'])].statusBar.setText(str(self.tab_id[idx]['index'])) self.focusWidgets[self.appsWidgets.index(self.tab_id[idx]['app'])].index=self.tab_id[idx]['index'] self._debug("Reasigned %s -> %s"%(idx,self.tab_id[idx]['index'])) th=self.tab_id[index].get('thread',None) if th: self.runner.send_signal_to_thread("term",th) xe=self.tab_id[index].get('xephyr',None) if xe: self.runner.send_signal_to_thread("term",xe) dsp=self.tab_id[index].get('display',None) self.focusWidgets[self.appsWidgets.index(self.tab_id[index]['app'])].statusBar.hide() if dsp: self.runner.stop_display(self.tab_id[index].get('wid',''),dsp) xlockFile=os.path.join("/tmp",".X%s-lock"%dsp.replace(":","")) if os.path.isfile(xlockFile): os.remove(xlockFile) self.tab_id[index]={} if self.launchErr: self.currentTab=self._get_tabId_from_index(0) else: self.currentTab=self._get_tabId_from_index(self.tabBar.currentIndex()) index=self.currentTab self._debug("New tab: %s"%self.currentTab) self._on_tabChanged() self.tabBar.blockSignals(False) self.tabBar.setFocus() self.tabBar.setCurrentIndex(index) self._debug("Removed tab: %s"%index) #def _on_tabRemove def _get_category_apps(self,category): apps=self.runner.get_category_apps(category.lower()) return (apps) #def _get_category_apps def _get_desktop_apps(self,desktop): #Check if desktop is from run-o-matic if "run-o-matic" in self.categories: if os.path.isdir(self.runoapps): if desktop in os.listdir(self.runoapps): desktop=os.path.join(self.runoapps,desktop) apps=self.runner.get_desktop_app(desktop,True) return (apps) #def _get_category_apps def _launchZone(self,app): tabContent=QWidget() box=QVBoxLayout() wid=self.runner.get_wid("Xephyr on",self.display) self._debug("Window Wid: %s"%wid) zone=None if wid: (zone,zone_window)=appZone(tabContent).createZone(wid) zone.setObjectName("appzone") if not zone or not wid: self._debug("Xephyr failed to launch") tabContent.destroy() wid=None else: box.addWidget(zone) zone.setFocusPolicy(Qt.NoFocus) tabContent.setLayout(box) icn=QtGui.QIcon.fromTheme(self.app_icons[app]) btn=QPushButton() btn.setObjectName("PushButton") btn.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE)) btn.setIcon(icn) self.id+=1 self._debug("New Tab id %s"%self.id) self.sigmap_tabSelect.setMapping(btn,self.id) btn.clicked.connect(self.sigmap_tabSelect.map) btn_close=QPushButton() btn_close.setObjectName("PushButton") btn_close.setIcon(self.closeIcon) btn_close.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE)) self.sigmap_tabRemove.setMapping(btn_close,self.id) btn_close.clicked.connect(self.sigmap_tabRemove.map) self.tab_id[self.id]={'index':self.tabBar.count(),'thread':None,'wid':zone_window,'show':btn,'close':btn_close,'display':self.display} self.tabBar.addTab(tabContent,"") return(wid) #def _launchZone def _launch(self,app): if app in self.launched: self._on_tabSelect(self.focusWidgets[self.appsWidgets.index(app)].index,True) return if app in self.blocked: return cursor=QtGui.QCursor(Qt.WaitCursor) self.setCursor(cursor) self.grabMouse() self.tabBar.setTabIcon(0,self.previousIcon) self._debug("Tab count: %s"%self.tabBar.count()) #Tabs BEFORE new tab is added tabCount=self.tabBar.count() btn=None try: btn=self.appsWidgets.index(app) except: btn=None if btn: self.currentBtn=btn os.environ["HOME"]="/home/%s"%self.username os.environ["XAUTHORITY"]="/home/%s/.Xauthority"%self.username self.display,self.pid,x_pid=self.runner.new_Xephyr(self.tabBar) if self._launchZone(app): self.tab_id[self.id]['thread']=self.runner.launch(app,self.display) self.tab_id[self.id]['xephyr']=x_pid self.tab_id[self.id]['app']=app self.launched.append(app) else: if self.pid: self.runner.send_signal_to_thread("kill",self.pid) cursor=QtGui.QCursor(Qt.PointingHandCursor) self.setCursor(cursor) self.releaseMouse() return self.tabBar.setCurrentIndex(tabCount) self.focusWidgets[self.appsWidgets.index(app)].index=tabCount self.focusWidgets[self.appsWidgets.index(app)].statusBar.setText(str(tabCount)) self.focusWidgets[self.appsWidgets.index(app)].statusBar.label.setStyleSheet(self.focusWidgets[self.currentBtn].cssRun) self.focusWidgets[self.appsWidgets.index(app)].statusBar.show(state='error') cursor=QtGui.QCursor(Qt.PointingHandCursor) self.releaseMouse() self.setEnabled(True) self.setCursor(cursor) self.focusWidgets[self.appsWidgets.index(app)].setEnabled(True) #def _launch def _get_tabId_from_index(self,index): idx=index self._debug("Search id for display: %s"%(index)) self._debug("Current id: %s"%(self.tab_id)) for key,data in self.tab_id.items(): if 'index' in data.keys(): if index==data['index']: idx=key break self._debug("Found tab idx: %s For selected index: %s"%(idx,index)) return idx #def _get_tabId_from_index def _get_tabId_from_thread(self,thread): idx=-1 self._debug("Search id for thread: %s"%(thread)) self._debug("Current id: %s"%(self.tab_id)) for key,data in self.tab_id.items(): if 'thread' in data.keys(): if thread==data['thread']: idx=key break self._debug("Found idx: %s For thread: %s"%(idx,thread)) return idx #def _get_tabId_from_thread def showMessage(self,msg,status="error",height=252): return() self.statusBar.height_=height self.statusBar.setText(msg) if status: self.statusBar.show(state=status) else: self.statusBar.show(state=None) #def _show_message def _define_css(self): css=""" #PushButton{ padding:10px; margin:0px; border:0px; background-color:transparent; } #PushButton:active{ font: 14px Roboto; color:black; background:none; background-color:transparent; } #PushButton:focus{ background:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 silver,stop:1 white); border-radius:25px; } #launcher{ background-repeat:no-repeat; background-position:center; border:0px solid red; } #appzone{ background-color:transparent; border:10px solid red; } """ self._debug("Setting background: %s"%self.bg) return(css)
class MainWindow(QMainWindow, Ui_MainWindow): dictChanged = pyqtSignal(str) # Tab indexes TabInfos = 0 TabSummary = 1 TabPersos = 2 TabPlots = 3 TabWorld = 4 TabOutline = 5 TabRedac = 6 def __init__(self): QMainWindow.__init__(self) self.setupUi(self) self.currentProject = None self.readSettings() # UI self.setupMoreUi() # Welcome self.welcome.updateValues() self.stack.setCurrentIndex(0) # Word count self.mprWordCount = QSignalMapper(self) for t, i in [(self.txtSummarySentence, 0), (self.txtSummaryPara, 1), (self.txtSummaryPage, 2), (self.txtSummaryFull, 3)]: t.textChanged.connect(self.mprWordCount.map) self.mprWordCount.setMapping(t, i) self.mprWordCount.mapped.connect(self.wordCount) # Snowflake Method Cycle self.mapperCycle = QSignalMapper(self) for t, i in [(self.btnStepTwo, 0), (self.btnStepThree, 1), (self.btnStepFour, 2), (self.btnStepFive, 3), (self.btnStepSix, 4), (self.btnStepSeven, 5), (self.btnStepEight, 6)]: t.clicked.connect(self.mapperCycle.map) self.mapperCycle.setMapping(t, i) self.mapperCycle.mapped.connect(self.clickCycle) self.cmbSummary.currentIndexChanged.connect(self.summaryPageChanged) self.cmbSummary.setCurrentIndex(0) self.cmbSummary.currentIndexChanged.emit(0) # Main Menu for i in [ self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp ]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) self.actSave.triggered.connect(self.saveDatas) self.actSaveAs.triggered.connect(self.welcome.saveAsFile) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) self.actSettings.triggered.connect(self.settingsWindow) self.actCloseProject.triggered.connect(self.closeProject) self.actQuit.triggered.connect(self.close) self.actToolFrequency.triggered.connect(self.frequencyAnalyzer) self.generateViewMenu() self.actModeGroup = QActionGroup(self) self.actModeSimple.setActionGroup(self.actModeGroup) self.actModeFiction.setActionGroup(self.actModeGroup) self.actModeSnowflake.setActionGroup(self.actModeGroup) self.actModeSimple.triggered.connect(self.setViewModeSimple) self.actModeFiction.triggered.connect(self.setViewModeFiction) self.actModeSnowflake.setEnabled(False) self.makeUIConnections() # self.loadProject(os.path.join(appPath(), "test_project.zip")) ############################################################################### # SUMMARY ############################################################################### def summaryPageChanged(self, index): fractalButtons = [ self.btnStepTwo, self.btnStepThree, self.btnStepFive, self.btnStepSeven, ] for b in fractalButtons: b.setVisible(fractalButtons.index(b) == index) ############################################################################### # OUTLINE ############################################################################### def outlineRemoveItemsRedac(self): self.treeRedacOutline.delete() def outlineRemoveItemsOutline(self): self.treeOutlineOutline.delete() ############################################################################### # CHARACTERS ############################################################################### def changeCurrentCharacter(self, trash=None): """ @return: """ c = self.lstCharacters.currentCharacter() if not c: self.tabPlot.setEnabled(False) return self.tabPersos.setEnabled(True) index = c.index() for w in [ self.txtPersoName, self.sldPersoImportance, self.txtPersoMotivation, self.txtPersoGoal, self.txtPersoConflict, self.txtPersoEpiphany, self.txtPersoSummarySentence, self.txtPersoSummaryPara, self.txtPersoSummaryFull, self.txtPersoNotes, ]: w.setCurrentModelIndex(index) # Button color self.updateCharacterColor(c.ID()) # Character Infos self.tblPersoInfos.setRootIndex(index) if self.mdlCharacter.rowCount(index): self.updatePersoInfoView() def updatePersoInfoView(self): self.tblPersoInfos.horizontalHeader().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.tblPersoInfos.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.tblPersoInfos.verticalHeader().hide() def updateCharacterColor(self, ID): c = self.mdlCharacter.getCharacterByID(ID) color = c.color().name() self.btnPersoColor.setStyleSheet("background:{};".format(color)) ############################################################################### # PLOTS ############################################################################### def changeCurrentPlot(self): index = self.lstPlots.currentPlotIndex() if not index.isValid(): self.tabPlot.setEnabled(False) return self.tabPlot.setEnabled(True) self.txtPlotName.setCurrentModelIndex(index) self.txtPlotDescription.setCurrentModelIndex(index) self.txtPlotResult.setCurrentModelIndex(index) self.sldPlotImportance.setCurrentModelIndex(index) self.lstPlotPerso.setRootIndex( index.sibling(index.row(), Plot.characters.value)) subplotindex = index.sibling(index.row(), Plot.steps.value) self.lstSubPlots.setRootIndex(subplotindex) if self.mdlPlots.rowCount(subplotindex): self.updateSubPlotView() # self.txtSubPlotSummary.setCurrentModelIndex(QModelIndex()) self.txtSubPlotSummary.setEnabled(False) self._updatingSubPlot = True self.txtSubPlotSummary.setPlainText("") self._updatingSubPlot = False self.lstPlotPerso.selectionModel().clear() def updateSubPlotView(self): # Hide columns for i in range(self.mdlPlots.columnCount()): self.lstSubPlots.hideColumn(i) self.lstSubPlots.showColumn(PlotStep.name.value) self.lstSubPlots.showColumn(PlotStep.meta.value) self.lstSubPlots.horizontalHeader().setSectionResizeMode( PlotStep.name.value, QHeaderView.Stretch) self.lstSubPlots.horizontalHeader().setSectionResizeMode( PlotStep.meta.value, QHeaderView.ResizeToContents) self.lstSubPlots.verticalHeader().hide() def changeCurrentSubPlot(self, index): # Got segfaults when using textEditView model system, so ad hoc stuff. index = index.sibling(index.row(), PlotStep.summary.value) item = self.mdlPlots.itemFromIndex(index) if not item: self.txtSubPlotSummary.setEnabled(False) return self.txtSubPlotSummary.setEnabled(True) txt = item.text() self._updatingSubPlot = True self.txtSubPlotSummary.setPlainText(txt) self._updatingSubPlot = False def updateSubPlotSummary(self): if self._updatingSubPlot: return index = self.lstSubPlots.currentIndex() if not index.isValid(): return index = index.sibling(index.row(), PlotStep.summary.value) item = self.mdlPlots.itemFromIndex(index) self._updatingSubPlot = True item.setText(self.txtSubPlotSummary.toPlainText()) self._updatingSubPlot = False def plotPersoSelectionChanged(self): "Enables or disables remove plot perso button." self.btnRmPlotPerso.setEnabled( len(self.lstPlotPerso.selectedIndexes()) != 0) ############################################################################### # WORLD ############################################################################### def changeCurrentWorld(self): index = self.mdlWorld.selectedIndex() if not index.isValid(): self.tabWorld.setEnabled(False) return self.tabWorld.setEnabled(True) self.txtWorldName.setCurrentModelIndex(index) self.txtWorldDescription.setCurrentModelIndex(index) self.txtWorldPassion.setCurrentModelIndex(index) # self.txtWorldConflict.setCurrentModelIndex(index) # # ############################################################################### # # LOAD AND SAVE # ############################################################################### def loadProject(self, project, loadFromFile=True): """Loads the project ``project``. If ``loadFromFile`` is False, then it does not load datas from file. It assumes that the datas have been populated in a different way.""" if loadFromFile and not os.path.exists(project): print( self.tr("The file {} does not exist. Try again.").format( project)) self.statusBar().showMessage( self.tr("The file {} does not exist. Try again.").format( project), 5000) return if loadFromFile: # Load empty settings imp.reload(settings) # Load data self.loadEmptyDatas() self.loadDatas(project) self.makeConnections() # Load settings for i in settings.openIndexes: idx = self.mdlOutline.getIndexByID(i) self.mainEditor.setCurrentModelIndex(idx, newTab=True) self.generateViewMenu() self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) self.actSpellcheck.setChecked(settings.spellcheck) self.toggleSpellcheck(settings.spellcheck) self.updateMenuDict() self.setDictionary() self.mainEditor.setFolderView(settings.folderView) self.mainEditor.updateFolderViewButtons(settings.folderView) self.tabMain.setCurrentIndex(settings.lastTab) # We force to emit even if it opens on the current tab self.tabMain.currentChanged.emit(settings.lastTab) self.mainEditor.updateCorkBackground() if settings.viewMode == "simple": self.setViewModeSimple() else: self.setViewModeFiction() # Set autosave self.saveTimer = QTimer() self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000) self.saveTimer.setSingleShot(False) self.saveTimer.timeout.connect(self.saveDatas) if settings.autoSave: self.saveTimer.start() # Set autosave if no changes self.saveTimerNoChanges = QTimer() self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000) self.saveTimerNoChanges.setSingleShot(True) self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges) self.mdlOutline.dataChanged.connect(self.startTimerNoChanges) self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges) self.mdlPlots.dataChanged.connect(self.startTimerNoChanges) self.mdlWorld.dataChanged.connect(self.startTimerNoChanges) # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges) self.mdlStatus.dataChanged.connect(self.startTimerNoChanges) self.mdlLabels.dataChanged.connect(self.startTimerNoChanges) self.saveTimerNoChanges.timeout.connect(self.saveDatas) self.saveTimerNoChanges.stop() # UI for i in [ self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp ]: i.setEnabled(True) # FIXME: set Window's name: project name # Stuff # self.checkPersosID() # Should'n be necessary any longer self.currentProject = project QSettings().setValue("lastProject", project) # Show main Window self.stack.setCurrentIndex(1) def closeProject(self): if not self.currentProject: return # Close open tabs in editor self.mainEditor.closeAllTabs() # Save datas self.saveDatas() self.currentProject = None QSettings().setValue("lastProject", "") # Clear datas self.loadEmptyDatas() self.saveTimer.stop() loadSave.clearSaveCache() # UI for i in [ self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp ]: i.setEnabled(False) # Reload recent files self.welcome.updateValues() # Show welcome dialog self.stack.setCurrentIndex(0) def readSettings(self): # Load State and geometry sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) if sttgns.contains("geometry"): self.restoreGeometry(sttgns.value("geometry")) if sttgns.contains("windowState"): self.restoreState(sttgns.value("windowState")) else: self.dckCheatSheet.hide() self.dckSearch.hide() if sttgns.contains("metadataState"): state = [ False if v == "false" else True for v in sttgns.value("metadataState") ] self.redacMetadata.restoreState(state) if sttgns.contains("revisionsState"): state = [ False if v == "false" else True for v in sttgns.value("revisionsState") ] self.redacMetadata.revisions.restoreState(state) if sttgns.contains("splitterRedacH"): self.splitterRedacH.restoreState(sttgns.value("splitterRedacH")) if sttgns.contains("splitterRedacV"): self.splitterRedacV.restoreState(sttgns.value("splitterRedacV")) if sttgns.contains("toolbar"): # self.toolbar is not initialized yet, so we just store balue self._toolbarState = sttgns.value("toolbar") else: self._toolbarState = "" def closeEvent(self, event): # Save State and geometry and other things sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) sttgns.setValue("geometry", self.saveGeometry()) sttgns.setValue("windowState", self.saveState()) sttgns.setValue("metadataState", self.redacMetadata.saveState()) sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState()) sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState()) sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState()) sttgns.setValue("toolbar", self.toolbar.saveState()) # Specific settings to save before quitting settings.lastTab = self.tabMain.currentIndex() if self.currentProject: # Remembering the current items (stores outlineItem's ID) sel = [] for i in range(self.mainEditor.tab.count()): sel.append( self.mdlOutline.ID( self.mainEditor.tab.widget(i).currentIndex)) settings.openIndexes = sel # Save data from models if self.currentProject and settings.saveOnQuit: self.saveDatas() # closeEvent # QMainWindow.closeEvent(self, event) # Causin segfaults? def startTimerNoChanges(self): if settings.autoSaveNoChanges: self.saveTimerNoChanges.start() def saveDatas(self, projectName=None): """Saves the current project (in self.currentProject). If ``projectName`` is given, currentProject becomes projectName. In other words, it "saves as...". """ if projectName: self.currentProject = projectName QSettings().setValue("lastProject", projectName) loadSave.saveProject() # version=0 self.saveTimerNoChanges.stop() # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) self.statusBar().showMessage( self.tr("Project {} saved.").format(self.currentProject), 5000) def loadEmptyDatas(self): self.mdlFlatData = QStandardItemModel(self) self.mdlCharacter = characterModel(self) # self.mdlPersosProxy = persosProxyModel(self) # self.mdlPersosInfos = QStandardItemModel(self) self.mdlLabels = QStandardItemModel(self) self.mdlStatus = QStandardItemModel(self) self.mdlPlots = plotModel(self) self.mdlOutline = outlineModel(self) self.mdlWorld = worldModel(self) def loadDatas(self, project): errors = loadSave.loadProject(project) # Giving some feedback if not errors: print(self.tr("Project {} loaded.").format(project)) self.statusBar().showMessage( self.tr("Project {} loaded.").format(project), 5000) else: print( self.tr("Project {} loaded with some errors:").format(project)) for e in errors: print(self.tr(" * {} wasn't found in project file.").format(e)) self.statusBar().showMessage( self.tr("Project {} loaded with some errors.").format(project), 5000) ############################################################################### # MAIN CONNECTIONS ############################################################################### def makeUIConnections(self): "Connections that have to be made once only, even when a new project is loaded." self.lstCharacters.currentItemChanged.connect( self.changeCurrentCharacter, AUC) self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC) self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC) self.txtSubPlotSummary.document().contentsChanged.connect( self.updateSubPlotSummary, AUC) self.lstSubPlots.activated.connect(self.changeCurrentSubPlot, AUC) self.btnRedacAddFolder.clicked.connect(self.treeRedacOutline.addFolder, AUC) self.btnOutlineAddFolder.clicked.connect( self.treeOutlineOutline.addFolder, AUC) self.btnRedacAddText.clicked.connect(self.treeRedacOutline.addText, AUC) self.btnOutlineAddText.clicked.connect(self.treeOutlineOutline.addText, AUC) self.btnRedacRemoveItem.clicked.connect(self.outlineRemoveItemsRedac, AUC) self.btnOutlineRemoveItem.clicked.connect( self.outlineRemoveItemsOutline, AUC) self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup) def makeConnections(self): # Flat datas (Summary and general infos) for widget, col in [ (self.txtSummarySituation, 0), (self.txtSummarySentence, 1), (self.txtSummarySentence_2, 1), (self.txtSummaryPara, 2), (self.txtSummaryPara_2, 2), (self.txtPlotSummaryPara, 2), (self.txtSummaryPage, 3), (self.txtSummaryPage_2, 3), (self.txtPlotSummaryPage, 3), (self.txtSummaryFull, 4), (self.txtPlotSummaryFull, 4), ]: widget.setModel(self.mdlFlatData) widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(1, col)) for widget, col in [ (self.txtGeneralTitle, 0), (self.txtGeneralSubtitle, 1), (self.txtGeneralSerie, 2), (self.txtGeneralVolume, 3), (self.txtGeneralGenre, 4), (self.txtGeneralLicense, 5), (self.txtGeneralAuthor, 6), (self.txtGeneralEmail, 7), ]: widget.setModel(self.mdlFlatData) widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(0, col)) # Characters self.lstCharacters.setCharactersModel(self.mdlCharacter) self.tblPersoInfos.setModel(self.mdlCharacter) self.btnAddPerso.clicked.connect(self.mdlCharacter.addCharacter, AUC) try: self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, AUC) self.btnPersoColor.clicked.connect( self.lstCharacters.choseCharacterColor, AUC) self.btnPersoAddInfo.clicked.connect( self.lstCharacters.addCharacterInfo, AUC) self.btnPersoRmInfo.clicked.connect( self.lstCharacters.removeCharacterInfo, AUC) except TypeError: # Connection has already been made pass for w, c in [(self.txtPersoName, Character.name.value), (self.sldPersoImportance, Character.importance.value), (self.txtPersoMotivation, Character.motivation.value), (self.txtPersoGoal, Character.goal.value), (self.txtPersoConflict, Character.conflict.value), (self.txtPersoEpiphany, Character.epiphany.value), (self.txtPersoSummarySentence, Character.summarySentence.value), (self.txtPersoSummaryPara, Character.summaryPara.value), (self.txtPersoSummaryFull, Character.summaryFull.value), (self.txtPersoNotes, Character.notes.value)]: w.setModel(self.mdlCharacter) w.setColumn(c) self.tabPersos.setEnabled(False) # Plots self.lstPlots.setPlotModel(self.mdlPlots) self.lstPlotPerso.setModel(self.mdlPlots) self.lstSubPlots.setModel(self.mdlPlots) self._updatingSubPlot = False self.btnAddPlot.clicked.connect(self.mdlPlots.addPlot, AUC) self.btnRmPlot.clicked.connect( lambda: self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()), AUC) self.btnAddSubPlot.clicked.connect(self.mdlPlots.addSubPlot, AUC) self.btnRmSubPlot.clicked.connect(self.mdlPlots.removeSubPlot, AUC) self.lstPlotPerso.selectionModel().selectionChanged.connect( self.plotPersoSelectionChanged) self.btnRmPlotPerso.clicked.connect(self.mdlPlots.removePlotPerso, AUC) for w, c in [ (self.txtPlotName, Plot.name.value), (self.txtPlotDescription, Plot.description.value), (self.txtPlotResult, Plot.result.value), (self.sldPlotImportance, Plot.importance.value), ]: w.setModel(self.mdlPlots) w.setColumn(c) self.tabPlot.setEnabled(False) self.mdlPlots.updatePlotPersoButton() self.mdlCharacter.dataChanged.connect( self.mdlPlots.updatePlotPersoButton) self.lstOutlinePlots.setPlotModel(self.mdlPlots) self.lstOutlinePlots.setShowSubPlot(True) self.plotCharacterDelegate = outlineCharacterDelegate( self.mdlCharacter, self) self.lstPlotPerso.setItemDelegate(self.plotCharacterDelegate) self.plotDelegate = plotDelegate(self) self.lstSubPlots.setItemDelegateForColumn(PlotStep.meta.value, self.plotDelegate) # World self.treeWorld.setModel(self.mdlWorld) for i in range(self.mdlWorld.columnCount()): self.treeWorld.hideColumn(i) self.treeWorld.showColumn(0) self.btnWorldEmptyData.setMenu(self.mdlWorld.emptyDataMenu()) self.treeWorld.selectionModel().selectionChanged.connect( self.changeCurrentWorld, AUC) self.btnAddWorld.clicked.connect(self.mdlWorld.addItem, AUC) self.btnRmWorld.clicked.connect(self.mdlWorld.removeItem, AUC) for w, c in [ (self.txtWorldName, World.name.value), (self.txtWorldDescription, World.description.value), (self.txtWorldPassion, World.passion.value), (self.txtWorldConflict, World.conflict.value), ]: w.setModel(self.mdlWorld) w.setColumn(c) self.tabWorld.setEnabled(False) self.treeWorld.expandAll() # Outline self.treeRedacOutline.setModel(self.mdlOutline) self.treeOutlineOutline.setModelCharacters(self.mdlCharacter) self.treeOutlineOutline.setModelLabels(self.mdlLabels) self.treeOutlineOutline.setModelStatus(self.mdlStatus) self.redacMetadata.setModels(self.mdlOutline, self.mdlCharacter, self.mdlLabels, self.mdlStatus) self.outlineItemEditor.setModels(self.mdlOutline, self.mdlCharacter, self.mdlLabels, self.mdlStatus) self.treeOutlineOutline.setModel(self.mdlOutline) # self.redacEditor.setModel(self.mdlOutline) self.storylineView.setModels(self.mdlOutline, self.mdlCharacter, self.mdlPlots) self.treeOutlineOutline.selectionModel().selectionChanged.connect( lambda: self.outlineItemEditor.selectionChanged( self.treeOutlineOutline), AUC) self.treeOutlineOutline.clicked.connect( lambda: self.outlineItemEditor.selectionChanged( self.treeOutlineOutline), AUC) # Sync selection self.treeRedacOutline.selectionModel().selectionChanged.connect( lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC) self.treeRedacOutline.clicked.connect( lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC) self.treeRedacOutline.selectionModel().selectionChanged.connect( self.mainEditor.selectionChanged, AUC) # Cheat Sheet self.cheatSheet.setModels() # Debug self.mdlFlatData.setVerticalHeaderLabels( ["Infos générales", "Summary"]) self.tblDebugFlatData.setModel(self.mdlFlatData) self.tblDebugPersos.setModel(self.mdlCharacter) self.tblDebugPersosInfos.setModel(self.mdlCharacter) self.tblDebugPersos.selectionModel().currentChanged.connect( lambda: self.tblDebugPersosInfos.setRootIndex( self.mdlCharacter.index( self.tblDebugPersos.selectionModel().currentIndex().row(), Character.name.value)), AUC) self.tblDebugPlots.setModel(self.mdlPlots) self.tblDebugPlotsPersos.setModel(self.mdlPlots) self.tblDebugSubPlots.setModel(self.mdlPlots) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugPlotsPersos.setRootIndex( self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), Plot.characters.value)), AUC) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugSubPlots.setRootIndex( self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), Plot.steps.value)), AUC) self.treeDebugWorld.setModel(self.mdlWorld) self.treeDebugOutline.setModel(self.mdlOutline) self.lstDebugLabels.setModel(self.mdlLabels) self.lstDebugStatus.setModel(self.mdlStatus) ############################################################################### # GENERAL AKA UNSORTED ############################################################################### def clickCycle(self, i): if i == 0: # step 2 - paragraph summary self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(1) if i == 1: # step 3 - characters summary self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(0) if i == 2: # step 4 - page summary self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(2) if i == 3: # step 5 - characters description self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(1) if i == 4: # step 6 - four page synopsis self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(3) if i == 5: # step 7 - full character charts self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(2) if i == 6: # step 8 - scene list self.tabMain.setCurrentIndex(self.TabPlots) def wordCount(self, i): src = { 0: self.txtSummarySentence, 1: self.txtSummaryPara, 2: self.txtSummaryPage, 3: self.txtSummaryFull }[i] lbl = { 0: self.lblSummaryWCSentence, 1: self.lblSummaryWCPara, 2: self.lblSummaryWCPage, 3: self.lblSummaryWCFull }[i] wc = wordCount(src.toPlainText()) if i in [2, 3]: pages = self.tr(" (~{} pages)").format(int(wc / 25) / 10.) else: pages = "" lbl.setText(self.tr("Words: {}{}").format(wc, pages)) def setupMoreUi(self): # Tool bar on the right self.toolbar = collapsibleDockWidgets(Qt.RightDockWidgetArea, self) self.toolbar.addCustomWidget(self.tr("Book summary"), self.grpPlotSummary, self.TabPlots) self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac) if self._toolbarState: self.toolbar.restoreState(self._toolbarState) # Custom "tab" bar on the left self.lstTabs.setIconSize(QSize(48, 48)) for i in range(self.tabMain.count()): icons = [ "general-128px.png", "summary-128px.png", "characters-128px.png", "plot-128px.png", "world-128px.png", "outline-128px.png", "redaction-128px.png", "" ] self.tabMain.setTabIcon( i, QIcon(appPath("icons/Custom/Tabs/{}".format(icons[i])))) item = QListWidgetItem(self.tabMain.tabIcon(i), self.tabMain.tabText(i)) item.setSizeHint(QSize(item.sizeHint().width(), 64)) item.setTextAlignment(Qt.AlignCenter) self.lstTabs.addItem(item) self.tabMain.tabBar().hide() self.lstTabs.currentRowChanged.connect(self.tabMain.setCurrentIndex) self.tabMain.currentChanged.connect(self.lstTabs.setCurrentRow) # Splitters self.splitterPersos.setStretchFactor(0, 25) self.splitterPersos.setStretchFactor(1, 75) self.splitterPlot.setStretchFactor(0, 20) self.splitterPlot.setStretchFactor(1, 60) self.splitterPlot.setStretchFactor(2, 30) self.splitterWorld.setStretchFactor(0, 25) self.splitterWorld.setStretchFactor(1, 75) self.splitterOutlineH.setStretchFactor(0, 25) self.splitterOutlineH.setStretchFactor(1, 75) self.splitterOutlineV.setStretchFactor(0, 75) self.splitterOutlineV.setStretchFactor(1, 25) self.splitterRedacV.setStretchFactor(0, 75) self.splitterRedacV.setStretchFactor(1, 25) self.splitterRedacH.setStretchFactor(0, 30) self.splitterRedacH.setStretchFactor(1, 40) self.splitterRedacH.setStretchFactor(2, 30) # QFormLayout stretch for w in [ self.txtWorldDescription, self.txtWorldPassion, self.txtWorldConflict ]: s = w.sizePolicy() s.setVerticalStretch(1) w.setSizePolicy(s) # Help box references = [ (self.lytTabOverview, self.tr("Enter infos about your book, and yourself."), 0), (self.lytSituation, self. tr("""The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous evil wizard could wasn't abled to kill a baby?' (Harry Potter)""" ), 1), (self.lytSummary, self. tr("""Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary."""), 1), (self.lytTabPersos, self.tr("Create your characters."), 0), (self.lytTabPlot, self.tr("Develop plots."), 0), (self.lytTabOutline, self.tr("Create the outline of your masterpiece."), 0), (self.lytTabRedac, self.tr("Write."), 0), (self.lytTabDebug, self.tr("Debug infos. Sometimes useful."), 0) ] for widget, text, pos in references: label = helpLabel(text, self) self.actShowHelp.toggled.connect(label.setVisible, AUC) widget.layout().insertWidget(pos, label) self.actShowHelp.setChecked(False) # Spellcheck if enchant: self.menuDict = QMenu(self.tr("Dictionary")) self.menuDictGroup = QActionGroup(self) self.updateMenuDict() self.menuTools.addMenu(self.menuDict) self.actSpellcheck.toggled.connect(self.toggleSpellcheck, AUC) self.dictChanged.connect(self.mainEditor.setDict, AUC) self.dictChanged.connect(self.redacMetadata.setDict, AUC) self.dictChanged.connect(self.outlineItemEditor.setDict, AUC) else: # No Spell check support self.actSpellcheck.setVisible(False) a = QAction(self.tr("Install PyEnchant to use spellcheck"), self) a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning)) a.triggered.connect(self.openPyEnchantWebPage, AUC) self.menuTools.addAction(a) ############################################################################### # SPELLCHECK ############################################################################### def updateMenuDict(self): if not enchant: return self.menuDict.clear() for i in enchant.list_dicts(): a = QAction(str(i[0]), self) a.setCheckable(True) if settings.dict is None: settings.dict = enchant.get_default_language() if str(i[0]) == settings.dict: a.setChecked(True) a.triggered.connect(self.setDictionary, AUC) self.menuDictGroup.addAction(a) self.menuDict.addAction(a) def setDictionary(self): if not enchant: return for i in self.menuDictGroup.actions(): if i.isChecked(): # self.dictChanged.emit(i.text().replace("&", "")) settings.dict = i.text().replace("&", "") # Find all textEditView from self, and toggle spellcheck for w in self.findChildren(textEditView, QRegExp(".*"), Qt.FindChildrenRecursively): w.setDict(settings.dict) def openPyEnchantWebPage(self): from PyQt5.QtGui import QDesktopServices QDesktopServices.openUrl(QUrl("http://pythonhosted.org/pyenchant/")) def toggleSpellcheck(self, val): settings.spellcheck = val # Find all textEditView from self, and toggle spellcheck for w in self.findChildren(textEditView, QRegExp(".*"), Qt.FindChildrenRecursively): w.toggleSpellcheck(val) ############################################################################### # SETTINGS ############################################################################### def settingsLabel(self): self.settingsWindow(3) def settingsStatus(self): self.settingsWindow(4) def settingsWindow(self, tab=None): self.sw = settingsWindow(self) self.sw.hide() self.sw.setWindowModality(Qt.ApplicationModal) self.sw.setWindowFlags(Qt.Dialog) r = self.sw.geometry() r2 = self.geometry() self.sw.move(r2.center() - r.center()) if tab: self.sw.setTab(tab) self.sw.show() ############################################################################### # TOOLS ############################################################################### def frequencyAnalyzer(self): self.fw = frequencyAnalyzer(self) self.fw.show() ############################################################################### # VIEW MENU ############################################################################### def generateViewMenu(self): values = [ (self.tr("Nothing"), "Nothing"), (self.tr("POV"), "POV"), (self.tr("Label"), "Label"), (self.tr("Progress"), "Progress"), (self.tr("Compile"), "Compile"), ] menus = [(self.tr("Tree"), "Tree"), (self.tr("Index cards"), "Cork"), (self.tr("Outline"), "Outline")] submenus = { "Tree": [ (self.tr("Icon color"), "Icon"), (self.tr("Text color"), "Text"), (self.tr("Background color"), "Background"), ], "Cork": [ (self.tr("Icon"), "Icon"), (self.tr("Text"), "Text"), (self.tr("Background"), "Background"), (self.tr("Border"), "Border"), (self.tr("Corner"), "Corner"), ], "Outline": [ (self.tr("Icon color"), "Icon"), (self.tr("Text color"), "Text"), (self.tr("Background color"), "Background"), ], } self.menuView.clear() self.menuView.addMenu(self.menuMode) self.menuView.addSeparator() # print("Generating menus with", settings.viewSettings) for mnu, mnud in menus: m = QMenu(mnu, self.menuView) for s, sd in submenus[mnud]: m2 = QMenu(s, m) agp = QActionGroup(m2) for v, vd in values: a = QAction(v, m) a.setCheckable(True) a.setData("{},{},{}".format(mnud, sd, vd)) if settings.viewSettings[mnud][sd] == vd: a.setChecked(True) a.triggered.connect(self.setViewSettingsAction, AUC) agp.addAction(a) m2.addAction(a) m.addMenu(m2) self.menuView.addMenu(m) def setViewSettingsAction(self): action = self.sender() item, part, element = action.data().split(",") self.setViewSettings(item, part, element) def setViewSettings(self, item, part, element): settings.viewSettings[item][part] = element if item == "Cork": self.mainEditor.updateCorkView() if item == "Outline": self.mainEditor.updateTreeView() self.treeOutlineOutline.viewport().update() if item == "Tree": self.treeRedacOutline.viewport().update() ############################################################################### # VIEW MODES ############################################################################### def setViewModeSimple(self): settings.viewMode = "simple" self.tabMain.setCurrentIndex(self.TabRedac) self.viewModeFictionVisibilitySwitch(False) self.actModeSimple.setChecked(True) def setViewModeFiction(self): settings.viewMode = "fiction" self.viewModeFictionVisibilitySwitch(True) self.actModeFiction.setChecked(True) def viewModeFictionVisibilitySwitch(self, val): """ Swtiches the visibility of some UI components useful for fiction only @param val: sets visibility to val """ # Menu navigation & boutton in toolbar self.toolbar.setDockVisibility(self.dckNavigation, val) # POV in metadatas from manuskript.ui.views.propertiesView import propertiesView for w in findWidgetsOfClass(propertiesView): w.lblPOV.setVisible(val) w.cmbPOV.setVisible(val) # POV in outline view if Outline.POV.value in settings.outlineViewColumns: settings.outlineViewColumns.remove(Outline.POV.value) from manuskript.ui.views.outlineView import outlineView for w in findWidgetsOfClass(outlineView): w.hideColumns() # TODO: clean up all other fiction things in non-fiction view mode # Character in search widget # POV in settings / views ############################################################################### # COMPILE ############################################################################### def doCompile(self): self.compileDialog = compileDialog() self.compileDialog.show()
class ScanTableWidget(QWidget): """ Used for displaying information in a table. =============================== ========================================= **Signals:** sigScanClicked Emitted when the user has clicked on a row of the table and returns the current index. This index contains information about the current rows column data. =============================== ========================================= """ sigScanClicked = pyqtSignal(QModelIndex, name="scanClicked") header = [ "MS level", "Index", "RT (min)", "precursor m/z", "charge", "ID", "PeptideSeq", "PeptideIons", ] def __init__(self, ms_experiment, *args): QWidget.__init__(self, *args) self.ms_experiment = ms_experiment self.table_model = ScanTableModel(self, self.ms_experiment, self.header) self.table_view = QTableView() # register a proxy class for filering and sorting the scan table self.proxy = QSortFilterProxyModel(self) self.proxy.setSourceModel(self.table_model) self.table_view.sortByColumn(1, Qt.AscendingOrder) # setup selection model self.table_view.setSelectionMode(QAbstractItemView.SingleSelection) self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.table_view.setModel(self.proxy) self.table_view.setSelectionModel(QItemSelectionModel(self.proxy)) # header self.horizontalHeader = self.table_view.horizontalHeader() self.horizontalHeader.sectionClicked.connect(self.onHeaderClicked) # enable sorting self.table_view.setSortingEnabled(True) # connect signals to slots self.table_view.selectionModel().currentChanged.connect( self.onCurrentChanged) # keyboard moves to new row self.horizontalHeader.sectionClicked.connect(self.onHeaderClicked) layout = QVBoxLayout(self) layout.addWidget(self.table_view) self.setLayout(layout) # hide column 7 with the PepIon data, intern information usage self.table_view.setColumnHidden(7, True) # Add rt in minutes for better TIC interaction self.table_view.setItemDelegateForColumn(2, RTUnitDelegate(self)) self.table_view.setColumnWidth(2, 160) # default : first row selected. in OpenMSWidgets def onRowSelected(self, index): """se_comment: hard-refactoring to comply to pep8""" if index.siblingAtColumn(1).data() is None: return # prevents crash if row gets filtered out self.curr_spec = self.ms_experiment.getSpectrum( index.siblingAtColumn(1).data()) self.scanClicked.emit(index) def onCurrentChanged(self, new_index, old_index): self.onRowSelected(new_index) def onHeaderClicked(self, logicalIndex): if logicalIndex != 0: return # allow filter on first column only for now self.logicalIndex = logicalIndex self.menuValues = QMenu(self) self.signalMapper = QSignalMapper(self) # get unique values from (unfiltered) model valuesUnique = set([ self.table_model.index(row, self.logicalIndex).data() for row in range( self.table_model.rowCount( self.table_model.index(-1, self.logicalIndex))) ]) if len(valuesUnique) == 1: return # no need to select anything actionAll = QAction("Show All", self) actionAll.triggered.connect(self.onShowAllRows) self.menuValues.addAction(actionAll) self.menuValues.addSeparator() """se_comment: hard-refactoring to comply to pep8""" l: enumerate = enumerate(sorted(list(set(valuesUnique)))) for actionNumber, actionName in l: action = QAction(actionName, self) self.signalMapper.setMapping(action, actionNumber) action.triggered.connect(self.signalMapper.map) self.menuValues.addAction(action) self.signalMapper.mapped.connect(self.onSignalMapper) # get screen position of table header and open menu headerPos = self.table_view.mapToGlobal(self.horizontalHeader.pos()) posY = headerPos.y() + self.horizontalHeader.height() posX = headerPos.x() + \ self.horizontalHeader.sectionPosition(self.logicalIndex) self.menuValues.exec_(QPoint(posX, posY)) def onShowAllRows(self): filterColumn = self.logicalIndex filterString = QRegExp("", Qt.CaseInsensitive, QRegExp.RegExp) self.proxy.setFilterRegExp(filterString) self.proxy.setFilterKeyColumn(filterColumn) def onSignalMapper(self, i): stringAction = self.signalMapper.mapping(i).text() filterColumn = self.logicalIndex filterString = QRegExp(stringAction, Qt.CaseSensitive, QRegExp.FixedString) self.proxy.setFilterRegExp(filterString) self.proxy.setFilterKeyColumn(filterColumn)
class FilterableTable(SQLTable): """a filterable Table Widget that displays content of an SQLite table; for individual widgets, subclass and overwrite the create_model method; add_color_proxy should be an (INT allele_status-column, INT lab_status-column) tuple """ def __init__(self, log, mydb = ": memory :", add_color_proxy = False, header_dic = None): super().__init__(log, mydb) self.add_color_proxy = add_color_proxy self.header_dic = header_dic self.create_model() self.fill_UI() self.create_filter_model() self.update_filterbox() def fill_UI(self): """sets up the layout """ self.log.debug("\t- Setting up the table...") self.table = QTableView() self.table.setContextMenuPolicy(Qt.CustomContextMenu) self.header = self.table.horizontalHeader() # table header self.header.setSectionResizeMode(QHeaderView.ResizeToContents) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setAlternatingRowColors(True) # self.header.sectionClicked.connect(self.on_header_sectionClicked) mode = QAbstractItemView.SingleSelection self.table.setSelectionMode(mode) self.grid.addWidget(self.table, 2, 0, 10, 10) self.filter_lbl = QLabel("Filter:", self) self.grid.addWidget(self.filter_lbl, 1, 2) self.filter_entry = QLineEdit(self) self.grid.addWidget(self.filter_entry, 1, 3) self.filter_entry.textChanged.connect(self.on_filter_entry_textChanged) self.filter_text = "" self.filter_cb = QComboBox(self) self.grid.addWidget(self.filter_cb, 1, 4) self.filter_cb.currentIndexChanged.connect(self.on_filter_cb_IndexChanged) self.filter_btn = QPushButton("Filter!", self) self.grid.addWidget(self.filter_btn, 1, 5) self.filter_btn.clicked.connect(self.on_filter_btn_clicked) self.unfilter_btn = QPushButton("Remove Filter", self) self.grid.addWidget(self.unfilter_btn, 1, 6) self.unfilter_btn.clicked.connect(self.on_actionAll_triggered) self.log.debug("\t=> Done!") def update_filterbox(self): """fills the filter-combobox with the header values after the model has been created and set """ column_num = self.model.columnCount() if self.header_dic: columns = [self.header_dic[i] for i in self.header_dic] else: columns = [self.proxy.headerData(i, Qt.Horizontal) for i in range(column_num)] self.filter_cb.addItems(columns) def create_filter_model(self): """creates the filter-proxy-model on top of self.model """ self.log.debug("Creating filter model...") self.proxy = QSortFilterProxyModel(self) if self.add_color_proxy: (allele_status_column, lab_status_column) = self.add_color_proxy self.log.debug("adding color filter to columns {} and {}".format(allele_status_column, lab_status_column)) self.color_proxy = ColorProxyModel(self, allele_status_column, lab_status_column) self.color_proxy.setSourceModel(self.model) self.proxy.setSourceModel(self.color_proxy) else: self.proxy.setSourceModel(self.model) self.table.setSortingEnabled(True) self.table.setModel(self.proxy) def on_filter_cb_IndexChanged(self, index): """restricts RegEx filter to selected column """ self.log.debug("Combobox: colum {} selected".format(index)) self.proxy.setFilterKeyColumn(index) def on_filter_entry_textChanged(self, text): """stores content of filter_entry as self.text """ self.log.debug("filter text: '{}'".format(text)) self.filter_text = text def on_filter_btn_clicked(self): """activates RegEx filter to current content of filter_entry and filter_cb """ column = self.filter_cb.currentIndex() self.log.debug("Filtering column {} for '{}'".format(column, self.filter_text)) self.proxy.setFilterKeyColumn(column) search = QRegExp(self.filter_text, Qt.CaseInsensitive, QRegExp.RegExp) self.proxy.setFilterRegExp(search) def on_header_sectionClicked(self, logicalIndex): """opens a dialog to choose between all unique values for this column, or revert to 'All' """ self.log.debug("Header clicked: column {}".format(logicalIndex)) self.logicalIndex = logicalIndex menuValues = QMenu(self) self.signalMapper = QSignalMapper(self) self.filter_cb.setCurrentIndex(self.logicalIndex) self.filter_cb.blockSignals(True) self.proxy.setFilterKeyColumn(self.logicalIndex) valuesUnique = [str(self.model.index(row, self.logicalIndex).data()) for row in range(self.model.rowCount()) ] actionAll = QAction("All", self) actionAll.triggered.connect(self.on_actionAll_triggered) menuValues.addAction(actionAll) menuValues.addSeparator() for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))): action = QAction(actionName, self) self.signalMapper.setMapping(action, actionNumber) action.triggered.connect(self.signalMapper.map) menuValues.addAction(action) self.signalMapper.mapped.connect(self.on_signalMapper_mapped) headerPos = self.table.mapToGlobal(self.header.pos()) posY = headerPos.y() + self.header.height() posX = headerPos.x() + self.header.sectionViewportPosition(self.logicalIndex) menuValues.exec_(QPoint(posX, posY)) def on_actionAll_triggered(self): """reverts table to unfiltered state """ self.log.debug("Unfiltering...") filterString = QRegExp("", Qt.CaseInsensitive, QRegExp.RegExp) self.proxy.setFilterRegExp(filterString) self.filter_entry.setText("") def on_signalMapper_mapped(self, i): """filters current column to mapping text """ text = self.signalMapper.mapping(i).text() self.log.debug("Filtering column {} to '{}'".format(self.logicalIndex, text)) filterString = QRegExp(text, Qt.CaseSensitive, QRegExp.FixedString) self.proxy.setFilterRegExp(filterString)