diff --git a/.gitignore b/.gitignore index 5d442ef0..9260acd3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.egg-info setup.iss novelwriter.desktop +novelWriter.pyw i18n/*.qm i18n/*.qph diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af3972d..8f3a0e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # novelWriter Changelog +## Version 1.2.2 [2021-03-28] + +### Release Notes + +This patch release is a bug fix release addressing some inconsistencies and issues with the +document header buttons when Focus Mode is active. The keyboard shortcuts for search and replace +should now also work in Focus Mode. In addition, the setup script for novelWriter has been improved +when installing on Windows. + +### Detailed Changelog + +**Bugfixes** + +* The way Focus Mode worked when activated through the menu and through the document header button + were inconsistent. The header button would deactivate the edit, search and close buttons, while + the menu entry would not. These two methods now call the same set of functions to ensure the + behaviour is consistent. PR #717. +* Closing the document while in Focus Mode now ends Focus Mode. Previously, the editor would be + left stuck in Focus Mode with no way to exit. PR #717. + +**User Interface** + +* The keyboard shortcuts for the search and replace tool now also work in Focus Mode. Previously, + the menu entries and their shortcuts were deactivated in this mode. Issue #716. PR #717. + +**Installation** + +* The setup script command do build minimal install archive files now also generate SHA 256 sum + files. PR #724. +* The setup script will now copy the `novelWriter.py` file to `novelWriter.pyw` if it doesn't exist + when the `win-install` command is run. Issue #727. PR #728. + +---- + ## Version 1.2.1 [2021-03-21] ### Release Notes diff --git a/nw/assets/text/release_notes.htm b/nw/assets/text/release_notes.htm index c610131c..864521b1 100644 --- a/nw/assets/text/release_notes.htm +++ b/nw/assets/text/release_notes.htm @@ -3,6 +3,17 @@ +

Release Notes for 1.2.2

+

Released on 28 March 2021

+

This patch release is a bug fix release addressing some inconsistencies and issues with the +document header buttons when Focus Mode is active. The keyboard shortcuts for search and replace +should now also work in Focus Mode. In addition, the setup script for novelWriter has been improved +when installing on Windows.

+ +

See also the Releases page.

+ +
+

Release Notes for 1.2.1

Released on 21 March 2021

This patch release is a bug fix release addressing issues with the document editor's search and @@ -10,8 +21,6 @@ replace tool. Due to some recently added restrictions on when various tools are on which part of the main window has the user's focus, the search tool keyboard shortcuts and buttons were blocked when they shouldn't. This release resolves these issues.

-

See also the Releases page.

-

Release Notes for 1.2

diff --git a/nw/constants/__init__.py b/nw/constants/__init__.py index 0aeb0ae4..f0f12c60 100644 --- a/nw/constants/__init__.py +++ b/nw/constants/__init__.py @@ -5,7 +5,7 @@ from nw.constants.constants import ( ) from nw.constants.enum import ( nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline, - nwDocInsert + nwDocInsert, nwWidget ) __all__ = [ @@ -26,4 +26,5 @@ __all__ = [ "nwItemType", "nwOutline", "nwDocInsert", + "nwWidget", ] diff --git a/nw/constants/enum.py b/nw/constants/enum.py index 4e800666..7136377d 100644 --- a/nw/constants/enum.py +++ b/nw/constants/enum.py @@ -112,6 +112,15 @@ class nwAlert(Enum): # END Enum nwAlert +class nwWidget(Enum): + + TREE = 1 + EDITOR = 2 + VIEWER = 3 + OUTLINE = 4 + +# END Enum nwWidget + class nwOutline(Enum): TITLE = 0 diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 92666b3e..e0aee4d3 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -2223,7 +2223,6 @@ class GuiDocEditHeader(QWidget): fPx = int(0.9*self.theTheme.fontPixelSize) hSp = self.mainConf.pxInt(6) - self.buttonSize = fPx + hSp # Main Widget Settings self.setAutoFillBackground(True) @@ -2369,6 +2368,17 @@ class GuiDocEditHeader(QWidget): return True + def updateFocusMode(self): + """Update the minimise/maximise icon of the Focus Mode button. + This function is called by the GuiMain class via the + toggleFocusMode function and should not be activated directly. + """ + if self.theParent.isFocusMode: + self.minmaxButton.setIcon(self.theTheme.getIcon("minimise")) + else: + self.minmaxButton.setIcon(self.theTheme.getIcon("maximise")) + return + ## # Slots ## @@ -2399,18 +2409,6 @@ class GuiDocEditHeader(QWidget): """Switch on or off Focus Mode. """ self.theParent.toggleFocusMode() - if self.theParent.isFocusMode: - self.minmaxButton.setIcon(self.theTheme.getIcon("minimise")) - self.setContentsMargins(self.buttonSize, 0, 0, 0) - self.editButton.setVisible(False) - self.searchButton.setVisible(False) - self.closeButton.setVisible(False) - else: - self.minmaxButton.setIcon(self.theTheme.getIcon("maximise")) - self.setContentsMargins(0, 0, 0, 0) - self.editButton.setVisible(True) - self.searchButton.setVisible(True) - self.closeButton.setVisible(True) return ## diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 6bf3a25f..3f2579fd 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -33,7 +33,7 @@ from PyQt5.QtWidgets import QMenuBar, QAction from nw.constants import ( trConst, nwItemType, nwItemClass, nwDocAction, nwDocInsert, nwKeyWords, - nwLabels, nwUnicode + nwLabels, nwUnicode, nwWidget ) logger = logging.getLogger(__name__) @@ -471,28 +471,28 @@ class GuiMainMenu(QMenuBar): self.aFocusTree = QAction(self.tr("Focus Project Tree"), self) self.aFocusTree.setStatusTip(self.tr("Move focus to project tree")) self.aFocusTree.setShortcut("Alt+1") - self.aFocusTree.triggered.connect(lambda: self.theParent.setFocus(1)) + self.aFocusTree.triggered.connect(lambda: self.theParent.switchFocus(nwWidget.TREE)) self.viewMenu.addAction(self.aFocusTree) # View > Document Pane 1 self.aFocusEditor = QAction(self.tr("Focus Document Editor"), self) self.aFocusEditor.setStatusTip(self.tr("Move focus to left document pane")) self.aFocusEditor.setShortcut("Alt+2") - self.aFocusEditor.triggered.connect(lambda: self.theParent.setFocus(2)) + self.aFocusEditor.triggered.connect(lambda: self.theParent.switchFocus(nwWidget.EDITOR)) self.viewMenu.addAction(self.aFocusEditor) # View > Document Pane 2 self.aFocusView = QAction(self.tr("Focus Document Viewer"), self) self.aFocusView.setStatusTip(self.tr("Move focus to right document pane")) self.aFocusView.setShortcut("Alt+3") - self.aFocusView.triggered.connect(lambda: self.theParent.setFocus(3)) + self.aFocusView.triggered.connect(lambda: self.theParent.switchFocus(nwWidget.VIEWER)) self.viewMenu.addAction(self.aFocusView) # View > Outline self.aFocusOutline = QAction(self.tr("Focus Outline"), self) self.aFocusOutline.setStatusTip(self.tr("Move focus to outline")) self.aFocusOutline.setShortcut("Alt+4") - self.aFocusOutline.triggered.connect(lambda: self.theParent.setFocus(4)) + self.aFocusOutline.triggered.connect(lambda: self.theParent.switchFocus(nwWidget.OUTLINE)) self.viewMenu.addAction(self.aFocusOutline) # View > Separator diff --git a/nw/guimain.py b/nw/guimain.py index f3ad2c13..d6da8940 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -46,7 +46,7 @@ from nw.gui import ( GuiProjectTree, GuiProjectWizard, GuiTheme, GuiWordList, GuiWritingStats ) from nw.core import NWProject, NWDoc, NWIndex -from nw.constants import nwItemType, nwItemClass, nwAlert, nwLists +from nw.constants import nwItemType, nwItemClass, nwAlert, nwLists, nwWidget from nw.common import getGuiItem, hexToInt logger = logging.getLogger(__name__) @@ -567,6 +567,10 @@ class GuiMain(QMainWindow): logger.error("No project open") return False + # Disable focus mode if it is active + if self.isFocusMode: + self.toggleFocusMode() + self.docEditor.saveCursorPosition() if self.docEditor.docChanged: self.saveDocument() @@ -813,10 +817,10 @@ class GuiMain(QMainWindow): return False if tHandle is None: - if self.treeView.hasFocus(): - tHandle = self.treeView.getSelectedHandle() - elif self.docEditor.hasFocus(): + if self.docEditor.anyFocus() or self.isFocusMode: tHandle = self.docEditor.theHandle + else: + tHandle = self.treeView.getSelectedHandle() if tHandle is None: logger.warning("No item selected") @@ -1176,18 +1180,18 @@ class GuiMain(QMainWindow): return True - def setFocus(self, paneNo): + def switchFocus(self, paneNo): """Switch focus between main GUI views. """ - if paneNo == 1: + if paneNo == nwWidget.TREE: self.treeView.setFocus() - elif paneNo == 2: + elif paneNo == nwWidget.EDITOR: self.mainTabs.setCurrentWidget(self.splitDocs) self.docEditor.setFocus() - elif paneNo == 3: + elif paneNo == nwWidget.VIEWER: self.mainTabs.setCurrentWidget(self.splitDocs) self.docViewer.setFocus() - elif paneNo == 4: + elif paneNo == nwWidget.OUTLINE: self.mainTabs.setCurrentWidget(self.splitOutline) self.projView.setFocus() return @@ -1224,6 +1228,7 @@ class GuiMain(QMainWindow): if self.isFocusMode: logger.debug("Activating Focus Mode") self.mainTabs.setCurrentWidget(self.splitDocs) + self.switchFocus(nwWidget.EDITOR) else: logger.debug("Deactivating Focus Mode") @@ -1235,6 +1240,7 @@ class GuiMain(QMainWindow): hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter self.docEditor.docFooter.setVisible(not hideDocFooter) + self.docEditor.docHeader.updateFocusMode() if self.splitView.isVisible(): self.splitView.setVisible(False) @@ -1272,11 +1278,12 @@ class GuiMain(QMainWindow): """ # Project self.addAction(self.mainMenu.aSaveProject) + self.addAction(self.mainMenu.aEditItem) self.addAction(self.mainMenu.aExitNW) # Document self.addAction(self.mainMenu.aSaveDoc) - self.addAction(self.mainMenu.aFileDetails) + self.addAction(self.mainMenu.aCloseDoc) # Edit self.addAction(self.mainMenu.aEditUndo) @@ -1320,6 +1327,13 @@ class GuiMain(QMainWindow): for mAction, _ in self.mainMenu.mInsKWItems.values(): self.addAction(mAction) + # Search + self.addAction(self.mainMenu.aFind) + self.addAction(self.mainMenu.aReplace) + self.addAction(self.mainMenu.aFindNext) + self.addAction(self.mainMenu.aFindPrev) + self.addAction(self.mainMenu.aReplaceNext) + # Format self.addAction(self.mainMenu.aFmtEmph) self.addAction(self.mainMenu.aFmtStrong) diff --git a/setup.py b/setup.py index f11b5014..dc47aca3 100755 --- a/setup.py +++ b/setup.py @@ -291,7 +291,8 @@ def makeMinimalPackage(targetOS): else: targName = "" - outFile = os.path.join("dist", f"novelWriter-{__version__}-minimal{targName}.zip") + zipFile = f"novelWriter-{__version__}-minimal{targName}.zip" + outFile = os.path.join("dist", zipFile) if os.path.isfile(outFile): os.unlink(outFile) @@ -332,7 +333,17 @@ def makeMinimalPackage(targetOS): zipObj.write(aFile) print("") - print("Built file: %s" % outFile) + print("Created File: %s" % outFile) + + try: + shaFile = open(outFile+".sha256", mode="w") + subprocess.call(["sha256sum", zipFile], stdout=shaFile, cwd="dist") + shaFile.close() + print("SHA256 Sum: %s" % (outFile+".sha256")) + except Exception as e: + print("Could not generate sha256 file") + print(str(e)) + print("") return @@ -710,6 +721,9 @@ def winInstall(): targetPy = os.path.join(targetDir, "novelWriter.pyw") targetIcon = os.path.join(targetDir, "nw", "assets", "icons", "novelwriter.ico") + if not os.path.isfile(targetPy): + shutil.copy2(os.path.join(targetDir, "novelWriter.py"), targetPy) + print("Collecting Info ...") print("Desktop Folder: %s" % desktopDir) print("Start Menu Folder: %s" % startMenuDir) diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index dfcf1f86..b53da662 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -33,7 +33,7 @@ from PyQt5.QtWidgets import QAction, QMessageBox, QDialog from nw.gui.itemeditor import GuiItemEditor from nw.gui.doceditor import GuiDocEditor from nw.gui.projtree import GuiProjectTree -from nw.constants import nwItemType, nwDocAction +from nw.constants import nwItemType, nwDocAction, nwWidget keyDelay = 2 typeDelay = 1 @@ -117,14 +117,14 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi nwGUI.mainConf.autoScroll = True # Add a Character File - nwGUI.setFocus(1) + nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) assert nwGUI.openSelectedItem() # Type something into the document - nwGUI.setFocus(2) + nwGUI.switchFocus(nwWidget.EDITOR) qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) for c in "# Jane Doe": qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) @@ -139,14 +139,14 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) # Add a Plot File - nwGUI.setFocus(1) + nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) assert nwGUI.openSelectedItem() # Type something into the document - nwGUI.setFocus(2) + nwGUI.switchFocus(nwWidget.EDITOR) qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) for c in "# Main Plot": qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) @@ -161,7 +161,7 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) # Add a World File - nwGUI.setFocus(1) + nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem("811786ad1ae74").setSelected(True) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) @@ -173,7 +173,7 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi nwGUI.docEditor.replaceText("") # Type something into the document - nwGUI.setFocus(2) + nwGUI.switchFocus(nwWidget.EDITOR) qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) for c in "# Main Location": qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) @@ -192,7 +192,7 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi nwGUI._autoSaveProject() # Select the 'New Scene' file - nwGUI.setFocus(1) + nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True) nwGUI.treeView._getTreeItem("31489056e0916").setExpanded(True) @@ -200,7 +200,7 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi assert nwGUI.openSelectedItem() # Type something into the document - nwGUI.setFocus(2) + nwGUI.switchFocus(nwWidget.EDITOR) qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) for c in "# Novel": qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) @@ -300,7 +300,7 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi qtbot.wait(stepDelay) # Open and view the edited document - nwGUI.setFocus(3) + nwGUI.switchFocus(nwWidget.VIEWER) assert nwGUI.openDocument("0e17daca5f3e1") assert nwGUI.viewDocument("0e17daca5f3e1") qtbot.wait(stepDelay) diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index 3e8886a8..e2ef781a 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -28,7 +28,9 @@ from PyQt5.QtGui import QTextCursor, QTextBlock from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from nw.gui.doceditor import GuiDocEditor -from nw.constants import nwUnicode, nwDocAction, nwDocInsert, nwKeyWords +from nw.constants import ( + nwUnicode, nwDocAction, nwDocInsert, nwKeyWords, nwWidget +) keyDelay = 2 typeDelay = 1 @@ -366,7 +368,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj): assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None - nwGUI.setFocus(1) + nwGUI.switchFocus(nwWidget.TREE) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True) assert nwGUI.openSelectedItem()