Skip to content

Commit

Permalink
Improve widget focus keyboard shortcuts (#1590)
Browse files Browse the repository at this point in the history
  • Loading branch information
vkbo authored Nov 10, 2023
2 parents 25697b0 + 98c62f0 commit f9fd351
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 79 deletions.
39 changes: 20 additions & 19 deletions docs/source/usage_shortcuts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ Main Window Shortcuts
":kbd:`F8`", "Toggle :guilabel:`Focus Mode`"
":kbd:`F9`", "Re-build the project index"
":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+E`", "Switch focus to the document editor"
":kbd:`Ctrl+T`", "Switch focus to the project/novel tree"
":kbd:`Ctrl+Q`", "Exit novelWriter"
":kbd:`Ctrl+Shift+,`", "Open the :guilabel:`Project Settings` dialog"
":kbd:`Ctrl+Shift+O`", "Open a 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:`Shift+F1`", "Open the local user manual (PDF) if it is available"
":kbd:`Shift+F6`", "Open the :guilabel:`Project Details` dialog"


.. _a_kb_tree:

Project Tree Shortcuts
Expand All @@ -46,21 +46,21 @@ Project Tree Shortcuts
.. csv-table::
:header: "Shortcut", "Description"

":kbd:`F2`", "Edit the label of the selected item"
":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+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+Right`", "Jump to the first child item in the project tree"
":kbd:`Ctrl+.`", "Open the context menu on the selected item"
":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu"
":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu"
":kbd:`Ctrl+O`", "Open selected document"
":kbd:`Ctrl+R`", "Open the selected document in the viewer"
":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+Shift+Z`", "Undo the last move of a project item, if possible"
":kbd:`Ctrl+Shift+Del`", "Move the selected item to Trash"
":kbd:`F2`", "Edit the label of the selected item"
":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+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+Right`", "Jump to the first child item in the project tree"
":kbd:`Ctrl+.`", "Open the context menu on the selected item"
":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu"
":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu"
":kbd:`Ctrl+O`", "Open selected document"
":kbd:`Ctrl+R`", "Open the selected document in the viewer"
":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+Del`", "Move the selected item to Trash"
":kbd:`Ctrl+Shift+Z`", "Undo the last move of a project item, if possible"


.. _a_kb_editor:
Expand Down Expand Up @@ -134,6 +134,7 @@ Other Editor Shortcuts
":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"


.. _a_kb_ins:

Insert Shortcuts
Expand Down
56 changes: 36 additions & 20 deletions novelwriter/gui/mainmenu.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class GuiMainMenu(QMenuBar):
requestDocInsert = pyqtSignal(nwDocInsert)
requestDocInsertText = pyqtSignal(str)
requestDocKeyWordInsert = pyqtSignal(str)
requestFocusChange = pyqtSignal(nwWidget)

def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
Expand Down Expand Up @@ -173,7 +174,7 @@ def _buildProjectMenu(self) -> None:

# Project > Delete
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))

# Project > Empty Trash
Expand Down Expand Up @@ -246,43 +247,57 @@ def _buildEditMenu(self) -> None:
# Edit > Undo
self.aEditUndo = self.editMenu.addAction(self.tr("Undo"))
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
self.aEditRedo = self.editMenu.addAction(self.tr("Redo"))
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
self.editMenu.addSeparator()

# Edit > Cut
self.aEditCut = self.editMenu.addAction(self.tr("Cut"))
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
self.aEditCopy = self.editMenu.addAction(self.tr("Copy"))
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
self.aEditPaste = self.editMenu.addAction(self.tr("Paste"))
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
self.editMenu.addSeparator()

# Edit > Select All
self.aSelectAll = self.editMenu.addAction(self.tr("Select All"))
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
self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph"))
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

Expand All @@ -293,23 +308,24 @@ def _buildViewMenu(self) -> None:

