def _init_widget(self, listbox: QTreeView): listbox.setModel(self) listbox.setItemDelegate(self.item_delegate) listbox.setContextMenuPolicy(Qt.CustomContextMenu) # listbox.customContextMenuRequested.connect(self.context_menu) listbox.customContextMenuRequested.connect(self.menu) listbox.setDragEnabled(True) listbox.setAcceptDrops(True) listbox.setDropIndicatorShown(True) listbox.setSelectionMode(listbox.ExtendedSelection) # listbox.setMovement(QTreeView.Snap) # listbox.setDragDropMode(QTreeView.InternalMove) listbox.setDragDropMode(QAbstractItemView.DragDrop) # listbox.setAlternatingRowColors(True) # listbox.setDefaultDropAction(Qt.MoveAction) # listbox.setDragDropOverwriteMode(True) # listbox.entered.connect(self.layer_entered) # listbox.setFont(QFont('Andale Mono', 13)) # the various signals that may result from the user changing the selections # listbox.activated.connect(self.changedSelection) # listbox.clicked.connect(self.changedSelection) # listbox.doubleClicked.connect(self.changedSelection) # listbox.pressed.connect(self.changedSelection) listbox.selectionModel().selectionChanged.connect( self.changedSelection) self.widgets.append(listbox)
class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.folderLayout = QWidget() self.pathRoot = QDir.rootPath() self.dirmodel = QFileSystemModel(self) self.dirmodel.setRootPath(QDir.currentPath()) self.indexRoot = self.dirmodel.index(self.dirmodel.rootPath()) self.folder_view = QTreeView() self.folder_view.setDragEnabled(True) self.folder_view.setModel(self.dirmodel) self.folder_view.setRootIndex(self.indexRoot) self.selectionModel = self.folder_view.selectionModel() self.left_layout = QVBoxLayout() self.left_layout.addWidget(self.folder_view) self.folderLayout.setLayout(self.left_layout) splitter_filebrowser = QSplitter(Qt.Horizontal) splitter_filebrowser.addWidget(self.folderLayout) splitter_filebrowser.addWidget(Figure_Canvas(self)) splitter_filebrowser.setStretchFactor(1, 1) hbox = QHBoxLayout(self) hbox.addWidget(splitter_filebrowser) self.centralWidget().setLayout(hbox) self.setWindowTitle('Simple drag & drop') self.setGeometry(0, 0, 1000, 500)
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel(self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) def terminate(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) @pyqtSlot(Document, Document) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() @pyqtSlot(QItemSelection, QItemSelection) def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() @pyqtSlot(QPoint) def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction(core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action("mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu().aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos))
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect( self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel( self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect( self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) def terminate(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect( self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) @pyqtSlot(Document, Document) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() @pyqtSlot(QItemSelection, QItemSelection) def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() @pyqtSlot(QPoint) def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction( core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action( "mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu( ).aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos))
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel(self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) # Add auto-hide capability. self._waitForCtrlRelease = False core.actionManager().action("mNavigation/aNext").triggered.connect( self._setWaitForCtrlRelease) core.actionManager().action("mNavigation/aPrevious").triggered.connect( self._setWaitForCtrlRelease) QApplication.instance().installEventFilter(self) def terminate(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") QApplication.instance().removeEventFilter(self) def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) @pyqtSlot(Document, Document) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() @pyqtSlot(QItemSelection, QItemSelection) def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() @pyqtSlot(QPoint) def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction(core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action("mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu().aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos)) def _setWaitForCtrlRelease(self): # We can't see actual Ctrl+PgUp/PgDn keypresses, since these get eaten # by the QAction and don't even show up in the event filter below. We # want to avoid waiting for a Ctrl release if the menu item brought us # here. As a workaround, check that Ctrl is pressed. If so, it's # unlikely to be the menu item. if QApplication.instance().keyboardModifiers() & Qt.ControlModifier: self._waitForCtrlRelease = True self.show() else: # If this was a menu selection, then update the MRU list. We can't # do this now, since the current document hasn't been changed yet. QTimer.singleShot(0, self.model.sortDocuments) def eventFilter(self, obj, event): """An event filter that looks for ctrl key releases and focus out events.""" # Wait for the user to release the Ctrl key. if ( self._waitForCtrlRelease and event.type() == QEvent.KeyRelease and event.key() == Qt.Key_Control and event.modifiers() == Qt.NoModifier): self.model.sortDocuments() self._waitForCtrlRelease = False if not self.isPinned(): self.hide() # Look for a focus out event sent by the containing widget's focus # proxy. if event.type() == QEvent.FocusOut and obj == self.focusProxy(): self.model.sortDocuments() return QObject.eventFilter(self, obj, event)
def createGUI(self): root = TagManager.createSimpleTree() model = TagTreeModel(root, self._tag_checker, self._color_helper, parent=self) model.invalidValueSetted.connect( self.invalidValueSettedByUserToTreeViewModel) #model.dataChanged.connect(dataChanged) self.setMinimumSize(300, 150) self.setWindowTitle('My Tag Manager') layout = QVBoxLayout(self) ## Menu Layout menu_layout = QHBoxLayout(self) but_save = QPushButton('Save', self) but_save.setIcon(QApplication.style().standardIcon( QStyle.SP_DialogSaveButton)) menu_layout.addWidget(but_save) but_save.clicked.connect(self.but_save_clicked) but_load = QPushButton('Load', self) but_load.setIcon(QApplication.style().standardIcon( QStyle.SP_DirOpenIcon)) menu_layout.addWidget(but_load) but_load.clicked.connect(self.but_load_clicked) but_new_f = QPushButton('New tag collections', self) but_new_f.setIcon(QApplication.style().standardIcon( QStyle.SP_FileIcon)) but_new_f.setToolTip('New tag collections') menu_layout.addWidget(but_new_f) but_new_f.clicked.connect(self.but_new_tree_clicked) layout.addLayout(menu_layout) ## Menu Layout END tv = QTreeView(self) tv.setDragDropMode(QAbstractItemView.InternalMove) tv.setDragEnabled(True) tv.setAcceptDrops(True) tv.setDropIndicatorShown(True) tv.setContextMenuPolicy(Qt.CustomContextMenu) tv.customContextMenuRequested.connect( self.customContextMenuRequestedForTreeView) tv.setModel(model) #tv.setAlternatingRowColors(True) layout.addWidget(tv) self._widget_tv = tv but = QPushButton('Remove Selected', self) but.setIcon(QApplication.style().standardIcon(QStyle.SP_TrashIcon)) layout.addWidget(but) but.clicked.connect(self.but_remove_clicked) but_add = QPushButton('Add', self) layout.addWidget(but_add) but_add.clicked.connect(self.but_add_clicked) but_add_to_root = QPushButton('Add new element to root') but_add_to_root.setToolTip("Add new element to root of tree.") layout.addWidget(but_add_to_root) but_add_to_root.clicked.connect(self.but_add_to_root_clicked) but_item_up = QPushButton("Up") but_item_up.setToolTip("Up selected item.") but_item_up.clicked.connect(self.but_item_up_clicked) layout.addWidget(but_item_up) but_item_down = QPushButton("Down") but_item_down.setToolTip("Down selected item.") but_item_down.clicked.connect(self.but_item_down_clicked) layout.addWidget(but_item_down) but_font = QPushButton('Set Font', self) layout.addWidget(but_font) but_font.clicked.connect(self.but_font_dialog_clicked) self.layout().deleteLater() # Remove default layout self.setLayout(layout)
class Editor(QMainWindow): """This is the main class. """ FORMATS = ("Aiken (*.txt);;Cloze (*.cloze);;GIFT (*.gift);;JSON (*.json)" ";;LaTex (*.tex);;Markdown (*.md);;PDF (*.pdf);;XML (*.xml)") SHORTCUTS = { "Create file": Qt.CTRL + Qt.Key_N, "Find questions": Qt.CTRL + Qt.Key_F, "Read file": Qt.CTRL + Qt.Key_O, "Read folder": Qt.CTRL + Qt.SHIFT + Qt.Key_O, "Save": Qt.CTRL + Qt.Key_S, "Save as": Qt.CTRL + Qt.SHIFT + Qt.Key_S, "Add hint": Qt.CTRL + Qt.SHIFT + Qt.Key_H, "Remove hint": Qt.CTRL + Qt.SHIFT + Qt.Key_Y, "Add answer": Qt.CTRL + Qt.SHIFT + Qt.Key_A, "Remove answer": Qt.CTRL + Qt.SHIFT + Qt.Key_Q, "Open datasets": Qt.CTRL + Qt.SHIFT + Qt.Key_D } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setWindowTitle("QAS Editor GUI") self._items: List[QWidget] = [] self._main_editor = None self.path: str = None self.top_quiz = Category() self.cxt_menu = QMenu(self) self.cxt_item: QStandardItem = None self.cxt_data: _Question | Category= None self.cur_question: _Question = None self.tagbar: GTagBar = None self.main_editor: GTextEditor = None self.is_open_find = self.is_open_dataset = False with resources.open_text("qas_editor.gui", "stylesheet.css") as ifile: self.setStyleSheet(ifile.read()) self._add_menu_bars() # Left side self.data_view = QTreeView() self.data_view.setIconSize(QSize(18, 18)) xframe_vbox = self._block_datatree() left = QWidget() left.setLayout(xframe_vbox) # Right side self.cframe_vbox = QVBoxLayout() self._block_general_data() self._block_answer() self._block_hints() self._block_units() self._block_zones() self._block_solution() self._block_template() self.cframe_vbox.addStretch() self.cframe_vbox.setSpacing(5) for value in self._items: value.setEnabled(False) frame = QFrame() frame.setLineWidth(2) frame.setLayout(self.cframe_vbox) right = QScrollArea() right.setWidget(frame) right.setWidgetResizable(True) # Create main window divider for the splitter splitter = QSplitter() splitter.addWidget(left) splitter.addWidget(right) splitter.setStretchFactor(1, 1) splitter.setSizes([250, 100]) self.setCentralWidget(splitter) # Create lower status bar. status = QStatusBar() self.setStatusBar(status) self.cat_name = QLabel() status.addWidget(self.cat_name) self._update_tree_item(self.top_quiz, self.root_item) self.data_view.expandAll() self.setGeometry(50, 50, 1200, 650) self.show() def _debug_me(self): self.path = "./test_lib/datasets/moodle/all.xml" self.top_quiz = Category.read_files(["./test_lib/datasets/moodle/all.xml"]) gtags = {} self.top_quiz.get_tags(gtags) self.tagbar.set_gtags(gtags) self.root_item.clear() self._update_tree_item(self.top_quiz, self.root_item) self.data_view.expandAll() def _add_menu_bars(self): file_menu = self.menuBar().addMenu("&File") tmp = QAction("New file", self) tmp.setStatusTip("New file") tmp.triggered.connect(self._create_file) tmp.setShortcut(self.SHORTCUTS["Create file"]) file_menu.addAction(tmp) tmp = QAction("Open file", self) tmp.setStatusTip("Open file") tmp.triggered.connect(self._read_file) tmp.setShortcut(self.SHORTCUTS["Read file"]) file_menu.addAction(tmp) tmp = QAction("Open folder", self) tmp.setStatusTip("Open folder") tmp.triggered.connect(self._read_folder) tmp.setShortcut(self.SHORTCUTS["Read folder"]) file_menu.addAction(tmp) tmp = QAction("Save", self) tmp.setStatusTip("Save top category to specified file on disk") tmp.triggered.connect(lambda: self._write_file(False)) tmp.setShortcut(self.SHORTCUTS["Save"]) file_menu.addAction(tmp) tmp = QAction("Save As...", self) tmp.setStatusTip("Save top category to specified file on disk") tmp.triggered.connect(lambda: self._write_file(True)) tmp.setShortcut(self.SHORTCUTS["Save as"]) file_menu.addAction(tmp) file_menu = self.menuBar().addMenu("&Edit") tmp = QAction("Shortcuts", self) #tmp.setShortcut(self.SHORTCUTS["Read file"]) file_menu.addAction(tmp) tmp = QAction("Datasets", self) tmp.triggered.connect(self._open_dataset_popup) tmp.setShortcut(self.SHORTCUTS["Open datasets"]) file_menu.addAction(tmp) tmp = QAction("Find Question", self) tmp.triggered.connect(self._open_find_popup) tmp.setShortcut(self.SHORTCUTS["Find questions"]) file_menu.addAction(tmp) self.toolbar = GTextToolbar(self) self.addToolBar(Qt.TopToolBarArea, self.toolbar) def _add_new_category(self): popup = PopupName(self, True) popup.show() if not popup.exec(): return self.cxt_data.add_subcat(popup.data) self._new_item(popup.data, self.cxt_item, "question") def _add_new_question(self): popup = PopupQuestion(self, self.cxt_data) popup.show() if not popup.exec(): return self._new_item(popup.question, self.cxt_item, "question") @action_handler def _append_category(self): path, _ = QFileDialog.getOpenFileName(self, "Open file", "", self.FORMATS) if not path: return quiz = Category.read_files([path], path.rsplit("/", 1)[-1]) self.cxt_data[quiz.name] = quiz self._update_tree_item(quiz, self.cxt_item) def _block_answer(self) -> None: frame = GCollapsible(self, "Answers") self.cframe_vbox.addLayout(frame) self._items.append(GOptions(self.toolbar, self.main_editor)) _shortcut = QShortcut(self.SHORTCUTS["Add answer"], self) _shortcut.activated.connect(self._items[-1].add) _shortcut = QShortcut(self.SHORTCUTS["Remove answer"], self) _shortcut.activated.connect(self._items[-1].pop) frame.setLayout(self._items[-1]) def _block_datatree(self) -> QVBoxLayout: self.data_view.setStyleSheet("margin: 5px 5px 0px 5px") self.data_view.setHeaderHidden(True) self.data_view.doubleClicked.connect(self._update_item) self.data_view.setContextMenuPolicy(Qt.CustomContextMenu) self.data_view.customContextMenuRequested.connect(self._data_view_cxt) self.data_view.setDragEnabled(True) self.data_view.setAcceptDrops(True) self.data_view.setDropIndicatorShown(True) self.data_view.setDragDropMode(QAbstractItemView.InternalMove) self.data_view.original_dropEvent = self.data_view.dropEvent self.data_view.dropEvent = self._dataview_dropevent self.root_item = QStandardItemModel(0, 1) self.root_item.setHeaderData(0, Qt.Horizontal, "Classification") self.data_view.setModel(self.root_item) xframe_vbox = QVBoxLayout() xframe_vbox.addWidget(self.data_view) return xframe_vbox def _block_general_data(self) -> None: clayout = GCollapsible(self, "Question Header") self.cframe_vbox.addLayout(clayout, 1) grid = QVBoxLayout() # No need of parent. It's inside GCollapsible grid.setSpacing(2) self.main_editor = GTextEditor(self.toolbar, "question") self._items.append(self.main_editor) self._items[-1].setToolTip("Question's description text") self._items[-1].setMinimumHeight(200) grid.addWidget(self._items[-1], 1) self.tagbar = GTagBar(self) self.tagbar.setToolTip("List of tags used by the question.") self._items.append(self.tagbar) grid.addWidget(self._items[-1], 0) others = QHBoxLayout() # No need of parent. It's inside GCollapsible grid.addLayout(others, 0) group_box = QGroupBox("General", self) _content = QVBoxLayout(group_box) _content.setSpacing(5) _content.setContentsMargins(5, 3, 5, 3) self._items.append(GField("dbid", self, int)) self._items[-1].setToolTip("Optional ID for the question.") self._items[-1].setFixedWidth(50) _content.addWidget(self._items[-1], 0) self._items.append(GField("default_grade", self, int)) self._items[-1].setToolTip("Default grade.") self._items[-1].setFixedWidth(50) self._items[-1].setText("1.0") _content.addWidget(self._items[-1], 0) self._items.append(GField("penalty", self, str)) self._items[-1].setToolTip("Penalty") self._items[-1].setFixedWidth(50) self._items[-1].setText("0.0") _content.addWidget(self._items[-1], 0) _content.addStretch() others.addWidget(group_box, 0) group_box = QGroupBox("Unit Handling", self) _content = QVBoxLayout(group_box) _content.setSpacing(5) _content.setContentsMargins(5, 3, 5, 3) self._items.append(GDropbox("grading_type", self, Grading)) self._items[-1].setToolTip("Grading") self._items[-1].setMinimumWidth(80) _content.addWidget(self._items[-1], 0) self._items.append(GDropbox("show_units", self, ShowUnits)) self._items[-1].setToolTip("Show units") _content.addWidget(self._items[-1], 0) self._items.append(GField("unit_penalty", self, float)) self._items[-1].setToolTip("Unit Penalty") self._items[-1].setText("0.0") _content.addWidget(self._items[-1], 0) self._items.append(GCheckBox("left", "Left side", self)) _content.addWidget(self._items[-1], 0) others.addWidget(group_box, 1) group_box = QGroupBox("Multichoices", self) _content = QVBoxLayout(group_box) _content.setSpacing(5) _content.setContentsMargins(5, 3, 5, 3) self._items.append(GDropbox("numbering", self, Numbering)) self._items[-1].setToolTip("How options will be enumerated") _content.addWidget(self._items[-1], 0) self._items.append(GCheckBox("show_instr", "Instructions", self)) self._items[-1].setToolTip("If the structions 'select one (or more " " options)' should be shown") _content.addWidget(self._items[-1], 0) self._items.append(GCheckBox("single", "Multi answer", self)) self._items[-1].setToolTip("If there is just a single or multiple " "valid answers") _content.addWidget(self._items[-1], 0) self._items.append(GCheckBox("shuffle", "Shuffle", self)) self._items[-1].setToolTip("If answers should be shuffled (e.g. order " "of options will change each time)") _content.addWidget(self._items[-1], 0) others.addWidget(group_box, 1) group_box = QGroupBox("Documents", self) _content = QGridLayout(group_box) _content.setSpacing(5) _content.setContentsMargins(5, 3, 5, 3) self._items.append(GDropbox("rsp_format", self, ResponseFormat)) self._items[-1].setToolTip("The format to be used in the reponse.") _content.addWidget(self._items[-1], 0, 0, 1, 2) self._items.append(GCheckBox("rsp_required", "Required", self)) self._items[-1].setToolTip("Require the student to enter some text.") _content.addWidget(self._items[-1], 0, 2) self._items.append(GField("min_words", self, int)) self._items[-1].setToolTip("Minimum word limit") self._items[-1].setText("0") _content.addWidget(self._items[-1], 1, 0) self._items.append(GField("max_words", self, int)) self._items[-1].setToolTip("Maximum word limit") self._items[-1].setText("10000") _content.addWidget(self._items[-1], 2, 0) self._items.append(GField("attachments", self, int)) self._items[-1].setToolTip("Number of attachments allowed. 0 is none." " -1 is unlimited. Should be bigger than " "field below.") self._items[-1].setText("-1") _content.addWidget(self._items[-1], 1, 1) self._items.append(GField("atts_required", self, int)) self._items[-1].setToolTip("Number of attachments required. 0 is none." " -1 is unlimited. Should be smaller than " "field above.") self._items[-1].setText("0") _content.addWidget(self._items[-1], 2, 1) self._items.append(GField("lines", self, int)) self._items[-1].setToolTip("Input box size.") self._items[-1].setText("15") _content.addWidget(self._items[-1], 1, 2) self._items.append(GField("max_bytes", self, int)) self._items[-1].setToolTip("Maximum file size.") self._items[-1].setText("1Mb") _content.addWidget(self._items[-1], 2, 2) self._items.append(GField("file_types", self, str)) self._items[-1].setToolTip("Accepted file types (comma separeted).") self._items[-1].setText(".txt, .pdf") _content.addWidget(self._items[-1], 3, 0, 1, 3) others.addWidget(group_box, 1) _wrapper = QVBoxLayout() # No need of parent. It's inside GCollapsible group_box = QGroupBox("Random", self) _wrapper.addWidget(group_box) _content = QVBoxLayout(group_box) _content.setSpacing(5) _content.setContentsMargins(5, 3, 5, 3) self._items.append(GCheckBox("subcats", "Subcats", self)) self._items[-1].setToolTip("If questions wshould be choosen from " "subcategories too.") _content.addWidget(self._items[-1]) self._items.append(GField("choose", self, int)) self._items[-1].setToolTip("Number of questions to select.") self._items[-1].setText("5") self._items[-1].setFixedWidth(85) _content.addWidget(self._items[-1]) group_box = QGroupBox("Fill-in", self) _wrapper.addWidget(group_box) _content = QVBoxLayout(group_box) _content.setContentsMargins(5, 3, 5, 3) self._items.append(GCheckBox("use_case", "Match case", self)) self._items[-1].setToolTip("If text is case sensitive.") _content.addWidget(self._items[-1]) others.addLayout(_wrapper, 0) group_box = QGroupBox("Datasets", self) _content = QGridLayout(group_box) _content.setSpacing(5) _content.setContentsMargins(5, 3, 5, 3) self._items.append(GList("datasets", self)) self._items[-1].setFixedHeight(70) self._items[-1].setToolTip("List of datasets used by this question.") _content.addWidget(self._items[-1], 0, 0, 1, 2) self._items.append(GDropbox("synchronize", self, Synchronise)) self._items[-1].setToolTip("How should the databases be synchronized.") self._items[-1].setMinimumWidth(70) _content.addWidget(self._items[-1], 1, 0) _gen = QPushButton("Gen", self) _gen.setToolTip("Generate new items based on the max, min and decimal " "values of the datasets, and the current solution.") _gen.clicked.connect(self._gen_items) _content.addWidget(_gen, 1, 1) others.addWidget(group_box, 2) others.addStretch() clayout.setLayout(grid) clayout._toggle() def _block_hints(self) -> None: clayout = GCollapsible(self, "Hints") self.cframe_vbox.addLayout(clayout) self._items.append(GHintsList(None, self.toolbar)) _shortcut = QShortcut(self.SHORTCUTS["Add hint"], self) _shortcut.activated.connect(self._items[-1].add) _shortcut = QShortcut(self.SHORTCUTS["Remove hint"], self) _shortcut.activated.connect(self._items[-1].pop) clayout.setLayout(self._items[-1]) def _block_solution(self) -> None: collapsible = GCollapsible(self, "Solution and Feedback") self.cframe_vbox.addLayout(collapsible) layout = QVBoxLayout() collapsible.setLayout(layout) self._items.append(GTextEditor(self.toolbar, "feedback")) self._items[-1].setMinimumHeight(100) self._items[-1].setToolTip("General feedback for the question. May " "also be used to describe solutions.") layout.addWidget(self._items[-1]) sframe = QFrame(self) sframe.setStyleSheet(".QFrame{border:1px solid rgb(41, 41, 41);" "background-color: #e4ebb7}") layout.addWidget(sframe) _content = QGridLayout(sframe) self._items.append(GTextEditor(self.toolbar, "if_correct")) self._items[-1].setToolTip("Feedback for correct answer") _content.addWidget(self._items[-1], 0, 0) self._items.append(GTextEditor(self.toolbar, "if_incomplete")) self._items[-1].setToolTip("Feedback for incomplete answer") _content.addWidget(self._items[-1], 0, 1) self._items.append(GTextEditor(self.toolbar, "if_incorrect")) self._items[-1].setToolTip("Feedback for incorrect answer") _content.addWidget(self._items[-1], 0, 2) self._items.append(GCheckBox("show_num", "Show the number of correct " "responses once the question has finished" , self)) _content.addWidget(self._items[-1], 2, 0, 1, 3) _content.setColumnStretch(3, 1) def _block_template(self) -> None: collapsible = GCollapsible(self, "Templates") self.cframe_vbox.addLayout(collapsible) layout = QVBoxLayout() collapsible.setLayout(layout) self._items.append(GTextEditor(self.toolbar, "template")) self._items[-1].setMinimumHeight(70) self._items[-1].setToolTip("Text displayed in the response input box " "when a new attempet is started.") layout.addWidget(self._items[-1]) self._items.append(GTextEditor(self.toolbar, "grader_info")) self._items[-1].setMinimumHeight(50) self._items[-1].setToolTip("Information for graders.") layout.addWidget(self._items[-1]) def _block_units(self): collapsible = GCollapsible(self, "Units") self.cframe_vbox.addLayout(collapsible) def _block_zones(self): collapsible = GCollapsible(self, "Background and Zones") self.cframe_vbox.addLayout(collapsible) @action_handler def _clone_shallow(self) -> None: new_data = copy.copy(self.cxt_data) self._new_item(new_data, self.cxt_item.parent(), "question") @action_handler def _clone_deep(self) -> None: new_data = copy.deepcopy(self.cxt_data) self._new_item(new_data, self.cxt_itemparent(), "question") @action_handler def _create_file(self, *_): self.top_quiz = Category() self.path = None self.root_item.clear() self._update_tree_item(self.top_quiz, self.root_item) @action_handler def _dataview_dropevent(self, event: QDropEvent): from_obj = self.data_view.selectedIndexes()[0].data(257) to_obj = self.data_view.indexAt(event.pos()).data(257) if isinstance(to_obj, Category): if isinstance(from_obj, _Question): to_obj.add_subcat(from_obj) else: to_obj.add_question(from_obj) else: event.ignore() self.data_view.original_dropEvent(event) def _data_view_cxt(self, event): model_idx = self.data_view.indexAt(event) self.cxt_item = self.root_item.itemFromIndex(model_idx) self.cxt_data = model_idx.data(257) self.cxt_menu.clear() rename = QAction("Rename", self) rename.triggered.connect(self._rename_category) self.cxt_menu.addAction(rename) if self.cxt_item != self.root_item.item(0): tmp = QAction("Delete", self) tmp.triggered.connect(self._delete_item) self.cxt_menu.addAction(tmp) tmp = QAction("Clone (Shallow)", self) tmp.triggered.connect(self._clone_shallow) self.cxt_menu.addAction(tmp) tmp = QAction("Clone (Deep)", self) tmp.triggered.connect(self._clone_deep) self.cxt_menu.addAction(tmp) if isinstance(self.cxt_data, Category): tmp = QAction("Save as", self) tmp.triggered.connect(lambda: self._write_quiz(self.cxt_data, True)) self.cxt_menu.addAction(tmp) tmp = QAction("Append", self) tmp.triggered.connect(self._append_category) self.cxt_menu.addAction(tmp) tmp = QAction("Sort", self) #tmp.triggered.connect(self._add_new_category) self.cxt_menu.addAction(tmp) tmp = QAction("New Question", self) tmp.triggered.connect(self._add_new_question) self.cxt_menu.addAction(tmp) tmp = QAction("New Category", self) tmp.triggered.connect(self._add_new_category) self.cxt_menu.addAction(tmp) self.cxt_menu.popup(self.data_view.mapToGlobal(event)) @action_handler def _delete_item(self, *_): self.cxt_item.parent().removeRow(self.cxt_item.index().row()) cat = self.cxt_data.parent if isinstance(self.cxt_data, _Question): cat.pop_question(self.cxt_data) elif isinstance(self.cxt_data, Category): cat.pop_subcat(self.cxt_data) @action_handler def _gen_items(self, _): pass def _new_item(self, data: Category, parent: QStandardItem, title: str): name = f"{data.__class__.__name__}_icon.png".lower() item = None with resources.path("qas_editor.images", name) as path: item = QStandardItem(QIcon(path.as_posix()), data.name) item.setEditable(False) item.setData(QVariant(data)) parent.appendRow(item) return item @action_handler def _open_dataset_popup(self, _): if not self.is_open_dataset: popup = PopupDataset(self, self.top_quiz) popup.show() self.is_open_dataset = True @action_handler def _open_find_popup(self, _): if not self.is_open_find: popup = PopupFind(self, self.top_quiz, self.tagbar.cat_tags) popup.show() self.is_open_find = True @action_handler def _read_file(self, _): files, _ = QFileDialog.getOpenFileNames(self, "Open file", "", self.FORMATS) if not files: return if len(files) == 1: self.path = files[0] self.top_quiz = Category.read_files(files) gtags = {} self.top_quiz.get_tags(gtags) self.tagbar.set_gtags(gtags) self.root_item.clear() self._update_tree_item(self.top_quiz, self.root_item) self.data_view.expandAll() @action_handler def _read_folder(self, _): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.FileMode.Directory) if not dialog.exec(): return self.top_quiz = Category() self.path = None for folder in dialog.selectedFiles(): cat = folder.rsplit("/", 1)[-1] quiz = Category.read_files(glob.glob(f"{folder}/*"), cat) self.top_quiz.add_subcat(quiz) gtags = {} self.top_quiz.get_tags(gtags) self.tagbar.set_gtags(gtags) self.root_item.clear() self._update_tree_item(self.top_quiz, self.root_item) self.data_view.expandAll() @action_handler def _rename_category(self, *_): popup = PopupName(self, False) popup.show() if not popup.exec(): return self.cxt_data.name = popup.data self.cxt_item.setText(popup.data) @action_handler def _update_item(self, model_index: QModelIndex) -> None: item = model_index.data(257) if isinstance(item, _Question): for key in self._items: attr = key.get_attr() if attr in item.__dict__: key.setEnabled(True) key.from_obj(item) else: key.setEnabled(False) self.cur_question = item path = [f" ({item.__class__.__name__})"] while item.parent: path.append(item.name) item = item.parent path.append(item.name) path.reverse() self.cat_name.setText(" > ".join(path[:-1]) + path[-1]) def _update_tree_item(self, data: Category, parent: QStandardItem) -> None: item = self._new_item(data, parent, "category") for k in data.questions: self._new_item(k, item, "question") for k in data: self._update_tree_item(data[k], item) @action_handler def _write_quiz(self, quiz: Category, save_as: bool): if save_as or self.path is None: path, _ = QFileDialog.getSaveFileName(self, "Save file", "", self.FORMATS) if not path: return None else: path = self.path ext = path.rsplit('.', 1)[-1] getattr(quiz, quiz.SERIALIZERS[ext][1])(path) return path def _write_file(self, save_as: bool) -> None: path = self._write_quiz(self.top_quiz, save_as) if path: self.path = path
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect( self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel( self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect( self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) # Add auto-hide capability. self._waitForCtrlRelease = False core.actionManager().action("mNavigation/aNext").triggered.connect( self._setWaitForCtrlRelease) core.actionManager().action("mNavigation/aPrevious").triggered.connect( self._setWaitForCtrlRelease) QApplication.instance().installEventFilter(self) def terminate(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") QApplication.instance().removeEventFilter(self) def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect( self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) @pyqtSlot(Document, Document) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() @pyqtSlot(QItemSelection, QItemSelection) def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() @pyqtSlot(QPoint) def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction( core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action( "mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu( ).aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos)) def _setWaitForCtrlRelease(self): # We can't see actual Ctrl+PgUp/PgDn keypresses, since these get eaten # by the QAction and don't even show up in the event filter below. We # want to avoid waiting for a Ctrl release if the menu item brought us # here. As a workaround, check that Ctrl is pressed. If so, it's # unlikely to be the menu item. if QApplication.instance().keyboardModifiers() & Qt.ControlModifier: self._waitForCtrlRelease = True self.show() else: # If this was a menu selection, then update the MRU list. We can't # do this now, since the current document hasn't been changed yet. QTimer.singleShot(0, self.model.sortDocuments) def eventFilter(self, obj, event): """An event filter that looks for ctrl key releases and focus out events.""" # Wait for the user to release the Ctrl key. if (self._waitForCtrlRelease and event.type() == QEvent.KeyRelease and event.key() == Qt.Key_Control and event.modifiers() == Qt.NoModifier): self.model.sortDocuments() self._waitForCtrlRelease = False if not self.isPinned(): self.hide() # Look for a focus out event sent by the containing widget's focus # proxy. if event.type() == QEvent.FocusOut and obj == self.focusProxy(): self.model.sortDocuments() return QObject.eventFilter(self, obj, event)
class FileBrowserWidget(QWidget): on_open = pyqtSignal(str) def __init__(self): super().__init__() self.initUI() def initUI(self): self.model = QFileSystemModel() self.rootFolder = '' self.model.setRootPath(self.rootFolder) self.tree = QTreeView() self.tree.setModel(self.model) self.tree.setAnimated(False) self.tree.setIndentation(20) self.tree.setSortingEnabled(True) self.tree.sortByColumn(0, 0) self.tree.setColumnWidth(0, 200) self.tree.setDragEnabled(True) self.tree.setWindowTitle("Dir View") self.tree.resize(640, 480) self.tree.doubleClicked.connect(self.onDblClick) self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect( self.onCustomContextMenuRequested) windowLayout = QVBoxLayout() windowLayout.addWidget(self.tree) windowLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(windowLayout) def onCustomContextMenuRequested(self, point): index = self.tree.indexAt(point) selectedFile = None selectedFolder = None ctx = QMenu("Context menu", self) if index.isValid(): file = self.model.fileInfo(index) selectedFile = file.absoluteFilePath() selectedFolder = selectedFile if file.isDir( ) else file.absolutePath() if file.isDir(): ctx.addAction( "Open in file manager", lambda: QDesktopServices.openUrl( QUrl.fromLocalFile(selectedFile))) if not file.isDir(): for wndTyp, meta in WindowTypes.types: text = 'Open with ' + meta.get('displayName', meta['name']) print(wndTyp, meta) ctx.addAction( QAction(text, self, statusTip=text, triggered=lambda dummy, meta=meta: navigate( "WINDOW", "Type=" + meta['name'], "FileName=" + selectedFile))) ctx.addSeparator() ctx.addAction("Set root folder ...", lambda: self.selectRootFolder(preselect=selectedFolder)) ctx.exec(self.tree.viewport().mapToGlobal(point)) def selectRootFolder(self, preselect=None): if preselect == None: preselect = self.rootFolder dir = QFileDialog.getExistingDirectory(self, "Set root folder", preselect) if dir != None: self.setRoot(dir) def setRoot(self, dir): self.rootFolder = dir self.model.setRootPath(dir) self.tree.setRootIndex(self.model.index(dir)) def onDblClick(self, index): if index.isValid(): file = self.model.fileInfo(index) if not file.isDir(): navigate("OPEN", "FileName=" + file.absoluteFilePath()) def saveState(self): if self.tree.currentIndex().isValid(): info = self.model.fileInfo(self.tree.currentIndex()) return {"sel": info.absoluteFilePath(), "root": self.rootFolder} def restoreState(self, state): try: self.setRoot(state["root"]) except: pass try: idx = self.model.index(state["sel"]) if idx.isValid(): self.tree.expand(idx) self.tree.setCurrentIndex(idx) self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter) except: pass
class App(QWidget): def __init__(self): super().__init__() self.title = 'Whenson\'s Custom INI Merger' self.geo = (200, 200, 800, 600) self.initGUI() def initGUI(self): # windows title and geometry self.setGeometry(*(self.geo)) self.setWindowTitle(self.title) self.show() # master layout and widget definition self.layout = QHBoxLayout() self.file_box = QVBoxLayout() self.list_box = QVBoxLayout() self.btn_group = QVBoxLayout() # deploy layout self.setLayout(self.layout) self.layout.addLayout(self.file_box) self.layout.addLayout(self.list_box) self.layout.addLayout(self.btn_group) # file box self.file_box.addWidget(QLabel('File Browser')) self.folder_tree = QTreeView() self.file_box.addWidget(self.folder_tree) self.file_list = QTreeView() self.file_box.addWidget(self.file_list) # list box self.normal_list = DropListWidget() self.demo_list = DropListWidget() self.list_box.addWidget(QLabel('Output File')) self.list_box.addWidget(QLineEdit()) self.list_box.addWidget(QLabel('Module List')) self.list_box.addWidget(self.normal_list) temp = QHBoxLayout() self.up_btn = QPushButton() self.up_btn.setText('ᐱ') self.down_btn = QPushButton() self.down_btn.setText('ᐯ') self.list_box.addLayout(temp) temp.addWidget(self.up_btn) temp.addWidget(self.down_btn) self.list_box.addWidget(QLabel('With Demostration')) self.list_box.addWidget(self.demo_list) # button group self.merge_btn = QPushButton('Merge') self.btn_group.addWidget(self.merge_btn) self.load_btn = QPushButton('Load') self.btn_group.addWidget(self.load_btn) self.init_file_tree() self.init_drag() def init_file_tree(self): self.dir_model = QFileSystemModel() self.dir_model.setRootPath('C:/') self.dir_model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot) self.folder_tree.setModel(self.dir_model) self.folder_tree.hideColumn(1) self.folder_tree.hideColumn(2) self.folder_tree.hideColumn(3) self.file_model = QFileSystemModel() self.file_model.setFilter(QDir.NoDotAndDotDot | QDir.Files) self.file_list.setModel(self.file_model) self.file_list.setRootIndex(self.file_model.setRootPath('C:/')) self.file_model.setNameFilters(['*.txt', '*.ini', '*.map']) self.file_model.setNameFilterDisables(False) self.folder_tree.clicked.connect(self.tree_on_clicked) self.file_list.setSelectionMode(QAbstractItemView.ExtendedSelection) def init_drag(self): self.file_list.setDragEnabled(True) self.file_list.setDropIndicatorShown(True) # self.file_list.setDefaultDropAction(Qt.MoveAction) def tree_on_clicked(self, index): path = self.dir_model.fileInfo(index).absoluteFilePath() self.file_list.setRootIndex(self.file_model.setRootPath(path))