Merge pull request #277 from vkbo/less_files
Merge a bunch of source files and folders
This commit is contained in:
+156
-7
@@ -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
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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__)
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user