From aad35e2d7b3fda3dd18dfb0ffe9dfa991dd8900b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 15 Jun 2020 16:35:24 +0200 Subject: [PATCH 01/25] First some cleanup of the document widgets --- nw/gui/doceditor.py | 24 +++++++++++++++--------- nw/gui/docviewer.py | 23 ++++++++++++----------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 4c5d5ce7..bd491a4f 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -7,7 +7,7 @@ File History: Created: 2018-09-29 [0.0.1] GuiDocEditor - Created: 2019-04-22 [0.0.1] WordCounter + Created: 2019-04-22 [0.0.1] BackgroundWordCounter Created: 2020-04-25 [0.4.5] GuiDocEditHeader This file is a part of novelWriter @@ -126,7 +126,7 @@ class GuiDocEditor(QTextEdit): self.wcTimer.setInterval(int(self.wcInterval*1000)) self.wcTimer.timeout.connect(self._runCounter) - self.wCounter = WordCounter(self) + self.wCounter = BackgroundWordCounter(self) self.wCounter.finished.connect(self._updateCounts) self.initEditor() @@ -338,11 +338,7 @@ class GuiDocEditor(QTextEdit): docFormat = self.qDocument.rootFrame().frameFormat() docFormat.setLeftMargin(tM) - docFormat.setRightMargin(tM) - if tT > 0: - docFormat.setTopMargin(tT) - else: - docFormat.setTopMargin(0) + docFormat.setTopMargin(max(0, tT)) # Updating root frame triggers a QTextDocument->contentsChange # signal, which we do not want as it re-runs the syntax @@ -1205,7 +1201,12 @@ class GuiDocEditor(QTextEdit): # END Class GuiDocEditor -class WordCounter(QThread): +# =============================================================================================== # +# The Off GUI Thread Word Counter +# Runs the word counter in the background for the doCEditor +# =============================================================================================== # + +class BackgroundWordCounter(QThread): def __init__(self, theParent): QThread.__init__(self, theParent) @@ -1228,7 +1229,12 @@ class WordCounter(QThread): return -## END Class WordCounter +## END Class BackgroundWordCounter + +# =============================================================================================== # +# The Embedded Document Header +# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport +# =============================================================================================== # class GuiDocEditHeader(QWidget): diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index 1059c277..8c2ea236 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -236,13 +236,14 @@ class GuiDocViewer(QTextBrowser): else: sW = 0 + cM = self.mainConf.getTextMargin() tB = self.frameWidth() tW = self.width() - 2*tB - sW tH = self.docHeader.height() fH = self.docFooter.height() fY = self.height() - fH - tB - tT = self.mainConf.getTextMargin() - tH - bT = self.mainConf.getTextMargin() - fH + tT = cM - tH + bT = cM - fH self.docHeader.setGeometry(tB, tB, tW, tH) self.docFooter.setGeometry(tB, fY, tW, fH) self.setViewportMargins(0, tH, 0, fH) @@ -558,16 +559,16 @@ class GuiDocViewHeader(QWidget): class GuiDocViewFooter(QWidget): - def __init__(self, theParent): - QWidget.__init__(self, theParent) + def __init__(self, docViewer): + QWidget.__init__(self, docViewer) logger.debug("Initialising GuiDocViewFooter ...") self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme - self.optState = theParent.theProject.optState - self.viewMeta = theParent.theParent.viewMeta + self.docViewer = docViewer + self.theParent = docViewer.theParent + self.theTheme = docViewer.theTheme + self.viewMeta = docViewer.theParent.viewMeta self.theHandle = None # Make a QPalette that matches the Syntax Theme @@ -669,9 +670,9 @@ class GuiDocViewFooter(QWidget): """Toggle the sticky flag for the reference panel. """ logger.verbose("Reference sticky is %s" % str(theState)) - self.theParent.stickyRef = theState - if not theState and self.theParent.theHandle is not None: - self.viewMeta.refreshReferences(self.theParent.theHandle) + self.docViewer.stickyRef = theState + if not theState and self.docViewer.theHandle is not None: + self.viewMeta.refreshReferences(self.docViewer.theHandle) return # END Class GuiDocViewFooter From 7374d0513e1822e3ceb79545985864cba66028d7 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 15 Jun 2020 16:39:31 +0200 Subject: [PATCH 02/25] Some minor changes for code consistency --- nw/gui/doceditor.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index bd491a4f..f3b70649 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -1203,14 +1203,14 @@ class GuiDocEditor(QTextEdit): # =============================================================================================== # # The Off GUI Thread Word Counter -# Runs the word counter in the background for the doCEditor +# Runs the word counter in the background for the DocEditor # =============================================================================================== # class BackgroundWordCounter(QThread): - def __init__(self, theParent): - QThread.__init__(self, theParent) - self.theParent = theParent + def __init__(self, docEditor): + QThread.__init__(self, docEditor) + self.docEditor = docEditor self.charCount = 0 self.wordCount = 0 self.paraCount = 0 @@ -1220,13 +1220,11 @@ class BackgroundWordCounter(QThread): """Overloaded run function for the word counter, forwarding the call to the function that does the actual counting. """ - theText = self.theParent.getText() + theText = self.docEditor.getText() cC, wC, pC = countWords(theText) - self.charCount = cC self.wordCount = wC self.paraCount = pC - return ## END Class BackgroundWordCounter From bbb0f5f9ddfa18d1587b009d3ec34aa2abffa7c7 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 15 Jun 2020 21:36:04 +0200 Subject: [PATCH 03/25] Moved the search bar code into a new class --- nw/gui/__init__.py | 2 - nw/gui/docbars.py | 168 --------------------------------------- nw/gui/doceditor.py | 189 +++++++++++++++++++++++++++++++++++++++++--- nw/guimain.py | 10 +-- 4 files changed, 180 insertions(+), 189 deletions(-) delete mode 100644 nw/gui/docbars.py diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 820a2a48..4e96b4ee 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -2,7 +2,6 @@ from nw.gui.about import GuiAbout from nw.gui.build import GuiBuildNovel -from nw.gui.docbars import GuiSearchBar from nw.gui.doceditor import GuiDocEditor from nw.gui.docmerge import GuiDocMerge from nw.gui.docsplit import GuiDocSplit @@ -23,7 +22,6 @@ from nw.gui.theme import GuiIcons, GuiTheme __all__ = [ "GuiAbout", "GuiBuildNovel", - "GuiSearchBar", "GuiDocEditor", "GuiDocMerge", "GuiDocSplit", diff --git a/nw/gui/docbars.py b/nw/gui/docbars.py deleted file mode 100644 index 079f989a..00000000 --- a/nw/gui/docbars.py +++ /dev/null @@ -1,168 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter GUI Main Window SearchBar - - novelWriter – GUI Main Window SearchBar -========================================= - Class holding the main window search bar - - File History: - Created: 2019-09-29 [0.2.1] GuiSearchBar - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw - -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtGui import QPalette, QColor, QIcon -from PyQt5.QtWidgets import ( - qApp, QWidget, QFrame, QGridLayout, QLabel, QLineEdit, QPushButton, - QHBoxLayout, QToolButton, QScrollArea -) - -from nw.constants import nwDocAction, nwUnicode - -logger = logging.getLogger(__name__) - -class GuiSearchBar(QWidget): - - def __init__(self, theParent): - QWidget.__init__(self, theParent) - - logger.debug("Initialising GuiSearchBar ...") - - self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme - self.repVisible = False - - self.setContentsMargins(0, 0, 0, 0) - - self.mainBox = QGridLayout(self) - self.setLayout(self.mainBox) - - self.searchBox = QLineEdit() - self.replaceBox = QLineEdit() - self.searchLabel = QLabel("Search") - self.replaceLabel = QLabel("Replace") - self.closeButton = QPushButton(self.theTheme.getIcon("close"),"") - self.searchButton = QPushButton(self.theTheme.getIcon("search"),"") - self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"),"") - - self.closeButton.clicked.connect(self._doClose) - self.searchButton.clicked.connect(self._doSearch) - self.replaceButton.clicked.connect(self._doReplace) - self.searchBox.returnPressed.connect(self._doSearch) - self.replaceBox.returnPressed.connect(self._doSearch) - - self.mainBox.addWidget(QLabel(""), 0, 0) - self.mainBox.addWidget(self.searchLabel, 0, 1) - self.mainBox.addWidget(self.searchBox, 0, 2) - self.mainBox.addWidget(self.searchButton, 0, 3) - self.mainBox.addWidget(self.closeButton, 0, 4) - self.mainBox.addWidget(self.replaceLabel, 1, 1) - self.mainBox.addWidget(self.replaceBox, 1, 2) - self.mainBox.addWidget(self.replaceButton, 1, 3) - - self.mainBox.setColumnStretch(0, 1) - self.mainBox.setColumnStretch(1, 0) - self.mainBox.setColumnStretch(2, 0) - self.mainBox.setColumnStretch(3, 0) - self.mainBox.setColumnStretch(4, 0) - self.mainBox.setContentsMargins(0, 0, 0, 0) - - boxWidth = 16*self.theTheme.textNWidth - self.searchBox.setMinimumWidth(boxWidth) - self.replaceBox.setMinimumWidth(boxWidth) - - self._replaceVisible(False) - - logger.debug("GuiSearchBar initialisation complete") - - return - - ## - # Get and Set Functions - ## - - def setSearchText(self, theText): - """Open the search bar and set the search text to the text - provided, if any. - """ - if not self.isVisible(): - self.setVisible(True) - self.searchBox.setText(theText) - self.searchBox.setFocus() - logger.verbose("Setting search text to '%s'" % theText) - return True - - def setReplaceText(self, theText): - """Set the replace text. - """ - self._replaceVisible(True) - self.replaceBox.setFocus() - self.replaceBox.setText(theText) - return True - - def getSearchText(self): - """Return the current search text. - """ - return self.searchBox.text() - - def getReplaceText(self): - """Return the current replace text. - """ - return self.replaceBox.text() - - ## - # Internal Functions - ## - - def _doClose(self): - """Hide the search/replace bar. - """ - self._replaceVisible(False) - self.setVisible(False) - return - - def _doSearch(self): - """Call the search action function for the document editor. - """ - modKey = qApp.keyboardModifiers() - if modKey == Qt.ShiftModifier: - self.theParent.docEditor.docAction(nwDocAction.GO_PREV) - else: - self.theParent.docEditor.docAction(nwDocAction.GO_NEXT) - return - - def _doReplace(self): - """Call the replace action function for the document editor. - """ - self.theParent.docEditor.docAction(nwDocAction.REPL_NEXT) - return - - def _replaceVisible(self, isVisible): - """Set the visibility of all the replace widgets. - """ - self.replaceLabel.setVisible(isVisible) - self.replaceBox.setVisible(isVisible) - self.replaceButton.setVisible(isVisible) - self.repVisible = isVisible - return True - -# END Class GuiSearchBar diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index f3b70649..1c0ac459 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -6,9 +6,11 @@ Class holding the document editor File History: - Created: 2018-09-29 [0.0.1] GuiDocEditor - Created: 2019-04-22 [0.0.1] BackgroundWordCounter - Created: 2020-04-25 [0.4.5] GuiDocEditHeader + Created: 2018-09-29 [0.0.1] GuiDocEditor + Created: 2019-04-22 [0.0.1] BackgroundWordCounter + Created: 2019-09-29 [0.2.1] GuiDocEditSearch + Created: 2020-04-25 [0.4.5] GuiDocEditHeader + Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen @@ -39,7 +41,7 @@ from PyQt5.QtGui import ( ) from PyQt5.QtWidgets import ( qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, - QToolButton, QHBoxLayout + QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton, QFrame, QVBoxLayout ) from nw.core import NWDoc @@ -87,6 +89,7 @@ class GuiDocEditor(QTextEdit): # Document Title self.docHeader = GuiDocEditHeader(self) + self.docSearch = GuiDocEditSearch(self) # Syntax self.hLight = GuiDocHighlighter(self.qDocument, self.theParent) @@ -309,6 +312,7 @@ class GuiDocEditor(QTextEdit): Config.textFixedW is enabled or we're in Zen mode. Otherwise, just ensure the margins are set correctly. """ + wW = self.width() cM = self.mainConf.getTextMargin() vBar = self.verticalScrollBar() @@ -322,7 +326,6 @@ class GuiDocEditor(QTextEdit): tW = self.mainConf.getZenWidth() else: tW = self.mainConf.getTextWidth() - wW = self.width() tM = int((wW - sW - tW)/2) if tM < cM: tM = cM @@ -330,15 +333,21 @@ class GuiDocEditor(QTextEdit): tM = cM tB = self.frameWidth() - tW = self.width() - 2*tB - sW + tW = wW - 2*tB - sW tH = self.docHeader.height() tT = cM - tH + + rH = self.docSearch.height() + rW = self.docSearch.width() + rL = wW - sW - rW - tB + self.docHeader.setGeometry(tB, tB, tW, tH) + self.docSearch.move(rL, tB) self.setViewportMargins(0, tH, 0, 0) docFormat = self.qDocument.rootFrame().frameFormat() docFormat.setLeftMargin(tM) - docFormat.setTopMargin(max(0, tT)) + docFormat.setTopMargin(max(0, tT, rH)) # Updating root frame triggers a QTextDocument->contentsChange # signal, which we do not want as it re-runs the syntax @@ -569,6 +578,13 @@ class GuiDocEditor(QTextEdit): )) return + def closeSearch(self): + """Close the search box. + """ + self.docSearch.setVisible(False) + self.updateDocMargins() + return self.docSearch.isVisible() + ## # Document Events and Maintenance ## @@ -1127,7 +1143,8 @@ class GuiDocEditor(QTextEdit): selText = theCursor.selectedText() else: selText = "" - self.theParent.searchBar.setSearchText(selText) + self.docSearch.setSearchText(selText) + self.updateDocMargins() return def _beginReplace(self): @@ -1135,14 +1152,14 @@ class GuiDocEditor(QTextEdit): text. """ self._beginSearch() - self.theParent.searchBar.setReplaceText("") + self.docSearch.setReplaceText("") return def _findNext(self): """Searches for the next occurrence of the search bar text in the document. Wraps back to the top if not found. """ - searchFor = self.theParent.searchBar.getSearchText() + searchFor = self.docSearch.getSearchText() wasFound = self.find(searchFor) if not wasFound: theCursor = self.textCursor() @@ -1154,7 +1171,7 @@ class GuiDocEditor(QTextEdit): """Searches for the previous occurrence of the search bar text in the document. Wraps back to the end if not found. """ - searchFor = self.theParent.searchBar.getSearchText() + searchFor = self.docSearch.getSearchText() wasFound = self.find(searchFor, QTextDocument.FindBackward) if not wasFound: theCursor = self.textCursor() @@ -1168,8 +1185,8 @@ class GuiDocEditor(QTextEdit): to the top if not found. """ theCursor = self.textCursor() - searchFor = self.theParent.searchBar.getSearchText() - replWith = self.theParent.searchBar.getReplaceText() + searchFor = self.docSearch.getSearchText() + replWith = self.docSearch.getReplaceText() if theCursor.hasSelection() and theCursor.selectedText() == searchFor: xPos = theCursor.selectionStart() theCursor.beginEditBlock() @@ -1229,6 +1246,152 @@ class BackgroundWordCounter(QThread): ## END Class BackgroundWordCounter +# =============================================================================================== # +# The Embedded Document Search/Replace Feature +# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport +# =============================================================================================== # + +class GuiDocEditSearch(QWidget): + + def __init__(self, docEditor): + QWidget.__init__(self, docEditor) + + logger.debug("Initialising GuiDocEditSearch ...") + + self.mainConf = nw.CONFIG + self.docEditor = docEditor + self.theParent = docEditor.theParent + self.theProject = docEditor.theProject + self.theTheme = docEditor.theTheme + + self.repVisible = False + + fPx = int(0.9*self.theTheme.fontPixelSize) + boxFont = self.theTheme.guiFont + boxFont.setPointSizeF(0.9*self.theTheme.fontPointSize) + + # self.setContentsMargins(0, 0, 0, 0) + # self.setFrameStyle(QFrame.Box) + self.setAutoFillBackground(True) + + self.mainBox = QGridLayout(self) + self.setLayout(self.mainBox) + + self.searchBox = QLineEdit() + self.searchBox.setFont(boxFont) + + self.replaceBox = QLineEdit() + self.replaceBox.setFont(boxFont) + + self.closeButton = QPushButton(self.theTheme.getIcon("close"),"") + self.searchButton = QPushButton(self.theTheme.getIcon("search"),"") + self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"),"") + + self.closeButton.clicked.connect(self._doClose) + self.searchButton.clicked.connect(self._doSearch) + self.replaceButton.clicked.connect(self._doReplace) + self.searchBox.returnPressed.connect(self._doSearch) + self.replaceBox.returnPressed.connect(self._doSearch) + + self.mainBox.addWidget(self.searchBox, 0, 0) + self.mainBox.addWidget(self.searchButton, 0, 1) + self.mainBox.addWidget(self.closeButton, 0, 2) + self.mainBox.addWidget(self.replaceBox, 1, 0) + self.mainBox.addWidget(self.replaceButton, 1, 1) + + self.mainBox.setColumnStretch(0, 1) + self.mainBox.setColumnStretch(1, 0) + self.mainBox.setColumnStretch(2, 0) + self.mainBox.setColumnStretch(3, 0) + self.mainBox.setColumnStretch(4, 0) + self.mainBox.setVerticalSpacing(0) + self.mainBox.setHorizontalSpacing(2) + # self.mainBox.setContentsMargins(0, 0, 0, 0) + + boxWidth = 16*self.theTheme.textNWidth + self.searchBox.setFixedWidth(boxWidth) + self.replaceBox.setFixedWidth(boxWidth) + + # self._replaceVisible(False) + + logger.debug("GuiDocEditSearch initialisation complete") + + return + + ## + # Get and Set Functions + ## + + def setSearchText(self, theText): + """Open the search bar and set the search text to the text + provided, if any. + """ + if not self.isVisible(): + self.setVisible(True) + self.searchBox.setText(theText) + self.searchBox.setFocus() + logger.verbose("Setting search text to '%s'" % theText) + return True + + def setReplaceText(self, theText): + """Set the replace text. + """ + self._replaceVisible(True) + self.replaceBox.setFocus() + self.replaceBox.setText(theText) + return True + + def getSearchText(self): + """Return the current search text. + """ + return self.searchBox.text() + + def getReplaceText(self): + """Return the current replace text. + """ + return self.replaceBox.text() + + ## + # Slots + ## + + def _doClose(self): + """Hide the search/replace bar. + """ + self._replaceVisible(False) + self.docEditor.closeSearch() + return + + def _doSearch(self): + """Call the search action function for the document editor. + """ + modKey = qApp.keyboardModifiers() + if modKey == Qt.ShiftModifier: + self.docEditor.docAction(nwDocAction.GO_PREV) + else: + self.docEditor.docAction(nwDocAction.GO_NEXT) + return + + def _doReplace(self): + """Call the replace action function for the document editor. + """ + self.docEditor.docAction(nwDocAction.REPL_NEXT) + return + + ## + # Internal Functions + ## + + def _replaceVisible(self, isVisible): + """Set the visibility of all the replace widgets. + """ + self.replaceBox.setVisible(isVisible) + self.replaceButton.setVisible(isVisible) + self.repVisible = isVisible + return True + +# END Class GuiDocEditSearch + # =============================================================================================== # # The Embedded Document Header # Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport diff --git a/nw/guimain.py b/nw/guimain.py index cb2f19a2..1be860f0 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -43,7 +43,7 @@ from nw.gui import ( GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus, GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, - GuiProjectSettings, GuiProjectTree, GuiSearchBar, GuiSessionLogView, GuiTheme + GuiProjectSettings, GuiProjectTree, GuiSessionLogView, GuiTheme ) from nw.core import NWProject, NWDoc, NWIndex from nw.constants import nwFiles, nwItemType, nwAlert @@ -93,7 +93,6 @@ class GuiMain(QMainWindow): self.docEditor = GuiDocEditor(self) self.viewMeta = GuiDocViewDetails(self) self.docViewer = GuiDocViewer(self) - self.searchBar = GuiSearchBar(self) self.treeMeta = GuiItemDetails(self) self.projView = GuiOutline(self) self.projMeta = GuiOutlineDetails(self) @@ -115,7 +114,6 @@ class GuiMain(QMainWindow): self.docEdit = QVBoxLayout() self.docEdit.setContentsMargins(0, 0, 0, 0) self.docEdit.setSpacing(self.mainConf.pxInt(2)) - self.docEdit.addWidget(self.searchBar) self.docEdit.addWidget(self.docEditor) self.editPane.setLayout(self.docEdit) @@ -168,7 +166,7 @@ class GuiMain(QMainWindow): self.splitView.setCollapsible(self.idxViewMeta, False) self.splitView.setVisible(False) - self.searchBar.setVisible(False) + self.docEditor.closeSearch() # Build the Tree View self.treeView.itemSelectionChanged.connect(self._treeSingleClick) @@ -1075,8 +1073,8 @@ class GuiMain(QMainWindow): """When the escape key is pressed somewhere in the main window, do the following, in order: """ - if self.searchBar.isVisible(): - self.searchBar.setVisible(False) + if self.docEditor.docSearch.isVisible(): + self.docEditor.closeSearch() return elif self.isZenMode: self.toggleZenMode() From 65492b13b74d5d51444f33e56bfa72b3db99d9e1 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 15 Jun 2020 22:24:26 +0200 Subject: [PATCH 04/25] Some cleanup of unneeded code in main gui --- nw/guimain.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/nw/guimain.py b/nw/guimain.py index 1be860f0..15bcdb18 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -42,8 +42,8 @@ from PyQt5.QtWidgets import ( from nw.gui import ( GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus, - GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, - GuiProjectSettings, GuiProjectTree, GuiSessionLogView, GuiTheme + GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiTheme, + GuiProjectSettings, GuiProjectTree, GuiSessionLogView ) from nw.core import NWProject, NWDoc, NWIndex from nw.constants import nwFiles, nwItemType, nwAlert @@ -110,13 +110,6 @@ class GuiMain(QMainWindow): self.treeBox.addWidget(self.treeMeta) self.treePane.setLayout(self.treeBox) - self.editPane = QWidget() - self.docEdit = QVBoxLayout() - self.docEdit.setContentsMargins(0, 0, 0, 0) - self.docEdit.setSpacing(self.mainConf.pxInt(2)) - self.docEdit.addWidget(self.docEditor) - self.editPane.setLayout(self.docEdit) - self.splitView = QSplitter(Qt.Vertical) self.splitView.addWidget(self.docViewer) self.splitView.addWidget(self.viewMeta) @@ -124,7 +117,7 @@ class GuiMain(QMainWindow): self.splitDocs = QSplitter(Qt.Horizontal) self.splitDocs.setOpaqueResize(False) - self.splitDocs.addWidget(self.editPane) + self.splitDocs.addWidget(self.docEditor) self.splitDocs.addWidget(self.splitView) self.splitOutline = QSplitter(Qt.Vertical) @@ -151,7 +144,7 @@ class GuiMain(QMainWindow): self.idxTree = self.splitMain.indexOf(self.treePane) self.idxMain = self.splitMain.indexOf(self.tabWidget) - self.idxEditor = self.splitDocs.indexOf(self.editPane) + self.idxEditor = self.splitDocs.indexOf(self.docEditor) self.idxViewer = self.splitDocs.indexOf(self.splitView) self.idxViewDoc = self.splitView.indexOf(self.docViewer) self.idxViewMeta = self.splitView.indexOf(self.viewMeta) @@ -599,15 +592,13 @@ class GuiMain(QMainWindow): return True def passDocumentAction(self, theAction): - """Pass on document action theAction to whatever document has - the focus. If no document has focus, the action is discarded. + """Pass on document action theAction to the document viewer if + it has focus, otherwise pass it to the document editor. """ - if self.docEditor.hasFocus(): - self.docEditor.docAction(theAction) - elif self.docViewer.hasFocus(): + if self.docViewer.hasFocus(): self.docViewer.docAction(theAction) else: - logger.debug("Document action requested, but no document has focus") + self.docEditor.docAction(theAction) return True ## From a4108222b01ab8561f60c3120e3faf9f41418aa8 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 15 Jun 2020 22:24:50 +0200 Subject: [PATCH 05/25] Search functionality mostly working again --- nw/gui/doceditor.py | 88 +++++++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 1c0ac459..a9aaff5c 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -41,7 +41,7 @@ from PyQt5.QtGui import ( ) from PyQt5.QtWidgets import ( qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, - QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton, QFrame, QVBoxLayout + QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton, QFrame, QVBoxLayout, QSizePolicy ) from nw.core import NWDoc @@ -337,9 +337,13 @@ class GuiDocEditor(QTextEdit): tH = self.docHeader.height() tT = cM - tH - rH = self.docSearch.height() - rW = self.docSearch.width() - rL = wW - sW - rW - tB + if self.docSearch.isVisible(): + rH = self.docSearch.height() + rW = self.docSearch.width() + rL = wW - sW - rW - tB + else: + rH = 0 + rL = 0 self.docHeader.setGeometry(tB, tB, tW, tH) self.docSearch.move(rL, tB) @@ -581,8 +585,7 @@ class GuiDocEditor(QTextEdit): def closeSearch(self): """Close the search box. """ - self.docSearch.setVisible(False) - self.updateDocMargins() + self.docSearch.closeSearch() return self.docSearch.isVisible() ## @@ -1159,24 +1162,36 @@ class GuiDocEditor(QTextEdit): """Searches for the next occurrence of the search bar text in the document. Wraps back to the top if not found. """ + if not self.docSearch.isVisible(): + self._beginSearch() + return + searchFor = self.docSearch.getSearchText() wasFound = self.find(searchFor) if not wasFound: theCursor = self.textCursor() theCursor.movePosition(QTextCursor.Start) self.setTextCursor(theCursor) + self.find(searchFor) + return def _findPrev(self): """Searches for the previous occurrence of the search bar text in the document. Wraps back to the end if not found. """ + if not self.docSearch.isVisible(): + self._beginSearch() + return + searchFor = self.docSearch.getSearchText() wasFound = self.find(searchFor, QTextDocument.FindBackward) if not wasFound: theCursor = self.textCursor() theCursor.movePosition(QTextCursor.End) self.setTextCursor(theCursor) + self.find(searchFor, QTextDocument.FindBackward) + return def _replaceNext(self): @@ -1184,6 +1199,10 @@ class GuiDocEditor(QTextEdit): the document and replaces it with the replace text. Wraps back to the top if not found. """ + if not self.docSearch.isVisible(): + self._beginSearch() + return + theCursor = self.textCursor() searchFor = self.docSearch.getSearchText() replWith = self.docSearch.getReplaceText() @@ -1200,6 +1219,7 @@ class GuiDocEditor(QTextEdit): )) if searchFor != "": self._findNext() + return def _setupSpellChecking(self): @@ -1266,32 +1286,45 @@ class GuiDocEditSearch(QWidget): self.repVisible = False + mPx = self.mainConf.pxInt(6) fPx = int(0.9*self.theTheme.fontPixelSize) boxFont = self.theTheme.guiFont boxFont.setPointSizeF(0.9*self.theTheme.fontPointSize) - # self.setContentsMargins(0, 0, 0, 0) - # self.setFrameStyle(QFrame.Box) + self.setContentsMargins(mPx, mPx, mPx, mPx) self.setAutoFillBackground(True) self.mainBox = QGridLayout(self) self.setLayout(self.mainBox) + # Text Boxes + # ========== self.searchBox = QLineEdit() self.searchBox.setFont(boxFont) + self.searchBox.returnPressed.connect(self._doSearch) - self.replaceBox = QLineEdit() + self.replaceBox = QLineEdit() self.replaceBox.setFont(boxFont) + self.replaceBox.returnPressed.connect(self._doSearch) + + # Buttons + # ======= + bPx = self.searchBox.sizeHint().height() + + self.searchButton = QPushButton(self.theTheme.getIcon("search"),"") + self.searchButton.setFixedSize(QSize(bPx, bPx)) + self.searchButton.setToolTip("Find in current document") + self.searchButton.clicked.connect(self._doSearch) + + self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"),"") + self.replaceButton.setFixedSize(QSize(bPx, bPx)) + self.replaceButton.setToolTip("Find and replace in current document") + self.replaceButton.clicked.connect(self._doReplace) self.closeButton = QPushButton(self.theTheme.getIcon("close"),"") - self.searchButton = QPushButton(self.theTheme.getIcon("search"),"") - self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"),"") - + self.closeButton.setFixedSize(QSize(bPx, bPx)) + self.closeButton.setToolTip("Close search tool") self.closeButton.clicked.connect(self._doClose) - self.searchButton.clicked.connect(self._doSearch) - self.replaceButton.clicked.connect(self._doReplace) - self.searchBox.returnPressed.connect(self._doSearch) - self.replaceBox.returnPressed.connect(self._doSearch) self.mainBox.addWidget(self.searchBox, 0, 0) self.mainBox.addWidget(self.searchButton, 0, 1) @@ -1304,20 +1337,29 @@ class GuiDocEditSearch(QWidget): self.mainBox.setColumnStretch(2, 0) self.mainBox.setColumnStretch(3, 0) self.mainBox.setColumnStretch(4, 0) - self.mainBox.setVerticalSpacing(0) - self.mainBox.setHorizontalSpacing(2) - # self.mainBox.setContentsMargins(0, 0, 0, 0) + self.mainBox.setSpacing(self.mainConf.pxInt(2)) + self.mainBox.setContentsMargins(0, 0, 0, 0) - boxWidth = 16*self.theTheme.textNWidth + boxWidth = 18*self.theTheme.textNWidth self.searchBox.setFixedWidth(boxWidth) self.replaceBox.setFixedWidth(boxWidth) + self.adjustSize() - # self._replaceVisible(False) + self._replaceVisible(False) logger.debug("GuiDocEditSearch initialisation complete") return + def closeSearch(self): + """Close the search box. + """ + self._replaceVisible(False) + self.setVisible(False) + self.docEditor.updateDocMargins() + self.docEditor.setFocus() + return + ## # Get and Set Functions ## @@ -1358,8 +1400,7 @@ class GuiDocEditSearch(QWidget): def _doClose(self): """Hide the search/replace bar. """ - self._replaceVisible(False) - self.docEditor.closeSearch() + self.closeSearch() return def _doSearch(self): @@ -1388,6 +1429,7 @@ class GuiDocEditSearch(QWidget): self.replaceBox.setVisible(isVisible) self.replaceButton.setVisible(isVisible) self.repVisible = isVisible + self.adjustSize() return True # END Class GuiDocEditSearch From 2c5692085e3b37a9cab1b6403d8235893705db34 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 16 Jun 2020 00:20:59 +0200 Subject: [PATCH 06/25] Use viewport margins for all scaling of editor doc margins --- nw/gui/doceditor.py | 34 ++++++++++------------------------ nw/guimain.py | 2 -- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index a9aaff5c..53ba7bc9 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -101,6 +101,7 @@ class GuiDocEditor(QTextEdit): # Editor State self.hasSelection = False self.setMinimumWidth(self.mainConf.pxInt(300)) + self.setAutoFillBackground(True) self.setAcceptRichText(False) # Custom Shortcuts @@ -185,12 +186,15 @@ class GuiDocEditor(QTextEdit): self.setFont(theFont) docPalette = self.palette() + docPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack)) docPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack)) docPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText)) self.setPalette(docPalette) # Set default text margins - self.qDocument.setDocumentMargin(self.mainConf.getTextMargin()) + cM = self.mainConf.getTextMargin() + self.qDocument.setDocumentMargin(0) + self.setViewportMargins(cM, cM, cM, cM) # Also set the document text options for the document text flow theOpt = QTextOption() @@ -326,7 +330,7 @@ class GuiDocEditor(QTextEdit): tW = self.mainConf.getZenWidth() else: tW = self.mainConf.getTextWidth() - tM = int((wW - sW - tW)/2) + tM = (wW - sW - tW)//2 if tM < cM: tM = cM else: @@ -335,36 +339,18 @@ class GuiDocEditor(QTextEdit): tB = self.frameWidth() tW = wW - 2*tB - sW tH = self.docHeader.height() - tT = cM - tH + self.docHeader.setGeometry(tB, tB, tW, tH) if self.docSearch.isVisible(): rH = self.docSearch.height() rW = self.docSearch.width() rL = wW - sW - rW - tB + self.docSearch.move(rL, tB) else: rH = 0 - rL = 0 - self.docHeader.setGeometry(tB, tB, tW, tH) - self.docSearch.move(rL, tB) - self.setViewportMargins(0, tH, 0, 0) - - docFormat = self.qDocument.rootFrame().frameFormat() - docFormat.setLeftMargin(tM) - docFormat.setTopMargin(max(0, tT, rH)) - - # Updating root frame triggers a QTextDocument->contentsChange - # signal, which we do not want as it re-runs the syntax - # highlighter and spell checker, so we block it briefly. - # We then emit a signal that does not trigger re-highlighting. - self.qDocument.blockSignals(True) - self.qDocument.rootFrame().setFrameFormat(docFormat) - self.qDocument.blockSignals(False) - - # The line below causes issues with large documents as it - # triggers an early repaint that seems to only render a part of - # the document. Leaving it here as a warning for now. - # self.qDocument.contentsChange.emit(0, 0, 0) + # print(tM, tH, rH, max(tM, tH, rH)) + self.setViewportMargins(tM, max(cM, tH, rH), tM, cM) return diff --git a/nw/guimain.py b/nw/guimain.py index 15bcdb18..5bfad545 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -116,7 +116,6 @@ class GuiMain(QMainWindow): self.splitView.setSizes(self.mainConf.getViewPanePos()) self.splitDocs = QSplitter(Qt.Horizontal) - self.splitDocs.setOpaqueResize(False) self.splitDocs.addWidget(self.docEditor) self.splitDocs.addWidget(self.splitView) @@ -135,7 +134,6 @@ class GuiMain(QMainWindow): xCM = self.mainConf.pxInt(4) self.splitMain = QSplitter(Qt.Horizontal) self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM) - self.splitMain.setOpaqueResize(False) self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.tabWidget) self.splitMain.setSizes(self.mainConf.getMainPanePos()) From 8755434f768b2ba57ab01e0cef70ccc21364f453 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 16 Jun 2020 20:44:24 +0200 Subject: [PATCH 07/25] Fix weird issue with text editor capturing enter key without having focus --- nw/gui/doceditor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 53ba7bc9..b2f2e49d 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -591,6 +591,9 @@ class GuiDocEditor(QTextEdit): as it is triggered on every keypress when typing. """ self.hasSelection = self.textCursor().hasSelection() + if not self.hasFocus(): + # Block the event when the focus is on the search bar. + return if keyEvent.modifiers() == Qt.ShiftModifier: theKey = keyEvent.key() From ad8463543ee75b8a28651b39c482d3462e8fc8d7 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 16 Jun 2020 21:35:57 +0200 Subject: [PATCH 08/25] Merged find next and prev functions, and added optrions for case sensitivity and whole words --- nw/gui/doceditor.py | 61 +++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index b2f2e49d..b3f913c9 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -34,7 +34,7 @@ import nw from time import time -from PyQt5.QtCore import Qt, QSize, QThread, QTimer, pyqtSlot +from PyQt5.QtCore import Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp from PyQt5.QtGui import ( QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, QTextDocument, QCursor @@ -527,7 +527,7 @@ class GuiDocEditor(QTextEdit): elif theAction == nwDocAction.GO_NEXT: self._findNext() elif theAction == nwDocAction.GO_PREV: - self._findPrev() + self._findNext(isBackward=True) elif theAction == nwDocAction.REPL_NEXT: self._replaceNext() elif theAction == nwDocAction.BLOCK_H1: @@ -1147,39 +1147,31 @@ class GuiDocEditor(QTextEdit): self.docSearch.setReplaceText("") return - def _findNext(self): - """Searches for the next occurrence of the search bar text in - the document. Wraps back to the top if not found. + def _findNext(self, isBackward=False): + """Searches for the next or previous occurrence of the search + bar text in the document. Wraps around if not found. """ if not self.docSearch.isVisible(): self._beginSearch() return - searchFor = self.docSearch.getSearchText() - wasFound = self.find(searchFor) - if not wasFound: - theCursor = self.textCursor() - theCursor.movePosition(QTextCursor.Start) - self.setTextCursor(theCursor) - self.find(searchFor) - - return - - def _findPrev(self): - """Searches for the previous occurrence of the search bar text - in the document. Wraps back to the end if not found. - """ - if not self.docSearch.isVisible(): - self._beginSearch() - return + findOpt = QTextDocument.FindFlag(0) + if isBackward: + findOpt |= QTextDocument.FindBackward + if self.docSearch.isCaseSense: + findOpt |= QTextDocument.FindCaseSensitively + if self.docSearch.isWholeWord: + findOpt |= QTextDocument.FindWholeWords searchFor = self.docSearch.getSearchText() - wasFound = self.find(searchFor, QTextDocument.FindBackward) + wasFound = self.find(searchFor, findOpt) if not wasFound: theCursor = self.textCursor() - theCursor.movePosition(QTextCursor.End) + theCursor.movePosition( + QTextCursor.End if isBackward else QTextCursor.Start + ) self.setTextCursor(theCursor) - self.find(searchFor, QTextDocument.FindBackward) + self.find(searchFor, findOpt) return @@ -1195,18 +1187,24 @@ class GuiDocEditor(QTextEdit): theCursor = self.textCursor() searchFor = self.docSearch.getSearchText() replWith = self.docSearch.getReplaceText() - if theCursor.hasSelection() and theCursor.selectedText() == searchFor: - xPos = theCursor.selectionStart() + selText = theCursor.selectedText() + + if not self.docSearch.isCaseSense: + searchFor = searchFor.lower() + selText = selText.lower() + + if theCursor.hasSelection() and selText == searchFor: theCursor.beginEditBlock() theCursor.removeSelectedText() theCursor.insertText(replWith) theCursor.endEditBlock() - theCursor.setPosition(xPos) + theCursor.setPosition(theCursor.selectionEnd()) self.setTextCursor(theCursor) logger.verbose("Replaced occurrence of '%s' with '%s' on line %d" % ( searchFor, replWith, theCursor.blockNumber() )) - if searchFor != "": + + if searchFor: self._findNext() return @@ -1273,7 +1271,10 @@ class GuiDocEditSearch(QWidget): self.theProject = docEditor.theProject self.theTheme = docEditor.theTheme - self.repVisible = False + self.repVisible = False + self.isCaseSense = False + self.isWholeWord = False + self.isRegEx = True mPx = self.mainConf.pxInt(6) fPx = int(0.9*self.theTheme.fontPixelSize) From f28a9f452321dfd5d5f6aa8389518e644114cdfa Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 16 Jun 2020 22:32:08 +0200 Subject: [PATCH 09/25] Regex searching also works now --- nw/gui/doceditor.py | 46 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index b3f913c9..8ad72649 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -1274,7 +1274,7 @@ class GuiDocEditSearch(QWidget): self.repVisible = False self.isCaseSense = False self.isWholeWord = False - self.isRegEx = True + self.isRegEx = False mPx = self.mainConf.pxInt(6) fPx = int(0.9*self.theTheme.fontPixelSize) @@ -1337,6 +1337,24 @@ class GuiDocEditSearch(QWidget): self._replaceVisible(False) + # Construct Box Colours + qPalette = self.searchBox.palette() + baseCol = qPalette.base().color() + rCol = baseCol.redF() + 0.1 + gCol = baseCol.greenF() - 0.1 + bCol = baseCol.blueF() - 0.1 + + mCol = max(rCol, gCol, bCol, 1.0) + errCol = QColor() + errCol.setRedF(rCol/mCol) + errCol.setGreenF(gCol/mCol) + errCol.setBlueF(bCol/mCol) + + self.rxCol = { + True : baseCol, + False : errCol + } + logger.debug("GuiDocEditSearch initialisation complete") return @@ -1362,6 +1380,8 @@ class GuiDocEditSearch(QWidget): self.setVisible(True) self.searchBox.setText(theText) self.searchBox.setFocus() + if self.isRegEx: + self._alertSearchValid(True) logger.verbose("Setting search text to '%s'" % theText) return True @@ -1374,9 +1394,20 @@ class GuiDocEditSearch(QWidget): return True def getSearchText(self): - """Return the current search text. + """Return the current search text either as text or as a regular + expression object. """ - return self.searchBox.text() + theText = self.searchBox.text() + if self.isRegEx and self.mainConf.verQtValue >= 50300: + if self.isCaseSense: + rxCase = Qt.CaseSensitive + else: + rxCase = Qt.CaseInsensitive + theRegEx = QRegExp(theText, rxCase) + self._alertSearchValid(theRegEx.isValid()) + return theRegEx + + return theText def getReplaceText(self): """Return the current replace text. @@ -1413,6 +1444,15 @@ class GuiDocEditSearch(QWidget): # Internal Functions ## + def _alertSearchValid(self, isValid): + """Highlight the search box to indicate the search string is or + isn't valid. Take the colour from the replace box. + """ + qPalette = self.replaceBox.palette() + qPalette.setColor(QPalette.Base, self.rxCol[isValid]) + self.searchBox.setPalette(qPalette) + return + def _replaceVisible(self, isVisible): """Set the visibility of all the replace widgets. """ From abc14db7d112dc6d5781446d54ba38bcdd3631e6 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 16 Jun 2020 23:07:18 +0200 Subject: [PATCH 10/25] Search options can now be set in a popup menu --- nw/gui/doceditor.py | 46 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 8ad72649..9deb071b 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -37,7 +37,7 @@ from time import time from PyQt5.QtCore import Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp from PyQt5.QtGui import ( QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, - QTextDocument, QCursor + QTextDocument, QCursor, QIcon ) from PyQt5.QtWidgets import ( qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, @@ -1297,6 +1297,32 @@ class GuiDocEditSearch(QWidget): self.replaceBox.setFont(boxFont) self.replaceBox.returnPressed.connect(self._doSearch) + self.searchOpt = QMenu(self) + + self.toggleCase = QAction("Case Sensitive", self) + self.toggleCase.setFont(boxFont) + self.toggleCase.setCheckable(True) + self.toggleCase.toggled.connect(self._doToggleCase) + self.searchOpt.addAction(self.toggleCase) + + self.toggleWord = QAction("Whole Words Only", self) + self.toggleWord.setFont(boxFont) + self.toggleWord.setCheckable(True) + self.toggleWord.toggled.connect(self._doToggleWord) + self.searchOpt.addAction(self.toggleWord) + + self.toggleRegEx = QAction("RegEx Mode", self) + self.toggleRegEx.setFont(boxFont) + self.toggleRegEx.setCheckable(True) + self.toggleRegEx.toggled.connect(self._doToggleRegEx) + self.searchOpt.addAction(self.toggleRegEx) + + self.optButton = QAction(self) + self.optButton.setIcon(self.theTheme.getIcon("edit")) + self.optButton.setMenu(self.searchOpt) + + self.searchBox.addAction(self.optButton, QLineEdit.TrailingPosition) + # Buttons # ======= bPx = self.searchBox.sizeHint().height() @@ -1440,6 +1466,24 @@ class GuiDocEditSearch(QWidget): self.docEditor.docAction(nwDocAction.REPL_NEXT) return + def _doToggleCase(self, theState): + """Enable/disable case sensitive mode. + """ + self.isCaseSense = theState + return + + def _doToggleWord(self, theState): + """Enable/disable whole word search mode. + """ + self.isWholeWord = theState + return + + def _doToggleRegEx(self, theState): + """Enable/disable regular expression search mode. + """ + self.isRegEx = theState + return + ## # Internal Functions ## From ef0d1fc89cbf42e761aa3d8520de31215b3fe458 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 16:57:20 +0200 Subject: [PATCH 11/25] Added icons for the search tool --- .../icons/fallback/search_cancel-dark.svg | 31 +++++++ nw/assets/icons/fallback/search_cancel.svg | 31 +++++++ nw/assets/icons/fallback/search_case-dark.svg | 49 ++++++++++++ nw/assets/icons/fallback/search_case.svg | 48 +++++++++++ nw/assets/icons/fallback/search_loop-dark.svg | 31 +++++++ nw/assets/icons/fallback/search_loop.svg | 31 +++++++ .../icons/fallback/search_preserve-dark.svg | 40 ++++++++++ nw/assets/icons/fallback/search_preserve.svg | 40 ++++++++++ .../icons/fallback/search_project-dark.svg | 54 +++++++++++++ nw/assets/icons/fallback/search_project.svg | 54 +++++++++++++ .../icons/fallback/search_regex-dark.svg | 49 ++++++++++++ nw/assets/icons/fallback/search_regex.svg | 48 +++++++++++ nw/assets/icons/fallback/search_word-dark.svg | 45 +++++++++++ nw/assets/icons/fallback/search_word.svg | 45 +++++++++++ nw/gui/theme.py | 80 ++++++++++--------- 15 files changed, 639 insertions(+), 37 deletions(-) create mode 100644 nw/assets/icons/fallback/search_cancel-dark.svg create mode 100644 nw/assets/icons/fallback/search_cancel.svg create mode 100644 nw/assets/icons/fallback/search_case-dark.svg create mode 100644 nw/assets/icons/fallback/search_case.svg create mode 100644 nw/assets/icons/fallback/search_loop-dark.svg create mode 100644 nw/assets/icons/fallback/search_loop.svg create mode 100644 nw/assets/icons/fallback/search_preserve-dark.svg create mode 100644 nw/assets/icons/fallback/search_preserve.svg create mode 100644 nw/assets/icons/fallback/search_project-dark.svg create mode 100644 nw/assets/icons/fallback/search_project.svg create mode 100644 nw/assets/icons/fallback/search_regex-dark.svg create mode 100644 nw/assets/icons/fallback/search_regex.svg create mode 100644 nw/assets/icons/fallback/search_word-dark.svg create mode 100644 nw/assets/icons/fallback/search_word.svg diff --git a/nw/assets/icons/fallback/search_cancel-dark.svg b/nw/assets/icons/fallback/search_cancel-dark.svg new file mode 100644 index 00000000..5a0b2182 --- /dev/null +++ b/nw/assets/icons/fallback/search_cancel-dark.svg @@ -0,0 +1,31 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/nw/assets/icons/fallback/search_cancel.svg b/nw/assets/icons/fallback/search_cancel.svg new file mode 100644 index 00000000..48630300 --- /dev/null +++ b/nw/assets/icons/fallback/search_cancel.svg @@ -0,0 +1,31 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/nw/assets/icons/fallback/search_case-dark.svg b/nw/assets/icons/fallback/search_case-dark.svg new file mode 100644 index 00000000..4701dfe1 --- /dev/null +++ b/nw/assets/icons/fallback/search_case-dark.svg @@ -0,0 +1,49 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/nw/assets/icons/fallback/search_case.svg b/nw/assets/icons/fallback/search_case.svg new file mode 100644 index 00000000..ce7e7673 --- /dev/null +++ b/nw/assets/icons/fallback/search_case.svg @@ -0,0 +1,48 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/nw/assets/icons/fallback/search_loop-dark.svg b/nw/assets/icons/fallback/search_loop-dark.svg new file mode 100644 index 00000000..60baf3c2 --- /dev/null +++ b/nw/assets/icons/fallback/search_loop-dark.svg @@ -0,0 +1,31 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/nw/assets/icons/fallback/search_loop.svg b/nw/assets/icons/fallback/search_loop.svg new file mode 100644 index 00000000..4d6d85c5 --- /dev/null +++ b/nw/assets/icons/fallback/search_loop.svg @@ -0,0 +1,31 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/nw/assets/icons/fallback/search_preserve-dark.svg b/nw/assets/icons/fallback/search_preserve-dark.svg new file mode 100644 index 00000000..e59a8616 --- /dev/null +++ b/nw/assets/icons/fallback/search_preserve-dark.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + image/svg+xml + + + + + + diff --git a/nw/assets/icons/fallback/search_preserve.svg b/nw/assets/icons/fallback/search_preserve.svg new file mode 100644 index 00000000..bcfd6082 --- /dev/null +++ b/nw/assets/icons/fallback/search_preserve.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + image/svg+xml + + + + + + diff --git a/nw/assets/icons/fallback/search_project-dark.svg b/nw/assets/icons/fallback/search_project-dark.svg new file mode 100644 index 00000000..552fed1f --- /dev/null +++ b/nw/assets/icons/fallback/search_project-dark.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/nw/assets/icons/fallback/search_project.svg b/nw/assets/icons/fallback/search_project.svg new file mode 100644 index 00000000..ee16c528 --- /dev/null +++ b/nw/assets/icons/fallback/search_project.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/nw/assets/icons/fallback/search_regex-dark.svg b/nw/assets/icons/fallback/search_regex-dark.svg new file mode 100644 index 00000000..1a81cfe8 --- /dev/null +++ b/nw/assets/icons/fallback/search_regex-dark.svg @@ -0,0 +1,49 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/nw/assets/icons/fallback/search_regex.svg b/nw/assets/icons/fallback/search_regex.svg new file mode 100644 index 00000000..2bbe1c22 --- /dev/null +++ b/nw/assets/icons/fallback/search_regex.svg @@ -0,0 +1,48 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/nw/assets/icons/fallback/search_word-dark.svg b/nw/assets/icons/fallback/search_word-dark.svg new file mode 100644 index 00000000..650c7dfc --- /dev/null +++ b/nw/assets/icons/fallback/search_word-dark.svg @@ -0,0 +1,45 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/nw/assets/icons/fallback/search_word.svg b/nw/assets/icons/fallback/search_word.svg new file mode 100644 index 00000000..8f0aed52 --- /dev/null +++ b/nw/assets/icons/fallback/search_word.svg @@ -0,0 +1,45 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/nw/gui/theme.py b/nw/gui/theme.py index 30bcb621..7df0c560 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -495,52 +495,58 @@ class GuiIcons: ICON_MAP = { # Project and GUI icons - "cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_character" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_world" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_timeline" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_object" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_entity" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_custom" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_trash" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "proj_document" : (QStyle.SP_FileIcon, "x-office-document"), - "proj_folder" : (QStyle.SP_DirIcon, "folder"), - "proj_orphan" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), - "proj_nwx" : (None, None), - "status_lang" : (None, None), - "status_time" : (None, None), - "status_stats" : (None, None), - "doc_h1" : (QStyle.SP_FileIcon, "x-office-document"), - "doc_h2" : (QStyle.SP_FileIcon, "x-office-document"), - "doc_h3" : (QStyle.SP_FileIcon, "x-office-document"), - "doc_h4" : (QStyle.SP_FileIcon, "x-office-document"), + "cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_character" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_world" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_timeline" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_object" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_entity" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_custom" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_trash" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "proj_document" : (QStyle.SP_FileIcon, "x-office-document"), + "proj_folder" : (QStyle.SP_DirIcon, "folder"), + "proj_orphan" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), + "proj_nwx" : (None, None), + "status_lang" : (None, None), + "status_time" : (None, None), + "status_stats" : (None, None), + "doc_h1" : (QStyle.SP_FileIcon, "x-office-document"), + "doc_h2" : (QStyle.SP_FileIcon, "x-office-document"), + "doc_h3" : (QStyle.SP_FileIcon, "x-office-document"), + "doc_h4" : (QStyle.SP_FileIcon, "x-office-document"), + "search_case" : (None, None), + "search_regex" : (None, None), + "search_word" : (None, None), + "search_loop" : (None, None), + "search_project" : (None, None), + "search_cancel" : (None, None), + "search_preserve" : (None, None), ## General Button Icons "folder-open" : (QStyle.SP_DirOpenIcon, "folder-open"), "delete" : (QStyle.SP_DialogDiscardButton, "edit-delete"), - "add" : (None, "list-add"), - "remove" : (None, "list-remove"), "close" : (QStyle.SP_DialogCloseButton, "window-close"), "done" : (QStyle.SP_DialogApplyButton, None), - "search" : (None, "edit-find"), - "search-replace" : (None, "edit-find-replace"), "clear" : (QStyle.SP_LineEditClearButton, "clear_left"), "save" : (QStyle.SP_DialogSaveButton, "document-save"), - "edit" : (None, None), - "check" : (None, None), - "cross" : (None, None), - "hash" : (None, None), - "maximise" : (None, None), - "minimise" : (None, None), - "refresh" : (None, None), - "reference" : (None, None), - "sticky-on" : (None, None), - "sticky-off" : (None, None), + "add" : (None, "list-add"), + "remove" : (None, "list-remove"), + "search" : (None, "edit-find"), + "search-replace" : (None, "edit-find-replace"), + "edit" : (None, None), + "check" : (None, None), + "cross" : (None, None), + "hash" : (None, None), + "maximise" : (None, None), + "minimise" : (None, None), + "refresh" : (None, None), + "reference" : (None, None), - ## Other Icons - "warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), + ## Switches + "sticky-on" : (None, None), + "sticky-off" : (None, None), } DECO_MAP = { From c3c0bd7abef7bcd95ceaba7c8d3312c41a06ccc9 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 16:57:49 +0200 Subject: [PATCH 12/25] Added search options as a toolbar instead --- nw/gui/doceditor.py | 153 ++++++++++++++++++++++++++++++++------------ 1 file changed, 111 insertions(+), 42 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 9deb071b..baa0b881 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -40,7 +40,7 @@ from PyQt5.QtGui import ( QTextDocument, QCursor, QIcon ) from PyQt5.QtWidgets import ( - qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, + qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, QToolBar, QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton, QFrame, QVBoxLayout, QSizePolicy ) @@ -344,8 +344,8 @@ class GuiDocEditor(QTextEdit): if self.docSearch.isVisible(): rH = self.docSearch.height() rW = self.docSearch.width() - rL = wW - sW - rW - tB - self.docSearch.move(rL, tB) + rL = wW - sW - rW - 2*tB + self.docSearch.move(rL, 2*tB) else: rH = 0 @@ -1149,7 +1149,8 @@ class GuiDocEditor(QTextEdit): def _findNext(self, isBackward=False): """Searches for the next or previous occurrence of the search - bar text in the document. Wraps around if not found. + bar text in the document. Wraps around if not found and loop is + enabled, or continues to next file if next file is enabled. """ if not self.docSearch.isVisible(): self._beginSearch() @@ -1165,7 +1166,7 @@ class GuiDocEditor(QTextEdit): searchFor = self.docSearch.getSearchText() wasFound = self.find(searchFor, findOpt) - if not wasFound: + if not wasFound and self.docSearch.doLoop: theCursor = self.textCursor() theCursor.movePosition( QTextCursor.End if isBackward else QTextCursor.Start @@ -1177,8 +1178,8 @@ class GuiDocEditor(QTextEdit): def _replaceNext(self): """Searches for the next occurrence of the search bar text in - the document and replaces it with the replace text. Wraps back - to the top if not found. + the document and replaces it with the replace text. Calls search + next automatically when done. """ if not self.docSearch.isVisible(): self._beginSearch() @@ -1258,10 +1259,10 @@ class BackgroundWordCounter(QThread): # Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport # =============================================================================================== # -class GuiDocEditSearch(QWidget): +class GuiDocEditSearch(QFrame): def __init__(self, docEditor): - QWidget.__init__(self, docEditor) + QFrame.__init__(self, docEditor) logger.debug("Initialising GuiDocEditSearch ...") @@ -1275,14 +1276,19 @@ class GuiDocEditSearch(QWidget): self.isCaseSense = False self.isWholeWord = False self.isRegEx = False + self.doLoop = False + self.doNextFile = False + self.doPreserve = False mPx = self.mainConf.pxInt(6) fPx = int(0.9*self.theTheme.fontPixelSize) + tPx = int(0.8*self.theTheme.fontPixelSize) boxFont = self.theTheme.guiFont boxFont.setPointSizeF(0.9*self.theTheme.fontPointSize) - self.setContentsMargins(mPx, mPx, mPx, mPx) + self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) + self.setFrameStyle(QFrame.StyledPanel | QFrame.Plain) self.mainBox = QGridLayout(self) self.setLayout(self.mainBox) @@ -1291,42 +1297,86 @@ class GuiDocEditSearch(QWidget): # ========== self.searchBox = QLineEdit() self.searchBox.setFont(boxFont) + self.searchBox.setPlaceholderText("Search") self.searchBox.returnPressed.connect(self._doSearch) self.replaceBox = QLineEdit() self.replaceBox.setFont(boxFont) + self.replaceBox.setPlaceholderText("Replace") self.replaceBox.returnPressed.connect(self._doSearch) - self.searchOpt = QMenu(self) + self.searchOpt = QToolBar(self) + self.searchOpt.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.searchOpt.setIconSize(QSize(tPx, tPx)) + self.searchOpt.setContentsMargins(0, 0, 0, 0) + self.searchOpt.setStyleSheet(r"QToolBar {padding: 0;}") + + self.searchLabel = QLabel("Search") + self.searchLabel.setFont(boxFont) + self.searchLabel.setIndent(self.mainConf.pxInt(6)) self.toggleCase = QAction("Case Sensitive", self) - self.toggleCase.setFont(boxFont) + self.toggleCase.setToolTip("Match case") + self.toggleCase.setIcon(self.theTheme.getIcon("search_case")) self.toggleCase.setCheckable(True) self.toggleCase.toggled.connect(self._doToggleCase) self.searchOpt.addAction(self.toggleCase) self.toggleWord = QAction("Whole Words Only", self) - self.toggleWord.setFont(boxFont) + self.toggleWord.setToolTip("Match whole words") + self.toggleWord.setIcon(self.theTheme.getIcon("search_word")) self.toggleWord.setCheckable(True) self.toggleWord.toggled.connect(self._doToggleWord) self.searchOpt.addAction(self.toggleWord) self.toggleRegEx = QAction("RegEx Mode", self) - self.toggleRegEx.setFont(boxFont) + self.toggleRegEx.setToolTip("Use regular expressions") + self.toggleRegEx.setIcon(self.theTheme.getIcon("search_regex")) self.toggleRegEx.setCheckable(True) self.toggleRegEx.toggled.connect(self._doToggleRegEx) self.searchOpt.addAction(self.toggleRegEx) - self.optButton = QAction(self) - self.optButton.setIcon(self.theTheme.getIcon("edit")) - self.optButton.setMenu(self.searchOpt) + self.toggleLoop = QAction("Loop Search", self) + self.toggleLoop.setToolTip("Loop the search when reaching the end") + self.toggleLoop.setIcon(self.theTheme.getIcon("search_loop")) + self.toggleLoop.setCheckable(True) + self.toggleLoop.toggled.connect(self._doToggleLoop) + self.searchOpt.addAction(self.toggleLoop) - self.searchBox.addAction(self.optButton, QLineEdit.TrailingPosition) + self.toggleProject = QAction("Search Next File", self) + self.toggleProject.setToolTip("Continue searching in the next file") + self.toggleProject.setIcon(self.theTheme.getIcon("search_project")) + self.toggleProject.setCheckable(True) + self.toggleProject.toggled.connect(self._doToggleProject) + self.searchOpt.addAction(self.toggleProject) + + self.searchOpt.addSeparator() + + self.togglePreserve = QAction("Preserve Case", self) + self.togglePreserve.setToolTip("Preserve case on replace") + self.togglePreserve.setIcon(self.theTheme.getIcon("search_preserve")) + self.togglePreserve.setCheckable(True) + self.togglePreserve.toggled.connect(self._doTogglePreserve) + self.searchOpt.addAction(self.togglePreserve) + + self.searchOpt.addSeparator() + + self.cancelSearch = QAction("Close Search", self) + self.cancelSearch.setToolTip("Close the search box [Esc]") + self.cancelSearch.setIcon(self.theTheme.getIcon("search_cancel")) + self.cancelSearch.triggered.connect(self._doClose) + self.searchOpt.addAction(self.cancelSearch) # Buttons # ======= bPx = self.searchBox.sizeHint().height() + self.showReplace = QToolButton(self) + self.showReplace.setArrowType(Qt.RightArrow) + self.showReplace.setCheckable(True) + self.showReplace.setStyleSheet(r"QToolButton {border: none; background: transparent;}") + self.showReplace.toggled.connect(self._doToggleReplace) + self.searchButton = QPushButton(self.theTheme.getIcon("search"),"") self.searchButton.setFixedSize(QSize(bPx, bPx)) self.searchButton.setToolTip("Find in current document") @@ -1337,16 +1387,13 @@ class GuiDocEditSearch(QWidget): self.replaceButton.setToolTip("Find and replace in current document") self.replaceButton.clicked.connect(self._doReplace) - self.closeButton = QPushButton(self.theTheme.getIcon("close"),"") - self.closeButton.setFixedSize(QSize(bPx, bPx)) - self.closeButton.setToolTip("Close search tool") - self.closeButton.clicked.connect(self._doClose) - - self.mainBox.addWidget(self.searchBox, 0, 0) - self.mainBox.addWidget(self.searchButton, 0, 1) - self.mainBox.addWidget(self.closeButton, 0, 2) - self.mainBox.addWidget(self.replaceBox, 1, 0) - self.mainBox.addWidget(self.replaceButton, 1, 1) + self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2, Qt.AlignLeft) + self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 2, Qt.AlignRight) + self.mainBox.addWidget(self.showReplace, 1, 0, 1, 1) + self.mainBox.addWidget(self.searchBox, 1, 1, 1, 2) + self.mainBox.addWidget(self.searchButton, 1, 3, 1, 1) + self.mainBox.addWidget(self.replaceBox, 2, 1, 1, 2) + self.mainBox.addWidget(self.replaceButton, 2, 3, 1, 1) self.mainBox.setColumnStretch(0, 1) self.mainBox.setColumnStretch(1, 0) @@ -1354,14 +1401,14 @@ class GuiDocEditSearch(QWidget): self.mainBox.setColumnStretch(3, 0) self.mainBox.setColumnStretch(4, 0) self.mainBox.setSpacing(self.mainConf.pxInt(2)) - self.mainBox.setContentsMargins(0, 0, 0, 0) + self.mainBox.setContentsMargins(mPx, mPx, mPx, mPx) - boxWidth = 18*self.theTheme.textNWidth + boxWidth = self.mainConf.pxInt(200) self.searchBox.setFixedWidth(boxWidth) self.replaceBox.setFixedWidth(boxWidth) self.adjustSize() - self._replaceVisible(False) + self._doToggleReplace(False) # Construct Box Colours qPalette = self.searchBox.palette() @@ -1388,7 +1435,7 @@ class GuiDocEditSearch(QWidget): def closeSearch(self): """Close the search box. """ - self._replaceVisible(False) + self.showReplace.setChecked(False) self.setVisible(False) self.docEditor.updateDocMargins() self.docEditor.setFocus() @@ -1414,7 +1461,7 @@ class GuiDocEditSearch(QWidget): def setReplaceText(self, theText): """Set the replace text. """ - self._replaceVisible(True) + self.showReplace.setChecked(True) self.replaceBox.setFocus() self.replaceBox.setText(theText) return True @@ -1466,6 +1513,19 @@ class GuiDocEditSearch(QWidget): self.docEditor.docAction(nwDocAction.REPL_NEXT) return + def _doToggleReplace(self, theState): + """Toggle the show/hide of the + """ + if theState: + self.showReplace.setArrowType(Qt.DownArrow) + else: + self.showReplace.setArrowType(Qt.RightArrow) + self.replaceBox.setVisible(theState) + self.replaceButton.setVisible(theState) + self.repVisible = theState + self.adjustSize() + return + def _doToggleCase(self, theState): """Enable/disable case sensitive mode. """ @@ -1484,6 +1544,24 @@ class GuiDocEditSearch(QWidget): self.isRegEx = theState return + def _doToggleLoop(self, theState): + """Enable/disable looping the search. + """ + self.doLoop = theState + return + + def _doToggleProject(self, theState): + """Enable/disable continuing search in next project file. + """ + self.doNextFile = theState + return + + def _doTogglePreserve(self, theState): + """Enable/disable preserving case when replacing. + """ + self.doPreserve = theState + return + ## # Internal Functions ## @@ -1497,15 +1575,6 @@ class GuiDocEditSearch(QWidget): self.searchBox.setPalette(qPalette) return - def _replaceVisible(self, isVisible): - """Set the visibility of all the replace widgets. - """ - self.replaceBox.setVisible(isVisible) - self.replaceButton.setVisible(isVisible) - self.repVisible = isVisible - self.adjustSize() - return True - # END Class GuiDocEditSearch # =============================================================================================== # From 0ddb7ad1dec39f5468981b0449477e932dcfb1a1 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 17:15:30 +0200 Subject: [PATCH 13/25] Preserve case repalce now works --- nw/common.py | 21 +++++++++++++++++++++ nw/gui/doceditor.py | 16 ++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/nw/common.py b/nw/common.py index bd535a71..0019c38a 100644 --- a/nw/common.py +++ b/nw/common.py @@ -172,3 +172,24 @@ def splitVersionNumber(vString): vInt = vMajor*10000 + vMinor*100 + vPatch return [vMajor, vMinor, vPatch, vInt] + +def transferCase(theSource, theTarget): + """Transfers the case of the source word to the target word. This + will consider all upper or lower, and first char capitalisation. + """ + theResult = theTarget + + if not isinstance(theSource, str) or not isinstance(theTarget, str): + return theResult + if len(theTarget) < 1 or len(theSource) < 1: + return theResult + + if theSource[0] == theSource[0].upper(): + theResult = theTarget[0].upper() + theTarget[1:] + + if theSource == theSource.upper(): + theResult = theTarget.upper() + elif theSource == theSource.lower(): + theResult = theTarget.lower() + + return theResult diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index baa0b881..950fef73 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -48,6 +48,7 @@ from nw.core import NWDoc from nw.gui.dochighlight import GuiDocHighlighter from nw.core import NWSpellSimple, countWords from nw.constants import nwUnicode, nwDocAction +from nw.common import transferCase logger = logging.getLogger(__name__) @@ -1185,16 +1186,23 @@ class GuiDocEditor(QTextEdit): self._beginSearch() return + if not theCursor.hasSelection(): + return + theCursor = self.textCursor() searchFor = self.docSearch.getSearchText() replWith = self.docSearch.getReplaceText() selText = theCursor.selectedText() - if not self.docSearch.isCaseSense: - searchFor = searchFor.lower() - selText = selText.lower() + if self.docSearch.doPreserve: + replWith = transferCase(selText, replWith) - if theCursor.hasSelection() and selText == searchFor: + if not self.docSearch.isCaseSense: + isMatch = searchFor.lower() == selText.lower() + else: + isMatch = searchFor == selText + + if isMatch: theCursor.beginEditBlock() theCursor.removeSelectedText() theCursor.insertText(replWith) From ad21c6e3a4a63d9a0420f3ee6618c069192f6d56 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 19:16:32 +0200 Subject: [PATCH 14/25] Continue search in next file is now working --- nw/gui/build.py | 2 +- nw/gui/doceditor.py | 17 ++++++++++------- nw/gui/projtree.py | 39 ++++++++++++++++++++++++++++++++------- nw/guimain.py | 25 +++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 15 deletions(-) diff --git a/nw/gui/build.py b/nw/gui/build.py index 70ea0703..51a9dd68 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -459,7 +459,7 @@ class GuiBuildNovel(QDialog): makeHtml.setStyles(not noStyling) # Make sure the tree order is correct - self.theParent.treeView.saveTreeOrder() + self.theParent.treeView.flushTreeOrder() self.buildProgress.setMaximum(len(self.theProject.projTree)) self.buildProgress.setValue(0) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 950fef73..382fc123 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -1167,13 +1167,16 @@ class GuiDocEditor(QTextEdit): searchFor = self.docSearch.getSearchText() wasFound = self.find(searchFor, findOpt) - if not wasFound and self.docSearch.doLoop: - theCursor = self.textCursor() - theCursor.movePosition( - QTextCursor.End if isBackward else QTextCursor.Start - ) - self.setTextCursor(theCursor) - self.find(searchFor, findOpt) + if not wasFound: + if self.docSearch.doLoop: + theCursor = self.textCursor() + theCursor.movePosition( + QTextCursor.End if isBackward else QTextCursor.Start + ) + self.setTextCursor(theCursor) + self.find(searchFor, findOpt) + elif self.docSearch.doNextFile: + self.theParent.openNextDocument(self.theHandle) return diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index 02e850ad..236b9f90 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -60,8 +60,9 @@ class GuiProjectTree(QTreeWidget): self.theProject = theParent.theProject # Tree Settings - self.theMap = None - self.orphRoot = None + self.theMap = None + self.orphRoot = None + self.treeChanged = False self.ctxMenu = GuiProjectTreeMenu(self) self.clearTree() @@ -256,7 +257,7 @@ class GuiProjectTree(QTreeWidget): pItem.insertChild(nIndex, cItem) self.clearSelection() cItem.setSelected(True) - self.theProject.setProjectChanged(True) + self._setTreeChanged(True) else: return False return True @@ -276,6 +277,16 @@ class GuiProjectTree(QTreeWidget): self.theProject.setTreeOrder(theList) return True + def flushTreeOrder(self): + """Calls saveTreeOrder if there are unsaved changes, otherwise + does nothing. + """ + if self.treeChanged: + logger.verbose("Flushing project tree to project class") + self.saveTreeOrder() + self._setTreeChanged(False) + return + def getTreeFromHandle(self, tHandle): """Recursively return all the children items starting from a given item handle. @@ -332,6 +343,9 @@ class GuiProjectTree(QTreeWidget): continue self.deleteItem(tHandle, True) + if nTrash > 0: + self._setTreeChanged(True) + return True def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False): @@ -413,7 +427,7 @@ class GuiProjectTree(QTreeWidget): trItemT.addChild(trItemC) nwItemS.setParent(self.theProject.projTree.trashRoot()) - self.theProject.setProjectChanged(True) + self._setTreeChanged(True) self.theParent.theIndex.deleteHandle(tHandle) elif nwItemS.itemType == nwItemType.FOLDER: @@ -436,7 +450,7 @@ class GuiProjectTree(QTreeWidget): if trItemS.childCount() == 0: self.takeTopLevelItem(tIndex) self.theParent.mainMenu.setAvailableRoot() - self.theProject.setProjectChanged(True) + self._setTreeChanged(True) else: self.makeAlert("Cannot delete root folder. It is not empty.", nwAlert.ERROR) return False @@ -732,6 +746,8 @@ class GuiProjectTree(QTreeWidget): elif nwItem.itemType == nwItemType.TRASH: newItem.setIcon(self.C_NAME, self.theTheme.getIcon(nwLabels.CLASS_ICON[tClass])) + self._setTreeChanged(True) + return newItem def _addTrashRoot(self): @@ -747,6 +763,7 @@ class GuiProjectTree(QTreeWidget): self.theProject.projTree[trashHandle] ) trItem.setExpanded(True) + self._setTreeChanged(True) return trItem def _addOrphanedRoot(self): @@ -793,7 +810,7 @@ class GuiProjectTree(QTreeWidget): nwItemS.setParent(pHandle) self.propagateCount(tHandle, wC) self.setTreeItemValues(tHandle) - self.theProject.setProjectChanged(True) + self._setTreeChanged(True) logger.debug("The parent of item %s has been changed to %s" % (tHandle,pHandle)) @@ -815,7 +832,15 @@ class GuiProjectTree(QTreeWidget): pHandle = trItemP.data(self.C_NAME, Qt.UserRole) nwItemS.setParent(pHandle) self.setTreeItemValues(tHandle) - self.theProject.setProjectChanged(True) + self._setTreeChanged(True) + return + + def _setTreeChanged(self, theState): + """Set the tree change flag, and propagate to the project. + """ + self.treeChanged = theState + if theState: + self.theProject.setProjectChanged(True) return # END Class GuiProjectTree diff --git a/nw/guimain.py b/nw/guimain.py index 5bfad545..1116f9aa 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -476,6 +476,31 @@ class GuiMain(QMainWindow): return False return True + def openNextDocument(self, tHandle): + """Opens the next document in the project tree, following the + document with the given handle. Stops when reaching the end. + """ + if self.hasProject: + self.treeView.flushTreeOrder() + nHandle = None + goNext = False + for tItem in self.theProject.projTree: + if tItem is None: + continue + if tItem.itemType != nwItemType.FILE: + continue + if tItem.itemHandle == tHandle: + goNext = True + elif goNext: + nHandle = tItem.itemHandle + break + + if nHandle is not None: + self.openDocument(nHandle, tLine=0) + return True + + return False + def saveDocument(self): """Save the current documents. """ From 32052b1bc315316e91f7f17573c08d0a080476c4 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 19:29:25 +0200 Subject: [PATCH 15/25] Searching through files can now loop back to first file again --- nw/gui/doceditor.py | 8 +++++--- nw/guimain.py | 16 +++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 382fc123..7873991f 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -1168,15 +1168,17 @@ class GuiDocEditor(QTextEdit): searchFor = self.docSearch.getSearchText() wasFound = self.find(searchFor, findOpt) if not wasFound: - if self.docSearch.doLoop: + if self.docSearch.doNextFile and not isBackward: + self.theParent.openNextDocument( + self.theHandle, wrapAround=self.docSearch.doLoop + ) + elif self.docSearch.doLoop: theCursor = self.textCursor() theCursor.movePosition( QTextCursor.End if isBackward else QTextCursor.Start ) self.setTextCursor(theCursor) self.find(searchFor, findOpt) - elif self.docSearch.doNextFile: - self.theParent.openNextDocument(self.theHandle) return diff --git a/nw/guimain.py b/nw/guimain.py index 1116f9aa..70a6ba0b 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -476,28 +476,34 @@ class GuiMain(QMainWindow): return False return True - def openNextDocument(self, tHandle): + def openNextDocument(self, tHandle, wrapAround=False): """Opens the next document in the project tree, following the document with the given handle. Stops when reaching the end. """ if self.hasProject: self.treeView.flushTreeOrder() - nHandle = None - goNext = False + nHandle = None # The next handle after tHandle + fHandle = None # The first file handle we encounter + foundIt = False # We've found tHandle, pick the next we see for tItem in self.theProject.projTree: if tItem is None: continue if tItem.itemType != nwItemType.FILE: continue + if fHandle is None: + fHandle = tItem.itemHandle if tItem.itemHandle == tHandle: - goNext = True - elif goNext: + foundIt = True + elif foundIt: nHandle = tItem.itemHandle break if nHandle is not None: self.openDocument(nHandle, tLine=0) return True + elif wrapAround: + self.openDocument(fHandle, tLine=0) + return False return False From 6712a7dfcd57a2ad0fce5638a4db121d871c215a Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 19:47:25 +0200 Subject: [PATCH 16/25] The state of the search buttons is now saved between sessions --- nw/config.py | 37 ++++++++++++++++++++++++++++++++++--- nw/gui/doceditor.py | 28 +++++++++++++++++++++------- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/nw/config.py b/nw/config.py index 4f690be3..09b3d363 100644 --- a/nw/config.py +++ b/nw/config.py @@ -133,6 +133,13 @@ class Config: self.spellTool = None self.spellLanguage = None + self.searchCase = False + self.searchWord = False + self.searchRegEx = False + self.searchLoop = False + self.searchNextFile = False + self.searchMatchCap = False + ## Backup self.backupPath = "" self.backupOnClose = False @@ -482,6 +489,24 @@ class Config: self.viewSynopsis = self._parseLine( cnfParse, cnfSec, "viewsynopsis", self.CNF_BOOL, self.viewSynopsis ) + self.searchCase = self._parseLine( + cnfParse, cnfSec, "searchcase", self.CNF_BOOL, self.searchCase + ) + self.searchWord = self._parseLine( + cnfParse, cnfSec, "searchword", self.CNF_BOOL, self.searchWord + ) + self.searchRegEx = self._parseLine( + cnfParse, cnfSec, "searchregex", self.CNF_BOOL, self.searchRegEx + ) + self.searchLoop = self._parseLine( + cnfParse, cnfSec, "searchloop", self.CNF_BOOL, self.searchLoop + ) + self.searchNextFile = self._parseLine( + cnfParse, cnfSec, "searchnextfile", self.CNF_BOOL, self.searchNextFile + ) + self.searchMatchCap = self._parseLine( + cnfParse, cnfSec, "searchmatchcap", self.CNF_BOOL, self.searchMatchCap + ) ## Path cnfSec = "Path" @@ -571,9 +596,15 @@ class Config: ## State cnfSec = "State" cnfParse.add_section(cnfSec) - cnfParse.set(cnfSec,"showrefpanel",str(self.showRefPanel)) - cnfParse.set(cnfSec,"viewcomments",str(self.viewComments)) - cnfParse.set(cnfSec,"viewsynopsis",str(self.viewSynopsis)) + cnfParse.set(cnfSec,"showrefpanel", str(self.showRefPanel)) + cnfParse.set(cnfSec,"viewcomments", str(self.viewComments)) + cnfParse.set(cnfSec,"viewsynopsis", str(self.viewSynopsis)) + cnfParse.set(cnfSec,"searchcase", str(self.searchCase)) + cnfParse.set(cnfSec,"searchword", str(self.searchWord)) + cnfParse.set(cnfSec,"searchregex", str(self.searchRegEx)) + cnfParse.set(cnfSec,"searchloop", str(self.searchLoop)) + cnfParse.set(cnfSec,"searchnextfile", str(self.searchNextFile)) + cnfParse.set(cnfSec,"searchmatchcap", str(self.searchMatchCap)) ## Path cnfSec = "Path" diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 7873991f..4c465109 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -1286,12 +1286,12 @@ class GuiDocEditSearch(QFrame): self.theTheme = docEditor.theTheme self.repVisible = False - self.isCaseSense = False - self.isWholeWord = False - self.isRegEx = False - self.doLoop = False - self.doNextFile = False - self.doPreserve = False + self.isCaseSense = self.mainConf.searchCase + self.isWholeWord = self.mainConf.searchWord + self.isRegEx = self.mainConf.searchRegEx + self.doLoop = self.mainConf.searchLoop + self.doNextFile = self.mainConf.searchNextFile + self.doPreserve = self.mainConf.searchMatchCap mPx = self.mainConf.pxInt(6) fPx = int(0.9*self.theTheme.fontPixelSize) @@ -1332,6 +1332,7 @@ class GuiDocEditSearch(QFrame): self.toggleCase.setToolTip("Match case") self.toggleCase.setIcon(self.theTheme.getIcon("search_case")) self.toggleCase.setCheckable(True) + self.toggleCase.setChecked(self.isCaseSense) self.toggleCase.toggled.connect(self._doToggleCase) self.searchOpt.addAction(self.toggleCase) @@ -1339,6 +1340,7 @@ class GuiDocEditSearch(QFrame): self.toggleWord.setToolTip("Match whole words") self.toggleWord.setIcon(self.theTheme.getIcon("search_word")) self.toggleWord.setCheckable(True) + self.toggleWord.setChecked(self.isWholeWord) self.toggleWord.toggled.connect(self._doToggleWord) self.searchOpt.addAction(self.toggleWord) @@ -1346,6 +1348,7 @@ class GuiDocEditSearch(QFrame): self.toggleRegEx.setToolTip("Use regular expressions") self.toggleRegEx.setIcon(self.theTheme.getIcon("search_regex")) self.toggleRegEx.setCheckable(True) + self.toggleRegEx.setChecked(self.isRegEx) self.toggleRegEx.toggled.connect(self._doToggleRegEx) self.searchOpt.addAction(self.toggleRegEx) @@ -1353,6 +1356,7 @@ class GuiDocEditSearch(QFrame): self.toggleLoop.setToolTip("Loop the search when reaching the end") self.toggleLoop.setIcon(self.theTheme.getIcon("search_loop")) self.toggleLoop.setCheckable(True) + self.toggleLoop.setChecked(self.doLoop) self.toggleLoop.toggled.connect(self._doToggleLoop) self.searchOpt.addAction(self.toggleLoop) @@ -1360,6 +1364,7 @@ class GuiDocEditSearch(QFrame): self.toggleProject.setToolTip("Continue searching in the next file") self.toggleProject.setIcon(self.theTheme.getIcon("search_project")) self.toggleProject.setCheckable(True) + self.toggleProject.setChecked(self.doNextFile) self.toggleProject.toggled.connect(self._doToggleProject) self.searchOpt.addAction(self.toggleProject) @@ -1369,6 +1374,7 @@ class GuiDocEditSearch(QFrame): self.togglePreserve.setToolTip("Preserve case on replace") self.togglePreserve.setIcon(self.theTheme.getIcon("search_preserve")) self.togglePreserve.setCheckable(True) + self.togglePreserve.setChecked(self.doPreserve) self.togglePreserve.toggled.connect(self._doTogglePreserve) self.searchOpt.addAction(self.togglePreserve) @@ -1428,7 +1434,7 @@ class GuiDocEditSearch(QFrame): baseCol = qPalette.base().color() rCol = baseCol.redF() + 0.1 gCol = baseCol.greenF() - 0.1 - bCol = baseCol.blueF() - 0.1 + bCol = baseCol.blueF() - 0.1 mCol = max(rCol, gCol, bCol, 1.0) errCol = QColor() @@ -1448,10 +1454,18 @@ class GuiDocEditSearch(QFrame): def closeSearch(self): """Close the search box. """ + self.mainConf.searchCase = self.isCaseSense + self.mainConf.searchWord = self.isWholeWord + self.mainConf.searchRegEx = self.isRegEx + self.mainConf.searchLoop = self.doLoop + self.mainConf.searchNextFile = self.doNextFile + self.mainConf.searchMatchCap = self.doPreserve + self.showReplace.setChecked(False) self.setVisible(False) self.docEditor.updateDocMargins() self.docEditor.setFocus() + return ## From 79d6e3594c17cf30456f0b8378854e3afc51c79f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 19:47:44 +0200 Subject: [PATCH 17/25] Fixed tests --- tests/reference/novelwriter.conf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/reference/novelwriter.conf b/tests/reference/novelwriter.conf index ba0f2db7..12e6ec11 100644 --- a/tests/reference/novelwriter.conf +++ b/tests/reference/novelwriter.conf @@ -1,5 +1,5 @@ [Main] -timestamp = 2020-06-13 22:59:36 +timestamp = 2020-06-17 19:45:53 theme = default syntax = default_light icons = typicons_colour_light @@ -55,6 +55,12 @@ askbeforebackup = True showrefpanel = True viewcomments = True viewsynopsis = True +searchcase = False +searchword = False +searchregex = False +searchloop = False +searchnextfile = False +searchmatchcap = False [Path] lastpath = From 22341c5e216ef689aa282aba0be0094eb5f1ae55 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 19:56:19 +0200 Subject: [PATCH 18/25] Fixed test and cleaned up imports --- nw/gui/doceditor.py | 7 ++++--- tests/test_gui.py | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 4c465109..cc761202 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -37,11 +37,12 @@ from time import time from PyQt5.QtCore import Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp from PyQt5.QtGui import ( QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, - QTextDocument, QCursor, QIcon + QTextDocument, QCursor ) from PyQt5.QtWidgets import ( - qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, QToolBar, - QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton, QFrame, QVBoxLayout, QSizePolicy + qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, + QToolBar, QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton, + QFrame ) from nw.core import NWDoc diff --git a/tests/test_gui.py b/tests/test_gui.py index cd375cd8..c4c03ea6 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -219,7 +219,6 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): qtbot.wait(stepDelay) # Save the document - assert nwGUI.docEditor.docChanged assert nwGUI.saveDocument() assert not nwGUI.docEditor.docChanged qtbot.wait(stepDelay) From f2c1ce1ccad7330e305f7bbb746113b7b0b373d3 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 20:05:30 +0200 Subject: [PATCH 19/25] Second attempt at fixing test where the word counter suddenly isn't working --- tests/test_gui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_gui.py b/tests/test_gui.py index c4c03ea6..eeb6d490 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -215,10 +215,11 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) qtbot.wait(stepDelay) - nwGUI.docEditor.wCounter.run() - qtbot.wait(stepDelay) + nwGUI.docEditor.wCounter.start() + qtbot.wait(1000) # Save the document + assert nwGUI.docEditor.docChanged assert nwGUI.saveDocument() assert not nwGUI.docEditor.docChanged qtbot.wait(stepDelay) From deec57c9c698fa813526e2b30603b844650ea8c3 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 20:23:20 +0200 Subject: [PATCH 20/25] Another attempt at forcing the test word counter --- .travis.yml | 2 +- tests/test_gui.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 89ca3727..873ded23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - pip install --upgrade pip - pip install -r requirements.txt # - pip install pytest-faulthandler - - pip install PyVirtualDisplay==0.2.5 + - pip install PyVirtualDisplay - pip install pytest-xvfb - pip install pytest-cov - pip install pytest-qt diff --git a/tests/test_gui.py b/tests/test_gui.py index eeb6d490..b83b67fd 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -215,8 +215,9 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) qtbot.wait(stepDelay) - nwGUI.docEditor.wCounter.start() - qtbot.wait(1000) + nwGUI.docEditor.wCounter.run() + nwGUI.docEditor._updateCounts() + qtbot.wait(stepDelay) # Save the document assert nwGUI.docEditor.docChanged From 7994491c5dcea1af004d437d0824c5bcc2435925 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 20:45:34 +0200 Subject: [PATCH 21/25] Force tests to run with qt<5.15 --- .travis.yml | 5 ++--- tests/test_gui.py | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 873ded23..7bcec702 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,14 @@ addons: apt: packages: - libenchant-dev - - python3-pyqt5 - - python3-pyqt5.qtsvg python: - "3.6" - "3.7" - "3.8" install: - pip install --upgrade pip - - pip install -r requirements.txt + - pip install lxml + - pip install pyqt5<5.15 # - pip install pytest-faulthandler - pip install PyVirtualDisplay - pip install pytest-xvfb diff --git a/tests/test_gui.py b/tests/test_gui.py index b83b67fd..cd375cd8 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -216,7 +216,6 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): qtbot.wait(stepDelay) nwGUI.docEditor.wCounter.run() - nwGUI.docEditor._updateCounts() qtbot.wait(stepDelay) # Save the document From b2747baf6d6ac65aa2c5d283f964ec7ca1b363e4 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 20:50:22 +0200 Subject: [PATCH 22/25] Pin exact version then ... --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7bcec702..5200e76f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ python: install: - pip install --upgrade pip - pip install lxml - - pip install pyqt5<5.15 + - pip install pyqt5==5.14.2 # - pip install pytest-faulthandler - pip install PyVirtualDisplay - pip install pytest-xvfb From 5832d3db711a2df784d15e09c2fc702d456bb26c Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 21:28:22 +0200 Subject: [PATCH 23/25] This seemed to fix the test on the local machine --- .travis.yml | 5 +++-- nw/gui/doceditor.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5200e76f..873ded23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,14 +8,15 @@ addons: apt: packages: - libenchant-dev + - python3-pyqt5 + - python3-pyqt5.qtsvg python: - "3.6" - "3.7" - "3.8" install: - pip install --upgrade pip - - pip install lxml - - pip install pyqt5==5.14.2 + - pip install -r requirements.txt # - pip install pytest-faulthandler - pip install PyVirtualDisplay - pip install pytest-xvfb diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index cc761202..cc72cf9a 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -593,7 +593,7 @@ class GuiDocEditor(QTextEdit): as it is triggered on every keypress when typing. """ self.hasSelection = self.textCursor().hasSelection() - if not self.hasFocus(): + if self.docSearch.searchBox.hasFocus(): # Block the event when the focus is on the search bar. return From c264e6100deabb8dae4064a8924662399f605d09 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 21:46:23 +0200 Subject: [PATCH 24/25] Replaced one of the icons on the search bar --- .../icons/fallback/search_project-dark.svg | 43 +++++-------------- nw/assets/icons/fallback/search_project.svg | 37 +++------------- 2 files changed, 17 insertions(+), 63 deletions(-) diff --git a/nw/assets/icons/fallback/search_project-dark.svg b/nw/assets/icons/fallback/search_project-dark.svg index 552fed1f..1e57b60e 100644 --- a/nw/assets/icons/fallback/search_project-dark.svg +++ b/nw/assets/icons/fallback/search_project-dark.svg @@ -5,13 +5,13 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - version="1.2" - width="24" - height="24" + id="svg8301" viewBox="0 0 24 24" - id="svg5828"> + height="24" + width="24" + version="1.2"> + id="metadata8307"> @@ -23,32 +23,9 @@ - - - - - - + id="defs8305" /> + diff --git a/nw/assets/icons/fallback/search_project.svg b/nw/assets/icons/fallback/search_project.svg index ee16c528..12afc290 100644 --- a/nw/assets/icons/fallback/search_project.svg +++ b/nw/assets/icons/fallback/search_project.svg @@ -5,13 +5,13 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - id="svg5828" + id="svg8301" viewBox="0 0 24 24" height="24" width="24" version="1.2"> + id="metadata8307"> @@ -23,32 +23,9 @@ - - - - - - + id="defs8305" /> + From c2b55530be60ce3ff175d6ed51b5c98b73cb4779 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 17 Jun 2020 21:46:59 +0200 Subject: [PATCH 25/25] Renamed a variable, and fixed the document margins of search box change of size --- nw/gui/doceditor.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index cc72cf9a..5e0fb025 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -1200,7 +1200,7 @@ class GuiDocEditor(QTextEdit): replWith = self.docSearch.getReplaceText() selText = theCursor.selectedText() - if self.docSearch.doPreserve: + if self.docSearch.doMatchCap: replWith = transferCase(selText, replWith) if not self.docSearch.isCaseSense: @@ -1292,7 +1292,7 @@ class GuiDocEditSearch(QFrame): self.isRegEx = self.mainConf.searchRegEx self.doLoop = self.mainConf.searchLoop self.doNextFile = self.mainConf.searchNextFile - self.doPreserve = self.mainConf.searchMatchCap + self.doMatchCap = self.mainConf.searchMatchCap mPx = self.mainConf.pxInt(6) fPx = int(0.9*self.theTheme.fontPixelSize) @@ -1371,13 +1371,13 @@ class GuiDocEditSearch(QFrame): self.searchOpt.addSeparator() - self.togglePreserve = QAction("Preserve Case", self) - self.togglePreserve.setToolTip("Preserve case on replace") - self.togglePreserve.setIcon(self.theTheme.getIcon("search_preserve")) - self.togglePreserve.setCheckable(True) - self.togglePreserve.setChecked(self.doPreserve) - self.togglePreserve.toggled.connect(self._doTogglePreserve) - self.searchOpt.addAction(self.togglePreserve) + self.toggleMatchCap = QAction("Preserve Case", self) + self.toggleMatchCap.setToolTip("Preserve case on replace") + self.toggleMatchCap.setIcon(self.theTheme.getIcon("search_preserve")) + self.toggleMatchCap.setCheckable(True) + self.toggleMatchCap.setChecked(self.doMatchCap) + self.toggleMatchCap.toggled.connect(self._doToggleMatchCap) + self.searchOpt.addAction(self.toggleMatchCap) self.searchOpt.addSeparator() @@ -1426,10 +1426,10 @@ class GuiDocEditSearch(QFrame): boxWidth = self.mainConf.pxInt(200) self.searchBox.setFixedWidth(boxWidth) self.replaceBox.setFixedWidth(boxWidth) + self.replaceBox.setVisible(False) + self.replaceButton.setVisible(False) self.adjustSize() - self._doToggleReplace(False) - # Construct Box Colours qPalette = self.searchBox.palette() baseCol = qPalette.base().color() @@ -1460,7 +1460,7 @@ class GuiDocEditSearch(QFrame): self.mainConf.searchRegEx = self.isRegEx self.mainConf.searchLoop = self.doLoop self.mainConf.searchNextFile = self.doNextFile - self.mainConf.searchMatchCap = self.doPreserve + self.mainConf.searchMatchCap = self.doMatchCap self.showReplace.setChecked(False) self.setVisible(False) @@ -1552,6 +1552,7 @@ class GuiDocEditSearch(QFrame): self.replaceButton.setVisible(theState) self.repVisible = theState self.adjustSize() + self.docEditor.updateDocMargins() return def _doToggleCase(self, theState): @@ -1584,10 +1585,10 @@ class GuiDocEditSearch(QFrame): self.doNextFile = theState return - def _doTogglePreserve(self, theState): - """Enable/disable preserving case when replacing. + def _doToggleMatchCap(self, theState): + """Enable/disable preserving capitalisation when replacing. """ - self.doPreserve = theState + self.doMatchCap = theState return ##