diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py
index 3ab9d7c2..0f7ac02f 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -135,7 +135,7 @@ class GuiNovelTree(QTreeWidget):
logger.verbose("Requesting refresh of the novel tree")
treeChanged = self.theParent.treeView.changedSince(self._lastBuild)
indexChanged = self.theIndex.novelChangedSince(self._lastBuild)
- if not (treeChanged or indexChanged):
+ if not (treeChanged or indexChanged or overRide):
logger.verbose("No changes have been made to the novel index")
return
@@ -175,10 +175,13 @@ class GuiNovelTree(QTreeWidget):
selected, return the first.
"""
selItem = self.selectedItems()
+ tHandle = None
+ tLine = 0
if selItem:
- return selItem[0].data(self.C_TITLE, Qt.UserRole)[0]
+ tHandle = selItem[0].data(self.C_TITLE, Qt.UserRole)[0]
+ tLine = checkInt(selItem[0].data(self.C_TITLE, Qt.UserRole)[1], 1) - 1
- return None
+ return tHandle, tLine
##
# Events
@@ -201,7 +204,7 @@ class GuiNovelTree(QTreeWidget):
if not isinstance(selItem, QTreeWidgetItem):
return
- tHandle = self.getSelectedHandle()
+ tHandle, _ = self.getSelectedHandle()
if tHandle is None:
return
@@ -218,13 +221,8 @@ class GuiNovelTree(QTreeWidget):
clicked, and send it to the main gui class for opening in the
document editor.
"""
- theData = tItem.data(self.C_TITLE, Qt.UserRole)
- tHandle = theData[0]
- tLine = checkInt(theData[1], 1)
-
- logger.verbose("User selected entry with handle '%s' on line %s", tHandle, tLine)
+ tHandle, tLine = self.getSelectedHandle()
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
-
return
def _itemSelected(self):
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index 687ba393..1ac83ff1 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -34,6 +34,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.enum import nwItemLayout, nwItemType, nwOutline
+from novelwriter.common import checkInt
from novelwriter.constants import trConst, nwKeyWords, nwLabels
logger = logging.getLogger(__name__)
@@ -196,6 +197,19 @@ class GuiOutline(QTreeWidget):
self._firstView = True
return
+ def getSelectedHandle(self):
+ """Get the currently selected handle. If multiple items are
+ selected, return the first.
+ """
+ selItem = self.selectedItems()
+ tHandle = None
+ tLine = 0
+ if selItem:
+ tHandle = selItem[0].data(self._colIdx[nwOutline.TITLE], Qt.UserRole)
+ tLine = checkInt(selItem[0].text(self._colIdx[nwOutline.LINE]), 1) - 1
+
+ return tHandle, tLine
+
##
# Slots
##
@@ -206,15 +220,8 @@ class GuiOutline(QTreeWidget):
clicked, and send it to the main gui class for opening in the
document editor.
"""
- tHandle = tItem.data(self._colIdx[nwOutline.TITLE], Qt.UserRole)
- try:
- tLine = int(tItem.text(self._colIdx[nwOutline.LINE]))
- except Exception:
- tLine = 1
-
- logger.verbose("User selected entry with handle '%s' on line %s", tHandle, tLine)
+ tHandle, tLine = self.getSelectedHandle()
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
-
return
@pyqtSlot()
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 2185e63c..5e1a3c53 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -262,9 +262,13 @@ class GuiMain(QMainWindow):
# Shortcuts and Actions
self._connectMenuActions()
- keyReturn = QShortcut(self.treeView)
+ keyReturn = QShortcut(self)
keyReturn.setKey(QKeySequence(Qt.Key_Return))
- keyReturn.activated.connect(self._treeKeyPressReturn)
+ keyReturn.activated.connect(self._keyPressReturn)
+
+ keyEnter = QShortcut(self)
+ keyEnter.setKey(QKeySequence(Qt.Key_Enter))
+ keyEnter.activated.connect(self._keyPressReturn)
keyEscape = QShortcut(self)
keyEscape.setKey(QKeySequence(Qt.Key_Escape))
@@ -350,7 +354,7 @@ class GuiMain(QMainWindow):
##
def newProject(self, projData=None):
- """Create new project via the new project wizard.
+ """Create a new project via the new project wizard.
"""
if self.hasProject:
if not self.closeProject():
@@ -396,9 +400,9 @@ class GuiMain(QMainWindow):
return True
def closeProject(self, isYes=False):
- """Closes the project if one is open. isYes is passed on from
- the close application event so the user doesn't get prompted
- twice to confirm.
+ """Close the project if one is open. isYes is passed on from the
+ close application event so the user doesn't get prompted twice
+ to confirm.
"""
if not self.hasProject:
# There is no project loaded, everything OK
@@ -598,6 +602,10 @@ class GuiMain(QMainWindow):
logger.error("No project open")
return False
+ if not self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
+ logger.debug("Requested item '%s' is not a document", tHandle)
+ return False
+
self.closeDocument()
self.mainTabs.setCurrentWidget(self.splitDocs)
if self.docEditor.loadText(tHandle, tLine):
@@ -798,22 +806,28 @@ class GuiMain(QMainWindow):
##
def openSelectedItem(self):
- """Open the selected documents.
+ """Open the selected item from the tree that is currently
+ active. It is not checked that the item is actually a document.
+ That should be handled by the openDocument function.
"""
if not self.hasProject:
logger.error("No project open")
return False
- tHandle = self.treeView.getSelectedHandle()
- if tHandle is None:
+ tHandle = None
+ tLine = None
+ if self.treeView.hasFocus():
+ tHandle = self.treeView.getSelectedHandle()
+ elif self.novelView.hasFocus():
+ tHandle, tLine = self.novelView.getSelectedHandle()
+ elif self.projView.hasFocus():
+ tHandle, tLine = self.projView.getSelectedHandle()
+ else:
logger.warning("No item selected")
return False
- logger.verbose("Opening item '%s'", tHandle)
- if self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
- self.openDocument(tHandle, doScroll=False)
- else:
- logger.verbose("Requested item '%s' is not a file", tHandle)
+ if tHandle is not None:
+ self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
return True
@@ -986,7 +1000,7 @@ class GuiMain(QMainWindow):
"""
if not self.hasProject:
logger.error("No project open")
- return
+ return False
dlgProj = GuiProjectSettings(self)
dlgProj.exec_()
@@ -996,14 +1010,14 @@ class GuiMain(QMainWindow):
self.docEditor.setDictionaries()
self._updateWindowTitle(self.theProject.projName)
- return
+ return True
def showProjectDetailsDialog(self):
"""Open the project details dialog.
"""
if not self.hasProject:
logger.error("No project open")
- return
+ return False
self.treeView.flushTreeOrder()
@@ -1016,14 +1030,14 @@ class GuiMain(QMainWindow):
dlgDetails.raise_()
dlgDetails.updateValues()
- return
+ return True
def showBuildProjectDialog(self):
"""Open the build project dialog.
"""
if not self.hasProject:
logger.error("No project open")
- return
+ return False
dlgBuild = getGuiItem("GuiBuildNovel")
if dlgBuild is None:
@@ -1035,14 +1049,14 @@ class GuiMain(QMainWindow):
qApp.processEvents()
dlgBuild.viewCachedDoc()
- return
+ return True
def showProjectWordListDialog(self):
"""Open the project word list dialog.
"""
if not self.hasProject:
logger.error("No project open")
- return
+ return False
dlgWords = GuiWordList(self)
dlgWords.exec_()
@@ -1051,14 +1065,14 @@ class GuiMain(QMainWindow):
logger.debug("Reloading word list")
self.docEditor.setDictionaries()
- return
+ return True
def showWritingStatsDialog(self):
"""Open the session stats dialog.
"""
if not self.hasProject:
logger.error("No project open")
- return
+ return False
dlgStats = getGuiItem("GuiWritingStats")
if dlgStats is None:
@@ -1070,7 +1084,7 @@ class GuiMain(QMainWindow):
qApp.processEvents()
dlgStats.populateGUI()
- return
+ return True
def showAboutNWDialog(self, showNotes=False):
"""Show the about dialog for novelWriter.
@@ -1555,9 +1569,9 @@ class GuiMain(QMainWindow):
"""Single click on a project tree item just updates the details
panel below the tree.
"""
- sHandle = self.treeView.getSelectedHandle()
- if sHandle is not None:
- self.treeMeta.updateViewBox(sHandle)
+ tHandle = self.treeView.getSelectedHandle()
+ if tHandle is not None:
+ self.treeMeta.updateViewBox(tHandle)
return
@pyqtSlot("QTreeWidgetItem*", int)
@@ -1565,14 +1579,9 @@ class GuiMain(QMainWindow):
"""The user double-clicked an item in the tree. If it is a file,
we open it. Otherwise, we do nothing.
"""
- tHandle = tItem.data(self.treeView.C_NAME, Qt.UserRole)
- logger.verbose("User double clicked tree item with handle '%s'", tHandle)
-
- if self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
+ tHandle = self.treeView.getSelectedHandle()
+ if tHandle is not None:
self.openDocument(tHandle, changeFocus=False, doScroll=False)
- else:
- logger.verbose("Requested item '%s' is a folder", tHandle)
-
return
@pyqtSlot()
@@ -1589,18 +1598,11 @@ class GuiMain(QMainWindow):
return
@pyqtSlot()
- def _treeKeyPressReturn(self):
- """The user pressed return on an item in the tree. If it is a
- file, we open it. Otherwise, we do nothing. Pressing return does
- not change focus to the editor as double click does.
+ def _keyPressReturn(self):
+ """Forward the return/enter keypress to the function that opens
+ the currently selected item.
"""
- tHandle = self.treeView.getSelectedHandle()
- logger.verbose("User pressed return on tree item with handle '%s'", tHandle)
- if self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
- self.openDocument(tHandle, changeFocus=False, doScroll=False)
- else:
- logger.verbose("Requested item '%s' is a folder", tHandle)
-
+ self.openSelectedItem()
return
@pyqtSlot()
@@ -1642,7 +1644,7 @@ class GuiMain(QMainWindow):
logger.verbose("Novel tree tab activated")
if self.hasProject:
self.novelView.refreshTree()
- sHandle = self.novelView.getSelectedHandle()
+ sHandle, _ = self.novelView.getSelectedHandle()
self.treeMeta.updateViewBox(sHandle)
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index 63cb8625..4db9ed7e 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -28,16 +28,104 @@ from tools import cmpFiles
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox, QDialog
-from novelwriter.dialogs.itemeditor import GuiItemEditor
-from novelwriter.gui.doceditor import GuiDocEditor
-from novelwriter.gui.projtree import GuiProjectTree
+from novelwriter.gui import (
+ GuiDocEditor, GuiProjectTree, GuiNovelTree, GuiOutline
+)
from novelwriter.enum import nwItemType, nwWidget
+from novelwriter.dialogs.itemeditor import GuiItemEditor
keyDelay = 2
typeDelay = 1
stepDelay = 20
+@pytest.mark.gui
+def testGuiMain_ProjectBlocker(monkeypatch, nwGUI):
+ """Test the blocking of features when there's no project open.
+ """
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+
+ # Test no-project blocking
+ assert nwGUI.closeProject() is True
+ assert nwGUI.saveProject() is False
+ assert nwGUI.closeDocument() is False
+ assert nwGUI.openDocument(None) is False
+ assert nwGUI.openNextDocument(None) is False
+ assert nwGUI.saveDocument() is False
+ assert nwGUI.viewDocument(None) is False
+ assert nwGUI.importDocument() is False
+ assert nwGUI.mergeDocuments() is False
+ assert nwGUI.splitDocument() is False
+ assert nwGUI.openSelectedItem() is False
+ assert nwGUI.editItem() is False
+ assert nwGUI.requestNovelTreeRefresh() is False
+ assert nwGUI.rebuildIndex() is False
+ assert nwGUI.rebuildOutline() is False
+ assert nwGUI.showProjectSettingsDialog() is False
+ assert nwGUI.showProjectDetailsDialog() is False
+ assert nwGUI.showBuildProjectDialog() is False
+ assert nwGUI.showProjectWordListDialog() is False
+ assert nwGUI.showWritingStatsDialog() is False
+
+# END Test testGuiMain_NoProject
+
+
+@pytest.mark.gui
+def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj):
+ """Test handling of project tree items based on GUI focus states.
+ """
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": fncProj}) is True
+ assert nwGUI.saveProject() is True
+
+ sHandle = "0e17daca5f3e1"
+ assert nwGUI.openSelectedItem() is False
+
+ # Project Tree has focus
+ nwGUI.switchFocus(nwWidget.TREE)
+ nwGUI.projTabs.setCurrentIndex(0)
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
+ assert nwGUI.docEditor.docHandle() is None
+ nwGUI.treeView._getTreeItem(sHandle).setSelected(True)
+ nwGUI._keyPressReturn()
+ assert nwGUI.docEditor.docHandle() == sHandle
+ assert nwGUI.closeDocument() is True
+
+ # Novel Tree has focus
+ nwGUI.projTabs.setCurrentIndex(1)
+ nwGUI.novelView.refreshTree(True)
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiNovelTree, "hasFocus", lambda *a: True)
+ assert nwGUI.docEditor.docHandle() is None
+ actItem = nwGUI.novelView.topLevelItem(0)
+ chpItem = actItem.child(0)
+ selItem = chpItem.child(0)
+ nwGUI.novelView.setCurrentItem(selItem)
+ nwGUI._keyPressReturn()
+ assert nwGUI.docEditor.docHandle() == sHandle
+ assert nwGUI.closeDocument() is True
+
+ # Project Outline has focus
+ nwGUI.switchFocus(nwWidget.OUTLINE)
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiOutline, "hasFocus", lambda *a: True)
+ assert nwGUI.docEditor.docHandle() is None
+ actItem = nwGUI.projView.topLevelItem(0)
+ chpItem = actItem.child(0)
+ selItem = chpItem.child(0)
+ nwGUI.projView.setCurrentItem(selItem)
+ nwGUI._keyPressReturn()
+ assert nwGUI.docEditor.docHandle() == sHandle
+ assert nwGUI.closeDocument() is True
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiMain_ProjectTreeItems
+
+
@pytest.mark.gui
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
"""Test the document editor.
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index daa564e1..15282fda 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -29,7 +29,7 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
from tools import writeFile
from novelwriter.gui.doceditor import GuiDocEditor
-from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget
+from novelwriter.enum import nwDocAction, nwDocInsert
from novelwriter.constants import nwKeyWords, nwUnicode
keyDelay = 2
@@ -471,11 +471,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert nwGUI.newProject({"projPath": fncProj})
assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
-
- nwGUI.switchFocus(nwWidget.TREE)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True)
- assert nwGUI.openSelectedItem()
+ assert nwGUI.openDocument("0e17daca5f3e1") is True
nwGUI.docEditor.clear()
# Test Faulty Inserts
diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py
index 1135de87..fab2e0d2 100644
--- a/tests/test_gui/test_gui_noveltree.py
+++ b/tests/test_gui/test_gui_noveltree.py
@@ -74,7 +74,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
assert not topItem.isSelected()
topItem.setSelected(True)
assert nwTree.selectedItems()[0] == topItem
- assert nwTree.getSelectedHandle() == "a35baf2e93843"
+ assert nwTree.getSelectedHandle() == ("a35baf2e93843", 0)
nwTree.refreshTree()
assert nwTree.topLevelItem(0).isSelected()
diff --git a/tests/test_gui/test_gui_outline.py b/tests/test_gui/test_gui_outline.py
index 057f929f..972e7456 100644
--- a/tests/test_gui/test_gui_outline.py
+++ b/tests/test_gui/test_gui_outline.py
@@ -77,6 +77,10 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
selItem = chpItem.child(0)
nwGUI.projView.setCurrentItem(selItem)
+ tHandle, tLine = nwGUI.projView.getSelectedHandle()
+ assert tHandle == "88243afbe5ed8"
+ assert tLine == 0
+
assert nwGUI.projMeta.titleLabel.text() == "Scene"
assert nwGUI.projMeta.titleValue.text() == "Scene One"
assert nwGUI.projMeta.fileValue.text() == "Scene One"
@@ -87,6 +91,25 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
nwGUI.projMeta._tagClicked("#pov=Bod")
assert nwGUI.docViewer.docHandle() == "4c4f28287af27"
+ # Scene One, Section Two
+ actItem = nwGUI.projView.topLevelItem(1)
+ chpItem = actItem.child(0)
+ scnItem = chpItem.child(0)
+ selItem = scnItem.child(0)
+
+ nwGUI.projView.setCurrentItem(selItem)
+ tHandle, tLine = nwGUI.projView.getSelectedHandle()
+ assert tHandle == "88243afbe5ed8"
+ assert tLine == 12
+
+ assert nwGUI.projMeta.titleLabel.text() == "Section"
+ assert nwGUI.projMeta.titleValue.text() == "Scene One, Section Two"
+ assert nwGUI.projMeta.fileValue.text() == "Scene One"
+ assert nwGUI.projMeta.itemValue.text() == "Finished"
+
+ nwGUI.projView._treeDoubleClick(selItem, 0)
+ assert nwGUI.docEditor.docHandle() == "88243afbe5ed8"
+
# qtbot.stopForInteraction()
# END Test testGuiOutline_Main