Merge pull request #193 from vkbo/gui_changes

New Preferences Dialog
This commit is contained in:
Veronica K. Berglyd Olsen
2020-05-03 20:24:18 +02:00
committed by GitHub
7 changed files with 944 additions and 341 deletions
+8
View File
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from nw.additions.qconfiglayout import QConfigLayout
from nw.additions.qswitch import QSwitch
__all__ = [
"QConfigLayout",
"QSwitch",
]
+178
View File
@@ -0,0 +1,178 @@
# -*- 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):
if intRow in self._itemMap:
self._itemMap[intRow]["help"].setText(theText)
return
def setLabelText(self, intRow, theText):
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):
"""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 = QLabel(str(helpText))
qHelp.setIndent(8)
lblCol = qHelp.palette()
lblCol.setColor(QPalette.WindowText, self._helpCol)
qHelp.setPalette(lblCol)
lblFont = qHelp.font()
lblFont.setPointSizeF(self._fontScale*lblFont.pointSizeF())
qHelp.setFont(lblFont)
labelBox = QVBoxLayout()
labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp)
labelBox.setSpacing(0)
thisEntry["help"] = qHelp
self.addLayout(labelBox, self._nextRow, 0, Qt.AlignLeft)
else:
self.addWidget(qLabel, self._nextRow, 0, Qt.AlignLeft)
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, Qt.AlignRight)
else:
self.addWidget(qWidget, self._nextRow, 1, Qt.AlignRight)
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
+241
View File
@@ -0,0 +1,241 @@
# -*- coding: utf-8 -*-
"""novelWriter Addition QSwitch
novelWriter Addition QSwitch
================================
A custom Qt switch button
File History:
Created: 2020-05-03 [0.4.5]
This class is based on Stack Overflow code by Stefan Scherfke, based
again on contribution by IMAN4K, published under license CC BY-SA 4.0.
https://stackoverflow.com/a/51825815/5825851
The above code has been modified to integrate with novelWriter, and
re-released under compatible GPLv3 (see creativecommons.org).
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._trackRadius = 10
self._thumbRadius = 8
self._margin = max(0, self._thumbRadius - self._trackRadius)
self._baseOffset = max(self._thumbRadius, self._trackRadius)
self._endOffset = {
True: lambda: self.width() - self._baseOffset,
False: lambda: self._baseOffset,
}
self._offset = self._baseOffset
palette = self.palette()
if self._thumbRadius > self._trackRadius:
self._trackColor = {
True: palette.highlight(),
False: palette.dark(),
}
self._thumbColor = {
True: palette.highlight(),
False: palette.light(),
}
self._textColor = {
True: palette.highlightedText().color(),
False: palette.dark().color(),
}
self._thumbText = {
True: '',
False: '',
}
self._trackOpacity = 0.5
else:
self._thumbColor = {
True: palette.highlightedText(),
False: palette.light(),
}
self._trackColor = {
True: palette.highlight(),
False: palette.dark(),
}
self._textColor = {
True: palette.highlight().color(),
False: palette.dark().color(),
}
self._thumbText = {
True: nwUnicode.U_CHECK,
False: nwUnicode.U_MULT,
}
self._trackOpacity = 1.0
return
##
# Properties
##
@pyqtProperty(int)
def offset(self):
return self._offset
@offset.setter
def offset(self, value):
self._offset = value
self.update()
return
##
# Getters and Setters
##
def trackRadius(self):
return self._trackRadius
def setTrackRadius(self, theValue):
if isinstance(theValue, int):
if theValue > 0:
self._trackRadius = theValue
return
raise ValueError("trackRadius must be an integer > 0")
self.update()
return
def thumbRadius(self):
return self._thumbRadius
def setThumbRadius(self, theValue):
if isinstance(theValue, int):
if theValue > 0:
self._thumbRadius = theValue
return
raise ValueError("thumbRadius must be an integer > 0")
self.update()
return
def setChecked(self, checked):
super().setChecked(checked)
self.offset = self._endOffset[checked]()
def sizeHint(self):
return QSize(
4 * self._trackRadius + 2 * self._margin,
2 * self._trackRadius + 2 * self._margin,
)
##
# Events
##
def resizeEvent(self, event):
"""Overload resize to ensure correct offset.
"""
super().resizeEvent(event)
self.offset = self._endOffset[self.isChecked()]()
return
def paintEvent(self, event):
"""Drawing the switch itself.
"""
qPaint = QPainter(self)
qPaint.setRenderHint(QPainter.Antialiasing, True)
qPaint.setPen(Qt.NoPen)
trackOpacity = self._trackOpacity
thumbOpacity = 1.0
textOpacity = 1.0
if self.isEnabled():
trackBrush = self._trackColor[self.isChecked()]
thumbBrush = self._thumbColor[self.isChecked()]
textColor = self._textColor[self.isChecked()]
else:
trackOpacity *= 0.8
trackBrush = self.palette().shadow()
thumbBrush = self.palette().mid()
textColor = self.palette().shadow().color()
qPaint.setBrush(trackBrush)
qPaint.setOpacity(trackOpacity)
qPaint.drawRoundedRect(
self._margin,
self._margin,
self.width() - 2*self._margin,
self.height() - 2*self._margin,
self._trackRadius,
self._trackRadius,
)
qPaint.setBrush(thumbBrush)
qPaint.setOpacity(thumbOpacity)
qPaint.drawEllipse(
self.offset - self._thumbRadius,
self._baseOffset - self._thumbRadius,
2*self._thumbRadius,
2*self._thumbRadius,
)
qPaint.setPen(textColor)
qPaint.setOpacity(textOpacity)
theFont = qPaint.font()
theFont.setPixelSize(1.5*self._thumbRadius)
qPaint.setFont(theFont)
qPaint.drawText(
QRectF(
self.offset - self._thumbRadius,
self._baseOffset - self._thumbRadius,
2*self._thumbRadius,
2*self._thumbRadius,
),
Qt.AlignCenter,
self._thumbText[self.isChecked()],
)
return
def mouseReleaseEvent(self, event):
"""Animate the switch.
"""
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton:
doAnim = QPropertyAnimation(self, b'offset', self)
doAnim.setDuration(120)
doAnim.setStartValue(self.offset)
doAnim.setEndValue(self._endOffset[self.isChecked()]())
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
+4
View File
@@ -233,6 +233,8 @@ class nwUnicode:
## Other
U_NBSP = "\u00a0" # Non-breaking space
U_PARA = "\u2029" # Paragraph separator
U_CHECK = "\u2714" # Heavy check mark
U_MULT = "\u2715" # Multiplication x
## Arrows
U_UTRI = "\u2bc5" # Up-pointing triangle
@@ -270,6 +272,8 @@ class nwUnicode:
## Other
H_NBSP = "&nbsp;"
H_CHECK = "&#10004;"
H_MULT = "&#10005;"
## Arrows
H_UTRI = "&#11205;"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -30,7 +30,7 @@ import nw
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QLabel, QFrame, QStyle
from PyQt5.QtWidgets import QLabel, QFrame
from nw.constants import nwUnicode
+17
View File
@@ -68,6 +68,7 @@ class GuiTheme:
self.statNone = [120,120,120]
self.statUnsaved = [120,120, 40]
self.statSaved = [ 40,120, 0]
self.helpText = [ 0, 0, 0]
# Loaded Syntax Settings
@@ -121,6 +122,8 @@ class GuiTheme:
##
def updateTheme(self):
"""Update the GUI theme from theme files.
"""
self.guiTheme = self.mainConf.guiTheme
self.guiSyntax = self.mainConf.guiSyntax
@@ -135,6 +138,20 @@ class GuiTheme:
self.loadTheme()
self.loadSyntax()
# Update dependant colours
backCol = qApp.palette().window().color()
textCol = qApp.palette().windowText().color()
backLCol = backCol.lightnessF()
textLCol = textCol.lightnessF()
if backLCol > textLCol:
helpLCol = textLCol + 0.65*(backLCol - textLCol)
else:
helpLCol = backLCol + 0.65*(textLCol - backLCol)
self.helpText = [int(255*helpLCol)]*3
return True
def loadTheme(self):