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
|
Class wrapping the data if a novelWriter project
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2018-09-29 [0.0.1] NWProject
|
Created: 2018-09-29 [0.0.1] NWProject
|
||||||
Created: 2018-10-27 [0.0.1] NWItem
|
Created: 2018-10-27 [0.0.1] NWItem
|
||||||
Created: 2019-05-19 [0.1.3] NWStatus
|
Created: 2019-05-19 [0.1.3] NWStatus
|
||||||
Merged: 2020-05-07 [0.4.5] Moved NWItem class to this file
|
Created: 2019-10-21 [0.3.1] OptionState
|
||||||
Merged: 2020-05-07 [0.4.5] Moved NWStatus class to this file
|
Merged: 2020-05-07 [0.4.5] Moved NWItem class to this file
|
||||||
Added: 2020-05-07 [0.4.5] NWTree
|
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
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2020, Veronica Berglyd Olsen
|
||||||
@@ -42,7 +44,6 @@ from shutil import make_archive
|
|||||||
|
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from nw.gui.tools import OptionState
|
|
||||||
from nw.core.document import NWDoc
|
from nw.core.document import NWDoc
|
||||||
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
|
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
@@ -2007,3 +2008,151 @@ class NWStatus():
|
|||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
|
||||||
# END Class NWStatus
|
# 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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Main Window Elements
|
from nw.gui.about import GuiAbout
|
||||||
from nw.gui.build import GuiBuildNovel
|
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.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.statusbar import GuiMainStatus
|
||||||
|
from nw.gui.theme import GuiIcons
|
||||||
from nw.gui.theme import GuiTheme
|
from nw.gui.theme import GuiTheme
|
||||||
|
from nw.gui.viewdetails import GuiDocViewDetails
|
||||||
# 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
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"GuiBuildNovel",
|
|
||||||
"GuiIcons",
|
|
||||||
"GuiMainMenu",
|
|
||||||
"GuiMainStatus",
|
|
||||||
"GuiTheme",
|
|
||||||
"GuiAbout",
|
"GuiAbout",
|
||||||
"GuiPreferences",
|
"GuiBuildNovel",
|
||||||
"GuiDocMerge",
|
"GuiDocTitleBar",
|
||||||
"GuiDocSplit",
|
"GuiNoticeBar",
|
||||||
"GuiItemEditor",
|
"GuiSearchBar",
|
||||||
"GuiProjectSettings",
|
|
||||||
"GuiProjectLoad",
|
|
||||||
"GuiSessionLogView",
|
|
||||||
"GuiDocDetails",
|
"GuiDocDetails",
|
||||||
"GuiDocEditor",
|
"GuiDocEditor",
|
||||||
"GuiDocTitleBar",
|
"GuiDocMerge",
|
||||||
|
"GuiDocSplit",
|
||||||
"GuiDocTree",
|
"GuiDocTree",
|
||||||
"GuiDocViewer",
|
"GuiDocViewer",
|
||||||
"GuiNoticeBar",
|
"GuiItemEditor",
|
||||||
|
"GuiMainMenu",
|
||||||
"GuiProjectOutline",
|
"GuiProjectOutline",
|
||||||
"GuiSearchBar",
|
"GuiPreferences",
|
||||||
|
"GuiProjectLoad",
|
||||||
|
"GuiProjectSettings",
|
||||||
|
"GuiSessionLogView",
|
||||||
|
"GuiMainStatus",
|
||||||
|
"GuiIcons",
|
||||||
|
"GuiTheme",
|
||||||
"GuiDocViewDetails",
|
"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
|
QFileDialog, QFontDialog, QSpinBox
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.gui.additions import QSwitch
|
from nw.gui.custom import QSwitch
|
||||||
from nw.core import ToHtml
|
from nw.core import ToHtml
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwAlert, nwFiles, nwItemType, nwItemLayout, nwItemClass
|
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
|
Class holding the main window search bar
|
||||||
|
|
||||||
File History:
|
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
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2020, Veronica Berglyd Olsen
|
||||||
@@ -29,11 +31,13 @@ import logging
|
|||||||
import nw
|
import nw
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtGui import QPalette, QColor
|
||||||
from PyQt5.QtWidgets import (
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -42,7 +46,7 @@ class GuiSearchBar(QFrame):
|
|||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
QFrame.__init__(self, theParent)
|
QFrame.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising SearchBar ...")
|
logger.debug("Initialising GuiSearchBar ...")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -89,7 +93,7 @@ class GuiSearchBar(QFrame):
|
|||||||
|
|
||||||
self._replaceVisible(False)
|
self._replaceVisible(False)
|
||||||
|
|
||||||
logger.debug("SearchBar initialisation complete")
|
logger.debug("GuiSearchBar initialisation complete")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -163,3 +167,133 @@ class GuiSearchBar(QFrame):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# END Class GuiSearchBar
|
# 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
|
Class holding the document editor
|
||||||
|
|
||||||
File History:
|
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
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2020, Veronica Berglyd Olsen
|
||||||
@@ -30,7 +31,7 @@ import nw
|
|||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
|
from PyQt5.QtCore import Qt, QThread, QTimer, pyqtSlot
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox
|
qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox
|
||||||
)
|
)
|
||||||
@@ -40,9 +41,9 @@ from PyQt5.QtGui import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from nw.core import NWDoc
|
from nw.core import NWDoc
|
||||||
from nw.gui.tools import GuiDocHighlighter, WordCounter
|
from nw.gui.dochighlight import GuiDocHighlighter
|
||||||
from nw.gui.elements.doctitlebar import GuiDocTitleBar
|
from nw.gui.docbars import GuiDocTitleBar
|
||||||
from nw.core import NWSpellSimple
|
from nw.core import NWSpellSimple, countWords
|
||||||
from nw.constants import nwUnicode, nwDocAction
|
from nw.constants import nwUnicode, nwDocAction
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -52,7 +53,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
def __init__(self, theParent, theProject):
|
def __init__(self, theParent, theProject):
|
||||||
QTextEdit.__init__(self)
|
QTextEdit.__init__(self)
|
||||||
|
|
||||||
logger.debug("Initialising DocEditor ...")
|
logger.debug("Initialising GuiDocEditor ...")
|
||||||
|
|
||||||
# Class Variables
|
# Class Variables
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
@@ -131,7 +132,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
self.initEditor()
|
self.initEditor()
|
||||||
|
|
||||||
logger.debug("DocEditor initialisation complete")
|
logger.debug("GuiDocEditor initialisation complete")
|
||||||
|
|
||||||
# Connect Functions
|
# Connect Functions
|
||||||
self.setSelectedHandle = self.theParent.treeView.setSelectedHandle
|
self.setSelectedHandle = self.theParent.treeView.setSelectedHandle
|
||||||
@@ -1082,3 +1083,28 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiDocEditor
|
# 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.constants import nwAlert, nwItemType
|
||||||
from nw.gui.additions import QHelpLabel
|
from nw.gui.custom import QHelpLabel
|
||||||
from nw.core import NWDoc
|
from nw.core import NWDoc
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -34,7 +34,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QListWidgetItem, QDialogButtonBox, QLabel
|
QListWidgetItem, QDialogButtonBox, QLabel
|
||||||
)
|
)
|
||||||
from nw.constants import nwAlert, nwItemType, nwItemClass, nwItemLayout
|
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
|
from nw.core import NWDoc
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -34,7 +34,7 @@ from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor, QTextCursor
|
|||||||
|
|
||||||
from nw.core import ToHtml
|
from nw.core import ToHtml
|
||||||
from nw.constants import nwAlert, nwItemType, nwDocAction
|
from nw.constants import nwAlert, nwItemType, nwDocAction
|
||||||
from nw.gui.elements.doctitlebar import GuiDocTitleBar
|
from nw.gui.docbars import GuiDocTitleBar
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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
|
QDialogButtonBox
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.gui.additions import QSwitch
|
from nw.gui.custom import QSwitch
|
||||||
from nw.constants import nwLabels, nwItemLayout, nwItemClass, nwItemType
|
from nw.constants import nwLabels, nwItemLayout, nwItemClass, nwItemType
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
+1
-1
@@ -32,7 +32,7 @@ from PyQt5.QtCore import QUrl
|
|||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox
|
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
|
from nw.constants import nwItemType, nwItemClass, nwDocAction
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QDialogButtonBox, QFileDialog, QFontDialog
|
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.core import NWSpellCheck, NWSpellSimple, NWSpellEnchant
|
||||||
from nw.constants import nwAlert, nwQuotes
|
from nw.constants import nwAlert, nwQuotes
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from nw.constants import nwAlert
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
+303
-4
@@ -6,7 +6,8 @@
|
|||||||
This class reads and store the main theme
|
This class reads and store the main theme
|
||||||
|
|
||||||
File History:
|
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
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2020, Veronica Berglyd Olsen
|
||||||
@@ -32,16 +33,22 @@ import nw
|
|||||||
from os import path, listdir
|
from os import path, listdir
|
||||||
from math import ceil
|
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 (
|
from PyQt5.QtGui import (
|
||||||
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase
|
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase, QPixmap
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.constants import nwAlert
|
from nw.constants import nwAlert
|
||||||
from nw.gui.icons import GuiIcons
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# =============================================================================================== #
|
||||||
|
# Gui Theme Class
|
||||||
|
# Handles the look and feel of novelWriter
|
||||||
|
# =============================================================================================== #
|
||||||
|
|
||||||
class GuiTheme:
|
class GuiTheme:
|
||||||
|
|
||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
@@ -447,3 +454,295 @@ class GuiTheme:
|
|||||||
return cnfDefault
|
return cnfDefault
|
||||||
|
|
||||||
# End Class GuiTheme
|
# 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