diff --git a/nw/gui/additions/__init__.py b/nw/gui/additions/__init__.py index dbacd16e..83c73cdc 100644 --- a/nw/gui/additions/__init__.py +++ b/nw/gui/additions/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from nw.gui.additions.pageddialog import PagedDialog +from nw.gui.additions.qconfiglayout import PagedDialog from nw.gui.additions.qconfiglayout import QConfigLayout from nw.gui.additions.qconfiglayout import QHelpLabel -from nw.gui.additions.qswitch import QSwitch +from nw.gui.additions.qconfiglayout import QSwitch __all__ = [ "PagedDialog", diff --git a/nw/gui/additions/pageddialog.py b/nw/gui/additions/pageddialog.py deleted file mode 100644 index 2b65cbb9..00000000 --- a/nw/gui/additions/pageddialog.py +++ /dev/null @@ -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 . -""" - -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 diff --git a/nw/gui/additions/qconfiglayout.py b/nw/gui/additions/qconfiglayout.py index 70b3042d..96e0c482 100644 --- a/nw/gui/additions/qconfiglayout.py +++ b/nw/gui/additions/qconfiglayout.py @@ -6,7 +6,9 @@ A custom Qt grid layout for config forms similar to QFormLayout File History: - Created: 2020-05-03 [0.4.5] + 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 @@ -28,16 +30,24 @@ import logging import nw -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QColor, QPalette +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 + 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): @@ -198,3 +208,223 @@ class QHelpLabel(QLabel): 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 diff --git a/nw/gui/additions/qswitch.py b/nw/gui/additions/qswitch.py deleted file mode 100644 index 7eb32830..00000000 --- a/nw/gui/additions/qswitch.py +++ /dev/null @@ -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 . -""" - -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