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()