Apply open document shortcuts to the tree that has focus (#945)
* Better handling of Enter/Return on GUI trees, resolves #913 * Update test * Generalise open selected handle functions * Improve test coverage * Update docstring
This commit is contained in:
committed by
GitHub
parent
7024254960
commit
b0f939af32
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
+48
-46
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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() == "<b>Scene</b>"
|
||||
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() == "<b>Section</b>"
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user