diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index 69c2e773..fa761a3e 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -48,7 +48,7 @@ The main shorcuts are as follows: ":kbd:`Ctrl`:kbd:`H`", "Open the search and replace bar and search for the selected word, if any is selected. (On Mac, this is :kbd:`Cmd`:kbd:`=`.)" ":kbd:`Ctrl`:kbd:`I`", "Format selected text, or word under cursor, with emphasis (italic)." ":kbd:`Ctrl`:kbd:`K`", "Activate the insert commands. The commands are listed in :ref:`a_kb_ins`." - ":kbd:`Ctrl`:kbd:`N`", "Create new document." + ":kbd:`Ctrl`:kbd:`N`", "Create new project item." ":kbd:`Ctrl`:kbd:`O`", "Open selected document." ":kbd:`Ctrl`:kbd:`Q`", "Exit novelWriter." ":kbd:`Ctrl`:kbd:`R`", "If in the project tree, open a document for viewing. If the editor has focus, open current document for viewing." @@ -72,7 +72,6 @@ The main shorcuts are as follows: ":kbd:`Ctrl`:kbd:`Shift`:kbd:`A`", "Select all text in current paragraph." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`G`", "Find previous occurrence of search word in current document." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`I`", "Import text to the current document from a text file." - ":kbd:`Ctrl`:kbd:`Shift`:kbd:`N`", "Create new folder." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`O`", "Open a project." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`R`", "Close the document viewer." ":kbd:`Ctrl`:kbd:`Shift`:kbd:`S`", "Save the current project." diff --git a/novelwriter/gui/__init__.py b/novelwriter/gui/__init__.py index 0ae23024..9560df1b 100644 --- a/novelwriter/gui/__init__.py +++ b/novelwriter/gui/__init__.py @@ -25,7 +25,7 @@ from novelwriter.gui.itemdetails import GuiItemDetails from novelwriter.gui.mainmenu import GuiMainMenu from novelwriter.gui.noveltree import GuiNovelTree from novelwriter.gui.outline import GuiOutline -from novelwriter.gui.projtree import GuiProjectWiew +from novelwriter.gui.projtree import GuiProjectView from novelwriter.gui.statusbar import GuiMainStatus from novelwriter.gui.theme import GuiTheme from novelwriter.gui.viewsbar import GuiViewsBar @@ -39,7 +39,7 @@ __all__ = [ "GuiMainStatus", "GuiNovelTree", "GuiOutline", - "GuiProjectWiew", + "GuiProjectView", "GuiTheme", "GuiViewsBar", ] diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 1996f151..37f1f75c 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -33,7 +33,7 @@ from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QMenuBar, QAction -from novelwriter.enum import nwItemType, nwDocAction, nwDocInsert, nwWidget +from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget from novelwriter.constants import trConst, nwKeyWords, nwLabels, nwUnicode logger = logging.getLogger(__name__) @@ -62,8 +62,6 @@ class GuiMainMenu(QMenuBar): # Function Pointers self._docAction = self.theParent.passDocumentAction - self._moveTreeItem = self.theParent.treeView.moveTreeItem - self._newTreeItem = self.theParent.treeView.newTreeItem self._docInsert = self.theParent.docEditor.insertText self._insertKeyWord = self.theParent.docEditor.insertKeyWord @@ -176,18 +174,6 @@ class GuiMainMenu(QMenuBar): self.aDeleteItem.triggered.connect(lambda: self.theParent.treeView.deleteItem(None)) self.projMenu.addAction(self.aDeleteItem) - # Project > Move Up - self.aMoveUp = QAction(self.tr("Move Item Up"), self) - self.aMoveUp.setShortcut("Ctrl+Up") - self.aMoveUp.triggered.connect(lambda: self._moveTreeItem(-1)) - self.projMenu.addAction(self.aMoveUp) - - # Project > Move Down - self.aMoveDown = QAction(self.tr("Move Item Down"), self) - self.aMoveDown.setShortcut("Ctrl+Down") - self.aMoveDown.triggered.connect(lambda: self._moveTreeItem(1)) - self.projMenu.addAction(self.aMoveDown) - # Project > Undo Last Action self.aMoveUndo = QAction(self.tr("Undo Last Move"), self) self.aMoveUndo.setShortcut("Ctrl+Shift+Z") @@ -217,12 +203,6 @@ class GuiMainMenu(QMenuBar): # Document self.docuMenu = self.addMenu(self.tr("&Document")) - # Document > New - self.aNewDoc = QAction(self.tr("New Document"), self) - self.aNewDoc.setShortcut("Ctrl+N") - self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE)) - self.docuMenu.addAction(self.aNewDoc) - # Document > Open self.aOpenDoc = QAction(self.tr("Open Document"), self) self.aOpenDoc.setShortcut("Ctrl+O") diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index f519fb96..fcd2cd83 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -6,7 +6,7 @@ GUI classes for the main window project tree File History: Created: 2018-09-29 [0.0.1] GuiProjectTree Created: 2020-06-04 [0.7] GuiProjectTreeMenu -Created: 2022-06-06 [1.7b1] GuiProjectWiew +Created: 2022-06-06 [1.7b1] GuiProjectView Created: 2022-06-06 [1.7b1] GuiProjectToolBar This file is a part of novelWriter @@ -35,9 +35,9 @@ from time import time from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import ( - QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction, QFrame, - QDialog, QHeaderView, QWidget, QVBoxLayout, QToolBar, QLabel, QToolButton, - QSizePolicy, QInputDialog + qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction, + QFrame, QDialog, QHeaderView, QWidget, QVBoxLayout, QToolBar, QLabel, + QToolButton, QSizePolicy, QInputDialog ) from novelwriter.core import NWDoc @@ -48,7 +48,7 @@ from novelwriter.constants import trConst, nwLabels logger = logging.getLogger(__name__) -class GuiProjectWiew(QWidget): +class GuiProjectView(QWidget): """This is a wrapper class holding all the elements of the project tree. The core object is the project tree itself. Most methods available are mapped through to the project tree class. @@ -87,7 +87,6 @@ class GuiProjectWiew(QWidget): # Function Mappings self.newTreeItem = self.projTree.newTreeItem self.revealNewTreeItem = self.projTree.revealNewTreeItem - self.moveTreeItem = self.projTree.moveTreeItem self.editTreeItem = self.projTree.editTreeItem self.getTreeFromHandle = self.projTree.getTreeFromHandle self.emptyTrash = self.projTree.emptyTrash @@ -121,9 +120,26 @@ class GuiProjectWiew(QWidget): self.projTree.buildTree() return + def setFocus(self): + """Forward the set focus call to the tree widget. + """ + self.projTree.setFocus() + return + def treeFocus(self): + """Check if the project tree has focus. + """ return self.projTree.hasFocus() + def anyFocus(self): + """Check if any widget or child widget has focus. + """ + if self.hasFocus(): + return True + if self.isAncestorOf(qApp.focusWidget()): + return True + return False + ## # Public Solts ## @@ -136,7 +152,7 @@ class GuiProjectWiew(QWidget): self.wordCountsChanged.emit() return -# END Class GuiProjectWiew +# END Class GuiProjectView class GuiProjectToolBar(QToolBar): @@ -171,6 +187,19 @@ class GuiProjectToolBar(QToolBar): self.projLabel.setContentsMargins(0, 0, mPx, 0) self.projLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + # Move Buttons + self.tbMoveU = QToolButton(self) + self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up")) + self.tbMoveU.setShortcut("Ctrl+Up") + self.tbMoveU.setIcon(self.theTheme.getIcon("up")) + self.tbMoveU.clicked.connect(lambda: self._forwardMoveItem(-1)) + + self.tbMoveD = QToolButton(self) + self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down")) + self.tbMoveD.setShortcut("Ctrl+Down") + self.tbMoveD.setIcon(self.theTheme.getIcon("down")) + self.tbMoveD.clicked.connect(lambda: self._forwardMoveItem(1)) + # Items Menu self.mItems = QMenu() @@ -207,6 +236,8 @@ class GuiProjectToolBar(QToolBar): self._addRootFolderEntry(nwItemClass.CUSTOM) self.tbItems = QToolButton(self) + self.tbItems.setToolTip("%s [Ctrl+N]" % self.tr("Add Item")) + self.tbItems.setShortcut("Ctrl+N") self.tbItems.setIcon(self.theTheme.getIcon("add")) self.tbItems.setMenu(self.mItems) self.tbItems.setPopupMode(QToolButton.InstantPopup) @@ -219,6 +250,8 @@ class GuiProjectToolBar(QToolBar): # Assemble self.addWidget(self.projLabel) self.addSeparator() + self.addWidget(self.tbMoveU) + self.addWidget(self.tbMoveD) self.addWidget(self.tbItems) self.addWidget(self.tbSettings) @@ -253,6 +286,13 @@ class GuiProjectToolBar(QToolBar): self.theWidget.projTree.newTreeItem(nwItemType.FOLDER) return + @pyqtSlot(int) + def _forwardMoveItem(self, steps): + """Forward the request to move an item up or down. + """ + self.theWidget.projTree.moveTreeItem(steps) + return + ## # Internal Functions ## @@ -492,7 +532,7 @@ class GuiProjectTree(QTreeWidget): logger.error("No project open") return False - if not self.hasFocus(): + if not self.theWidget.anyFocus(): return False tHandle = self.getSelectedHandle() @@ -831,7 +871,7 @@ class GuiProjectTree(QTreeWidget): dstItem = self._lastMove.get("parent", None) dstIndex = self._lastMove.get("index", None) - if not self.hasFocus(): + if not self.theWidget.anyFocus(): return False if srcItem is None or dstItem is None or dstIndex is None: diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index d18d4d17..2716213e 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -40,7 +40,7 @@ from PyQt5.QtWidgets import ( from novelwriter.gui import ( GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu, - GuiMainStatus, GuiNovelTree, GuiOutline, GuiProjectWiew, GuiTheme, + GuiMainStatus, GuiNovelTree, GuiOutline, GuiProjectView, GuiTheme, GuiViewsBar ) from novelwriter.dialogs import ( @@ -105,7 +105,7 @@ class GuiMain(QMainWindow): # Main GUI Elements self.statusBar = GuiMainStatus(self) - self.treeView = GuiProjectWiew(self) + self.treeView = GuiProjectView(self) self.novelView = GuiNovelTree(self) self.docEditor = GuiDocEditor(self) self.viewMeta = GuiDocViewDetails(self) diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index 81c9a801..031e7680 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -26,7 +26,7 @@ from tools import buildTestProject from PyQt5.QtWidgets import QAction, QMessageBox, QInputDialog -from novelwriter.gui.projtree import GuiProjectTree +from novelwriter.gui.projtree import GuiProjectView, GuiProjectTree from novelwriter.enum import nwItemType, nwItemClass @@ -163,11 +163,12 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True)) + monkeypatch.setattr(GuiProjectView, "anyFocus", lambda *a: True) nwTree = nwGUI.treeView # Try to move item with no project - assert nwTree.moveTreeItem(1) is False + assert nwTree.projTree.moveTreeItem(1) is False # Create a project prjDir = os.path.join(fncDir, "project") @@ -187,33 +188,33 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): ] # Move item without focus - monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False) - assert nwTree.moveTreeItem(1) is False - assert nwTree.getTreeFromHandle("000000000000d") == [ - "000000000000d", "000000000000e", "000000000000f", - "0000000000010", "0000000000011", "0000000000012", - ] - monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True) + with monkeypatch.context() as mp: + mp.setattr(GuiProjectView, "anyFocus", lambda *a: False) + assert nwTree.projTree.moveTreeItem(1) is False + assert nwTree.getTreeFromHandle("000000000000d") == [ + "000000000000d", "000000000000e", "000000000000f", + "0000000000010", "0000000000011", "0000000000012", + ] # Move with no selections nwTree.projTree.clearSelection() - assert nwTree.moveTreeItem(1) is False + assert nwTree.projTree.moveTreeItem(1) is False # Move second item up twice (should give same result) nwTree.setSelectedHandle("000000000000f") - assert nwTree.moveTreeItem(-1) is True + assert nwTree.projTree.moveTreeItem(-1) is True assert nwTree.getTreeFromHandle("000000000000d") == [ "000000000000d", "000000000000f", "000000000000e", "0000000000010", "0000000000011", "0000000000012", ] - assert nwTree.moveTreeItem(-1) is False + assert nwTree.projTree.moveTreeItem(-1) is False assert nwTree.getTreeFromHandle("000000000000d") == [ "000000000000d", "000000000000f", "000000000000e", "0000000000010", "0000000000011", "0000000000012", ] - # Restore via menu entry - nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger) + # Restore + assert nwTree.projTree.moveTreeItem(1) is True assert nwTree.getTreeFromHandle("000000000000d") == [ "000000000000d", "000000000000e", "000000000000f", "0000000000010", "0000000000011", "0000000000012", @@ -221,19 +222,19 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): # Move fifth item down twice (should give same result) nwTree.setSelectedHandle("0000000000011") - assert nwTree.moveTreeItem(1) is True + assert nwTree.projTree.moveTreeItem(1) is True assert nwTree.getTreeFromHandle("000000000000d") == [ "000000000000d", "000000000000e", "000000000000f", "0000000000010", "0000000000012", "0000000000011", ] - assert nwTree.moveTreeItem(1) is False + assert nwTree.projTree.moveTreeItem(1) is False assert nwTree.getTreeFromHandle("000000000000d") == [ "000000000000d", "000000000000e", "000000000000f", "0000000000010", "0000000000012", "0000000000011", ] - # Restore via menu entry - nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger) + # Restore + assert nwTree.projTree.moveTreeItem(-1) is True assert nwTree.getTreeFromHandle("000000000000d") == [ "000000000000d", "000000000000e", "000000000000f", "0000000000010", "0000000000011", "0000000000012", @@ -241,7 +242,7 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): # Move down again, and restore via undo nwTree.setSelectedHandle("0000000000011") - assert nwTree.moveTreeItem(1) is True + assert nwTree.projTree.moveTreeItem(1) is True assert nwTree.getTreeFromHandle("000000000000d") == [ "000000000000d", "000000000000e", "000000000000f", "0000000000010", "0000000000012", "0000000000011", @@ -259,15 +260,15 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0 # Move novel folder up - assert nwTree.moveTreeItem(-1) is False + assert nwTree.projTree.moveTreeItem(-1) is False assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0 # Move novel folder down - assert nwTree.moveTreeItem(1) is True + assert nwTree.projTree.moveTreeItem(1) is True assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 1 # Move novel folder up again - assert nwTree.moveTreeItem(-1) is True + assert nwTree.projTree.moveTreeItem(-1) is True assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0 # Clean up