diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 98f1fb9d..4a9c198d 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -33,9 +33,9 @@ from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QMenuBar, QAction from novelwriter import CONFIG, SHARED -from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget from novelwriter.common import openExternalPath from novelwriter.constants import nwConst, trConst, nwKeyWords, nwLabels, nwUnicode +from novelwriter.enum import nwDocAction, nwDocInsert, nwView, nwWidget from novelwriter.extensions.eventfilters import StatusTipFilter if TYPE_CHECKING: # pragma: no cover @@ -55,6 +55,7 @@ class GuiMainMenu(QMenuBar): requestDocInsertText = pyqtSignal(str) requestDocKeyWordInsert = pyqtSignal(str) requestFocusChange = pyqtSignal(nwWidget) + requestViewChange = pyqtSignal(nwView) def __init__(self, mainGui: GuiMain) -> None: super().__init__(parent=mainGui) @@ -861,6 +862,14 @@ class GuiMainMenu(QMenuBar): self.aReplaceNext.setShortcut("Ctrl+Shift+1") self.aReplaceNext.triggered.connect(lambda: self.mainGui.docEditor.replaceNext()) + # Search > Separator + self.srcMenu.addSeparator() + + # Search > Find in Project + self.aFindProj = self.srcMenu.addAction(self.tr("Find in Project")) + self.aFindProj.setShortcut("Ctrl+Shift+F") + self.aFindProj.triggered.connect(lambda: self.requestViewChange.emit(nwView.SEARCH)) + return def _buildToolsMenu(self) -> None: diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 35247799..8bb7cc0c 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -798,14 +798,20 @@ class GuiOutlineDetails(QScrollArea): hSpace = int(CONFIG.pxInt(10)) vSpace = int(CONFIG.pxInt(4)) + bFont = SHARED.theme.guiFontB + # Details Area - self.titleLabel = QLabel("%s" % self.tr("Title")) - self.fileLabel = QLabel("%s" % self.tr("Document")) - self.itemLabel = QLabel("%s" % self.tr("Status")) + self.titleLabel = QLabel(self.tr("Title")) + self.fileLabel = QLabel(self.tr("Document")) + self.itemLabel = QLabel(self.tr("Status")) self.titleValue = QLabel("") self.fileValue = QLabel("") self.itemValue = QLabel("") + self.titleLabel.setFont(bFont) + self.fileLabel.setFont(bFont) + self.itemLabel.setFont(bFont) + self.titleValue.setMinimumWidth(minTitle) self.titleValue.setMaximumWidth(maxTitle) self.fileValue.setMinimumWidth(minTitle) @@ -814,13 +820,17 @@ class GuiOutlineDetails(QScrollArea): self.itemValue.setMaximumWidth(maxTitle) # Stats Area - self.cCLabel = QLabel("%s" % self.tr("Characters")) - self.wCLabel = QLabel("%s" % self.tr("Words")) - self.pCLabel = QLabel("%s" % self.tr("Paragraphs")) + self.cCLabel = QLabel(self.tr("Characters")) + self.wCLabel = QLabel(self.tr("Words")) + self.pCLabel = QLabel(self.tr("Paragraphs")) self.cCValue = QLabel("") self.wCValue = QLabel("") self.pCValue = QLabel("") + self.cCLabel.setFont(bFont) + self.wCLabel.setFont(bFont) + self.pCLabel.setFont(bFont) + self.cCValue.setMinimumWidth(wCount) self.wCValue.setMinimumWidth(wCount) self.pCValue.setMinimumWidth(wCount) @@ -829,23 +839,36 @@ class GuiOutlineDetails(QScrollArea): self.pCValue.setAlignment(Qt.AlignRight) # Synopsis - self.synopLabel = QLabel("%s" % self.tr("Synopsis")) + self.synopLabel = QLabel(self.tr("Synopsis")) + self.synopLabel.setFont(bFont) + self.synopValue = QLabel("") - self.synopLWrap = QHBoxLayout() self.synopValue.setWordWrap(True) self.synopValue.setAlignment(Qt.AlignTop | Qt.AlignLeft) + + self.synopLWrap = QHBoxLayout() self.synopLWrap.addWidget(self.synopValue, 1) # Tags - self.povKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])) - self.focKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])) - self.chrKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY])) - self.pltKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])) - self.timKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.TIME_KEY])) - self.wldKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY])) - self.objKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY])) - self.entKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY])) - self.cstKeyLabel = QLabel("%s" % trConst(nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY])) + self.povKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])) + self.focKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])) + self.chrKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY])) + self.pltKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])) + self.timKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.TIME_KEY])) + self.wldKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY])) + self.objKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY])) + self.entKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY])) + self.cstKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY])) + + self.povKeyLabel.setFont(bFont) + self.focKeyLabel.setFont(bFont) + self.chrKeyLabel.setFont(bFont) + self.pltKeyLabel.setFont(bFont) + self.timKeyLabel.setFont(bFont) + self.wldKeyLabel.setFont(bFont) + self.objKeyLabel.setFont(bFont) + self.entKeyLabel.setFont(bFont) + self.cstKeyLabel.setFont(bFont) self.povKeyLWrap = QHBoxLayout() self.focKeyLWrap = QHBoxLayout() @@ -989,7 +1012,7 @@ class GuiOutlineDetails(QScrollArea): def clearDetails(self) -> None: """Clear all the data labels.""" - self.titleLabel.setText("%s" % self.tr("Title")) + self.titleLabel.setText(self.tr("Title")) self.titleValue.setText("") self.fileValue.setText("") self.itemValue.setText("") @@ -1023,7 +1046,7 @@ class GuiOutlineDetails(QScrollArea): novIdx = pIndex.getItemHeading(tHandle, sTitle) novRefs = pIndex.getReferences(tHandle, sTitle) if nwItem and novIdx: - self.titleLabel.setText("%s" % self.tr(self.LVL_MAP.get(novIdx.level, "H1"))) + self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1"))) self.titleValue.setText(novIdx.title) itemStatus, _ = nwItem.getImportStatus(incIcon=False) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 7299aff0..d78b84d5 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -270,7 +270,8 @@ class GuiProjectToolBar(QWidget): self.setAutoFillBackground(True) # Widget Label - self.viewLabel = QLabel("%s" % self.tr("Project Content")) + self.viewLabel = QLabel(self.tr("Project Content")) + self.viewLabel.setFont(SHARED.theme.guiFontB) self.viewLabel.setContentsMargins(0, 0, 0, 0) self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) diff --git a/novelwriter/gui/search.py b/novelwriter/gui/search.py new file mode 100644 index 00000000..70b84de8 --- /dev/null +++ b/novelwriter/gui/search.py @@ -0,0 +1,99 @@ +""" +novelWriter – GUI Project Search +================================ + +File History: +Created: 2024-03-21 [2.4b1] GuiProjectSearch + +This file is a part of novelWriter +Copyright 2018–2024, 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 . +""" +from __future__ import annotations + +import logging + +from PyQt5.QtGui import QPalette +from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QTreeWidget, QVBoxLayout, QWidget + +from novelwriter import CONFIG, SHARED +from novelwriter.extensions.modified import NIconToolButton +from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON + +logger = logging.getLogger(__name__) + + +class GuiProjectSearch(QWidget): + + def __init__(self, parent: QWidget) -> None: + super().__init__(parent=parent) + + logger.debug("Create: GuiProjectSearch") + + iPx = SHARED.theme.baseIconSize + mPx = CONFIG.pxInt(2) + + # Header + self.viewLabel = QLabel(self.tr("Project Search")) + self.viewLabel.setFont(SHARED.theme.guiFontB) + self.viewLabel.setContentsMargins(mPx, mPx, 0, mPx) + + # Controls + self.searchBox = QLineEdit(self) + self.searchBox.setPlaceholderText(self.tr("Search text ...")) + + self.searchButton = NIconToolButton(self, iPx) + + self.searchBar = QHBoxLayout() + self.searchBar.addWidget(self.searchBox) + self.searchBar.addWidget(self.searchButton) + + # Search Result + self.searchResult = QTreeWidget(self) + self.searchResult.setHeaderHidden(True) + + # Assemble + self.outerBox = QVBoxLayout() + self.outerBox.addWidget(self.viewLabel, 0) + self.outerBox.addLayout(self.searchBar, 0) + self.outerBox.addWidget(self.searchResult, 1) + self.outerBox.setContentsMargins(0, 0, 0, 0) + self.outerBox.setSpacing(0) + + self.setLayout(self.outerBox) + self.updateTheme() + + logger.debug("Ready: GuiProjectSearch") + + return + + ## + # Methods + ## + + def updateTheme(self) -> None: + """Update theme elements.""" + qPalette = self.palette() + qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base()) + self.setPalette(qPalette) + + buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) + self.searchButton.setStyleSheet(buttonStyle) + + self.searchButton.setIcon(SHARED.theme.getIcon("search")) + + return + +# END Class GuiProjectSearch diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index 00111af8..dfc936c5 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -45,7 +45,7 @@ logger = logging.getLogger(__name__) class GuiSideBar(QWidget): - viewChangeRequested = pyqtSignal(nwView) + requestViewChange = pyqtSignal(nwView) def __init__(self, mainGui: GuiMain) -> None: super().__init__(parent=mainGui) @@ -61,19 +61,19 @@ class GuiSideBar(QWidget): # Buttons self.tbProject = NIconToolButton(self, iPx) self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View"))) - self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT)) + self.tbProject.clicked.connect(lambda: self.requestViewChange.emit(nwView.PROJECT)) self.tbNovel = NIconToolButton(self, iPx) self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View"))) - self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL)) + self.tbNovel.clicked.connect(lambda: self.requestViewChange.emit(nwView.NOVEL)) self.tbSearch = NIconToolButton(self, iPx) self.tbSearch.setToolTip("{0} [Ctrl+Shift+F]".format(self.tr("Search Project"))) - self.tbSearch.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.SEARCH)) + self.tbSearch.clicked.connect(lambda: self.requestViewChange.emit(nwView.SEARCH)) self.tbOutline = NIconToolButton(self, iPx) self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View"))) - self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE)) + self.tbOutline.clicked.connect(lambda: self.requestViewChange.emit(nwView.OUTLINE)) self.tbBuild = NIconToolButton(self, iPx) self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript"))) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 0342b57f..f40642c4 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -152,6 +152,8 @@ class GuiTheme: # Fonts self.guiFont = qApp.font() + self.guiFontB = qApp.font() + self.guiFontB.setBold(True) qMetric = QFontMetrics(self.guiFont) self.fontPointSize = self.guiFont.pointSizeF() diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 21db48b6..5983b2dc 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -38,33 +38,31 @@ from PyQt5.QtWidgets import ( ) from novelwriter import CONFIG, SHARED, __hexversion__, __version__ +from novelwriter.common import formatFileFilter, formatVersion, hexToInt from novelwriter.constants import nwConst -from novelwriter.gui.theme import GuiTheme -from novelwriter.gui.sidebar import GuiSideBar -from novelwriter.gui.outline import GuiOutlineView -from novelwriter.gui.mainmenu import GuiMainMenu -from novelwriter.gui.projtree import GuiProjectView -from novelwriter.gui.doceditor import GuiDocEditor -from novelwriter.gui.docviewer import GuiDocViewer -from novelwriter.gui.noveltree import GuiNovelView -from novelwriter.gui.statusbar import GuiMainStatus -from novelwriter.gui.itemdetails import GuiItemDetails -from novelwriter.gui.docviewerpanel import GuiDocViewerPanel from novelwriter.dialogs.about import GuiAbout -from novelwriter.dialogs.wordlist import GuiWordList from novelwriter.dialogs.preferences import GuiPreferences from novelwriter.dialogs.projectsettings import GuiProjectSettings -from novelwriter.tools.welcome import GuiWelcome -from novelwriter.tools.manuscript import GuiManuscript +from novelwriter.dialogs.wordlist import GuiWordList +from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView +from novelwriter.gui.doceditor import GuiDocEditor +from novelwriter.gui.docviewer import GuiDocViewer +from novelwriter.gui.docviewerpanel import GuiDocViewerPanel +from novelwriter.gui.itemdetails import GuiItemDetails +from novelwriter.gui.mainmenu import GuiMainMenu +from novelwriter.gui.noveltree import GuiNovelView +from novelwriter.gui.outline import GuiOutlineView +from novelwriter.gui.projtree import GuiProjectView +from novelwriter.gui.search import GuiProjectSearch +from novelwriter.gui.sidebar import GuiSideBar +from novelwriter.gui.statusbar import GuiMainStatus +from novelwriter.gui.theme import GuiTheme from novelwriter.tools.dictionaries import GuiDictionaries +from novelwriter.tools.manuscript import GuiManuscript from novelwriter.tools.noveldetails import GuiNovelDetails +from novelwriter.tools.welcome import GuiWelcome from novelwriter.tools.writingstats import GuiWritingStats -from novelwriter.enum import ( - nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView -) -from novelwriter.common import formatFileFilter, formatVersion, hexToInt - logger = logging.getLogger(__name__) @@ -123,6 +121,7 @@ class GuiMain(QMainWindow): # Main GUI Elements self.mainStatus = GuiMainStatus(self) self.projView = GuiProjectView(self) + self.projSearch = GuiProjectSearch(self) self.novelView = GuiNovelView(self) self.docEditor = GuiDocEditor(self) self.docViewer = GuiDocViewer(self) @@ -136,6 +135,7 @@ class GuiMain(QMainWindow): self.projStack = QStackedWidget(self) self.projStack.addWidget(self.projView) self.projStack.addWidget(self.novelView) + self.projStack.addWidget(self.projSearch) self.projStack.currentChanged.connect(self._projStackChanged) # Project Tree View @@ -190,6 +190,7 @@ class GuiMain(QMainWindow): self.idxOutlineView = self.mainStack.indexOf(self.outlineView) self.idxProjView = self.projStack.indexOf(self.projView) self.idxNovelView = self.projStack.indexOf(self.novelView) + self.idxProjSearch = self.projStack.indexOf(self.projSearch) # Splitter Behaviour self.splitMain.setCollapsible(self.idxTree, False) @@ -243,8 +244,9 @@ class GuiMain(QMainWindow): self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert) self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord) self.mainMenu.requestFocusChange.connect(self.switchFocus) + self.mainMenu.requestViewChange.connect(self._changeView) - self.sideBar.viewChangeRequested.connect(self._changeView) + self.sideBar.requestViewChange.connect(self._changeView) self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox) self.projView.openDocumentRequest.connect(self._openDocument) @@ -1077,6 +1079,7 @@ class GuiMain(QMainWindow): self.sideBar.updateTheme() self.projView.updateTheme() self.novelView.updateTheme() + self.projSearch.updateTheme() self.outlineView.updateTheme() self.itemDetails.updateTheme() self.mainStatus.updateTheme() @@ -1168,6 +1171,9 @@ class GuiMain(QMainWindow): elif view == nwView.NOVEL: self.mainStack.setCurrentWidget(self.splitMain) self.projStack.setCurrentWidget(self.novelView) + elif view == nwView.SEARCH: + self.mainStack.setCurrentWidget(self.splitMain) + self.projStack.setCurrentWidget(self.projSearch) elif view == nwView.OUTLINE: self.mainStack.setCurrentWidget(self.outlineView) return