Merge pull request #277 from vkbo/less_files

Merge a bunch of source files and folders
This commit is contained in:
Veronica K. Berglyd Olsen
2020-06-01 23:57:25 +02:00
committed by GitHub
34 changed files with 1102 additions and 1400 deletions
+156 -7
View File
@@ -6,12 +6,14 @@
Class wrapping the data if a novelWriter project
File History:
Created: 2018-09-29 [0.0.1] NWProject
Created: 2018-10-27 [0.0.1] NWItem
Created: 2019-05-19 [0.1.3] NWStatus
Merged: 2020-05-07 [0.4.5] Moved NWItem class to this file
Merged: 2020-05-07 [0.4.5] Moved NWStatus class to this file
Added: 2020-05-07 [0.4.5] NWTree
Created: 2018-09-29 [0.0.1] NWProject
Created: 2018-10-27 [0.0.1] NWItem
Created: 2019-05-19 [0.1.3] NWStatus
Created: 2019-10-21 [0.3.1] OptionState
Merged: 2020-05-07 [0.4.5] Moved NWItem class to this file
Merged: 2020-05-07 [0.4.5] Moved NWStatus class to this file
Added: 2020-05-07 [0.4.5] NWTree
Rewritten: 2020-02-19 [0.4.5] OptionState
This file is a part of novelWriter
Copyright 2020, Veronica Berglyd Olsen
@@ -42,7 +44,6 @@ from shutil import make_archive
from PyQt5.QtWidgets import QMessageBox
from nw.gui.tools import OptionState
from nw.core.document import NWDoc
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
from nw.constants import (
@@ -2007,3 +2008,151 @@ class NWStatus():
raise StopIteration
# END Class NWStatus
# =============================================================================================== #
# OptionState
# Save the project-wise state of options that don't go into project XML or main config
# =============================================================================================== #
class OptionState():
def __init__(self, theProject):
self.theProject = theProject
self.theState = {}
self.stringOpt = ()
self.boolOpt = ()
self.intOpt = ()
return
def loadSettings(self):
"""Load the options dictionary from the project settings file.
"""
if self.theProject.projMeta is None:
return False
stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
theState = {}
if path.isfile(stateFile):
logger.debug("Loading GUI options file")
try:
with open(stateFile,mode="r",encoding="utf8") as inFile:
theJson = inFile.read()
theState = json.loads(theJson)
except Exception as e:
logger.error("Failed to load GUI options file")
logger.error(str(e))
return False
for anOpt in theState:
self.theState[anOpt] = theState[anOpt]
return True
def saveSettings(self):
"""Save the options dictionary to the project settings file.
"""
if self.theProject.projMeta is None:
return False
stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
logger.debug("Saving GUI options file")
try:
with open(stateFile,mode="w+",encoding="utf8") as outFile:
outFile.write(json.dumps(self.theState, indent=2))
except Exception as e:
logger.error("Failed to save GUI options file")
logger.error(str(e))
return False
return True
def setValue(self, setGroup, setName, setValue):
"""Saves a value, with a given group and name.
"""
if not setGroup in self.theState:
self.theState[setGroup] = {}
self.theState[setGroup][setName] = setValue
return True
def getValue(self, getGroup, getName, defaultValue):
"""Return an arbitrary type value, if it exists. Otherwise,
return the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return self.theState[getGroup][getName]
except:
return defaultValue
return defaultValue
def getString(self, getGroup, getName, defaultValue):
"""Return the value as a string, if it exists. Otherwise, return
the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return str(self.theState[getGroup][getName])
except:
return defaultValue
return defaultValue
def getInt(self, getGroup, getName, defaultValue):
"""Return the value as an int, if it exists. Otherwise, return
the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return int(self.theState[getGroup][getName])
except:
return defaultValue
return defaultValue
def getFloat(self, getGroup, getName, defaultValue):
"""Return the value as a float, if it exists. Otherwise, return
the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return float(self.theState[getGroup][getName])
except:
return defaultValue
return defaultValue
def getBool(self, getGroup, getName, defaultValue):
"""Return the value as a bool, if it exists. Otherwise, return
the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return bool(self.theState[getGroup][getName])
except:
return defaultValue
return defaultValue
def validIntRange(self, theValue, intA, intB, intDefault):
"""Check that an int is in a given range. If it isn't, return
the default value.
"""
if isinstance(theValue, int):
if theValue >= intA and theValue <= intB:
return theValue
return intDefault
def validIntTuple(self, theValue, theTuple, intDefault):
"""Check that an int is an element of a tuple. If it isn't,
return the default value.
"""
if isinstance(theValue, int):
if theValue in theTuple:
return theValue
return intDefault
# END Class OptionState
+33 -46
View File
@@ -1,62 +1,49 @@
# -*- coding: utf-8 -*-
# Main Window Elements
from nw.gui.about import GuiAbout
from nw.gui.build import GuiBuildNovel
from nw.gui.icons import GuiIcons
from nw.gui.docbars import GuiDocTitleBar
from nw.gui.docbars import GuiNoticeBar
from nw.gui.docbars import GuiSearchBar
from nw.gui.docdetails import GuiDocDetails
from nw.gui.doceditor import GuiDocEditor
from nw.gui.docmerge import GuiDocMerge
from nw.gui.docsplit import GuiDocSplit
from nw.gui.doctree import GuiDocTree
from nw.gui.docviewer import GuiDocViewer
from nw.gui.itemeditor import GuiItemEditor
from nw.gui.mainmenu import GuiMainMenu
from nw.gui.outline import GuiProjectOutline
from nw.gui.preferences import GuiPreferences
from nw.gui.projectload import GuiProjectLoad
from nw.gui.projectsettings import GuiProjectSettings
from nw.gui.sessionlog import GuiSessionLogView
from nw.gui.statusbar import GuiMainStatus
from nw.gui.theme import GuiIcons
from nw.gui.theme import GuiTheme
# Dialogs
from nw.gui.dialogs.about import GuiAbout
from nw.gui.dialogs.preferences import GuiPreferences
from nw.gui.dialogs.docmerge import GuiDocMerge
from nw.gui.dialogs.docsplit import GuiDocSplit
from nw.gui.dialogs.itemeditor import GuiItemEditor
from nw.gui.dialogs.projectsettings import GuiProjectSettings
from nw.gui.dialogs.projectload import GuiProjectLoad
from nw.gui.dialogs.sessionlog import GuiSessionLogView
# GUI Elements
from nw.gui.elements.docdetails import GuiDocDetails
from nw.gui.elements.doceditor import GuiDocEditor
from nw.gui.elements.doctitlebar import GuiDocTitleBar
from nw.gui.elements.doctree import GuiDocTree
from nw.gui.elements.docviewer import GuiDocViewer
from nw.gui.elements.noticebar import GuiNoticeBar
from nw.gui.elements.outline import GuiProjectOutline
from nw.gui.elements.searchbar import GuiSearchBar
from nw.gui.elements.viewdetails import GuiDocViewDetails
# Tools
from nw.gui.tools.dochighlight import GuiDocHighlighter
from nw.gui.tools.optionstate import OptionState
from nw.gui.tools.wordcounter import WordCounter
from nw.gui.viewdetails import GuiDocViewDetails
__all__ = [
"GuiBuildNovel",
"GuiIcons",
"GuiMainMenu",
"GuiMainStatus",
"GuiTheme",
"GuiAbout",
"GuiPreferences",
"GuiDocMerge",
"GuiDocSplit",
"GuiItemEditor",
"GuiProjectSettings",
"GuiProjectLoad",
"GuiSessionLogView",
"GuiBuildNovel",
"GuiDocTitleBar",
"GuiNoticeBar",
"GuiSearchBar",
"GuiDocDetails",
"GuiDocEditor",
"GuiDocTitleBar",
"GuiDocMerge",
"GuiDocSplit",
"GuiDocTree",
"GuiDocViewer",
"GuiNoticeBar",
"GuiItemEditor",
"GuiMainMenu",
"GuiProjectOutline",
"GuiSearchBar",
"GuiPreferences",
"GuiProjectLoad",
"GuiProjectSettings",
"GuiSessionLogView",
"GuiMainStatus",
"GuiIcons",
"GuiTheme",
"GuiDocViewDetails",
"GuiDocHighlighter",
"OptionState",
"WordCounter",
]
-12
View File
@@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
from nw.gui.additions.pageddialog import PagedDialog
from nw.gui.additions.qconfiglayout import QConfigLayout
from nw.gui.additions.qconfiglayout import QHelpLabel
from nw.gui.additions.qswitch import QSwitch
__all__ = [
"PagedDialog",
"QConfigLayout",
"QHelpLabel",
"QSwitch",
]
-125
View File
@@ -1,125 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter Paged Dialog
novelWriter Paged Dialog
============================
A custom QDialog with a built-in QTabWidget and vertical tabs.
File History:
Created: 2020-05-17 [0.5.1]
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, QRect, QPoint
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QTabWidget, QTabBar, QStyle,
QStylePainter, QStyleOptionTab
)
logger = logging.getLogger(__name__)
class PagedDialog(QDialog):
def __init__(self, theParent=None):
QDialog.__init__(self, parent=theParent)
self._outerBox = QVBoxLayout()
self._buttonBox = QHBoxLayout()
self._tabBox = QTabWidget()
self._tabBar = VerticalTabBar(self)
self._tabBox.setTabBar(self._tabBar)
self._tabBox.setTabPosition(QTabWidget.West)
self._tabBar.setExpanding(False)
self._outerBox.addWidget(self._tabBox)
self._outerBox.addLayout(self._buttonBox)
self.setLayout(self._outerBox)
# Default Margins
qM = self._outerBox.contentsMargins()
mL = qM.left()
mR = qM.right()
mT = qM.top()
mB = qM.bottom()
self.setContentsMargins(0, 0, 0, 0)
self._outerBox.setContentsMargins(0, 0, 0, mB)
self._buttonBox.setContentsMargins(mL, 0, mR, 0)
self._outerBox.setSpacing(mT)
return
def addTab(self, tabWidget, tabLabel):
"""Forwards the adding of tabs to the QTabWidget.
"""
self._tabBox.addTab(tabWidget, tabLabel)
return
def addControls(self, buttonBar):
"""Adds a button bar to the dialog.
"""
self._buttonBox.addWidget(buttonBar)
return
# END Class PagedDialog
class VerticalTabBar(QTabBar):
def __init__(self, theParent=None):
QTabBar.__init__(self, parent=theParent)
return
def tabSizeHint(self, theIndex):
"""Returns a transposed size hint for the rotated bar.
"""
tSize = QTabBar.tabSizeHint(self, theIndex)
tSize.transpose()
return tSize
def paintEvent(self, theEvent):
"""Custom implementation of the label painter that rotates the
label 90 degrees.
"""
pObj = QStylePainter(self)
oObj = QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(oObj, i)
pObj.drawControl(QStyle.CE_TabBarTabShape, oObj)
pObj.save()
oSize = oObj.rect.size()
oSize.transpose()
oRect = QRect(QPoint(), oSize)
oRect.moveCenter(oObj.rect.center())
oObj.rect = oRect
oCenter = self.tabRect(i).center()
pObj.translate(oCenter)
pObj.rotate(90)
pObj.translate(-oCenter)
pObj.drawControl(QStyle.CE_TabBarTabLabel, oObj)
pObj.restore()
return
# END Class VerticalTabBar
-200
View File
@@ -1,200 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter Addition QConfigLayout
novelWriter Addition QConfigLayout
======================================
A custom Qt grid layout for config forms similar to QFormLayout
File History:
Created: 2020-05-03 [0.4.5]
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
from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtWidgets import (
QGridLayout, QLabel, QWidget, QVBoxLayout, QHBoxLayout
)
from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
class QConfigLayout(QGridLayout):
def __init__(self):
super().__init__()
self._nextRow = 0
self._helpCol = QColor(0, 0, 0)
self._fontScale = 0.9
self._itemMap = {}
self.setHorizontalSpacing(8)
self.setVerticalSpacing(8)
return
##
# Getters and Setters
##
def setHelpTextStyle(self, helpCol, fontScale=0.9):
"""Set the text color for the help text.
"""
if isinstance(helpCol, QColor):
self._helpCol = helpCol
else:
self._helpCol = QColor(*helpCol)
self._fontScale = fontScale
return
def setHelpText(self, intRow, theText):
"""Set the text for the help label.
"""
if intRow in self._itemMap:
self._itemMap[intRow]["help"].setText(theText)
return
def setLabelText(self, intRow, theText):
"""Set the text for the main label.
"""
if intRow in self._itemMap:
self._itemMap[intRow]["label"].setText(theText)
return
##
# Class Methods
##
def addGroupLabel(self, theLabel):
"""Adds a text label to separate groups of settings.
"""
if isinstance(theLabel, QLabel):
qLabel = theLabel
elif isinstance(theLabel, str):
qLabel = QLabel("<b>%s</b>" % theLabel)
else:
qLabel = None
raise ValueError("theLabel must be a QLabel")
qLabel.setContentsMargins(0, 4, 0, 4)
self.addWidget(qLabel, self._nextRow, 0, 1, 2, Qt.AlignLeft)
self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1)
self._nextRow += 1
return
def addRow(self, theLabel, theWidget, helpText=None, theUnit=None, theButton=None):
"""Add a label and a widget as a new row of the grid.
"""
thisEntry = {
"label" : None,
"help" : None,
"widget" : None,
}
if isinstance(theLabel, QLabel):
qLabel = theLabel
elif isinstance(theLabel, str):
qLabel = QLabel(theLabel)
else:
qLabel = None
raise ValueError("theLabel must be a QLabel")
if isinstance(theWidget, QWidget):
qWidget = theWidget
else:
qWidget = None
raise ValueError("theWidget must be a QWidget")
qLabel.setIndent(8)
if helpText is not None:
qHelp = QHelpLabel(str(helpText), self._helpCol, self._fontScale)
qHelp.setIndent(8)
labelBox = QVBoxLayout()
labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp)
labelBox.setSpacing(0)
thisEntry["help"] = qHelp
self.addLayout(labelBox, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
else:
self.addWidget(qLabel, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
if theUnit is not None:
controlBox = QHBoxLayout()
controlBox.addWidget(qWidget, 0, Qt.AlignVCenter)
controlBox.addWidget(QLabel(theUnit), 0, Qt.AlignVCenter)
controlBox.setSpacing(8)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
elif theButton is not None:
controlBox = QHBoxLayout()
controlBox.addWidget(qWidget, 0, Qt.AlignVCenter)
controlBox.addWidget(theButton, 0, Qt.AlignVCenter)
controlBox.setSpacing(8)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
else:
self.addWidget(qWidget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
qLabel.setBuddy(qWidget)
self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1)
thisEntry["label"] = qLabel
thisEntry["widget"] = qWidget
self._itemMap[self._nextRow] = thisEntry
self._nextRow += 1
return self._nextRow - 1
# END Class QConfigLayout
class QHelpLabel(QLabel):
def __init__(self, theText, textCol, fontSize=0.9):
QLabel.__init__(self, theText)
if isinstance(textCol, QColor):
qCol = textCol
else:
qCol = QColor(*textCol)
lblCol = self.palette()
lblCol.setColor(QPalette.WindowText, qCol)
self.setPalette(lblCol)
lblFont = self.font()
lblFont.setPointSizeF(fontSize*lblFont.pointSizeF())
self.setFont(lblFont)
return
# END Class QHelpLabel
-164
View File
@@ -1,164 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter Addition QSwitch
novelWriter Addition QSwitch
================================
A custom Qt switch button
File History:
Created: 2020-05-03 [0.4.5]
The core code of this class is based on Stack Overflow example by
Stefan Scherfke: https://stackoverflow.com/a/51825815/5825851
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, QRectF, QPropertyAnimation, pyqtProperty
from PyQt5.QtWidgets import QAbstractButton, QSizePolicy
from PyQt5.QtGui import QPainter
from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
class QSwitch(QAbstractButton):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setCheckable(True)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setFixedWidth(40)
self.setFixedHeight(20)
self._offset = 10
return
##
# Properties
##
@pyqtProperty(int)
def offset(self):
return self._offset
@offset.setter
def offset(self, theOffset):
self._offset = theOffset
self.update()
return
##
# Getters and Setters
##
def setChecked(self, isChecked):
"""Overload setChecked to also alter the offset.
"""
super().setChecked(isChecked)
if isChecked:
self.offset = 30
else:
self.offset = 10
return
##
# Events
##
def resizeEvent(self, theEvent):
"""Overload resize to ensure correct offset.
"""
super().resizeEvent(theEvent)
if self.isChecked():
self.offset = 30
else:
self.offset = 10
return
def paintEvent(self, event):
"""Drawing the switch itself.
"""
qPaint = QPainter(self)
qPaint.setRenderHint(QPainter.Antialiasing, True)
qPaint.setPen(Qt.NoPen)
qPalette = self.palette()
if self.isChecked():
trackBrush = qPalette.highlight()
thumbBrush = qPalette.highlightedText()
textColor = qPalette.highlight().color()
thumbText = nwUnicode.U_CHECK
else:
trackBrush = qPalette.dark()
thumbBrush = qPalette.light()
textColor = qPalette.dark().color()
thumbText = nwUnicode.U_MULT
if self.isEnabled():
trackOpacity = 1.0
else:
trackOpacity = 0.6
trackBrush = qPalette.shadow()
thumbBrush = qPalette.mid()
textColor = qPalette.shadow().color()
qPaint.setBrush(trackBrush)
qPaint.setOpacity(trackOpacity)
qPaint.drawRoundedRect(0, 0, 40, 20, 10, 10)
qPaint.setBrush(thumbBrush)
qPaint.drawEllipse(self.offset - 8, 2, 16, 16)
theFont = qPaint.font()
theFont.setPixelSize(12)
qPaint.setPen(textColor)
qPaint.setFont(theFont)
qPaint.drawText(
QRectF(self.offset - 8, 2, 16, 16),
Qt.AlignCenter, thumbText
)
return
def mouseReleaseEvent(self, event):
"""Animate the switch on mouse release.
"""
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton:
doAnim = QPropertyAnimation(self, b'offset', self)
doAnim.setDuration(120)
doAnim.setStartValue(self.offset)
if self.isChecked():
doAnim.setEndValue(30)
else:
doAnim.setEndValue(10)
doAnim.start()
return
def enterEvent(self, event):
"""Change the cursor when hovering the button.
"""
self.setCursor(Qt.PointingHandCursor)
super().enterEvent(event)
return
# END Class QSwitch
+1 -1
View File
@@ -43,7 +43,7 @@ from PyQt5.QtWidgets import (
QFileDialog, QFontDialog, QSpinBox
)
from nw.gui.additions import QSwitch
from nw.gui.custom import QSwitch
from nw.core import ToHtml
from nw.constants import (
nwAlert, nwFiles, nwItemType, nwItemLayout, nwItemClass
+430
View File
@@ -0,0 +1,430 @@
# -*- coding: utf-8 -*-
"""novelWriter Addition QConfigLayout
novelWriter Addition QConfigLayout
======================================
A custom Qt grid layout for config forms similar to QFormLayout
File History:
Created: 2020-05-03 [0.4.5] QConfigLayout
Created: 2020-05-03 [0.4.5] QSwitch
Created: 2020-05-17 [0.5.1] PagedDialog
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.QtGui import QColor, QPalette, QPainter
from PyQt5.QtCore import (
Qt, QRect, QPoint, QSize, QRectF, QPropertyAnimation, pyqtProperty
)
from PyQt5.QtWidgets import (
QGridLayout, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy,
QAbstractButton, QDialog, QTabWidget, QTabBar, QStyle,
QStylePainter, QStyleOptionTab
)
from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
# =============================================================================================== #
# Config FOrm Layout
# =============================================================================================== #
class QConfigLayout(QGridLayout):
def __init__(self):
super().__init__()
self._nextRow = 0
self._helpCol = QColor(0, 0, 0)
self._fontScale = 0.9
self._itemMap = {}
self.setHorizontalSpacing(8)
self.setVerticalSpacing(8)
return
##
# Getters and Setters
##
def setHelpTextStyle(self, helpCol, fontScale=0.9):
"""Set the text color for the help text.
"""
if isinstance(helpCol, QColor):
self._helpCol = helpCol
else:
self._helpCol = QColor(*helpCol)
self._fontScale = fontScale
return
def setHelpText(self, intRow, theText):
"""Set the text for the help label.
"""
if intRow in self._itemMap:
self._itemMap[intRow]["help"].setText(theText)
return
def setLabelText(self, intRow, theText):
"""Set the text for the main label.
"""
if intRow in self._itemMap:
self._itemMap[intRow]["label"].setText(theText)
return
##
# Class Methods
##
def addGroupLabel(self, theLabel):
"""Adds a text label to separate groups of settings.
"""
if isinstance(theLabel, QLabel):
qLabel = theLabel
elif isinstance(theLabel, str):
qLabel = QLabel("<b>%s</b>" % theLabel)
else:
qLabel = None
raise ValueError("theLabel must be a QLabel")
qLabel.setContentsMargins(0, 4, 0, 4)
self.addWidget(qLabel, self._nextRow, 0, 1, 2, Qt.AlignLeft)
self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1)
self._nextRow += 1
return
def addRow(self, theLabel, theWidget, helpText=None, theUnit=None, theButton=None):
"""Add a label and a widget as a new row of the grid.
"""
thisEntry = {
"label" : None,
"help" : None,
"widget" : None,
}
if isinstance(theLabel, QLabel):
qLabel = theLabel
elif isinstance(theLabel, str):
qLabel = QLabel(theLabel)
else:
qLabel = None
raise ValueError("theLabel must be a QLabel")
if isinstance(theWidget, QWidget):
qWidget = theWidget
else:
qWidget = None
raise ValueError("theWidget must be a QWidget")
qLabel.setIndent(8)
if helpText is not None:
qHelp = QHelpLabel(str(helpText), self._helpCol, self._fontScale)
qHelp.setIndent(8)
labelBox = QVBoxLayout()
labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp)
labelBox.setSpacing(0)
thisEntry["help"] = qHelp
self.addLayout(labelBox, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
else:
self.addWidget(qLabel, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
if theUnit is not None:
controlBox = QHBoxLayout()
controlBox.addWidget(qWidget, 0, Qt.AlignVCenter)
controlBox.addWidget(QLabel(theUnit), 0, Qt.AlignVCenter)
controlBox.setSpacing(8)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
elif theButton is not None:
controlBox = QHBoxLayout()
controlBox.addWidget(qWidget, 0, Qt.AlignVCenter)
controlBox.addWidget(theButton, 0, Qt.AlignVCenter)
controlBox.setSpacing(8)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
else:
self.addWidget(qWidget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
qLabel.setBuddy(qWidget)
self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1)
thisEntry["label"] = qLabel
thisEntry["widget"] = qWidget
self._itemMap[self._nextRow] = thisEntry
self._nextRow += 1
return self._nextRow - 1
# END Class QConfigLayout
class QHelpLabel(QLabel):
def __init__(self, theText, textCol, fontSize=0.9):
QLabel.__init__(self, theText)
if isinstance(textCol, QColor):
qCol = textCol
else:
qCol = QColor(*textCol)
lblCol = self.palette()
lblCol.setColor(QPalette.WindowText, qCol)
self.setPalette(lblCol)
lblFont = self.font()
lblFont.setPointSizeF(fontSize*lblFont.pointSizeF())
self.setFont(lblFont)
return
# END Class QHelpLabel
# =============================================================================================== #
# Switch Widget
# =============================================================================================== #
class QSwitch(QAbstractButton):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setCheckable(True)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setFixedWidth(40)
self.setFixedHeight(20)
self._offset = 10
return
##
# Properties
##
@pyqtProperty(int)
def offset(self):
return self._offset
@offset.setter
def offset(self, theOffset):
self._offset = theOffset
self.update()
return
##
# Getters and Setters
##
def setChecked(self, isChecked):
"""Overload setChecked to also alter the offset.
"""
super().setChecked(isChecked)
if isChecked:
self.offset = 30
else:
self.offset = 10
return
##
# Events
##
def resizeEvent(self, theEvent):
"""Overload resize to ensure correct offset.
"""
super().resizeEvent(theEvent)
if self.isChecked():
self.offset = 30
else:
self.offset = 10
return
def paintEvent(self, event):
"""Drawing the switch itself.
"""
qPaint = QPainter(self)
qPaint.setRenderHint(QPainter.Antialiasing, True)
qPaint.setPen(Qt.NoPen)
qPalette = self.palette()
if self.isChecked():
trackBrush = qPalette.highlight()
thumbBrush = qPalette.highlightedText()
textColor = qPalette.highlight().color()
thumbText = nwUnicode.U_CHECK
else:
trackBrush = qPalette.dark()
thumbBrush = qPalette.light()
textColor = qPalette.dark().color()
thumbText = nwUnicode.U_MULT
if self.isEnabled():
trackOpacity = 1.0
else:
trackOpacity = 0.6
trackBrush = qPalette.shadow()
thumbBrush = qPalette.mid()
textColor = qPalette.shadow().color()
qPaint.setBrush(trackBrush)
qPaint.setOpacity(trackOpacity)
qPaint.drawRoundedRect(0, 0, 40, 20, 10, 10)
qPaint.setBrush(thumbBrush)
qPaint.drawEllipse(self.offset - 8, 2, 16, 16)
theFont = qPaint.font()
theFont.setPixelSize(12)
qPaint.setPen(textColor)
qPaint.setFont(theFont)
qPaint.drawText(
QRectF(self.offset - 8, 2, 16, 16),
Qt.AlignCenter, thumbText
)
return
def mouseReleaseEvent(self, event):
"""Animate the switch on mouse release.
"""
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton:
doAnim = QPropertyAnimation(self, b'offset', self)
doAnim.setDuration(120)
doAnim.setStartValue(self.offset)
if self.isChecked():
doAnim.setEndValue(30)
else:
doAnim.setEndValue(10)
doAnim.start()
return
def enterEvent(self, event):
"""Change the cursor when hovering the button.
"""
self.setCursor(Qt.PointingHandCursor)
super().enterEvent(event)
return
# END Class QSwitch
# =============================================================================================== #
# Paged Dialog w/Custom TabWidget
# =============================================================================================== #
class PagedDialog(QDialog):
def __init__(self, theParent=None):
QDialog.__init__(self, parent=theParent)
self._outerBox = QVBoxLayout()
self._buttonBox = QHBoxLayout()
self._tabBox = QTabWidget()
self._tabBar = VerticalTabBar(self)
self._tabBox.setTabBar(self._tabBar)
self._tabBox.setTabPosition(QTabWidget.West)
self._tabBar.setExpanding(False)
self._outerBox.addWidget(self._tabBox)
self._outerBox.addLayout(self._buttonBox)
self.setLayout(self._outerBox)
# Default Margins
qM = self._outerBox.contentsMargins()
mL = qM.left()
mR = qM.right()
mT = qM.top()
mB = qM.bottom()
self.setContentsMargins(0, 0, 0, 0)
self._outerBox.setContentsMargins(0, 0, 0, mB)
self._buttonBox.setContentsMargins(mL, 0, mR, 0)
self._outerBox.setSpacing(mT)
return
def addTab(self, tabWidget, tabLabel):
"""Forwards the adding of tabs to the QTabWidget.
"""
self._tabBox.addTab(tabWidget, tabLabel)
return
def addControls(self, buttonBar):
"""Adds a button bar to the dialog.
"""
self._buttonBox.addWidget(buttonBar)
return
# END Class PagedDialog
class VerticalTabBar(QTabBar):
def __init__(self, theParent=None):
QTabBar.__init__(self, parent=theParent)
return
def tabSizeHint(self, theIndex):
"""Returns a transposed size hint for the rotated bar.
"""
tSize = QTabBar.tabSizeHint(self, theIndex)
tSize.transpose()
return tSize
def paintEvent(self, theEvent):
"""Custom implementation of the label painter that rotates the
label 90 degrees.
"""
pObj = QStylePainter(self)
oObj = QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(oObj, i)
pObj.drawControl(QStyle.CE_TabBarTabShape, oObj)
pObj.save()
oSize = oObj.rect.size()
oSize.transpose()
oRect = QRect(QPoint(), oSize)
oRect.moveCenter(oObj.rect.center())
oObj.rect = oRect
oCenter = self.tabRect(i).center()
pObj.translate(oCenter)
pObj.rotate(90)
pObj.translate(-oCenter)
pObj.drawControl(QStyle.CE_TabBarTabLabel, oObj)
pObj.restore()
return
# END Class VerticalTabBar
-21
View File
@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
from nw.gui.dialogs.about import GuiAbout
from nw.gui.dialogs.preferences import GuiPreferences
from nw.gui.dialogs.docmerge import GuiDocMerge
from nw.gui.dialogs.docsplit import GuiDocSplit
from nw.gui.dialogs.itemeditor import GuiItemEditor
from nw.gui.dialogs.projectsettings import GuiProjectSettings
from nw.gui.dialogs.projectload import GuiProjectLoad
from nw.gui.dialogs.sessionlog import GuiSessionLogView
__all__ = [
"GuiAbout",
"GuiPreferences",
"GuiDocMerge",
"GuiDocSplit",
"GuiItemEditor",
"GuiProjectSettings",
"GuiProjectLoad",
"GuiSessionLogView",
]
@@ -6,7 +6,9 @@
Class holding the main window search bar
File History:
Created: 2019-09-29 [0.2.1]
Created: 2019-09-29 [0.2.1] GuiSearchBar
Created: 2019-10-31 [0.3.2] GuiNoticeBar
Created: 2020-04-25 [0.4.5] GuiDocTitleBar
This file is a part of novelWriter
Copyright 2020, Veronica Berglyd Olsen
@@ -29,11 +31,13 @@ import logging
import nw
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import (
qApp, QFrame, QGridLayout, QLabel, QLineEdit, QPushButton
qApp, QFrame, QGridLayout, QLabel, QLineEdit, QPushButton,
QHBoxLayout
)
from nw.constants import nwDocAction
from nw.constants import nwDocAction, nwUnicode
logger = logging.getLogger(__name__)
@@ -42,7 +46,7 @@ class GuiSearchBar(QFrame):
def __init__(self, theParent):
QFrame.__init__(self, theParent)
logger.debug("Initialising SearchBar ...")
logger.debug("Initialising GuiSearchBar ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
@@ -89,7 +93,7 @@ class GuiSearchBar(QFrame):
self._replaceVisible(False)
logger.debug("SearchBar initialisation complete")
logger.debug("GuiSearchBar initialisation complete")
return
@@ -163,3 +167,133 @@ class GuiSearchBar(QFrame):
return True
# END Class GuiSearchBar
class GuiNoticeBar(QFrame):
def __init__(self, theParent):
QFrame.__init__(self, theParent)
logger.debug("Initialising GuiNoticeBar ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.setContentsMargins(0,0,0,0)
self.setFrameShape(QFrame.Box)
self.mainBox = QHBoxLayout(self)
self.mainBox.setContentsMargins(8,2,2,2)
self.noteLabel = QLabel("")
self.closeButton = QPushButton(self.theTheme.getIcon("close"),"")
self.closeButton.clicked.connect(self.hideNote)
self.mainBox.addWidget(self.noteLabel)
self.mainBox.addWidget(self.closeButton)
self.mainBox.setStretch(0, 1)
self.setLayout(self.mainBox)
self.hideNote()
logger.debug("GuiNoticeBar initialisation complete")
return
def showNote(self, theNote):
"""Show the note on the noticebar.
"""
self.noteLabel.setText("<b>Note:</b> %s" % theNote)
self.setVisible(True)
return
def hideNote(self):
"""Clear the noticebar and hide it.
"""
self.noteLabel.setText("")
self.setVisible(False)
return
# END Class GuiNoticeBar
class GuiDocTitleBar(QLabel):
def __init__(self, theParent, theProject):
QLabel.__init__(self, theParent)
logger.debug("Initialising GuiDocTitleBar ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.theTheme = theParent.theTheme
self.theHandle = None
self.setText("")
self.setIndent(0)
self.setMargin(0)
self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True)
self.setAlignment(Qt.AlignCenter)
self.setWordWrap(True)
self.setFrameShape(QFrame.NoFrame)
self.setLineWidth(0)
lblPalette = self.palette()
lblPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
lblPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
self.setPalette(lblPalette)
lblFont = self.font()
lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
self.setFont(lblFont)
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.setText("")
self.theHandle = tHandle
if tHandle is None:
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.setText(sSep.join(tTitle))
else:
nwItem = self.theProject.projTree[tHandle]
if nwItem is None:
return False
self.setText(nwItem.itemName)
return True
##
# 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
@@ -6,7 +6,8 @@
Class holding the document editor
File History:
Created: 2018-09-29 [0.0.1]
Created: 2018-09-29 [0.0.1] GuiDocEditor
Created: 2019-04-22 [0.0.1] WordCounter
This file is a part of novelWriter
Copyright 2020, Veronica Berglyd Olsen
@@ -30,7 +31,7 @@ import nw
from time import time
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
from PyQt5.QtCore import Qt, QThread, QTimer, pyqtSlot
from PyQt5.QtWidgets import (
qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox
)
@@ -40,9 +41,9 @@ from PyQt5.QtGui import (
)
from nw.core import NWDoc
from nw.gui.tools import GuiDocHighlighter, WordCounter
from nw.gui.elements.doctitlebar import GuiDocTitleBar
from nw.core import NWSpellSimple
from nw.gui.dochighlight import GuiDocHighlighter
from nw.gui.docbars import GuiDocTitleBar
from nw.core import NWSpellSimple, countWords
from nw.constants import nwUnicode, nwDocAction
logger = logging.getLogger(__name__)
@@ -52,7 +53,7 @@ class GuiDocEditor(QTextEdit):
def __init__(self, theParent, theProject):
QTextEdit.__init__(self)
logger.debug("Initialising DocEditor ...")
logger.debug("Initialising GuiDocEditor ...")
# Class Variables
self.mainConf = nw.CONFIG
@@ -131,7 +132,7 @@ class GuiDocEditor(QTextEdit):
self.initEditor()
logger.debug("DocEditor initialisation complete")
logger.debug("GuiDocEditor initialisation complete")
# Connect Functions
self.setSelectedHandle = self.theParent.treeView.setSelectedHandle
@@ -1082,3 +1083,28 @@ class GuiDocEditor(QTextEdit):
return
# END Class GuiDocEditor
class WordCounter(QThread):
def __init__(self, theParent):
QThread.__init__(self, theParent)
self.theParent = theParent
self.charCount = 0
self.wordCount = 0
self.paraCount = 0
return
def run(self):
"""Overloaded run function for the word counter, forwarding the
call to the function that does the actual counting.
"""
theText = self.theParent.getText()
cC, wC, pC = countWords(theText)
self.charCount = cC
self.wordCount = wC
self.paraCount = pC
return
## END Class WordCounter
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (
)
from nw.constants import nwAlert, nwItemType
from nw.gui.additions import QHelpLabel
from nw.gui.custom import QHelpLabel
from nw.core import NWDoc
logger = logging.getLogger(__name__)
@@ -34,7 +34,7 @@ from PyQt5.QtWidgets import (
QListWidgetItem, QDialogButtonBox, QLabel
)
from nw.constants import nwAlert, nwItemType, nwItemClass, nwItemLayout
from nw.gui.additions import QHelpLabel
from nw.gui.custom import QHelpLabel
from nw.core import NWDoc
logger = logging.getLogger(__name__)
@@ -34,7 +34,7 @@ from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor, QTextCursor
from nw.core import ToHtml
from nw.constants import nwAlert, nwItemType, nwDocAction
from nw.gui.elements.doctitlebar import GuiDocTitleBar
from nw.gui.docbars import GuiDocTitleBar
logger = logging.getLogger(__name__)
-23
View File
@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
from nw.gui.elements.docdetails import GuiDocDetails
from nw.gui.elements.doceditor import GuiDocEditor
from nw.gui.elements.doctitlebar import GuiDocTitleBar
from nw.gui.elements.doctree import GuiDocTree
from nw.gui.elements.docviewer import GuiDocViewer
from nw.gui.elements.noticebar import GuiNoticeBar
from nw.gui.elements.outline import GuiProjectOutline
from nw.gui.elements.searchbar import GuiSearchBar
from nw.gui.elements.viewdetails import GuiDocViewDetails
__all__ = [
"GuiDocDetails",
"GuiDocEditor",
"GuiDocTitleBar",
"GuiDocTree",
"GuiDocViewer",
"GuiNoticeBar",
"GuiProjectOutline",
"GuiSearchBar",
"GuiDocViewDetails",
]
-117
View File
@@ -1,117 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Document Title Bar
novelWriter GUI Document Title Bar
======================================
Class holding the document title bar class
File History:
Created: 2020-04-25 [0.4.5]
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
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QLabel, QFrame
from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
class GuiDocTitleBar(QLabel):
def __init__(self, theParent, theProject):
QLabel.__init__(self, theParent)
logger.debug("Initialising DocTitleBar ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.theTheme = theParent.theTheme
self.theHandle = None
self.setText("")
self.setIndent(0)
self.setMargin(0)
self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True)
self.setAlignment(Qt.AlignCenter)
self.setWordWrap(True)
self.setFrameShape(QFrame.NoFrame)
self.setLineWidth(0)
lblPalette = self.palette()
lblPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
lblPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
self.setPalette(lblPalette)
lblFont = self.font()
lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
self.setFont(lblFont)
logger.debug("DocTitleBar initialisation complete")
return
##
# Setters
##
def setTitleFromHandle(self, tHandle):
"""Sets the document title from the handle, or alternatively,
set the whole document path.
"""
self.setText("")
self.theHandle = tHandle
if tHandle is None:
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.setText(sSep.join(tTitle))
else:
nwItem = self.theProject.projTree[tHandle]
if nwItem is None:
return False
self.setText(nwItem.itemName)
return True
##
# 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
-83
View File
@@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Main Window Notice Bar
novelWriter GUI Main Window Notice Bar
==========================================
Class holding the main window notice bar for the doc editor
File History:
Created: 2019-10-31 [0.3.2]
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.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton
logger = logging.getLogger(__name__)
class GuiNoticeBar(QFrame):
def __init__(self, theParent):
QFrame.__init__(self, theParent)
logger.debug("Initialising NoticeBar ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.setContentsMargins(0,0,0,0)
self.setFrameShape(QFrame.Box)
self.mainBox = QHBoxLayout(self)
self.mainBox.setContentsMargins(8,2,2,2)
self.noteLabel = QLabel("")
self.closeButton = QPushButton(self.theTheme.getIcon("close"),"")
self.closeButton.clicked.connect(self.hideNote)
self.mainBox.addWidget(self.noteLabel)
self.mainBox.addWidget(self.closeButton)
self.mainBox.setStretch(0, 1)
self.setLayout(self.mainBox)
self.hideNote()
logger.debug("NoticeBar initialisation complete")
return
def showNote(self, theNote):
"""Show the note on the noticebar.
"""
self.noteLabel.setText("<b>Note:</b> %s" % theNote)
self.setVisible(True)
return
def hideNote(self):
"""Clear the noticebar and hide it.
"""
self.noteLabel.setText("")
self.setVisible(False)
return
# END Class GuiNoticeBar
-327
View File
@@ -1,327 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter Icons Class
novelWriter Icons Class
===========================
This class manages the GUI icons
File History:
Created: 2019-11-08 [0.4.0]
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 configparser
import nw
from os import path, listdir
from PyQt5.QtCore import QSize
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QStyle, qApp
logger = logging.getLogger(__name__)
class GuiIcons:
"""The icon class manages the content of the assets/icons folder,
and provides a simple interface for requesting icons. Only icons
listed in the ICON_MAP are handled.
Icons are loaded on first request, and then cached for further
requests. Each icon key in the ICON_MAP has a series of fallbacks:
* The first lookup is in the key-to-file map for the selected icon
theme. The map is specified in the icons.conf file in the theme
folder. The map makes it possible to preserve the original file
name from the icon theme were the icons were extracted.
* Second, if the icon does not exist in the theme map, the
GuiIcons class will check if there is a QStyle icon specified in
the ICON_MAP data tuple[0]. This will let Qt pull the closest
system icon.
* Third action is to look up the freedesktop icon theme name using
the fromTheme Qt call. This generally produces the same results
as the step above, but has more icons available in other cases.
* Fourth, and finally, the icon is looked up in the fallback
folder. Files in this folder must have the same file name as the
novelWriter internal icon key, with '-dark' appended to it for
the dark background version of the icon.
"""
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_nwx" : (None, None),
"status_lang" : (None, None),
"status_time" : (None, None),
"status_stats" : (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),
## Other Icons
"warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"),
}
DECO_MAP = {
"nwicon" : ["icons", "novelwriter.svg"],
}
def __init__(self, theParent):
self.mainConf = nw.CONFIG
self.theParent = theParent
# Storage
self.qIcons = {}
self.themeMap = {}
self.themeList = []
self.fbackName = "fallback"
self.confName = "icons.conf"
# Icon Theme Path
self.iconPath = None
self.confFile = None
# Icon Theme Meta
self.themeName = ""
self.themeDescription = ""
self.themeAuthor = ""
self.themeCredit = ""
self.themeUrl = ""
self.themeLicense = ""
self.themeLicenseUrl = ""
return
##
# Actions
##
def updateTheme(self):
"""Update the theme map. This is more of an init, since many of
the GUI icons cannot really be replaced without writing specific
update functions for the classes where they're used.
"""
logger.debug("Loading icon theme files")
self.themeMap = {}
checkPath = path.join(self.mainConf.iconPath, self.mainConf.guiIcons)
if path.isdir(checkPath):
logger.debug("Loading icon theme '%s'" % self.mainConf.guiIcons)
self.iconPath = checkPath
self.confFile = path.join(checkPath, self.confName)
else:
return False
# Config File
confParser = configparser.ConfigParser()
try:
confParser.read_file(open(self.confFile, mode="r", encoding="utf8"))
except Exception as e:
logger.error("Could not load icon theme settings from: %s" % self.confFile)
return False
## Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
self.themeName = self._parseLine( confParser, cnfSec, "name", "")
self.themeDescription = self._parseLine( confParser, cnfSec, "description", "")
self.themeAuthor = self._parseLine( confParser, cnfSec, "author", "")
self.themeCredit = self._parseLine( confParser, cnfSec, "credit", "")
self.themeUrl = self._parseLine( confParser, cnfSec, "url", "")
self.themeLicense = self._parseLine( confParser, cnfSec, "license", "")
self.themeLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "")
## Palette
cnfSec = "Map"
if confParser.has_section(cnfSec):
for iconName, iconFile in confParser.items(cnfSec):
if iconName not in self.ICON_MAP:
logger.error("Unknown icon name '%s' in config file" % iconName)
else:
iconPath = path.join(self.iconPath, iconFile)
if path.isfile(iconPath):
self.themeMap[iconName] = iconPath
logger.verbose("Icon slot '%s' using file '%s'" % (iconName, iconFile))
else:
logger.error("Icon file '%s' not in theme folder" % iconFile)
logger.info("Loaded icon theme '%s'" % self.mainConf.guiIcons)
return True
##
# Access Functions
##
def loadDecoration(self, decoKey, decoSize=None):
"""Load graphical decoration element based on the decoration
map. This function always returns a QSwgWidget.
"""
if decoKey not in self.DECO_MAP:
logger.error("Decoration with name '%s' does not exist" % decoKey)
return QSvgWidget()
svgPath = path.join(
self.mainConf.assetPath,
self.DECO_MAP[decoKey][0],
self.DECO_MAP[decoKey][1]
)
if not path.isfile(svgPath):
logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey])
return QSvgWidget()
svgDeco = QSvgWidget(svgPath)
if decoSize is not None:
svgDeco.setFixedSize(QSize(decoSize[0],decoSize[1]))
return svgDeco
def getIcon(self, iconKey, iconSize=None):
"""Return an icon from the icon buffer. If it doesn't exist,
return, load it, and if it still doesn't exist, return an empty
icon.
"""
if iconKey in self.qIcons:
return self.qIcons[iconKey]
else:
qIcon = self._loadIcon(iconKey)
self.qIcons[iconKey] = qIcon
return qIcon
def getPixmap(self, iconKey, iconSize):
"""Return an icon from the icon buffer as a QPixmap. If it
doesn't exist, return an empty QPixmap.
"""
qIcon = self.getIcon(iconKey)
return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal)
def listThemes(self):
"""Scan the icons themes folder and list all themes.
"""
if self.themeList:
return self.themeList
confParser = configparser.ConfigParser()
for themeDir in listdir(self.mainConf.iconPath):
themePath = path.join(self.mainConf.iconPath, themeDir)
if not path.isdir(themePath) or themeDir == self.fbackName:
continue
themeConf = path.join(themePath, self.confName)
logger.verbose("Checking icon theme config for '%s'" % themeDir)
try:
confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
except Exception as e:
self.theParent.makeAlert(
["Could not load theme config file.",str(e)], nwAlert.ERROR
)
continue
themeName = ""
if confParser.has_section("Main"):
if confParser.has_option("Main", "name"):
themeName = confParser.get("Main", "name")
logger.verbose("Theme name is '%s'" % themeName)
if themeName != "":
self.themeList.append((themeDir, themeName))
self.themeList = sorted(self.themeList, key=lambda x: x[1])
return self.themeList
##
# Internal Functions
##
def _loadIcon(self, iconKey):
"""Load an icon from the assets or theme folder, with a
preference for dark/light icons depending on theme type, if such
an icon exists. Prefer svg files over png files. Always returns
a QIcon.
"""
if iconKey not in self.ICON_MAP:
logger.error("Requested unknown icon name '%s'" % iconKey)
return QIcon()
if iconKey in self.themeMap:
logger.verbose("Loading: %s" % path.relpath(self.themeMap[iconKey]))
return QIcon(self.themeMap[iconKey])
# Next, we try to load the Qt style icons
if self.ICON_MAP[iconKey][0] is not None:
logger.verbose("Loading icon '%s' from Qt QStyle.standardIcon" % iconKey)
return qApp.style().standardIcon(self.ICON_MAP[iconKey][0])
# If we're still here, try to set from system theme
if self.ICON_MAP[iconKey][1] is not None:
logger.verbose("Loading icon '%s' from system theme" % iconKey)
if QIcon().hasThemeIcon(self.ICON_MAP[iconKey][1]):
return QIcon().fromTheme(self.ICON_MAP[iconKey][1])
# Finally. we check if we have a fallback icon
if self.mainConf.guiDark:
fbackIcon = path.join(
self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey
)
if path.isfile(fbackIcon):
logger.verbose("Loading icon '%s' from fallback theme" % iconKey)
return QIcon(fbackIcon)
fbackIcon = path.join(self.mainConf.iconPath, self.fbackName, "%s.svg" % iconKey)
if path.isfile(fbackIcon):
logger.verbose("Loading icon '%s' from fallback theme" % iconKey)
return QIcon(fbackIcon)
# Give up and return an empty icon
logger.warning("Did not load an icon for '%s'" % iconKey)
return QIcon()
def _parseLine(self, confParser, cnfSec, cnfName, cnfDefault):
"""Simple wrapper for the config parser check for entry existing
before arrempting to load.
"""
if confParser.has_section(cnfSec):
if confParser.has_option(cnfSec, cnfName):
return confParser.get(cnfSec, cnfName)
return cnfDefault
# END Class GuiIcons
@@ -34,7 +34,7 @@ from PyQt5.QtWidgets import (
QDialogButtonBox
)
from nw.gui.additions import QSwitch
from nw.gui.custom import QSwitch
from nw.constants import nwLabels, nwItemLayout, nwItemClass, nwItemType
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -32,7 +32,7 @@ from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox
from nw.gui.dialogs import GuiAbout
from nw.gui.about import GuiAbout
from nw.constants import nwItemType, nwItemClass, nwDocAction
logger = logging.getLogger(__name__)
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
QDialogButtonBox, QFileDialog, QFontDialog
)
from nw.gui.additions import QSwitch, QConfigLayout, PagedDialog
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog
from nw.core import NWSpellCheck, NWSpellSimple, NWSpellEnchant
from nw.constants import nwAlert, nwQuotes
@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import (
)
from nw.constants import nwAlert
from nw.gui.additions import QSwitch, PagedDialog, QConfigLayout
from nw.gui.custom import QSwitch, PagedDialog, QConfigLayout
logger = logging.getLogger(__name__)
+303 -4
View File
@@ -6,7 +6,8 @@
This class reads and store the main theme
File History:
Created: 2019-05-18 [0.1.3]
Created: 2019-05-18 [0.1.3] GuiTheme
Created: 2019-11-08 [0.4.0] GuiIcons
This file is a part of novelWriter
Copyright 2020, Veronica Berglyd Olsen
@@ -32,16 +33,22 @@ import nw
from os import path, listdir
from math import ceil
from PyQt5.QtWidgets import qApp
from PyQt5.QtCore import QSize
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import QStyle, qApp
from PyQt5.QtGui import (
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase, QPixmap
)
from nw.constants import nwAlert
from nw.gui.icons import GuiIcons
logger = logging.getLogger(__name__)
# =============================================================================================== #
# Gui Theme Class
# Handles the look and feel of novelWriter
# =============================================================================================== #
class GuiTheme:
def __init__(self, theParent):
@@ -447,3 +454,295 @@ class GuiTheme:
return cnfDefault
# End Class GuiTheme
# =============================================================================================== #
# Icons Class
# =============================================================================================== #
class GuiIcons:
"""The icon class manages the content of the assets/icons folder,
and provides a simple interface for requesting icons. Only icons
listed in the ICON_MAP are handled.
Icons are loaded on first request, and then cached for further
requests. Each icon key in the ICON_MAP has a series of fallbacks:
* The first lookup is in the key-to-file map for the selected icon
theme. The map is specified in the icons.conf file in the theme
folder. The map makes it possible to preserve the original file
name from the icon theme were the icons were extracted.
* Second, if the icon does not exist in the theme map, the
GuiIcons class will check if there is a QStyle icon specified in
the ICON_MAP data tuple[0]. This will let Qt pull the closest
system icon.
* Third action is to look up the freedesktop icon theme name using
the fromTheme Qt call. This generally produces the same results
as the step above, but has more icons available in other cases.
* Fourth, and finally, the icon is looked up in the fallback
folder. Files in this folder must have the same file name as the
novelWriter internal icon key, with '-dark' appended to it for
the dark background version of the icon.
"""
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_nwx" : (None, None),
"status_lang" : (None, None),
"status_time" : (None, None),
"status_stats" : (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),
## Other Icons
"warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"),
}
DECO_MAP = {
"nwicon" : ["icons", "novelwriter.svg"],
}
def __init__(self, theParent):
self.mainConf = nw.CONFIG
self.theParent = theParent
# Storage
self.qIcons = {}
self.themeMap = {}
self.themeList = []
self.fbackName = "fallback"
self.confName = "icons.conf"
# Icon Theme Path
self.iconPath = None
self.confFile = None
# Icon Theme Meta
self.themeName = ""
self.themeDescription = ""
self.themeAuthor = ""
self.themeCredit = ""
self.themeUrl = ""
self.themeLicense = ""
self.themeLicenseUrl = ""
return
##
# Actions
##
def updateTheme(self):
"""Update the theme map. This is more of an init, since many of
the GUI icons cannot really be replaced without writing specific
update functions for the classes where they're used.
"""
logger.debug("Loading icon theme files")
self.themeMap = {}
checkPath = path.join(self.mainConf.iconPath, self.mainConf.guiIcons)
if path.isdir(checkPath):
logger.debug("Loading icon theme '%s'" % self.mainConf.guiIcons)
self.iconPath = checkPath
self.confFile = path.join(checkPath, self.confName)
else:
return False
# Config File
confParser = configparser.ConfigParser()
try:
confParser.read_file(open(self.confFile, mode="r", encoding="utf8"))
except Exception as e:
logger.error("Could not load icon theme settings from: %s" % self.confFile)
return False
## Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
self.themeName = self._parseLine( confParser, cnfSec, "name", "")
self.themeDescription = self._parseLine( confParser, cnfSec, "description", "")
self.themeAuthor = self._parseLine( confParser, cnfSec, "author", "")
self.themeCredit = self._parseLine( confParser, cnfSec, "credit", "")
self.themeUrl = self._parseLine( confParser, cnfSec, "url", "")
self.themeLicense = self._parseLine( confParser, cnfSec, "license", "")
self.themeLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "")
## Palette
cnfSec = "Map"
if confParser.has_section(cnfSec):
for iconName, iconFile in confParser.items(cnfSec):
if iconName not in self.ICON_MAP:
logger.error("Unknown icon name '%s' in config file" % iconName)
else:
iconPath = path.join(self.iconPath, iconFile)
if path.isfile(iconPath):
self.themeMap[iconName] = iconPath
logger.verbose("Icon slot '%s' using file '%s'" % (iconName, iconFile))
else:
logger.error("Icon file '%s' not in theme folder" % iconFile)
logger.info("Loaded icon theme '%s'" % self.mainConf.guiIcons)
return True
##
# Access Functions
##
def loadDecoration(self, decoKey, decoSize=None):
"""Load graphical decoration element based on the decoration
map. This function always returns a QSwgWidget.
"""
if decoKey not in self.DECO_MAP:
logger.error("Decoration with name '%s' does not exist" % decoKey)
return QSvgWidget()
svgPath = path.join(
self.mainConf.assetPath,
self.DECO_MAP[decoKey][0],
self.DECO_MAP[decoKey][1]
)
if not path.isfile(svgPath):
logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey])
return QSvgWidget()
svgDeco = QSvgWidget(svgPath)
if decoSize is not None:
svgDeco.setFixedSize(QSize(decoSize[0],decoSize[1]))
return svgDeco
def getIcon(self, iconKey, iconSize=None):
"""Return an icon from the icon buffer. If it doesn't exist,
return, load it, and if it still doesn't exist, return an empty
icon.
"""
if iconKey in self.qIcons:
return self.qIcons[iconKey]
else:
qIcon = self._loadIcon(iconKey)
self.qIcons[iconKey] = qIcon
return qIcon
def getPixmap(self, iconKey, iconSize):
"""Return an icon from the icon buffer as a QPixmap. If it
doesn't exist, return an empty QPixmap.
"""
qIcon = self.getIcon(iconKey)
return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal)
def listThemes(self):
"""Scan the icons themes folder and list all themes.
"""
if self.themeList:
return self.themeList
confParser = configparser.ConfigParser()
for themeDir in listdir(self.mainConf.iconPath):
themePath = path.join(self.mainConf.iconPath, themeDir)
if not path.isdir(themePath) or themeDir == self.fbackName:
continue
themeConf = path.join(themePath, self.confName)
logger.verbose("Checking icon theme config for '%s'" % themeDir)
try:
confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
except Exception as e:
self.theParent.makeAlert(
["Could not load theme config file.",str(e)], nwAlert.ERROR
)
continue
themeName = ""
if confParser.has_section("Main"):
if confParser.has_option("Main", "name"):
themeName = confParser.get("Main", "name")
logger.verbose("Theme name is '%s'" % themeName)
if themeName != "":
self.themeList.append((themeDir, themeName))
self.themeList = sorted(self.themeList, key=lambda x: x[1])
return self.themeList
##
# Internal Functions
##
def _loadIcon(self, iconKey):
"""Load an icon from the assets or theme folder, with a
preference for dark/light icons depending on theme type, if such
an icon exists. Prefer svg files over png files. Always returns
a QIcon.
"""
if iconKey not in self.ICON_MAP:
logger.error("Requested unknown icon name '%s'" % iconKey)
return QIcon()
if iconKey in self.themeMap:
logger.verbose("Loading: %s" % path.relpath(self.themeMap[iconKey]))
return QIcon(self.themeMap[iconKey])
# Next, we try to load the Qt style icons
if self.ICON_MAP[iconKey][0] is not None:
logger.verbose("Loading icon '%s' from Qt QStyle.standardIcon" % iconKey)
return qApp.style().standardIcon(self.ICON_MAP[iconKey][0])
# If we're still here, try to set from system theme
if self.ICON_MAP[iconKey][1] is not None:
logger.verbose("Loading icon '%s' from system theme" % iconKey)
if QIcon().hasThemeIcon(self.ICON_MAP[iconKey][1]):
return QIcon().fromTheme(self.ICON_MAP[iconKey][1])
# Finally. we check if we have a fallback icon
if self.mainConf.guiDark:
fbackIcon = path.join(
self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey
)
if path.isfile(fbackIcon):
logger.verbose("Loading icon '%s' from fallback theme" % iconKey)
return QIcon(fbackIcon)
fbackIcon = path.join(self.mainConf.iconPath, self.fbackName, "%s.svg" % iconKey)
if path.isfile(fbackIcon):
logger.verbose("Loading icon '%s' from fallback theme" % iconKey)
return QIcon(fbackIcon)
# Give up and return an empty icon
logger.warning("Did not load an icon for '%s'" % iconKey)
return QIcon()
def _parseLine(self, confParser, cnfSec, cnfName, cnfDefault):
"""Simple wrapper for the config parser check for entry existing
before arrempting to load.
"""
if confParser.has_section(cnfSec):
if confParser.has_option(cnfSec, cnfName):
return confParser.get(cnfSec, cnfName)
return cnfDefault
# END Class GuiIcons
-11
View File
@@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
from nw.gui.tools.dochighlight import GuiDocHighlighter
from nw.gui.tools.optionstate import OptionState
from nw.gui.tools.wordcounter import WordCounter
__all__ = [
"GuiDocHighlighter",
"OptionState",
"WordCounter",
]
-180
View File
@@ -1,180 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter Options State
novelWriter Options State
=============================
Class holding the last state of GUI options
File History:
Created: 2019-10-21 [0.3.1] - Original version meant to be sub classed
Created: 2020-02-19 [0.4.5] - Rewritten from superclass to single file tool
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 json
import nw
from os import path
from nw.constants import nwFiles
logger = logging.getLogger(__name__)
class OptionState():
def __init__(self, theProject):
self.theProject = theProject
self.theState = {}
self.stringOpt = ()
self.boolOpt = ()
self.intOpt = ()
return
def loadSettings(self):
"""Load the options dictionary from the project settings file.
"""
if self.theProject.projMeta is None:
return False
stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
theState = {}
if path.isfile(stateFile):
logger.debug("Loading GUI options file")
try:
with open(stateFile,mode="r",encoding="utf8") as inFile:
theJson = inFile.read()
theState = json.loads(theJson)
except Exception as e:
logger.error("Failed to load GUI options file")
logger.error(str(e))
return False
for anOpt in theState:
self.theState[anOpt] = theState[anOpt]
return True
def saveSettings(self):
"""Save the options dictionary to the project settings file.
"""
if self.theProject.projMeta is None:
return False
stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
logger.debug("Saving GUI options file")
try:
with open(stateFile,mode="w+",encoding="utf8") as outFile:
outFile.write(json.dumps(self.theState, indent=2))
except Exception as e:
logger.error("Failed to save GUI options file")
logger.error(str(e))
return False
return True
def setValue(self, setGroup, setName, setValue):
"""Saves a value, with a given group and name.
"""
if not setGroup in self.theState:
self.theState[setGroup] = {}
self.theState[setGroup][setName] = setValue
return True
def getValue(self, getGroup, getName, defaultValue):
"""Return an arbitrary type value, if it exists. Otherwise,
return the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return self.theState[getGroup][getName]
except:
return defaultValue
return defaultValue
def getString(self, getGroup, getName, defaultValue):
"""Return the value as a string, if it exists. Otherwise, return
the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return str(self.theState[getGroup][getName])
except:
return defaultValue
return defaultValue
def getInt(self, getGroup, getName, defaultValue):
"""Return the value as an int, if it exists. Otherwise, return
the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return int(self.theState[getGroup][getName])
except:
return defaultValue
return defaultValue
def getFloat(self, getGroup, getName, defaultValue):
"""Return the value as a float, if it exists. Otherwise, return
the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return float(self.theState[getGroup][getName])
except:
return defaultValue
return defaultValue
def getBool(self, getGroup, getName, defaultValue):
"""Return the value as a bool, if it exists. Otherwise, return
the default value.
"""
if getGroup in self.theState:
if getName in self.theState[getGroup]:
try:
return bool(self.theState[getGroup][getName])
except:
return defaultValue
return defaultValue
def validIntRange(self, theValue, intA, intB, intDefault):
"""Check that an int is in a given range. If it isn't, return
the default value.
"""
if isinstance(theValue, int):
if theValue >= intA and theValue <= intB:
return theValue
return intDefault
def validIntTuple(self, theValue, theTuple, intDefault):
"""Check that an int is an element of a tuple. If it isn't,
return the default value.
"""
if isinstance(theValue, int):
if theValue in theTuple:
return theValue
return intDefault
# END Class OptionState
-60
View File
@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Document Word Counter
novelWriter GUI Document Word Counter
===================================
A thread for counting words and characters in a document
File History:
Created: 2019-04-22 [0.0.1]
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 QThread
from nw.core.tools import countWords
logger = logging.getLogger(__name__)
class WordCounter(QThread):
def __init__(self, theParent):
QThread.__init__(self, theParent)
self.theParent = theParent
self.charCount = 0
self.wordCount = 0
self.paraCount = 0
return
def run(self):
"""Overloaded run function for the word counter, forwarding the
call to the function that does the actual counting.
"""
theText = self.theParent.getText()
cC, wC, pC = countWords(theText)
self.charCount = cC
self.wordCount = wC
self.paraCount = pC
return
## END Class WordCounter