Improve widget focus keyboard shortcuts (#1590)

This commit is contained in:
Veronica Berglyd Olsen
2023-11-10 19:14:10 +01:00
committed by GitHub
5 changed files with 101 additions and 79 deletions
+20 -19
View File
@@ -25,19 +25,19 @@ Main Window Shortcuts
":kbd:`F8`", "Toggle :guilabel:`Focus Mode`" ":kbd:`F8`", "Toggle :guilabel:`Focus Mode`"
":kbd:`F9`", "Re-build the project index" ":kbd:`F9`", "Re-build the project index"
":kbd:`F11`", "Toggle full screen mode" ":kbd:`F11`", "Toggle full screen mode"
":kbd:`Alt+1`", "Switch focus to the project tree (Windows :kbd:`Ctrl+Alt+1`)"
":kbd:`Alt+2`", "Switch focus to document editor (Windows :kbd:`Ctrl+Alt+2`)"
":kbd:`Alt+3`", "Switch focus to document viewer (Windows :kbd:`Ctrl+Alt+3`)"
":kbd:`Alt+4`", "Switch focus to outline view (Windows :kbd:`Ctrl+Alt+4`)"
":kbd:`Ctrl+,`", "Open the :guilabel:`Preferences` dialog" ":kbd:`Ctrl+,`", "Open the :guilabel:`Preferences` dialog"
":kbd:`Ctrl+E`", "Switch focus to the document editor"
":kbd:`Ctrl+T`", "Switch focus to the project/novel tree"
":kbd:`Ctrl+Q`", "Exit novelWriter" ":kbd:`Ctrl+Q`", "Exit novelWriter"
":kbd:`Ctrl+Shift+,`", "Open the :guilabel:`Project Settings` dialog" ":kbd:`Ctrl+Shift+,`", "Open the :guilabel:`Project Settings` dialog"
":kbd:`Ctrl+Shift+O`", "Open a project" ":kbd:`Ctrl+Shift+O`", "Open a project"
":kbd:`Ctrl+Shift+S`", "Save the current project" ":kbd:`Ctrl+Shift+S`", "Save the current project"
":kbd:`Ctrl+Shift+T`", "Switch focus to the outline view"
":kbd:`Ctrl+Shift+W`", "Close the current project" ":kbd:`Ctrl+Shift+W`", "Close the current project"
":kbd:`Shift+F1`", "Open the local user manual (PDF) if it is available" ":kbd:`Shift+F1`", "Open the local user manual (PDF) if it is available"
":kbd:`Shift+F6`", "Open the :guilabel:`Project Details` dialog" ":kbd:`Shift+F6`", "Open the :guilabel:`Project Details` dialog"
.. _a_kb_tree: .. _a_kb_tree:
Project Tree Shortcuts Project Tree Shortcuts
@@ -46,21 +46,21 @@ Project Tree Shortcuts
.. csv-table:: .. csv-table::
:header: "Shortcut", "Description" :header: "Shortcut", "Description"
":kbd:`F2`", "Edit the label of the selected item" ":kbd:`F2`", "Edit the label of the selected item"
":kbd:`Return`", "Open the selected document in the editor" ":kbd:`Return`", "Open the selected document in the editor"
":kbd:`Alt+Up`", "Jump or go to the previous item at same level in the tree" ":kbd:`Alt+Up`", "Jump or go to the previous item at same level in the tree"
":kbd:`Alt+Down`", "Jump or go to the next item at same level in the tree" ":kbd:`Alt+Down`", "Jump or go to the next item at same level in the tree"
":kbd:`Alt+Left`", "Jump to the parent item in the tree" ":kbd:`Alt+Left`", "Jump to the parent item in the tree"
":kbd:`Alt+Right`", "Jump to the first child item in the project tree" ":kbd:`Alt+Right`", "Jump to the first child item in the project tree"
":kbd:`Ctrl+.`", "Open the context menu on the selected item" ":kbd:`Ctrl+.`", "Open the context menu on the selected item"
":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu" ":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu"
":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu" ":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu"
":kbd:`Ctrl+O`", "Open selected document" ":kbd:`Ctrl+O`", "Open selected document"
":kbd:`Ctrl+R`", "Open the selected document in the viewer" ":kbd:`Ctrl+R`", "Open the selected document in the viewer"
":kbd:`Ctrl+Up`", "Move selected item one step up in the tree" ":kbd:`Ctrl+Up`", "Move selected item one step up in the tree"
":kbd:`Ctrl+Down`", "Move selected item one step down in the tree" ":kbd:`Ctrl+Down`", "Move selected item one step down in the tree"
":kbd:`Ctrl+Shift+Z`", "Undo the last move of a project item, if possible" ":kbd:`Ctrl+Del`", "Move the selected item to Trash"
":kbd:`Ctrl+Shift+Del`", "Move the selected item to Trash" ":kbd:`Ctrl+Shift+Z`", "Undo the last move of a project item, if possible"
.. _a_kb_editor: .. _a_kb_editor:
@@ -134,6 +134,7 @@ Other Editor Shortcuts
":kbd:`Ctrl+Shift+A`", "Select all text in the current paragraph" ":kbd:`Ctrl+Shift+A`", "Select all text in the current paragraph"
":kbd:`Ctrl+Shift+I`", "Import text to the current document from a text file" ":kbd:`Ctrl+Shift+I`", "Import text to the current document from a text file"
.. _a_kb_ins: .. _a_kb_ins:
Insert Shortcuts Insert Shortcuts
+36 -20
View File
@@ -54,6 +54,7 @@ class GuiMainMenu(QMenuBar):
requestDocInsert = pyqtSignal(nwDocInsert) requestDocInsert = pyqtSignal(nwDocInsert)
requestDocInsertText = pyqtSignal(str) requestDocInsertText = pyqtSignal(str)
requestDocKeyWordInsert = pyqtSignal(str) requestDocKeyWordInsert = pyqtSignal(str)
requestFocusChange = pyqtSignal(nwWidget)
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
@@ -173,7 +174,7 @@ class GuiMainMenu(QMenuBar):
# Project > Delete # Project > Delete
self.aDeleteItem = self.projMenu.addAction(self.tr("Delete Item")) self.aDeleteItem = self.projMenu.addAction(self.tr("Delete Item"))
self.aDeleteItem.setShortcut("Ctrl+Shift+Del") self.aDeleteItem.setShortcuts(["Ctrl+Del", "Ctrl+Shift+Del"]) # Latter is deprecated
self.aDeleteItem.triggered.connect(lambda: self.mainGui.projView.requestDeleteItem(None)) self.aDeleteItem.triggered.connect(lambda: self.mainGui.projView.requestDeleteItem(None))
# Project > Empty Trash # Project > Empty Trash
@@ -246,12 +247,16 @@ class GuiMainMenu(QMenuBar):
# Edit > Undo # Edit > Undo
self.aEditUndo = self.editMenu.addAction(self.tr("Undo")) self.aEditUndo = self.editMenu.addAction(self.tr("Undo"))
self.aEditUndo.setShortcut("Ctrl+Z") self.aEditUndo.setShortcut("Ctrl+Z")
self.aEditUndo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.UNDO)) self.aEditUndo.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.UNDO)
)
# Edit > Redo # Edit > Redo
self.aEditRedo = self.editMenu.addAction(self.tr("Redo")) self.aEditRedo = self.editMenu.addAction(self.tr("Redo"))
self.aEditRedo.setShortcut("Ctrl+Y") self.aEditRedo.setShortcut("Ctrl+Y")
self.aEditRedo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.REDO)) self.aEditRedo.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.REDO)
)
# Edit > Separator # Edit > Separator
self.editMenu.addSeparator() self.editMenu.addSeparator()
@@ -259,17 +264,23 @@ class GuiMainMenu(QMenuBar):
# Edit > Cut # Edit > Cut
self.aEditCut = self.editMenu.addAction(self.tr("Cut")) self.aEditCut = self.editMenu.addAction(self.tr("Cut"))
self.aEditCut.setShortcut("Ctrl+X") self.aEditCut.setShortcut("Ctrl+X")
self.aEditCut.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.CUT)) self.aEditCut.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.CUT)
)
# Edit > Copy # Edit > Copy
self.aEditCopy = self.editMenu.addAction(self.tr("Copy")) self.aEditCopy = self.editMenu.addAction(self.tr("Copy"))
self.aEditCopy.setShortcut("Ctrl+C") self.aEditCopy.setShortcut("Ctrl+C")
self.aEditCopy.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.COPY)) self.aEditCopy.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.COPY)
)
# Edit > Paste # Edit > Paste
self.aEditPaste = self.editMenu.addAction(self.tr("Paste")) self.aEditPaste = self.editMenu.addAction(self.tr("Paste"))
self.aEditPaste.setShortcut("Ctrl+V") self.aEditPaste.setShortcut("Ctrl+V")
self.aEditPaste.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.PASTE)) self.aEditPaste.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.PASTE)
)
# Edit > Separator # Edit > Separator
self.editMenu.addSeparator() self.editMenu.addSeparator()
@@ -277,12 +288,16 @@ class GuiMainMenu(QMenuBar):
# Edit > Select All # Edit > Select All
self.aSelectAll = self.editMenu.addAction(self.tr("Select All")) self.aSelectAll = self.editMenu.addAction(self.tr("Select All"))
self.aSelectAll.setShortcut("Ctrl+A") self.aSelectAll.setShortcut("Ctrl+A")
self.aSelectAll.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL)) self.aSelectAll.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL)
)
# Edit > Select Paragraph # Edit > Select Paragraph
self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph")) self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph"))
self.aSelectPar.setShortcut("Ctrl+Shift+A") self.aSelectPar.setShortcut("Ctrl+Shift+A")
self.aSelectPar.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA)) self.aSelectPar.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA)
)
return return
@@ -293,23 +308,24 @@ class GuiMainMenu(QMenuBar):
# View > TreeView # View > TreeView
self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Project Tree")) self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Project Tree"))
self.aFocusTree.setShortcut("Ctrl+Alt+1" if CONFIG.osWindows else "Alt+1") self.aFocusTree.setShortcut("Ctrl+T")
self.aFocusTree.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.TREE)) self.aFocusTree.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.TREE)
)
# View > Document Pane 1 # View > Document Editor
self.aFocusEditor = self.viewMenu.addAction(self.tr("Go to Document Editor")) self.aFocusEditor = self.viewMenu.addAction(self.tr("Go to Document Editor"))
self.aFocusEditor.setShortcut("Ctrl+Alt+2" if CONFIG.osWindows else "Alt+2") self.aFocusEditor.setShortcut("Ctrl+E")
self.aFocusEditor.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.EDITOR)) self.aFocusEditor.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.EDITOR)
# View > Document Pane 2 )
self.aFocusView = self.viewMenu.addAction(self.tr("Go to Document Viewer"))
self.aFocusView.setShortcut("Ctrl+Alt+3" if CONFIG.osWindows else "Alt+3")
self.aFocusView.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.VIEWER))
# View > Outline # View > Outline
self.aFocusOutline = self.viewMenu.addAction(self.tr("Go to Outline")) self.aFocusOutline = self.viewMenu.addAction(self.tr("Go to Outline"))
self.aFocusOutline.setShortcut("Ctrl+Alt+4" if CONFIG.osWindows else "Alt+4") self.aFocusOutline.setShortcut("Ctrl+Shift+T")
self.aFocusOutline.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.OUTLINE)) self.aFocusOutline.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.OUTLINE)
)
# View > Separator # View > Separator
self.viewMenu.addSeparator() self.viewMenu.addSeparator()
+1 -1
View File
@@ -189,7 +189,7 @@ class GuiProjectView(QWidget):
self.projTree.buildTree() self.projTree.buildTree()
return return
def setFocus(self) -> None: def setTreeFocus(self) -> None:
"""Forward the set focus call to the tree widget.""" """Forward the set focus call to the tree widget."""
self.projTree.setFocus() self.projTree.setFocus()
return return
+6 -6
View File
@@ -57,32 +57,32 @@ class GuiSideBar(QWidget):
# Buttons # Buttons
self.tbProject = QToolButton(self) self.tbProject = QToolButton(self)
self.tbProject.setToolTip(self.tr("Project Tree View")) self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
self.tbProject.setIconSize(iconSize) self.tbProject.setIconSize(iconSize)
self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT)) self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
self.tbNovel = QToolButton(self) self.tbNovel = QToolButton(self)
self.tbNovel.setToolTip(self.tr("Novel Tree View")) self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
self.tbNovel.setIconSize(iconSize) self.tbNovel.setIconSize(iconSize)
self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL)) self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
self.tbOutline = QToolButton(self) self.tbOutline = QToolButton(self)
self.tbOutline.setToolTip(self.tr("Novel Outline View")) self.tbOutline.setToolTip(f"{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
self.tbOutline.setIconSize(iconSize) self.tbOutline.setIconSize(iconSize)
self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE)) self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
self.tbBuild = QToolButton(self) self.tbBuild = QToolButton(self)
self.tbBuild.setToolTip(self.tr("Build Manuscript")) self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
self.tbBuild.setIconSize(iconSize) self.tbBuild.setIconSize(iconSize)
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog) self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
self.tbDetails = QToolButton(self) self.tbDetails = QToolButton(self)
self.tbDetails.setToolTip(self.tr("Project Details")) self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Project Details")))
self.tbDetails.setIconSize(iconSize) self.tbDetails.setIconSize(iconSize)
self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog) self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog)
self.tbStats = QToolButton(self) self.tbStats = QToolButton(self)
self.tbStats.setToolTip(self.tr("Writing Statistics")) self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
self.tbStats.setIconSize(iconSize) self.tbStats.setIconSize(iconSize)
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog) self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
+38 -33
View File
@@ -31,7 +31,7 @@ from pathlib import Path
from datetime import datetime from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, pyqtSlot from PyQt5.QtCore import Qt, QTimer, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence from PyQt5.QtGui import QCloseEvent, QCursor, QIcon
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut, QDialog, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut,
QSplitter, QStackedWidget, QVBoxLayout, QWidget, qApp QSplitter, QStackedWidget, QVBoxLayout, QWidget, qApp
@@ -247,6 +247,7 @@ class GuiMain(QMainWindow):
self.mainMenu.requestDocInsert.connect(self._passDocumentInsert) self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert) self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord) self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
self.mainMenu.requestFocusChange.connect(self.switchFocus)
self.sideBar.viewChangeRequested.connect(self._changeView) self.sideBar.viewChangeRequested.connect(self._changeView)
@@ -300,17 +301,17 @@ class GuiMain(QMainWindow):
# Shortcuts and Actions # Shortcuts and Actions
self._connectMenuActions() self._connectMenuActions()
keyReturn = QShortcut(self) self.keyReturn = QShortcut(self)
keyReturn.setKey(QKeySequence(Qt.Key_Return)) self.keyReturn.setKey(Qt.Key.Key_Return)
keyReturn.activated.connect(self._keyPressReturn) self.keyReturn.activated.connect(self._keyPressReturn)
keyEnter = QShortcut(self) self.keyEnter = QShortcut(self)
keyEnter.setKey(QKeySequence(Qt.Key_Enter)) self.keyEnter.setKey(Qt.Key.Key_Enter)
keyEnter.activated.connect(self._keyPressReturn) self.keyEnter.activated.connect(self._keyPressReturn)
keyEscape = QShortcut(self) self.keyEscape = QShortcut(self)
keyEscape.setKey(QKeySequence(Qt.Key_Escape)) self.keyEscape.setKey(Qt.Key.Key_Escape)
keyEscape.activated.connect(self._keyPressEscape) self.keyEscape.activated.connect(self._keyPressEscape)
# Check that config loaded fine # Check that config loaded fine
self.reportConfErr() self.reportConfErr()
@@ -1093,25 +1094,6 @@ class GuiMain(QMainWindow):
return True return True
def switchFocus(self, paneNo: nwWidget) -> None:
"""Switch focus between main GUI views."""
if paneNo == nwWidget.TREE:
tabIdx = self.projStack.currentIndex()
if tabIdx == self.idxProjView:
self.projView.setFocus()
elif tabIdx == self.idxNovelView:
self.novelView.setTreeFocus()
elif paneNo == nwWidget.EDITOR:
self._changeView(nwView.EDITOR)
self.docEditor.setFocus()
elif paneNo == nwWidget.VIEWER:
self._changeView(nwView.EDITOR)
self.docViewer.setFocus()
elif paneNo == nwWidget.OUTLINE:
self._changeView(nwView.OUTLINE)
self.outlineView.setTreeFocus()
return
def closeDocViewer(self, byUser: bool = True) -> bool: def closeDocViewer(self, byUser: bool = True) -> bool:
"""Close the document view panel.""" """Close the document view panel."""
self.docViewer.clearViewer() self.docViewer.clearViewer()
@@ -1189,6 +1171,33 @@ class GuiMain(QMainWindow):
return return
@pyqtSlot(nwWidget)
def switchFocus(self, paneNo: nwWidget) -> None:
"""Switch focus between main GUI views."""
if paneNo == nwWidget.TREE:
if self.projStack.currentWidget() is self.projView:
if self.projView.treeHasFocus():
self._changeView(nwView.NOVEL)
self.novelView.setTreeFocus()
else:
self.projView.setTreeFocus()
else:
if self.novelView.treeHasFocus():
self._changeView(nwView.PROJECT)
self.projView.setTreeFocus()
else:
self.novelView.setTreeFocus()
elif paneNo == nwWidget.EDITOR:
self._changeView(nwView.EDITOR)
self.docEditor.setFocus()
elif paneNo == nwWidget.VIEWER:
self._changeView(nwView.EDITOR)
self.docViewer.setFocus()
elif paneNo == nwWidget.OUTLINE:
self._changeView(nwView.OUTLINE)
self.outlineView.setTreeFocus()
return
## ##
# Private Slots # Private Slots
## ##
@@ -1224,18 +1233,14 @@ class GuiMain(QMainWindow):
if view == nwView.EDITOR: if view == nwView.EDITOR:
# Only change the main stack, but not the project stack # Only change the main stack, but not the project stack
self.mainStack.setCurrentWidget(self.splitMain) self.mainStack.setCurrentWidget(self.splitMain)
elif view == nwView.PROJECT: elif view == nwView.PROJECT:
self.mainStack.setCurrentWidget(self.splitMain) self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.projView) self.projStack.setCurrentWidget(self.projView)
elif view == nwView.NOVEL: elif view == nwView.NOVEL:
self.mainStack.setCurrentWidget(self.splitMain) self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.novelView) self.projStack.setCurrentWidget(self.novelView)
elif view == nwView.OUTLINE: elif view == nwView.OUTLINE:
self.mainStack.setCurrentWidget(self.outlineView) self.mainStack.setCurrentWidget(self.outlineView)
return return
@pyqtSlot(nwDocAction) @pyqtSlot(nwDocAction)