Merge branch 'dev' into editor_footer

This commit is contained in:
Veronica K. B. Olsen
2020-06-17 22:20:36 +02:00
26 changed files with 1188 additions and 320 deletions
-2
View File
@@ -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",
+1 -1
View File
@@ -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)
-168
View File
@@ -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 <https://www.gnu.org/licenses/>.
"""
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
+448 -67
View File
@@ -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] WordCounter
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
@@ -32,20 +34,22 @@ 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, QIcon,
QTextDocument, QCursor
)
from PyQt5.QtWidgets import (
qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel,
QToolButton, QHBoxLayout
QToolBar, QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton,
QFrame
)
from nw.core import NWDoc
from nw.gui.dochighlight import GuiDocHighlighter
from nw.core import NWSpellSimple, countWords
from nw.constants import nwUnicode, nwDocAction, nwItemClass
from nw.common import transferCase
logger = logging.getLogger(__name__)
@@ -88,6 +92,7 @@ class GuiDocEditor(QTextEdit):
# Document Title
self.docHeader = GuiDocEditHeader(self)
self.docFooter = GuiDocEditFooter(self)
self.docSearch = GuiDocEditSearch(self)
# Syntax
self.hLight = GuiDocHighlighter(self.qDocument, self.theParent)
@@ -99,6 +104,7 @@ class GuiDocEditor(QTextEdit):
# Editor State
self.hasSelection = False
self.setMinimumWidth(self.mainConf.pxInt(300))
self.setAutoFillBackground(True)
self.setAcceptRichText(False)
# Custom Shortcuts
@@ -127,7 +133,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()
@@ -183,12 +189,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()
@@ -311,6 +320,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()
@@ -324,42 +334,29 @@ class GuiDocEditor(QTextEdit):
tW = self.mainConf.getZenWidth()
else:
tW = self.mainConf.getTextWidth()
wW = self.width()
tM = int((wW - sW - tW)/2)
tM = (wW - sW - tW)//2
if tM < cM:
tM = cM
else:
tM = cM
tB = self.frameWidth()
tW = self.width() - 2*tB - sW
tW = wW - 2*tB - sW
tH = self.docHeader.height()
fH = self.docFooter.height()
fY = self.height() - fH - tB
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)
docFormat = self.qDocument.rootFrame().frameFormat()
docFormat.setLeftMargin(tM)
docFormat.setRightMargin(tM)
docFormat.setTopMargin(max(0, tT))
docFormat.setBottomMargin(max(0, bT))
if self.docSearch.isVisible():
rH = self.docSearch.height()
rW = self.docSearch.width()
rL = wW - sW - rW - 2*tB
self.docSearch.move(rL, 2*tB)
else:
rH = 0
# 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)
self.setViewportMargins(tM, max(cM, tH, rH), tM, max(cM, fH))
return
@@ -536,7 +533,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:
@@ -577,6 +574,12 @@ class GuiDocEditor(QTextEdit):
))
return
def closeSearch(self):
"""Close the search box.
"""
self.docSearch.closeSearch()
return self.docSearch.isVisible()
##
# Document Events and Maintenance
##
@@ -594,6 +597,9 @@ class GuiDocEditor(QTextEdit):
as it is triggered on every keypress when typing.
"""
self.hasSelection = self.textCursor().hasSelection()
if self.docSearch.searchBox.hasFocus():
# Block the event when the focus is on the search bar.
return
if keyEvent.modifiers() == Qt.ShiftModifier:
theKey = keyEvent.key()
@@ -1135,7 +1141,8 @@ class GuiDocEditor(QTextEdit):
selText = theCursor.selectedText()
else:
selText = ""
self.theParent.searchBar.setSearchText(selText)
self.docSearch.setSearchText(selText)
self.updateDocMargins()
return
def _beginReplace(self):
@@ -1143,54 +1150,82 @@ 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.
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 and loop is
enabled, or continues to next file if next file is enabled.
"""
searchFor = self.theParent.searchBar.getSearchText()
wasFound = self.find(searchFor)
if not wasFound:
theCursor = self.textCursor()
theCursor.movePosition(QTextCursor.Start)
self.setTextCursor(theCursor)
return
if not self.docSearch.isVisible():
self._beginSearch()
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.
"""
searchFor = self.theParent.searchBar.getSearchText()
wasFound = self.find(searchFor, QTextDocument.FindBackward)
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, findOpt)
if not wasFound:
theCursor = self.textCursor()
theCursor.movePosition(QTextCursor.End)
self.setTextCursor(theCursor)
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)
return
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()
return
if not theCursor.hasSelection():
return
theCursor = self.textCursor()
searchFor = self.theParent.searchBar.getSearchText()
replWith = self.theParent.searchBar.getReplaceText()
if theCursor.hasSelection() and theCursor.selectedText() == searchFor:
xPos = theCursor.selectionStart()
searchFor = self.docSearch.getSearchText()
replWith = self.docSearch.getReplaceText()
selText = theCursor.selectedText()
if self.docSearch.doMatchCap:
replWith = transferCase(selText, replWith)
if not self.docSearch.isCaseSense:
isMatch = searchFor.lower() == selText.lower()
else:
isMatch = searchFor == selText
if isMatch:
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
def _setupSpellChecking(self):
@@ -1209,11 +1244,16 @@ 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
# =============================================================================================== #
def __init__(self, theParent):
QThread.__init__(self, theParent)
self.theParent = theParent
class BackgroundWordCounter(QThread):
def __init__(self, docEditor):
QThread.__init__(self, docEditor)
self.docEditor = docEditor
self.charCount = 0
self.wordCount = 0
self.paraCount = 0
@@ -1223,16 +1263,357 @@ class WordCounter(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
# =============================================================================================== #
# The Embedded Document Search/Replace Feature
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
# =============================================================================================== #
class GuiDocEditSearch(QFrame):
def __init__(self, docEditor):
QFrame.__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
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.doMatchCap = self.mainConf.searchMatchCap
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(0, 0, 0, 0)
self.setAutoFillBackground(True)
self.setFrameStyle(QFrame.StyledPanel | QFrame.Plain)
self.mainBox = QGridLayout(self)
self.setLayout(self.mainBox)
# Text Boxes
# ==========
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 = 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.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)
self.toggleWord = QAction("Whole Words Only", self)
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)
self.toggleRegEx = QAction("RegEx Mode", self)
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)
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.setChecked(self.doLoop)
self.toggleLoop.toggled.connect(self._doToggleLoop)
self.searchOpt.addAction(self.toggleLoop)
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.setChecked(self.doNextFile)
self.toggleProject.toggled.connect(self._doToggleProject)
self.searchOpt.addAction(self.toggleProject)
self.searchOpt.addSeparator()
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()
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")
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.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)
self.mainBox.setColumnStretch(2, 0)
self.mainBox.setColumnStretch(3, 0)
self.mainBox.setColumnStretch(4, 0)
self.mainBox.setSpacing(self.mainConf.pxInt(2))
self.mainBox.setContentsMargins(mPx, mPx, mPx, mPx)
boxWidth = self.mainConf.pxInt(200)
self.searchBox.setFixedWidth(boxWidth)
self.replaceBox.setFixedWidth(boxWidth)
self.replaceBox.setVisible(False)
self.replaceButton.setVisible(False)
self.adjustSize()
# 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
## END Class WordCounter
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.doMatchCap
self.showReplace.setChecked(False)
self.setVisible(False)
self.docEditor.updateDocMargins()
self.docEditor.setFocus()
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()
if self.isRegEx:
self._alertSearchValid(True)
logger.verbose("Setting search text to '%s'" % theText)
return True
def setReplaceText(self, theText):
"""Set the replace text.
"""
self.showReplace.setChecked(True)
self.replaceBox.setFocus()
self.replaceBox.setText(theText)
return True
def getSearchText(self):
"""Return the current search text either as text or as a regular
expression object.
"""
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.
"""
return self.replaceBox.text()
##
# Slots
##
def _doClose(self):
"""Hide the search/replace bar.
"""
self.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
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()
self.docEditor.updateDocMargins()
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
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 _doToggleMatchCap(self, theState):
"""Enable/disable preserving capitalisation when replacing.
"""
self.doMatchCap = theState
return
##
# 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
# END Class GuiDocEditSearch
# =============================================================================================== #
# The Embedded Document Header
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
# =============================================================================================== #
# =============================================================================================== #
# The Embedded Document Header
+9 -9
View File
@@ -559,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
@@ -670,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
+32 -7
View File
@@ -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
+43 -37
View File
@@ -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 = {