# -*- 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 Created: 2020-04-25 [0.4.5] GuiDocTitleBar 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 class GuiDocTitleBar(QWidget): def __init__(self, theParent, isEditor): QWidget.__init__(self, theParent) logger.debug("Initialising GuiDocTitleBar ...") self.mainConf = nw.CONFIG self.theParent = theParent self.theProject = theParent.theProject self.theTheme = theParent.theTheme self.theHandle = None self.isEditor = isEditor # Make a QPalette that matches the Syntax Theme self.thePalette = QPalette() self.thePalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack)) self.thePalette.setColor(QPalette.Text, QColor(*self.theTheme.colText)) fPx = int(0.9*self.theTheme.fontPixelSize) hSp = self.mainConf.pxInt(6) self.buttonSize = fPx + hSp # Main Widget Settings self.setContentsMargins(2*self.buttonSize, 0, 0, 0) self.setAutoFillBackground(True) self.setPalette(self.thePalette) # Title Label self.theTitle = QLabel() self.theTitle.setText("") self.theTitle.setIndent(0) self.theTitle.setMargin(0) self.theTitle.setContentsMargins(0, 0, 0, 0) self.theTitle.setAutoFillBackground(True) self.theTitle.setAlignment(Qt.AlignHCenter | Qt.AlignTop) self.theTitle.setFixedHeight(fPx) self.theTitle.setFrameShape(QFrame.NoFrame) self.theTitle.setLineWidth(0) self.theTitle.setPalette(self.thePalette) lblFont = self.theTitle.font() lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize) self.theTitle.setFont(lblFont) buttonStyle = ( "QToolButton {{border: none; background: transparent;}} " "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" ).format(*self.theTheme.colText) # Buttons self.closeButton = QToolButton(self) self.closeButton.setIcon(self.theTheme.getIcon("close")) self.closeButton.setContentsMargins(0, 0, 0, 0) self.closeButton.setIconSize(QSize(fPx, fPx)) self.closeButton.setFixedSize(fPx, fPx) self.closeButton.setStyleSheet(buttonStyle) self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.closeButton.setVisible(False) self.closeButton.clicked.connect(self._closeDocument) if self.isEditor: self.minmaxButton = QToolButton(self) self.minmaxButton.setIcon(self.theTheme.getIcon("maximise")) self.minmaxButton.setContentsMargins(0, 0, 0, 0) self.minmaxButton.setIconSize(QSize(fPx, fPx)) self.minmaxButton.setFixedSize(fPx, fPx) self.minmaxButton.setStyleSheet(buttonStyle) self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.minmaxButton.setVisible(False) self.minmaxButton.clicked.connect(self._minmaxDocument) else: self.refreshButton = QToolButton(self) self.refreshButton.setIcon(self.theTheme.getIcon("refresh")) self.refreshButton.setContentsMargins(0, 0, 0, 0) self.refreshButton.setIconSize(QSize(fPx, fPx)) self.refreshButton.setFixedSize(fPx, fPx) self.refreshButton.setStyleSheet(buttonStyle) self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.refreshButton.setVisible(False) self.refreshButton.clicked.connect(self._refreshDocument) # Assemble Layout self.outerBox = QHBoxLayout() self.outerBox.setSpacing(hSp) self.outerBox.addWidget(self.theTitle, 1) if self.isEditor: self.outerBox.addWidget(self.minmaxButton, 0) else: self.outerBox.addWidget(self.refreshButton, 0) self.outerBox.addWidget(self.closeButton, 0) self.setLayout(self.outerBox) logger.debug("GuiDocTitleBar initialisation complete") return ## # Setters ## def setTitleFromHandle(self, tHandle): """Sets the document title from the handle, or alternatively, set the whole document path. """ self.theTitle.setText("") self.theHandle = tHandle if tHandle is None: self.closeButton.setVisible(False) if self.isEditor: self.minmaxButton.setVisible(False) else: self.refreshButton.setVisible(False) return False if self.mainConf.showFullPath: tTitle = [] tTree = self.theProject.projTree.getItemPath(tHandle) for aHandle in reversed(tTree): nwItem = self.theProject.projTree[aHandle] if nwItem is not None: tTitle.append(nwItem.itemName) sSep = " %s " % nwUnicode.U_RSAQUO self.theTitle.setText(sSep.join(tTitle)) else: nwItem = self.theProject.projTree[tHandle] if nwItem is None: return False self.theTitle.setText(nwItem.itemName) self.closeButton.setVisible(True) if self.isEditor: self.minmaxButton.setVisible(True) else: self.refreshButton.setVisible(True) return True ## # Slots ## def _closeDocument(self): """Trigger the close editor/viewer on the main window. """ if self.isEditor: self.theParent.theParent.closeDocEditor() else: self.theParent.theParent.closeDocViewer() return def _minmaxDocument(self): """Switch on or off zen mode. """ self.theParent.theParent.toggleZenMode() if self.theParent.theParent.isZenMode: self.minmaxButton.setIcon(self.theTheme.getIcon("minimise")) self.setContentsMargins(self.buttonSize, 0, 0, 0) self.closeButton.setVisible(False) else: self.minmaxButton.setIcon(self.theTheme.getIcon("maximise")) self.setContentsMargins(2*self.buttonSize, 0, 0, 0) self.closeButton.setVisible(True) return def _refreshDocument(self): """Reload the content of the document. """ self.theParent.reloadText() return ## # Events ## def mousePressEvent(self, theEvent): """Capture a click on the title and ensure that the item is selected in the project tree. """ self.theParent.setSelectedHandle(self.theHandle) return # END Class GuiDocTitleBar class GuiDocViewFooter(QWidget): def __init__(self, theParent): QWidget.__init__(self, theParent) 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.theHandle = None # Make a QPalette that matches the Syntax Theme self.thePalette = QPalette() self.thePalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack)) self.thePalette.setColor(QPalette.Text, QColor(*self.theTheme.colText)) fPx = int(0.9*self.theTheme.fontPixelSize) hSp = self.mainConf.pxInt(6) # Main Widget Settings self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) self.setPalette(self.thePalette) buttonStyle = ( "QToolButton {{border: none; background: transparent;}} " "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" ).format(*self.theTheme.colText) # Show/Hide Details self.showHide = QToolButton(self) self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly) self.showHide.setStyleSheet(buttonStyle) self.showHide.setIcon(self.theTheme.getIcon("reference")) self.showHide.setIconSize(QSize(fPx, fPx)) self.showHide.setFixedSize(QSize(fPx, fPx)) self.showHide.clicked.connect(self._doShowHide) # Sticky Button stickyOn = self.theTheme.getPixmap("sticky-on", (fPx, fPx)) stickyOff = self.theTheme.getPixmap("sticky-off", (fPx, fPx)) stickyIcon = QIcon() stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On) stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off) self.stickyRefs = QToolButton(self) self.stickyRefs.setCheckable(True) self.stickyRefs.setToolButtonStyle(Qt.ToolButtonIconOnly) self.stickyRefs.setStyleSheet(buttonStyle) self.stickyRefs.setIcon(stickyIcon) self.stickyRefs.setIconSize(QSize(fPx, fPx)) self.stickyRefs.setFixedSize(QSize(fPx, fPx)) self.stickyRefs.toggled.connect(self._doToggleSticky) # Labels self.lblRefs = QLabel("References") self.lblRefs.setBuddy(self.showHide) self.lblRefs.setIndent(0) self.lblRefs.setMargin(0) self.lblRefs.setContentsMargins(0, 0, 0, 0) self.lblRefs.setAutoFillBackground(True) self.lblRefs.setFixedHeight(fPx) self.lblRefs.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.lblRefs.setPalette(self.thePalette) self.lblSticky = QLabel("Sticky") self.lblSticky.setBuddy(self.stickyRefs) self.lblSticky.setIndent(0) self.lblSticky.setMargin(0) self.lblSticky.setContentsMargins(0, 0, 0, 0) self.lblSticky.setAutoFillBackground(True) self.lblSticky.setFixedHeight(fPx) self.lblSticky.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.lblSticky.setPalette(self.thePalette) lblFont = self.font() lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize) self.lblRefs.setFont(lblFont) self.lblSticky.setFont(lblFont) # Assemble Layout self.outerBox = QHBoxLayout() self.outerBox.setSpacing(hSp) self.outerBox.addWidget(self.showHide, 0) self.outerBox.addWidget(self.lblRefs, 0) self.outerBox.addWidget(self.stickyRefs, 0) self.outerBox.addWidget(self.lblSticky, 0) self.outerBox.addStretch(1) self.setLayout(self.outerBox) logger.debug("GuiDocViewFooter initialisation complete") return ## # Slots ## def _doShowHide(self): """Toggle the expand/collapse of the panel. """ isVisible = self.viewMeta.isVisible() self.viewMeta.setVisible(not isVisible) return def _doToggleSticky(self, theState): """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) return # END Class GuiDocViewFooter class GuiDocViewDetails(QScrollArea): def __init__(self, theParent): QScrollArea.__init__(self, theParent) logger.debug("Initialising DocViewDetails ...") self.mainConf = nw.CONFIG self.theParent = theParent self.theProject = theParent.theProject self.theTheme = theParent.theTheme self.currHandle = None self.refList = QLabel("None") self.refList.setWordWrap(True) self.refList.setAlignment(Qt.AlignTop) self.refList.setScaledContents(True) self.refList.linkActivated.connect(self._linkClicked) hCol = self.palette().highlight().color() self.linkStyle = "style='color: rgb({0},{1},{2})'".format( *self.theTheme.colLink ) # Assemble self.outerWidget = QWidget() self.outerBox = QHBoxLayout() self.outerBox.addWidget(self.refList, 1) self.outerWidget.setLayout(self.outerBox) self.setWidget(self.outerWidget) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setWidgetResizable(True) self.setMinimumHeight(self.mainConf.pxInt(50)) logger.debug("DocViewDetails initialisation complete") return def refreshReferences(self, tHandle): """Update the current list of document references from the project index. """ self.currHandle = tHandle if self.theParent.docViewer.stickyRef: return theRefs = self.theParent.theIndex.getBackReferenceList(tHandle) theList = [] for tHandle in theRefs: tItem = self.theProject.projTree[tHandle] if tItem is not None: theList.append("%s" % ( tHandle, theRefs[tHandle], self.linkStyle, tItem.itemName )) self.refList.setText(", ".join(theList)) return ## # Internal Functions ## def _linkClicked(self, theLink): """Capture the link-click and forward it to the document viewer class for handling. """ logger.verbose("Clicked link: '%s'" % theLink) if len(theLink) == 27: tHandle = theLink[6:19] self.theParent.viewDocument(tHandle, theLink) return # END Class GuiDocViewDetails