diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index 86e60afa..4e931351 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -77,10 +77,10 @@ Text Search Shortcuts ":kbd:`F3`", "Find the next occurrence of the search word" ":kbd:`Ctrl+F`", "Open the search bar and search for the selected word, if any is selected" - ":kbd:`Ctrl+G`", "Find next occurrence of search word in current document" + ":kbd:`Ctrl+G`", "Find the next occurrence of the search word" ":kbd:`Ctrl+H`", "Open the search tool and populate with the selected word (Mac :kbd:`Cmd+=`)" ":kbd:`Ctrl+Shift+1`", "Replace selected occurrence of the search word, and move to the next" - ":kbd:`Ctrl+Shift+G`", "Find previous occurrence of the search word" + ":kbd:`Ctrl+Shift+G`", "Find the previous occurrence of the search word" ":kbd:`Shift+F3`", "Find the previous occurrence of the search word" diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index fa8b64ad..6355ca63 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -209,7 +209,7 @@ class GuiMainMenu(QMenuBar): # Document > Close self.aCloseDoc = self.docuMenu.addAction(self.tr("Close Document")) self.aCloseDoc.setShortcut("Ctrl+W") - self.aCloseDoc.triggered.connect(lambda: self.mainGui.closeDocEditor()) + self.aCloseDoc.triggered.connect(self.mainGui.closeDocEditor) # Document > Separator self.docuMenu.addSeparator() @@ -330,7 +330,7 @@ class GuiMainMenu(QMenuBar): # View > Focus Mode self.aFocusMode = self.viewMenu.addAction(self.tr("Focus Mode")) self.aFocusMode.setShortcut("F8") - self.aFocusMode.triggered.connect(lambda: self.mainGui.toggleFocusMode()) + self.aFocusMode.triggered.connect(self.mainGui.toggleFocusMode) # View > Toggle Full Screen self.aFullScreen = self.viewMenu.addAction(self.tr("Full Screen Mode")) diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index 4b7b4e7b..be719a42 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -115,7 +115,6 @@ class GuiSideBar(QWidget): self.outerBox.setSpacing(CONFIG.pxInt(4)) self.setLayout(self.outerBox) - self.updateTheme() logger.debug("Ready: GuiSideBar") diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 4d324677..adcebca8 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -267,6 +267,8 @@ class GuiMain(QMainWindow): self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta) self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage) self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState) + self.docEditor.closeDocumentRequest.connect(self.closeDocEditor) + self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode) self.docViewer.loadDocumentTagRequest.connect(self._followTag) @@ -1119,12 +1121,6 @@ class GuiMain(QMainWindow): self.outlineView.setTreeFocus() return - def closeDocEditor(self) -> None: - """Close the document editor. This does not hide the editor.""" - self.closeDocument() - SHARED.project.data.setLastHandle(None, "editor") - return - def closeDocViewer(self, byUser: bool = True) -> bool: """Close the document view panel.""" self.docViewer.clearViewer() @@ -1139,13 +1135,44 @@ class GuiMain(QMainWindow): return not self.splitView.isVisible() - def toggleFocusMode(self) -> bool: + def toggleFullScreenMode(self) -> None: + """Toggle full screen mode""" + self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) + return + + ## + # Events + ## + + def closeEvent(self, event: QCloseEvent): + """Capture the closing event of the GUI and call the close + function to handle all the close process steps. + """ + if self.closeMain(): + event.accept() + else: + event.ignore() + return + + ## + # Public Slots + ## + + @pyqtSlot() + def closeDocEditor(self) -> None: + """Close the document editor. This does not hide the editor.""" + self.closeDocument() + SHARED.project.data.setLastHandle(None, "editor") + return + + @pyqtSlot() + def toggleFocusMode(self) -> None: """Handle toggle focus mode. The Main GUI Focus Mode hides tree, view, statusbar and menu. """ if self.docEditor.docHandle is None: logger.error("No document open, so not activating Focus Mode") - return False + return self.isFocusMode = not self.isFocusMode if self.isFocusMode: @@ -1169,11 +1196,141 @@ class GuiMain(QMainWindow): elif self.docViewer.docHandle is not None: self.splitView.setVisible(True) - return True + return - def toggleFullScreenMode(self) -> None: - """Toggle full screen mode""" - self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) + ## + # Private Slots + ## + + @pyqtSlot(str, nwDocMode) + def _followTag(self, tag: str, mode: nwDocMode) -> None: + """Follow a tag after user interaction with a link.""" + tHandle, sTitle = self._getTagSource(tag) + if tHandle is not None: + if mode == nwDocMode.EDIT: + self.openDocument(tHandle) + elif mode == nwDocMode.VIEW: + self.viewDocument(tHandle=tHandle, sTitle=sTitle) + return + + @pyqtSlot(str, nwDocMode, str, bool) + def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None: + """Handle an open document request.""" + if tHandle is not None: + if mode == nwDocMode.EDIT: + tLine = None + hItem = SHARED.project.index.getItemHeader(tHandle, sTitle) + if hItem is not None: + tLine = hItem.line + self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus) + elif mode == nwDocMode.VIEW: + self.viewDocument(tHandle=tHandle, sTitle=sTitle) + return + + @pyqtSlot(nwView) + def _changeView(self, view: nwView) -> None: + """Handle the requested change of view from the GuiViewBar.""" + 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() + def _timeTick(self) -> None: + """Process time tick of the main timer.""" + if not SHARED.hasProject: + return + currTime = time() + editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime + userIdle = qApp.applicationState() != Qt.ApplicationActive + self.mainStatus.setUserIdle(editIdle or userIdle) + SHARED.updateIdleTime(currTime, editIdle or userIdle) + self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime) + return + + @pyqtSlot() + def _autoSaveProject(self) -> None: + """Autosave of the project. This is a timer-activated slot.""" + doSave = SHARED.hasProject + doSave &= SHARED.project.projChanged + doSave &= SHARED.project.storage.isOpen() + if doSave: + logger.debug("Autosaving project") + self.saveProject(autoSave=True) + return + + @pyqtSlot() + def _autoSaveDocument(self) -> None: + """Autosave of the document. This is a timer-activated slot.""" + if SHARED.hasProject and self.docEditor.docChanged: + logger.debug("Autosaving document") + self.saveDocument() + return + + @pyqtSlot() + def _updateStatusWordCount(self) -> None: + """Update the word count on the status bar.""" + if not SHARED.hasProject: + self.mainStatus.setProjectStats(0, 0) + + SHARED.project.updateWordCounts() + if CONFIG.incNotesWCount: + iTotal = sum(SHARED.project.data.initCounts) + cTotal = sum(SHARED.project.data.currCounts) + self.mainStatus.setProjectStats(cTotal, cTotal - iTotal) + else: + iNovel, _ = SHARED.project.data.initCounts + cNovel, _ = SHARED.project.data.currCounts + self.mainStatus.setProjectStats(cNovel, cNovel - iNovel) + + return + + @pyqtSlot() + def _keyPressReturn(self) -> None: + """Forward the return/enter keypress to the function that opens + the currently selected item. + """ + self.openSelectedItem() + return + + @pyqtSlot() + def _keyPressEscape(self) -> None: + """Process escape keypress in the main window.""" + if self.docEditor.docSearch.isVisible(): + self.docEditor.closeSearch() + elif self.isFocusMode: + self.toggleFocusMode() + return + + @pyqtSlot(int) + def _mainStackChanged(self, index: int) -> None: + """Process main window tab change.""" + if index == self.idxOutlineView: + if SHARED.hasProject: + self.outlineView.refreshTree() + return + + @pyqtSlot(int) + def _projStackChanged(self, index: int) -> None: + """Process project view tab change.""" + sHandle = None + if index == self.idxProjView: + sHandle = self.projView.getSelectedHandle() + elif index == self.idxNovelView: + sHandle, _ = self.novelView.getSelectedHandle() + self.itemDetails.updateViewBox(sHandle) return ## @@ -1328,153 +1485,4 @@ class GuiMain(QMainWindow): return None, None return tHandle, sTitle - ## - # Events - ## - - def closeEvent(self, event: QCloseEvent): - """Capture the closing event of the GUI and call the close - function to handle all the close process steps. - """ - if self.closeMain(): - event.accept() - else: - event.ignore() - return - - ## - # Private Slots - ## - - @pyqtSlot(str, nwDocMode) - def _followTag(self, tag: str, mode: nwDocMode) -> None: - """Follow a tag after user interaction with a link.""" - tHandle, sTitle = self._getTagSource(tag) - if tHandle is not None: - if mode == nwDocMode.EDIT: - self.openDocument(tHandle) - elif mode == nwDocMode.VIEW: - self.viewDocument(tHandle=tHandle, sTitle=sTitle) - return - - @pyqtSlot(str, nwDocMode, str, bool) - def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None: - """Handle an open document request.""" - if tHandle is not None: - if mode == nwDocMode.EDIT: - tLine = None - hItem = SHARED.project.index.getItemHeader(tHandle, sTitle) - if hItem is not None: - tLine = hItem.line - self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus) - elif mode == nwDocMode.VIEW: - self.viewDocument(tHandle=tHandle, sTitle=sTitle) - return - - @pyqtSlot(nwView) - def _changeView(self, view: nwView) -> None: - """Handle the requested change of view from the GuiViewBar.""" - 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() - def _timeTick(self) -> None: - """Process time tick of the main timer.""" - if not SHARED.hasProject: - return - currTime = time() - editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime - userIdle = qApp.applicationState() != Qt.ApplicationActive - self.mainStatus.setUserIdle(editIdle or userIdle) - SHARED.updateIdleTime(currTime, editIdle or userIdle) - self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime) - return - - @pyqtSlot() - def _autoSaveProject(self) -> None: - """Autosave of the project. This is a timer-activated slot.""" - doSave = SHARED.hasProject - doSave &= SHARED.project.projChanged - doSave &= SHARED.project.storage.isOpen() - if doSave: - logger.debug("Autosaving project") - self.saveProject(autoSave=True) - return - - @pyqtSlot() - def _autoSaveDocument(self) -> None: - """Autosave of the document. This is a timer-activated slot.""" - if SHARED.hasProject and self.docEditor.docChanged: - logger.debug("Autosaving document") - self.saveDocument() - return - - @pyqtSlot() - def _updateStatusWordCount(self) -> None: - """Update the word count on the status bar.""" - if not SHARED.hasProject: - self.mainStatus.setProjectStats(0, 0) - - SHARED.project.updateWordCounts() - if CONFIG.incNotesWCount: - iTotal = sum(SHARED.project.data.initCounts) - cTotal = sum(SHARED.project.data.currCounts) - self.mainStatus.setProjectStats(cTotal, cTotal - iTotal) - else: - iNovel, _ = SHARED.project.data.initCounts - cNovel, _ = SHARED.project.data.currCounts - self.mainStatus.setProjectStats(cNovel, cNovel - iNovel) - - return - - @pyqtSlot() - def _keyPressReturn(self) -> None: - """Forward the return/enter keypress to the function that opens - the currently selected item. - """ - self.openSelectedItem() - return - - @pyqtSlot() - def _keyPressEscape(self) -> None: - """Process escape keypress in the main window.""" - if self.docEditor.docSearch.isVisible(): - self.docEditor.closeSearch() - elif self.isFocusMode: - self.toggleFocusMode() - return - - @pyqtSlot(int) - def _mainStackChanged(self, index: int) -> None: - """Process main window tab change.""" - if index == self.idxOutlineView: - if SHARED.hasProject: - self.outlineView.refreshTree() - return - - @pyqtSlot(int) - def _projStackChanged(self, index: int) -> None: - """Process project view tab change.""" - sHandle = None - if index == self.idxProjView: - sHandle = self.projView.getSelectedHandle() - elif index == self.idxNovelView: - sHandle, _ = self.novelView.getSelectedHandle() - self.itemDetails.updateViewBox(sHandle) - return - # END Class GuiMain diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 74e0e352..52e2bc0f 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -594,14 +594,18 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd): # ========== # No document open, so not allowing focus mode - assert nwGUI.toggleFocusMode() is False + nwGUI.toggleFocusMode() + assert nwGUI.treePane.isVisible() is True + assert nwGUI.mainStatus.isVisible() is True + assert nwGUI.mainMenu.isVisible() is True + assert nwGUI.sideBar.isVisible() is True # Open a file in editor and viewer assert nwGUI.openDocument(C.hSceneDoc) assert nwGUI.viewDocument(C.hSceneDoc) # Enable focus mode - assert nwGUI.toggleFocusMode() is True + nwGUI.toggleFocusMode() assert nwGUI.treePane.isVisible() is False assert nwGUI.mainStatus.isVisible() is False assert nwGUI.mainMenu.isVisible() is False @@ -609,7 +613,7 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd): assert nwGUI.splitView.isVisible() is False # Disable focus mode - assert nwGUI.toggleFocusMode() is True + nwGUI.toggleFocusMode() assert nwGUI.treePane.isVisible() is True assert nwGUI.mainStatus.isVisible() is True assert nwGUI.mainMenu.isVisible() is True