From 65d4fcc968b5d1a956ae4abb154cedc382d13392 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" Date: Sat, 27 Apr 2019 22:05:01 +0200 Subject: [PATCH] A bit of shuffling in the menu, and connected a lot more menu items to actions. --- nw/gui/doctree.py | 84 +++++++++++++----- nw/gui/mainmenu.py | 144 ++++++++++++++++++------------- nw/gui/winmain.py | 107 +++++++++++------------ nw/project/project.py | 8 +- sample/sampleNovel/nwProject.nwx | 30 +++---- 5 files changed, 217 insertions(+), 156 deletions(-) diff --git a/nw/gui/doctree.py b/nw/gui/doctree.py index cc26228e..42d05be2 100644 --- a/nw/gui/doctree.py +++ b/nw/gui/doctree.py @@ -16,7 +16,7 @@ import nw from os import path from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QIcon, QFont, QColor -from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, QInputDialog, QLineEdit +from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, QInputDialog, QLineEdit, QApplication from nw.enum import nwItemType, nwItemClass from nw.project.item import NWItem @@ -30,12 +30,13 @@ class GuiDocTree(QTreeWidget): C_FLAGS = 2 C_HANDLE = 3 - def __init__(self, theProject): - QTreeWidget.__init__(self) + def __init__(self, theParent, theProject): + QTreeWidget.__init__(self, theParent) logger.debug("Initialising DocTree ...") self.mainConf = nw.CONFIG self.debugGUI = self.mainConf.debugGUI + self.theParent = theParent self.theProject = theProject self.theMap = {} @@ -64,38 +65,75 @@ class GuiDocTree(QTreeWidget): return - def newTreeItem(self, pHandle, itemType, itemClass): + def newTreeItem(self, itemType, itemClass): + + pHandle = self.getSelectedHandle() + if itemClass is None and pHandle is not None: + itemClass = self.theProject.getItem(pHandle).itemClass logger.verbose("Adding new item of type %s and class %s to handle %s" % ( itemType.name,itemClass.name,str(pHandle)) ) - # Create the new item - if itemType == nwItemType.FILE: - tHandle = self.theProject.newFile("New File", itemClass, pHandle) - elif itemType == nwItemType.FOLDER: - tHandle = self.theProject.newFolder("New Folder", itemClass, pHandle) - elif itemType == nwItemType.ROOT: + if itemType == nwItemType.ROOT: tHandle = self.theProject.newRoot(NWItem.CLASS_NAME[itemClass], itemClass) + else: - logger.error("Failed to add new item") - return + # If no parent has been selected, make the new file under the root NOVEL item. + if pHandle is None: + pHandle = self.theProject.findRootItem(nwItemClass.NOVEL) + + # If still nothing, give up + if pHandle is None: + logger.error("Did not find anywhere to add the item!") + return False + + # Now check if the selected item is a file, in which case the new file will be a sibling + pItem = self.theProject.getItem(pHandle) + if pItem.itemType == nwItemType.FILE: + pHandle = pItem.parHandle + + # If we again has no home, give up + if pHandle is None: + logger.error("Did not find anywhere to add the item!") + return False + + # If we're still here, add the file + if itemType == nwItemType.FILE: + tHandle = self.theProject.newFile("New File", itemClass, pHandle) + elif itemType == nwItemType.FOLDER: + tHandle = self.theProject.newFolder("New Folder", itemClass, pHandle) + else: + logger.error("Failed to add new item") + return False # Add the new item to the tree nwItem = self.theProject.getItem(tHandle) - self._addTreeItem(nwItem) + trItem = self._addTreeItem(nwItem) + if pHandle is not None: + self.theMap[pHandle].setExpanded(True) + self.clearSelection() + trItem.setSelected(True) + self.theParent.editItem() return - def moveTreeItem(self, tHandle, nStep): - tItem = self.theMap[tHandle] - pItem = tItem.parent() - tIndex = pItem.indexOfChild(tItem) - nChild = pItem.childCount() - nIndex = tIndex + nStep - if nIndex < 0 or nIndex >= nChild: return - cItem = pItem.takeChild(tIndex) - pItem.insertChild(nIndex, cItem) + def moveTreeItem(self, nStep): + """Move an item up or down in the tree, but only if the treeView has focus. This also + applies when the menu is used. + """ + if QApplication.focusWidget() == self: + tHandle = self.getSelectedHandle() + tItem = self.theMap[tHandle] + pItem = tItem.parent() + tIndex = pItem.indexOfChild(tItem) + nChild = pItem.childCount() + nIndex = tIndex + nStep + if nIndex < 0 or nIndex >= nChild: return + cItem = pItem.takeChild(tIndex) + pItem.insertChild(nIndex, cItem) + self.clearSelection() + cItem.setSelected(True) return def renameTreeItem(self, tHandle): @@ -187,7 +225,7 @@ class GuiDocTree(QTreeWidget): elif nwItem.itemType == nwItemType.FILE: newItem.setIcon(self.C_NAME, QIcon.fromTheme("x-office-document")) - return True + return newItem def propagateCount(self, tHandle, theCount, nDepth=0): tItem = self.theMap[tHandle] diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index c34fd2af..2375b214 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -16,7 +16,7 @@ import nw from PyQt5.QtWidgets import qApp, QMenuBar, QAction from PyQt5.QtGui import QIcon -from nw.enum import nwItemClass, nwDocAction +from nw.enum import nwItemType, nwItemClass, nwDocAction logger = logging.getLogger(__name__) @@ -31,15 +31,17 @@ class GuiMainMenu(QMenuBar): self.theProject = theProject self._buildProjectMenu() - self._buildStructureMenu() self._buildDocumentMenu() self._buildEditMenu() + self._buildViewMenu() self._buildFormatMenu() self._buildToolsMenu() self._buildHelpMenu() # Function Pointers - self._docAction = self.theParent.docEditor.docAction + self._docAction = self.theParent.docEditor.docAction + self._moveTreeItem = self.theParent.treeView.moveTreeItem + self._newTreeItem = self.theParent.treeView.newTreeItem logger.debug("Main Menu initialisation complete") @@ -101,9 +103,6 @@ class GuiMainMenu(QMenuBar): menuItem.triggered.connect(lambda menuItem, n=n : self.openRecentProject(menuItem, n)) recentMenu.addAction(menuItem) - # Project > Separator - self.projMenu.addSeparator() - # Project > Project Settings menuItem = QAction(QIcon.fromTheme("document-properties"), "Project Settings", self) menuItem.setStatusTip("Project Settings") @@ -113,6 +112,40 @@ class GuiMainMenu(QMenuBar): # Project > Separator self.projMenu.addSeparator() + # Project > New Root + rootMenu = self.projMenu.addMenu(QIcon.fromTheme("folder-new"), "Create Root Folder") + self.rootItems = {} + self.rootItems[nwItemClass.NOVEL] = QAction(QIcon.fromTheme("folder-new"), "Novel Root", rootMenu) + self.rootItems[nwItemClass.PLOT] = QAction(QIcon.fromTheme("folder-new"), "Plot Root", rootMenu) + self.rootItems[nwItemClass.CHARACTER] = QAction(QIcon.fromTheme("folder-new"), "Character Root", rootMenu) + self.rootItems[nwItemClass.WORLD] = QAction(QIcon.fromTheme("folder-new"), "Location Root", rootMenu) + self.rootItems[nwItemClass.TIMELINE] = QAction(QIcon.fromTheme("folder-new"), "Timeline Root", rootMenu) + self.rootItems[nwItemClass.OBJECT] = QAction(QIcon.fromTheme("folder-new"), "Object Root", rootMenu) + self.rootItems[nwItemClass.CUSTOM] = QAction(QIcon.fromTheme("folder-new"), "Custom Root", rootMenu) + rootMenu.addActions(self.rootItems.values()) + + # Project > New Folder + menuItem = QAction(QIcon.fromTheme("folder-new"), "Create Folder", self) + menuItem.setStatusTip("Create Folder") + menuItem.setShortcut("Ctrl+Shift+N") + menuItem.triggered.connect(lambda : self._newTreeItem(nwItemType.FOLDER, None)) + self.projMenu.addAction(menuItem) + + # Project > Rename Folder + menuItem = QAction(QIcon.fromTheme("folder-new"), "Rename Folder", self) + menuItem.setStatusTip("Rename Selected Folder") + menuItem.setShortcut("Ctrl+Shift+E") + self.projMenu.addAction(menuItem) + + # Project > Delete Folder + menuItem = QAction(QIcon.fromTheme("edit-delete"), "Delete Folder", self) + menuItem.setStatusTip("Delete Selected Folder") + menuItem.setShortcut("Ctrl+Shift+Del") + self.projMenu.addAction(menuItem) + + # Project > Separator + self.projMenu.addSeparator() + # Project > Exit menuItem = QAction(QIcon.fromTheme("application-exit"), "Exit", self) menuItem.setStatusTip("Exit %s" % nw.__package__) @@ -122,58 +155,6 @@ class GuiMainMenu(QMenuBar): return - def _buildStructureMenu(self): - - # Structure - self.structMenu = self.addMenu("&Structure") - - # Structure > New Folder - menuItem = QAction(QIcon.fromTheme("folder-new"), "Create Folder", self) - menuItem.setStatusTip("Create Folder") - menuItem.setShortcut("Ctrl+Shift+N") - self.structMenu.addAction(menuItem) - - # Structure > New Root - rootMenu = self.structMenu.addMenu(QIcon.fromTheme("folder-new"), "Create Root Group") - self.rootItems = {} - self.rootItems[nwItemClass.NOVEL] = QAction(QIcon.fromTheme("folder-new"), "Novel Root", rootMenu) - self.rootItems[nwItemClass.PLOT] = QAction(QIcon.fromTheme("folder-new"), "Plot Root", rootMenu) - self.rootItems[nwItemClass.CHARACTER] = QAction(QIcon.fromTheme("folder-new"), "Character Root", rootMenu) - self.rootItems[nwItemClass.WORLD] = QAction(QIcon.fromTheme("folder-new"), "Location Root", rootMenu) - self.rootItems[nwItemClass.TIMELINE] = QAction(QIcon.fromTheme("folder-new"), "Timeline Root", rootMenu) - self.rootItems[nwItemClass.OBJECT] = QAction(QIcon.fromTheme("folder-new"), "Object Root", rootMenu) - self.rootItems[nwItemClass.CUSTOM] = QAction(QIcon.fromTheme("folder-new"), "Custom Root", rootMenu) - rootMenu.addActions(self.rootItems.values()) - - # Structure > Rename Folder - menuItem = QAction(QIcon.fromTheme("folder-new"), "Rename Folder", self) - menuItem.setStatusTip("Rename Selected Folder") - menuItem.setShortcut("Ctrl+Shift+E") - self.structMenu.addAction(menuItem) - - # Structure > Delete Folder - menuItem = QAction(QIcon.fromTheme("edit-delete"), "Delete Folder", self) - menuItem.setStatusTip("Delete Selected Folder") - menuItem.setShortcut("Ctrl+Shift+Del") - self.structMenu.addAction(menuItem) - - # Structure > Separator - self.structMenu.addSeparator() - - # Structure > Move Up - menuItem = QAction(QIcon.fromTheme("go-up"), "Move Item Up", self) - menuItem.setStatusTip("Move Item Up") - menuItem.setShortcut("Ctrl+Up") - self.structMenu.addAction(menuItem) - - # Structure > Move Down - menuItem = QAction(QIcon.fromTheme("go-down"), "Move Item Down", self) - menuItem.setStatusTip("Move Item Down") - menuItem.setShortcut("Ctrl+Down") - self.structMenu.addAction(menuItem) - - return - def _buildDocumentMenu(self): # Document @@ -183,12 +164,14 @@ class GuiMainMenu(QMenuBar): menuItem = QAction(QIcon.fromTheme("document-new"), "&New Document", self) menuItem.setStatusTip("Create New Document") menuItem.setShortcut("Ctrl+N") + menuItem.triggered.connect(lambda : self._newTreeItem(nwItemType.FILE, None)) self.docuMenu.addAction(menuItem) # Document > Open menuItem = QAction(QIcon.fromTheme("document-open"), "&Open Document", self) menuItem.setStatusTip("Open Selected Document") menuItem.setShortcut("Ctrl+O") + menuItem.triggered.connect(self.theParent.openSelectedItem) self.docuMenu.addAction(menuItem) # Document > Save @@ -229,6 +212,34 @@ class GuiMainMenu(QMenuBar): return + def _buildViewMenu(self): + + # View + self.viewMenu = self.addMenu("&View") + + # View > TreeView + menuItem = QAction(QIcon.fromTheme("go-home"), "TreeView", self) + menuItem.setStatusTip("Move to TreeView Panel") + menuItem.setShortcut("Ctrl+1") + menuItem.triggered.connect(lambda : self.theParent.setFocus(1)) + self.viewMenu.addAction(menuItem) + + # View > Document Pane 1 + menuItem = QAction(QIcon.fromTheme("go-first"), "Left Document Pane", self) + menuItem.setStatusTip("Move to Left Document Pane") + menuItem.setShortcut("Ctrl+2") + menuItem.triggered.connect(lambda : self.theParent.setFocus(2)) + self.viewMenu.addAction(menuItem) + + # View > Document Pane 2 + menuItem = QAction(QIcon.fromTheme("go-last"), "Right Document Pane", self) + menuItem.setStatusTip("Move to Right Document Pane") + menuItem.setShortcut("Ctrl+3") + menuItem.triggered.connect(lambda : self.theParent.setFocus(3)) + self.viewMenu.addAction(menuItem) + + return + def _buildEditMenu(self): # Edit @@ -341,6 +352,23 @@ class GuiMainMenu(QMenuBar): # Tools self.toolsMenu = self.addMenu("&Tools") + # Tools > Move Up + self.toolsMoveUp = QAction(QIcon.fromTheme("go-up"), "Move Tree Item Up", self) + self.toolsMoveUp.setStatusTip("Move Item Up") + self.toolsMoveUp.setShortcut("Ctrl+Shift+Up") + self.toolsMoveUp.triggered.connect(lambda : self._moveTreeItem(-1)) + self.toolsMenu.addAction(self.toolsMoveUp) + + # Tools > Move Down + self.toolsMoveDown = QAction(QIcon.fromTheme("go-down"), "Move Tree Item Down", self) + self.toolsMoveDown.setStatusTip("Move Item Down") + self.toolsMoveDown.setShortcut("Ctrl+Shift+Down") + self.toolsMoveDown.triggered.connect(lambda : self._moveTreeItem(1)) + self.toolsMenu.addAction(self.toolsMoveDown) + + # Tools > Separator + self.toolsMenu.addSeparator() + # Tools > Settings menuItem = QAction(QIcon.fromTheme("preferences-system"), "Preferences", self) menuItem.setStatusTip("Preferences") diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index d7f15298..8850f5ff 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -14,12 +14,11 @@ import logging import nw from os import path -from PyQt5.QtWidgets import qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QAction, QToolBar, QFileDialog, QStackedWidget -from PyQt5.QtCore import Qt, QSize, pyqtSlot +from PyQt5.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QStackedWidget, QShortcut from PyQt5.QtGui import QIcon +from PyQt5.QtCore import Qt from nw.gui.doctree import GuiDocTree -from nw.gui.doctreectx import GuiDocTreeCtx from nw.gui.doceditor import GuiDocEditor from nw.gui.docdetails import GuiDocDetails from nw.gui.mainmenu import GuiMainMenu @@ -29,7 +28,7 @@ from nw.gui.statusbar import GuiMainStatus from nw.project.project import NWProject from nw.project.document import NWDoc from nw.project.item import NWItem -from nw.enum import nwItemType, nwItemClass, nwDocAction, nwItemAction +from nw.enum import nwItemType, nwItemAction logger = logging.getLogger(__name__) @@ -50,7 +49,7 @@ class GuiMain(QMainWindow): # Main GUI Elements self.docEditor = GuiDocEditor(self) self.docDetails = GuiDocDetails(self.theProject) - self.treeView = GuiDocTree(self.theProject) + self.treeView = GuiDocTree(self, self.theProject) self.mainMenu = GuiMainMenu(self, self.theProject) self.statusBar = GuiMainStatus() @@ -75,11 +74,10 @@ class GuiMain(QMainWindow): self.setCentralWidget(self.splitMain) # Build GUI Elements - self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) - self.treeView.customContextMenuRequested.connect(self._openDocTreeContextMenu) self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemDoubleClicked.connect(self._treeDoubleClick) self.treeView.buildTree() + QShortcut(Qt.Key_Return, self.treeView, context=Qt.WidgetShortcut, activated=self._treeKeyPressReturn) # Set Main Window Elements self.setMenuBar(self.mainMenu) @@ -138,24 +136,40 @@ class GuiMain(QMainWindow): return def saveDocument(self): - docHtml = self.docEditor.getText() - self.theDocument.theItem.setCharCount(self.docEditor.charCount) - self.theDocument.theItem.setWordCount(self.docEditor.wordCount) - self.theDocument.theItem.setParaCount(self.docEditor.paraCount) - self.theDocument.saveDocument(docHtml) + if self.theDocument.theItem is not None: + docHtml = self.docEditor.getText() + self.theDocument.theItem.setCharCount(self.docEditor.charCount) + self.theDocument.theItem.setWordCount(self.docEditor.wordCount) + self.theDocument.theItem.setParaCount(self.docEditor.paraCount) + self.theDocument.saveDocument(docHtml) return ## # Tree Item Actions ## - def editItem(self): + def openSelectedItem(self): tHandle = self.treeView.getSelectedHandle() - logger.verbose("Requesting change to item %s" % tHandle) if tHandle is None: logger.warning("No item selected") return - + + logger.verbose("Opening item %s" % tHandle) + nwItem = self.theProject.getItem(tHandle) + if nwItem.itemType == nwItemType.FILE: + logger.verbose("Requested item %s is a file" % tHandle) + self.openDocument(tHandle) + else: + logger.verbose("Requested item %s is not a file" % tHandle) + return + + def editItem(self): + tHandle = self.treeView.getSelectedHandle() + if tHandle is None: + logger.warning("No item selected") + return + + logger.verbose("Requesting change to item %s" % tHandle) dlgProj = GuiItemEditor(self, self.theProject, tHandle) dlgProj.exec_() @@ -202,6 +216,14 @@ class GuiMain(QMainWindow): self.mainConf.saveConfig() return + def setFocus(self, paneNo): + if paneNo == 1: + self.treeView.setFocus() + elif paneNo == 2: + if self.stackPane.currentIndex() == self.stackDoc: + self.docEditor.setFocus() + return + ## # Internal Functions ## @@ -213,50 +235,6 @@ class GuiMain(QMainWindow): self.setWindowTitle(winTitle) return True - ## - # DocTree Context Menu - ## - - def _openDocTreeContextMenu(self, thePosition): - - ctxMenu = GuiDocTreeCtx(self.treeView, self.theProject, thePosition) - selHandle = ctxMenu.selHandle - selAction = ctxMenu.selAction - selClass = ctxMenu.selClass - selType = ctxMenu.selType - selTarget = ctxMenu.selTarget - - # print(selHandle, selAction, selClass, selType, selTarget) - - if selAction == nwItemAction.ADD_ROOT: - self.treeView.newTreeItem(selHandle, selType, selClass) - elif selAction == nwItemAction.ADD_FOLDER: - self.treeView.newTreeItem(selHandle, selType, selClass) - elif selAction == nwItemAction.ADD_FILE: - self.treeView.newTreeItem(selHandle, selType, selClass) - elif selAction == nwItemAction.MOVE_UP: - self.treeView.moveTreeItem(selHandle, -1) - elif selAction == nwItemAction.MOVE_DOWN: - self.treeView.moveTreeItem(selHandle, 1) - elif selAction == nwItemAction.MOVE_TO: - pass - elif selAction == nwItemAction.MOVE_TRASH: - pass - elif selAction == nwItemAction.SPLIT: - pass - elif selAction == nwItemAction.MERGE: - pass - elif selAction == nwItemAction.DELETE: - pass - elif selAction == nwItemAction.DELETE_ROOT: - pass - elif selAction == nwItemAction.EMPTY_TRASH: - pass - elif selAction == nwItemAction.RENAME: - self.treeView.renameTreeItem(selHandle) - - return - ## # Events ## @@ -295,6 +273,17 @@ class GuiMain(QMainWindow): logger.verbose("Requested item %s is a folder" % tHandle) return + def _treeKeyPressReturn(self): + tHandle = self.treeView.getSelectedHandle() + logger.verbose("User pressed return on tree item with handle %s" % tHandle) + nwItem = self.theProject.getItem(tHandle) + if nwItem.itemType == nwItemType.FILE: + logger.verbose("Requested item %s is a file" % tHandle) + self.openDocument(tHandle) + else: + logger.verbose("Requested item %s is a folder" % tHandle) + return + def _splitMainMove(self, pWidth, pHeight): """Alert dependent GUI elements that the main pane splitter has been moved. """ diff --git a/nw/project/project.py b/nw/project/project.py index 2038126e..37188075 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -266,6 +266,12 @@ class NWProject(): logger.error("No tree item with handle %s" % str(tHandle)) return None + def findRootItem(self, theClass): + for aRoot in self.treeRoots: + if theClass == self.projTree[aRoot].itemClass: + return self.projTree[aRoot].itemHandle + return None + def getActionList(self, tHandle): """Returns a dictionary of possible actions to perform on a give handle. """ @@ -345,7 +351,7 @@ class NWProject(): """Checks if there already is a root entry of class 'theClass' in the root of the project tree. """ - if theClass == nwItemClass.NO_CLASS: + if theClass == nwItemClass.CUSTOM: return True for aRoot in self.treeRoots: if theClass == self.projTree[aRoot].itemClass: diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index 0e2e2c4b..b800ff14 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -60,20 +60,7 @@ True True - - John Smith - FILE - CHARACTER - 0 - 1 - False - False - NOTE - 42 - 8 - 1 - - + Jane Smith FILE CHARACTER @@ -86,6 +73,19 @@ 9 1 + + John Smith + FILE + CHARACTER + 0 + 1 + False + False + NOTE + 42 + 8 + 1 + Locations ROOT