# View > TreeView
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.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.TREE))
self.aFocusTree.setShortcut("Ctrl+T")
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.setShortcut("Ctrl+Alt+2" if CONFIG.osWindows else "Alt+2")
self.aFocusEditor.triggered.connect(lambda: self.mainGui.switchFocus(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))
self.aFocusEditor.setShortcut("Ctrl+E")
self.aFocusEditor.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.EDITOR)
)

# View > 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.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.OUTLINE))
self.aFocusOutline.setShortcut("Ctrl+Shift+T")
self.aFocusOutline.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.OUTLINE)
)

# View > Separator
self.viewMenu.addSeparator()
Expand Down
2 changes: 1 addition & 1 deletion novelwriter/gui/projtree.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def populateTree(self) -> None:
self.projTree.buildTree()
return

def setFocus(self) -> None:
def setTreeFocus(self) -> None:
"""Forward the set focus call to the tree widget."""
self.projTree.setFocus()
return
Expand Down
12 changes: 6 additions & 6 deletions novelwriter/gui/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,32 +57,32 @@ def __init__(self, mainGui: GuiMain) -> None:

# Buttons
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.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))

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.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))

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.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))

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.clicked.connect(self.mainGui.showBuildManuscriptDialog)

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.clicked.connect(self.mainGui.showProjectDetailsDialog)

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.clicked.connect(self.mainGui.showWritingStatsDialog)

Expand Down
71 changes: 38 additions & 33 deletions novelwriter/guimain.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from datetime import datetime

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 (
QDialog, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut,
QSplitter, QStackedWidget, QVBoxLayout, QWidget, qApp
Expand Down Expand Up @@ -247,6 +247,7 @@ def __init__(self) -> None:
self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
self.mainMenu.requestFocusChange.connect(self.switchFocus)

self.sideBar.viewChangeRequested.connect(self._changeView)

Expand Down Expand Up @@ -300,17 +301,17 @@ def __init__(self) -> None:
# Shortcuts and Actions
self._connectMenuActions()

keyReturn = QShortcut(self)
keyReturn.setKey(QKeySequence(Qt.Key_Return))
keyReturn.activated.connect(self._keyPressReturn)
self.keyReturn = QShortcut(self)
self.keyReturn.setKey(Qt.Key.Key_Return)
self.keyReturn.activated.connect(self._keyPressReturn)

keyEnter = QShortcut(self)
keyEnter.setKey(QKeySequence(Qt.Key_Enter))
keyEnter.activated.connect(self._keyPressReturn)
self.keyEnter = QShortcut(self)
self.keyEnter.setKey(Qt.Key.Key_Enter)
self.keyEnter.activated.connect(self._keyPressReturn)

keyEscape = QShortcut(self)
keyEscape.setKey(QKeySequence(Qt.Key_Escape))
keyEscape.activated.connect(self._keyPressEscape)
self.keyEscape = QShortcut(self)
self.keyEscape.setKey(Qt.Key.Key_Escape)
self.keyEscape.activated.connect(self._keyPressEscape)

# Check that config loaded fine
self.reportConfErr()
Expand Down Expand Up @@ -1093,25 +1094,6 @@ def closeMain(self) -> bool:

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:
"""Close the document view panel."""
self.docViewer.clearViewer()
Expand Down Expand Up @@ -1189,6 +1171,33 @@ def toggleFocusMode(self) -> None:

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
##
Expand Down Expand Up @@ -1224,18 +1233,14 @@ def _changeView(self, view: nwView) -> None:
if view == nwView.EDITOR:
# Only change the main stack, but not the project stack
self.mainStack.setCurrentWidget(self.splitMain)

elif view == nwView.PROJECT:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.projView)

elif view == nwView.NOVEL:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.novelView)

elif view == nwView.OUTLINE:
self.mainStack.setCurrentWidget(self.outlineView)

return

@pyqtSlot(nwDocAction)
Expand Down

0 comments on commit f9fd351

Please sign in to comment.