Add circular progress bar on manuscript preview

This commit is contained in:
Veronica Berglyd Olsen
2023-06-08 23:33:16 +02:00
parent 8ef1a12201
commit 8b7aab36a2
2 changed files with 105 additions and 27 deletions
+48 -9
View File
@@ -1,6 +1,6 @@
"""
novelWriter Custom Widget: ProgressCircle
===========================================
novelWriter Custom Widget: Progress Circle
============================================
File History:
Created: 2023-06-07 [2.1b1]
@@ -24,39 +24,78 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from math import ceil
from PyQt5.QtGui import QPaintEvent, QPainter, QPen
from PyQt5.QtGui import QBrush, QColor, QPaintEvent, QPainter, QPen
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtWidgets import QProgressBar, QSizePolicy, QWidget
class NProgressCircle(QProgressBar):
"""Extension: Circular Progress Widget
__slots__ = ("_mPx", "_bPen", "_cPen", "_cRect", "_tColor")
A custom widget that paints a circular progress indicator instead of
a straight bar. It is also possible to set custom text for iṫ.
"""
__slots__ = (
"_text", "_point", "_dRect", "_cRect", "_dPen", "_dBrush",
"_cPen", "_bPen", "_tColor"
)
def __init__(self, parent: QWidget, size: int, point: int):
super().__init__(parent=parent)
self._mPx = point
self._bPen = QPen(self.palette().alternateBase(), point, Qt.SolidLine, Qt.RoundCap)
self._cPen = QPen(self.palette().highlight(), point, Qt.SolidLine, Qt.RoundCap)
self._text = None
self._point = point
self._dRect = QRect(0, 0, size, size)
self._cRect = QRect(point, point, size - 2*point, size - 2*point)
self._tColor = self.palette().text().color()
self._dPen = QPen(Qt.transparent)
self._dBrush = QBrush(Qt.transparent)
self.setColours(
track=self.palette().alternateBase().color(),
bar=self.palette().highlight().color(),
text=self.palette().text().color()
)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setFixedWidth(size)
self.setFixedHeight(size)
return
def setColours(
self, back: QColor | None = None, track: QColor | None = None,
bar: QColor | None = None, text: QColor | None = None
):
"""Set the colours of the widget."""
if isinstance(back, QColor):
self._dPen = QPen(back)
self._dBrush = QBrush(back)
if isinstance(bar, QColor):
self._cPen = QPen(QBrush(bar), self._point, Qt.SolidLine, Qt.RoundCap)
if isinstance(track, QColor):
self._bPen = QPen(QBrush(track), self._point, Qt.SolidLine, Qt.RoundCap)
if isinstance(text, QColor):
self._tColor = text
return
def setCentreText(self, text: str | None):
"""Replace the progress text with a custom string."""
self._text = text
self.setValue(self.value()) # Triggers a redraw
return
def paintEvent(self, event: QPaintEvent):
"""Custom painter for the progress bar."""
progress = 100.0*self.value()/self.maximum()
angle = ceil(16*3.6*progress)
qPaint = QPainter(self)
qPaint.setRenderHint(QPainter.Antialiasing, True)
qPaint.setPen(self._dPen)
qPaint.setBrush(self._dBrush)
qPaint.drawEllipse(self._dRect)
qPaint.setPen(self._bPen)
qPaint.drawArc(self._cRect, 0, 360*16)
qPaint.setPen(self._cPen)
qPaint.drawArc(self._cRect, 90*16, -angle)
qPaint.setPen(self._tColor)
qPaint.drawText(self._cRect, Qt.AlignCenter, f"{progress:.1f} %")
qPaint.drawText(self._cRect, Qt.AlignCenter, self._text or f"{progress:.1f} %")
return
# END Class NProgressCircle
+57 -18
View File
@@ -144,10 +144,7 @@ class GuiManuscript(QDialog):
# Process Controls
# ================
self.buildProgress = NProgressCircle(self, CONFIG.pxInt(140), CONFIG.pxInt(16))
self.buildProgress.setValue(0)
self.btnPreview = QPushButton(self.tr("Preview"))
self.btnPreview = QPushButton(self.tr("Build Preview"))
self.btnPreview.clicked.connect(self._generatePreview)
self.btnBuild = QPushButton(self.tr("Build"))
@@ -163,17 +160,13 @@ class GuiManuscript(QDialog):
self.btnClose = QPushButton(self.tr("Close"))
self.btnClose.clicked.connect(self._doClose)
self.buildBox = QVBoxLayout()
self.buildBox.addStretch(1)
self.buildBox.addWidget(self.btnPreview, 0)
self.buildBox.addWidget(self.btnPrint, 0)
self.buildBox = QHBoxLayout()
self.buildBox.addWidget(self.btnBuild, 0)
self.buildBox.addWidget(self.btnPrint, 0)
self.buildBox.addWidget(self.btnClose, 0)
self.buildBox.addStretch(1)
self.buildBox.setSpacing(CONFIG.pxInt(2))
self.processBox = QHBoxLayout()
self.processBox.addWidget(self.buildProgress)
self.processBox = QVBoxLayout()
self.processBox.addWidget(self.btnPreview)
self.processBox.addLayout(self.buildBox)
# Assemble GUI
@@ -294,10 +287,11 @@ class GuiManuscript(QDialog):
docBuild = NWBuildDocument(self.theProject, build)
docBuild.queueAll()
self.buildProgress.setMaximum(len(docBuild))
for step, status in docBuild.iterBuildHTML(None):
self.buildProgress.setValue(step + 1)
self.docPreview.beginNewBuild(len(docBuild))
for step, _ in docBuild.iterBuildHTML(None):
self.docPreview.buildStep(step + 1)
qApp.processEvents()
qApp.thread().msleep(5)
buildObj = docBuild.lastBuild
assert isinstance(buildObj, ToHtml)
@@ -457,6 +451,8 @@ class _PreviewWidget(QTextBrowser):
"Press the \"Build Preview\" button to generate ..."
))
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
# Document Age
aPalette = self.palette()
aPalette.setColor(QPalette.Background, aPalette.toolTipBase().color())
@@ -473,6 +469,17 @@ class _PreviewWidget(QTextBrowser):
self.ageLabel.setAlignment(Qt.AlignCenter)
self.ageLabel.setFixedHeight(int(2.1*self.mainTheme.fontPixelSize))
# Progress
self.buildProgress = NProgressCircle(self, CONFIG.pxInt(160), CONFIG.pxInt(16))
self.buildProgress.setVisible(False)
self.buildProgress.setMaximum(1)
self.buildProgress.setValue(0)
self.buildProgress.setColours(
back=QColor(255, 255, 255, 224),
track=QColor(196, 196, 196, 128),
text=QColor(0, 0, 0)
)
self._updateDocMargins()
self._updateBuildAge()
@@ -516,11 +523,30 @@ class _PreviewWidget(QTextBrowser):
# Methods
##
def beginNewBuild(self, length: int):
"""Clear the document and show the progress bar."""
self.buildProgress.setMaximum(length)
self.buildProgress.setValue(0)
self.buildProgress.setCentreText(None)
self.buildProgress.setVisible(True)
self.setPlaceholderText("")
self.clear()
return
def buildStep(self, value: int):
"""Update the progress bar value."""
self.buildProgress.setValue(value)
qApp.processEvents()
return
def setContent(self, data: dict):
"""Set the content of the preview widget."""
sPos = self.verticalScrollBar().value()
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
self.buildProgress.setCentreText(self.tr("Processing ..."))
qApp.processEvents()
styles = "\n".join(data.get("styles", [
"h1, h2 {color: rgb(66, 113, 174);}",
"h3, h4 {color: rgb(50, 50, 50);}",
@@ -544,9 +570,13 @@ class _PreviewWidget(QTextBrowser):
self._updateBuildAge()
# Since we change the content while it may still be rendering, we mark
# the document dirty again to make sure it's re-rendered properly.
# the document as dirty again to make sure it's re-rendered properly.
self.document().markContentsDirty(0, self.document().characterCount())
self.buildProgress.setCentreText(self.tr("Done"))
qApp.restoreOverrideCursor()
qApp.processEvents()
QTimer.singleShot(300, self._hideProgress)
return
@@ -580,6 +610,12 @@ class _PreviewWidget(QTextBrowser):
self.ageLabel.setText(text)
return
@pyqtSlot()
def _hideProgress(self):
"""Clean up the build progress bar."""
self.buildProgress.setVisible(False)
return
##
# Internal Functions
##
@@ -591,10 +627,13 @@ class _PreviewWidget(QTextBrowser):
vBar = self.verticalScrollBar()
sW = vBar.width() if vBar.isVisible() else 0
tB = self.frameWidth()
tW = self.width() - 2*tB - sW
vW = self.width() - 2*tB - sW
vH = self.height() - 2*tB
tH = self.ageLabel.height()
self.ageLabel.setGeometry(tB, tB, tW, tH)
pS = self.buildProgress.width()
self.ageLabel.setGeometry(tB, tB, vW, tH)
self.setViewportMargins(0, tH, 0, 0)
self.buildProgress.move((vW-pS)//2, (vH-pS)//2)
return
# END Class _PreviewWidget