def _delete(self): index_list = [] for model_index in self.tableView.selectionModel().selectedRows(): index = QPersistentModelIndex(model_index) index_list.append(index) for index in index_list: self.model.removeRow(index.row()) self._remove_project_from_config(index.data()) if index_list: self.dirty = True
def test_model_persistent_index(model_test): ''' ☑ 🛇 read-only.ro ☑ ★ user.json ☑ ⎕ commands.json ☐ 🛇 asset:plover:assets/main.json ''' persistent_index = QPersistentModelIndex(model_test.model.index(1)) assert persistent_index.row() == 1 assert persistent_index.data(Qt.CheckStateRole) == Qt.Checked assert persistent_index.data(Qt.DecorationRole) == 'favorite' assert persistent_index.data(Qt.DisplayRole) == 'user.json' model_test.configure(classic_dictionaries_display_order=True) assert persistent_index.row() == 2 assert persistent_index.data(Qt.CheckStateRole) == Qt.Checked assert persistent_index.data(Qt.DecorationRole) == 'favorite' assert persistent_index.data(Qt.DisplayRole) == 'user.json' model_test.model.setData(persistent_index, Qt.Unchecked, Qt.CheckStateRole) assert persistent_index.row() == 2 assert persistent_index.data(Qt.CheckStateRole) == Qt.Unchecked assert persistent_index.data(Qt.DecorationRole) == 'normal' assert persistent_index.data(Qt.DisplayRole) == 'user.json'
class Viewer(QtMainWindow): jobAdded = Signal() jobFinished = Signal() def __init__(self, weboob, parent=None): super(Viewer, self).__init__(parent) self.ui = Ui_Viewer() self.ui.setupUi(self) self.ui.prevButton.clicked.connect(self.prev) self.ui.nextButton.clicked.connect(self.next) self.ui.firstButton.clicked.connect(self.first) self.ui.lastButton.clicked.connect(self.last) self.ui.actionZoomIn.triggered.connect(self.zoomIn) self.ui.actionZoomOut.triggered.connect(self.zoomOut) self.ui.actionFullSize.triggered.connect(self.zoomFullSize) self.ui.actionFitWindow.triggered.connect(self.zoomFit) self.ui.actionSaveImage.setShortcut(QKeySequence.Save) self.ui.actionSaveImage.triggered.connect(self.saveImage) self.ui.actionClose.setShortcut(QKeySequence.Close) self.ui.actionClose.triggered.connect(self.close) self.model = None self.current = None self.total = 0 self.zoomFactor = 1 self.zoomMode = ZOOM_FACTOR self.weboob = weboob def setData(self, model, qidx): self.model = model self.current = QPersistentModelIndex(qidx) self.model.rowsInserted.connect(self.updatePos) self.model.rowsRemoved.connect(self.updatePos) self.model.rowsInserted.connect(self.updateNavButtons) self.model.rowsRemoved.connect(self.updateNavButtons) self.model.dataChanged.connect(self._dataChanged) self.model.modelReset.connect(self.disable) self.updateImage() @Slot() def disable(self): self.setEnabled(False) def updateNavButtons(self): prev = self.current.row() > 0 self.ui.prevButton.setEnabled(prev) self.ui.firstButton.setEnabled(prev) next = self.current.row() < self.total - 1 self.ui.nextButton.setEnabled(next) self.ui.lastButton.setEnabled(next) def updatePos(self): self.total = self.model.rowCount(self.current.parent()) self.ui.posLabel.setText('%d / %d' % (self.current.row() + 1, self.total)) def updateImage(self): self.updatePos() self.updateNavButtons() obj = self.current.data(ResultModel.RoleObject) if obj.data is NotLoaded: self.model.fillObj(obj, ['data'], QModelIndex(self.current)) self.pixmap = None elif obj.data: self.pixmap = QPixmap(QImage.fromData(obj.data)) else: self.pixmap = QPixmap() self._rebuildImage() @Slot(QModelIndex) def _dataChanged(self, qidx): if qidx == self.current: obj = qidx.data(ResultModel.RoleObject) if obj.data: self.pixmap = QPixmap(QImage.fromData(obj.data)) else: self.pixmap = QPixmap() self._rebuildImage() @Slot() def next(self): new = self.current.sibling(self.current.row() + 1, 0) if not new.isValid(): return self.current = QPersistentModelIndex(new) self.updateImage() @Slot() def prev(self): if self.current.row() == 0: return self.current = QPersistentModelIndex(self.current.sibling(self.current.row() - 1, 0)) self.updateImage() @Slot() def first(self): self.current = QPersistentModelIndex(self.current.sibling(0, 0)) self.updateImage() @Slot() def last(self): self.current = QPersistentModelIndex(self.current.sibling(self.total - 1, 0)) self.updateImage() @Slot() def zoomIn(self): self.zoomFactor *= 1.25 self.zoomMode = ZOOM_FACTOR self._rebuildImage() @Slot() def zoomOut(self): self.zoomFactor *= 0.75 self.zoomMode = ZOOM_FACTOR self._rebuildImage() @Slot() def zoomFullSize(self): self.zoomFactor = 1 self.zoomMode = ZOOM_FACTOR self._rebuildImage() @Slot() def zoomFit(self): self.zoomMode = ZOOM_FIT self._rebuildImage() def resizeEvent(self, ev): super(Viewer, self).resizeEvent(ev) if self.zoomMode == ZOOM_FIT: self._rebuildImage() def _rebuildZoom(self): if self.zoomMode == ZOOM_FACTOR: new_width = int(self.pixmap.width() * self.zoomFactor) pixmap = self.pixmap.scaledToWidth(new_width, Qt.SmoothTransformation) else: new_size = self.ui.scrollArea.viewport().size() pixmap = self.pixmap.scaled(new_size, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.zoomFactor = pixmap.width() / float(self.pixmap.width()) return pixmap def _rebuildImage(self): if self.pixmap is None: self.ui.view.setText('Loading...') return elif self.pixmap.isNull(): self.ui.view.setText('Image could not be loaded') return pixmap = self._rebuildZoom() self.ui.view.setPixmap(pixmap) @Slot() def saveImage(self): def ext_for_filter(s): return re.match(r'(?:[A-Z]+) \(\*\.([a-z]+)\)$', s).group(1) if not self.pixmap: return filters = ['PNG (*.png)', 'JPEG (*.jpg)', 'GIF (*.gif)'] obj = self.current.data(ResultModel.RoleObject) name = '%s.%s' % (obj.title or obj.id or u'', obj.ext or 'png') default = filters[0] for f in filters: if name.endswith(ext_for_filter(f)): default = f filters = ';;'.join(filters) target = os.path.join(self.parent().lastSaveDir, name) out, filter = QFileDialog.getSaveFileName(self, 'Save image', target, filters, default) if not out: return ext = ext_for_filter(filter) self.parent().lastSaveDir = os.path.dirname(out) if not os.path.splitext(out)[1]: out = '%s.%s' % (out, ext) if os.path.exists(out): q = self.tr('%s already exists, are you sure you want to replace it?') % out reply = QMessageBox.question(self, self.tr('Overwrite?'), q) if reply == QMessageBox.No: return self.saveImage() self.pixmap.save(out, ext.upper())
class Tab(QWidget): currItemChanged = pyqtSignal(['QModelIndex']) def __init__(self, packItem=QModelIndex(), parent: TabWidget = None): super(Tab, self).__init__(parent) self.tabWidget = parent self.icon = QIcon() self.initActions() self.pathToolBar = ToolBar(self) self.pathToolBar.addAction(self.backAct) self.pathToolBar.addAction(self.forwardAct) self.pathLine: AddressLine = AddressLine(self) self.objTypeLine = LineEdit(self, placeholderText="Object Type") self.objTypeLine.setFixedWidth(168) self.objTypeLine.setReadOnly(True) self.descrLabel = QLabel(self) self.descrLabel.setWordWrap(True) self.descrLabel.setTextInteractionFlags(Qt.TextSelectableByMouse) QWebEngineSettings.defaultSettings().setAttribute( QWebEngineSettings.PluginsEnabled, True) self.mediaWidget = QWebEngineView() self.saveMediaAsBtn = QPushButton( f"Save media as..", self, toolTip="Save media file as..", clicked=lambda: self.saveMediaAsWithDialog( QStandardPaths.writableLocation(QStandardPaths. DocumentsLocation))) self.saveMediaBtn = QPushButton( f"Save media on desktop", self, toolTip="Save media file on desktop", clicked=lambda: self.saveMediaAsWithDialog( QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) )) self.mediaViewWidget = QWidget() mediaViewWidgetLayout = QVBoxLayout(self.mediaViewWidget) mediaViewWidgetLayout.setContentsMargins(0, 0, 0, 0) mediaViewWidgetLayout.addWidget(self.mediaWidget) mediaViewWidgetLayout.addWidget(self.saveMediaBtn) mediaViewWidgetLayout.addWidget(self.saveMediaAsBtn) self.mediaViewWidget.hide() self.attrsTreeView = AttrsTreeView(self) self.attrsTreeView.setFrameShape(QFrame.NoFrame) self.toolBar = ToolBar(self) self.toolBar.addAction(self.attrsTreeView.zoomInAct) self.toolBar.addAction(self.attrsTreeView.zoomOutAct) self.toolBar.addAction(self.attrsTreeView.collapseAllAct) self.toolBar.addAction(self.attrsTreeView.expandAllAct) self.toolBar.addSeparator() self.toolBar.addAction(self.attrsTreeView.undoAct) self.toolBar.addAction(self.attrsTreeView.redoAct) self.toolBar.addAction(self.attrsTreeView.copyAct) self.toolBar.addAction(self.attrsTreeView.cutAct) self.toolBar.addAction(self.attrsTreeView.pasteAct) self.toolBar.addAction(self.attrsTreeView.delClearAct) self.toolBar.addAction(self.attrsTreeView.editCreateInDialogAct) self.toolBar.addAction(self.attrsTreeView.addAct) self.searchBar = SearchBar( self.attrsTreeView, parent=self, filterColumns=[ATTRIBUTE_COLUMN, VALUE_COLUMN], closable=True) self.searchBar.hide() self.openSearchBarSC = QShortcut(SC_SEARCH, self, activated=self.openSearchBar) self.packItem = QPersistentModelIndex(QModelIndex()) self.prevItems = [] self.nextItems = [] if packItem.isValid(): self.openItem(packItem) else: self.openEmptyItem() self._initLayout() # noinspection PyArgumentList def initActions(self): self.backAct = QAction(BACK_ICON, "Back", self, statusTip=f"Go back one item", toolTip=f"Go back one item", shortcut=SC_BACK, triggered=self.openPrevItem, enabled=False) self.forwardAct = QAction(FORWARD_ICON, "Forward", self, statusTip=f"Go forward one item", toolTip=f"Go forward one item", shortcut=SC_FORWARD, triggered=self.openNextItem, enabled=False) def windowTitle(self) -> str: return self.packItem.data(NAME_ROLE) def windowIcon(self) -> QIcon: return self.packItem.data(Qt.DecorationRole) def openSearchBar(self): self.searchBar.show() self.searchBar.searchLine.setFocus() def openItem(self, packItem: QModelIndex): if not packItem == QModelIndex(self.packItem): self.nextItems.clear() if self.packItem.isValid(): self.prevItems.append(self.packItem) self._openItem(packItem) def openPrevItem(self): if self.prevItems: prevItem = self.prevItems.pop() self.nextItems.append(self.packItem) self._openItem(QModelIndex(prevItem)) def openNextItem(self): if self.nextItems: nextItem = self.nextItems.pop() self.prevItems.append(self.packItem) self._openItem(QModelIndex(nextItem)) def openEmptyItem(self): self._openItem(QModelIndex()) def _openItem(self, packItem: QModelIndex): try: currTab: Tab = self.tabWidget.currentWidget() state = currTab.attrsTreeView.header().saveState() except AttributeError: # if there is no curr widget, there is no current header state, it state = None self.packItem = QPersistentModelIndex(packItem.siblingAtColumn(0)) self.descrLabel.setText("") self.packItemObj = self.packItem.data(OBJECT_ROLE) self.updateMediaWidget() self.pathLine.setText(getTreeItemPath(self.packItem)) self.objTypeLine.setText(getTypeName(type(self.packItemObj))) icon = self.packItem.data(Qt.DecorationRole) if icon: self.setWindowIcon(icon) self.setWindowTitle(self.packItem.data(Qt.DisplayRole)) self.attrsTreeView.newPackItem(self.packItem) self.attrsTreeView.selectionModel().currentChanged.connect( self.showDetailInfoItemDoc) self.currItemChanged.emit(QModelIndex(self.packItem)) self.forwardAct.setEnabled( True) if self.nextItems else self.forwardAct.setDisabled(True) self.backAct.setEnabled( True) if self.prevItems else self.backAct.setDisabled(True) if state: self.attrsTreeView.header().restoreState(state) def updateMediaWidget(self): if self.packItem.data(IS_MEDIA_ROLE): self.mediaWidget.setContent(b"loading...") self.mediaViewWidget.show() if not self.mediaWidget.width(): # set equal sizes oldSizes = self.splitter.sizes() newSizes = [ sum(oldSizes) / (len(oldSizes)) for size in oldSizes ] self.splitter.setSizes(newSizes) try: mediaContent = self.packItem.data(MEDIA_CONTENT_ROLE) if self.packItem.data(IS_URL_MEDIA_ROLE): self.mediaWidget.load(QUrl(mediaContent.value)) else: self.mediaWidget.setContent(mediaContent.value, mediaContent.mime_type) except Exception as e: print(e) self.mediaWidget.setContent( b"Error occurred while loading media") self.mediaWidget.setZoomFactor(1.0) else: self.mediaViewWidget.hide() def saveMediaAsWithDialog(self, directory="") -> bool: mediaContent = self.packItem.data(MEDIA_CONTENT_ROLE) file = self.packItem.data(NAME_ROLE) saved = False while not saved: try: file = QFileDialog.getSaveFileName( self, 'Save media File', directory + "/" + file.strip("/"), options=FILE_DIALOG_OPTIONS)[0] except AttributeError as e: QMessageBox.critical(self, "Error", f"{e}") else: if file: saved = self.saveMedia(mediaContent, file) else: # cancel pressed return def saveMedia(self, media=None, file: str = None) -> bool: try: with open(file, "wb") as f: f.write(media.value) return True except (TypeError, ValueError) as e: QMessageBox.critical(self, "Error", f"Media couldn't be saved: {file}: {e}") except AttributeError as e: QMessageBox.critical(self, "Error", f"No chosen media to save: {e}") return False def showDetailInfoItemDoc(self, detailInfoItem: QModelIndex): self.descrLabel.setText(detailInfoItem.data(Qt.WhatsThisRole)) def _initLayout(self): pathWidget = QWidget() pathLayout = QHBoxLayout(pathWidget) pathLayout.setContentsMargins(0, 0, 0, 0) pathLayout.addWidget(self.pathToolBar) pathLayout.addWidget(self.pathLine) pathLayout.addWidget(self.objTypeLine) pathWidget.setFixedHeight(TOOLBARS_HEIGHT) toolBarWidget = QWidget() toolBarLayout = QHBoxLayout(toolBarWidget) toolBarLayout.setContentsMargins(0, 0, 0, 0) toolBarLayout.addWidget(self.toolBar) toolBarLayout.addWidget(self.searchBar) toolBarWidget.setFixedHeight(TOOLBARS_HEIGHT) treeViewWidget = QWidget() treeViewLayout = QVBoxLayout(treeViewWidget) treeViewLayout.setContentsMargins(0, 0, 0, 0) treeViewLayout.addWidget(pathWidget) treeViewLayout.addWidget(self.attrsTreeView) treeViewLayout.addWidget(self.descrLabel) self.splitter = QSplitter() self.splitter.setOrientation(Qt.Horizontal) self.splitter.setContentsMargins(0, 0, 0, 0) self.splitter.addWidget(treeViewWidget) self.splitter.addWidget(self.mediaViewWidget) layout = QVBoxLayout(self) layout.setObjectName("tabLayout") layout.addWidget(pathWidget) layout.addWidget(toolBarWidget) layout.addWidget(self.splitter) layout.setSpacing(2) layout.setContentsMargins(0, 2, 0, 2)
class Viewer(QtMainWindow): jobAdded = Signal() jobFinished = Signal() def __init__(self, weboob, parent=None): super(Viewer, self).__init__(parent) self.ui = Ui_Viewer() self.ui.setupUi(self) self.ui.prevButton.clicked.connect(self.prev) self.ui.nextButton.clicked.connect(self.next) self.ui.firstButton.clicked.connect(self.first) self.ui.lastButton.clicked.connect(self.last) self.ui.actionZoomIn.triggered.connect(self.zoomIn) self.ui.actionZoomOut.triggered.connect(self.zoomOut) self.ui.actionFullSize.triggered.connect(self.zoomFullSize) self.ui.actionFitWindow.triggered.connect(self.zoomFit) self.ui.actionSaveImage.setShortcut(QKeySequence.Save) self.ui.actionSaveImage.triggered.connect(self.saveImage) self.ui.actionClose.setShortcut(QKeySequence.Close) self.ui.actionClose.triggered.connect(self.close) self.model = None self.current = None self.total = 0 self.zoomFactor = 1 self.zoomMode = ZOOM_FACTOR self.weboob = weboob def setData(self, model, qidx): self.model = model self.current = QPersistentModelIndex(qidx) self.model.rowsInserted.connect(self.updatePos) self.model.rowsRemoved.connect(self.updatePos) self.model.rowsInserted.connect(self.updateNavButtons) self.model.rowsRemoved.connect(self.updateNavButtons) self.model.dataChanged.connect(self._dataChanged) self.model.modelReset.connect(self.disable) self.updateImage() @Slot() def disable(self): self.setEnabled(False) def updateNavButtons(self): prev = self.current.row() > 0 self.ui.prevButton.setEnabled(prev) self.ui.firstButton.setEnabled(prev) next = self.current.row() < self.total - 1 self.ui.nextButton.setEnabled(next) self.ui.lastButton.setEnabled(next) def updatePos(self): self.total = self.model.rowCount(self.current.parent()) self.ui.posLabel.setText('%d / %d' % (self.current.row() + 1, self.total)) def updateImage(self): self.updatePos() self.updateNavButtons() obj = self.current.data(ResultModel.RoleObject) if obj.data is NotLoaded: self.model.fillObj(obj, ['data'], QModelIndex(self.current)) self.pixmap = None elif obj.data: self.pixmap = QPixmap(QImage.fromData(obj.data)) else: self.pixmap = QPixmap() self._rebuildImage() @Slot(QModelIndex) def _dataChanged(self, qidx): if qidx == self.current: obj = qidx.data(ResultModel.RoleObject) if obj.data: self.pixmap = QPixmap(QImage.fromData(obj.data)) else: self.pixmap = QPixmap() self._rebuildImage() @Slot() def next(self): new = self.current.sibling(self.current.row() + 1, 0) if not new.isValid(): return self.current = QPersistentModelIndex(new) self.updateImage() @Slot() def prev(self): if self.current.row() == 0: return self.current = QPersistentModelIndex( self.current.sibling(self.current.row() - 1, 0)) self.updateImage() @Slot() def first(self): self.current = QPersistentModelIndex(self.current.sibling(0, 0)) self.updateImage() @Slot() def last(self): self.current = QPersistentModelIndex( self.current.sibling(self.total - 1, 0)) self.updateImage() @Slot() def zoomIn(self): self.zoomFactor *= 1.25 self.zoomMode = ZOOM_FACTOR self._rebuildImage() @Slot() def zoomOut(self): self.zoomFactor *= 0.75 self.zoomMode = ZOOM_FACTOR self._rebuildImage() @Slot() def zoomFullSize(self): self.zoomFactor = 1 self.zoomMode = ZOOM_FACTOR self._rebuildImage() @Slot() def zoomFit(self): self.zoomMode = ZOOM_FIT self._rebuildImage() def resizeEvent(self, ev): super(Viewer, self).resizeEvent(ev) if self.zoomMode == ZOOM_FIT: self._rebuildImage() def _rebuildZoom(self): if self.zoomMode == ZOOM_FACTOR: new_width = int(self.pixmap.width() * self.zoomFactor) pixmap = self.pixmap.scaledToWidth(new_width, Qt.SmoothTransformation) else: new_size = self.ui.scrollArea.viewport().size() pixmap = self.pixmap.scaled(new_size, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.zoomFactor = pixmap.width() / float(self.pixmap.width()) return pixmap def _rebuildImage(self): if self.pixmap is None: self.ui.view.setText('Loading...') return elif self.pixmap.isNull(): self.ui.view.setText('Image could not be loaded') return pixmap = self._rebuildZoom() self.ui.view.setPixmap(pixmap) @Slot() def saveImage(self): def ext_for_filter(s): return re.match(r'(?:[A-Z]+) \(\*\.([a-z]+)\)$', s).group(1) if not self.pixmap: return filters = ['PNG (*.png)', 'JPEG (*.jpg)', 'GIF (*.gif)'] obj = self.current.data(ResultModel.RoleObject) name = '%s.%s' % (obj.title or obj.id or u'', obj.ext or 'png') default = filters[0] for f in filters: if name.endswith(ext_for_filter(f)): default = f filters = ';;'.join(filters) target = os.path.join(self.parent().lastSaveDir, name) out, filter = QFileDialog.getSaveFileName(self, 'Save image', target, filters, default) if not out: return ext = ext_for_filter(filter) self.parent().lastSaveDir = os.path.dirname(out) if not os.path.splitext(out)[1]: out = '%s.%s' % (out, ext) if os.path.exists(out): q = self.tr( '%s already exists, are you sure you want to replace it?' ) % out reply = QMessageBox.question(self, self.tr('Overwrite?'), q) if reply == QMessageBox.No: return self.saveImage() self.pixmap.save(out, ext.upper())