Add custom painting of project items in the project list
This commit is contained in:
@@ -29,17 +29,21 @@ from typing import TYPE_CHECKING
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtGui import QCloseEvent, QPaintEvent, QPainter
|
from PyQt5.QtGui import QCloseEvent, QColor, QFont, QPaintEvent, QPainter, QPen
|
||||||
from PyQt5.QtCore import QEvent, QPoint, Qt, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import (
|
||||||
|
QAbstractListModel, QEvent, QModelIndex, QObject, QPoint, QSize, Qt,
|
||||||
|
pyqtSignal, pyqtSlot
|
||||||
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout,
|
QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout,
|
||||||
QHBoxLayout, QLabel, QLineEdit, QMenu, QPushButton, QScrollArea, QSpinBox,
|
QHBoxLayout, QLabel, QLineEdit, QListView, QMenu, QPushButton, QScrollArea,
|
||||||
QStackedWidget, QToolButton, QTreeWidget, QVBoxLayout, QWidget
|
QSpinBox, QStackedWidget, QStyleOptionViewItem, QStyledItemDelegate,
|
||||||
|
QToolButton, QVBoxLayout, QWidget, qApp
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED, __version__, __date__
|
from novelwriter import CONFIG, SHARED, __version__, __date__
|
||||||
from novelwriter.enum import nwItemClass
|
from novelwriter.enum import nwItemClass
|
||||||
from novelwriter.common import makeFileNameSafe
|
from novelwriter.common import formatInt, makeFileNameSafe
|
||||||
from novelwriter.constants import nwUnicode
|
from novelwriter.constants import nwUnicode
|
||||||
from novelwriter.core.coretools import ProjectBuilder
|
from novelwriter.core.coretools import ProjectBuilder
|
||||||
from novelwriter.extensions.switch import NSwitch
|
from novelwriter.extensions.switch import NSwitch
|
||||||
@@ -199,8 +203,12 @@ class _OpenProjectPage(QWidget):
|
|||||||
def __init__(self, parent: QWidget) -> None:
|
def __init__(self, parent: QWidget) -> None:
|
||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
self.listWidget = QTreeWidget(self)
|
self.itemDelegate = _ProjectListItem(self)
|
||||||
self.listWidget.setHeaderHidden(True)
|
self.listModel = _ProjectListModel(self)
|
||||||
|
|
||||||
|
self.listWidget = QListView(self)
|
||||||
|
self.listWidget.setItemDelegate(self.itemDelegate)
|
||||||
|
self.listWidget.setModel(self.listModel)
|
||||||
|
|
||||||
self.outerBox = QVBoxLayout()
|
self.outerBox = QVBoxLayout()
|
||||||
self.outerBox.addWidget(self.listWidget)
|
self.outerBox.addWidget(self.listWidget)
|
||||||
@@ -210,7 +218,7 @@ class _OpenProjectPage(QWidget):
|
|||||||
|
|
||||||
baseCol = self.palette().base().color()
|
baseCol = self.palette().base().color()
|
||||||
self.setStyleSheet((
|
self.setStyleSheet((
|
||||||
"QTreeWidget {{border: none; background: rgba({r},{g},{b},0.5);}}"
|
"QListView {{border: none; background: rgba({r},{g},{b},0.5);}}"
|
||||||
).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue()))
|
).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue()))
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -218,6 +226,93 @@ class _OpenProjectPage(QWidget):
|
|||||||
# END Class _OpenProjectPage
|
# END Class _OpenProjectPage
|
||||||
|
|
||||||
|
|
||||||
|
class _ProjectListItem(QStyledItemDelegate):
|
||||||
|
|
||||||
|
__slots__ = ("_pos", "_hPx", "_tFont", "_dFont", "_dPen", "_icon")
|
||||||
|
|
||||||
|
def __init__(self, parent: QWidget) -> None:
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
|
fPx = SHARED.theme.fontPixelSize
|
||||||
|
fPt = SHARED.theme.fontPointSize
|
||||||
|
tPx = round(1.2 * fPx)
|
||||||
|
mPx = CONFIG.pxInt(4)
|
||||||
|
iPx = tPx + fPx
|
||||||
|
|
||||||
|
self._pos = (
|
||||||
|
3*mPx//2, # Icon y pos
|
||||||
|
iPx + mPx, # Text x pos
|
||||||
|
mPx, # Line 1 y pos
|
||||||
|
mPx + tPx, # Line 2 y pos
|
||||||
|
mPx + tPx + fPx, # Line 3 y pos
|
||||||
|
)
|
||||||
|
self._hPx = 2*mPx + tPx + 2*fPx
|
||||||
|
|
||||||
|
self._tFont = qApp.font()
|
||||||
|
self._tFont.setPointSizeF(1.2*fPt)
|
||||||
|
self._tFont.setWeight(QFont.Weight.Bold)
|
||||||
|
|
||||||
|
self._dFont = qApp.font()
|
||||||
|
self._dFont.setPointSizeF(fPt)
|
||||||
|
self._dPen = QPen(QColor(*SHARED.theme.helpText))
|
||||||
|
|
||||||
|
self._icon = SHARED.theme.getPixmap("proj_nwx", (iPx, iPx))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def paint(self, painter: QPainter, opt: QStyleOptionViewItem, index: QModelIndex) -> None:
|
||||||
|
"""Paint a project entry on the canvas."""
|
||||||
|
rect = opt.rect
|
||||||
|
title, path, details = index.data()
|
||||||
|
tFlag = Qt.TextFlag.TextSingleLine
|
||||||
|
m, x, y1, y2, y3 = self._pos
|
||||||
|
|
||||||
|
painter.save()
|
||||||
|
painter.drawPixmap(0, rect.top() + m, self._icon)
|
||||||
|
painter.setFont(self._tFont)
|
||||||
|
painter.drawText(rect.adjusted(x, y1, -x, 0), tFlag, str(title))
|
||||||
|
painter.setFont(self._dFont)
|
||||||
|
painter.setPen(self._dPen)
|
||||||
|
painter.drawText(rect.adjusted(x, y2, -x, 0), tFlag, details)
|
||||||
|
painter.drawText(rect.adjusted(x, y3, -x, 0), tFlag, path)
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def sizeHint(self, opt: QStyleOptionViewItem, index: QModelIndex) -> QSize:
|
||||||
|
"""Set the size hint to fixed height."""
|
||||||
|
return QSize(opt.rect.width(), self._hPx)
|
||||||
|
|
||||||
|
# END Class _ProjectListItem
|
||||||
|
|
||||||
|
|
||||||
|
class _ProjectListModel(QAbstractListModel):
|
||||||
|
|
||||||
|
def __init__(self, parent: QObject) -> None:
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
|
trWords = self.tr("words")
|
||||||
|
|
||||||
|
data = []
|
||||||
|
records = sorted(CONFIG.recentProjects.listEntries(), key=lambda x: x[3], reverse=True)
|
||||||
|
for path, title, words, time in records:
|
||||||
|
when = datetime.fromtimestamp(time).strftime("%x")
|
||||||
|
data.append((title, path, f"{when}, {formatInt(words)} {trWords}"))
|
||||||
|
self._data = data
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def rowCount(self, parent: QModelIndex | None = None) -> int:
|
||||||
|
"""Return the size of the model."""
|
||||||
|
return len(self._data)
|
||||||
|
|
||||||
|
def data(self, index: QModelIndex, role: int = 0) -> tuple[str, str, str, str]:
|
||||||
|
"""Return data for an individual item."""
|
||||||
|
return self._data[index.row()] if index.isValid() else ("", "", "", "")
|
||||||
|
|
||||||
|
# END Class _ProjectListModel
|
||||||
|
|
||||||
|
|
||||||
class _NewProjectPage(QWidget):
|
class _NewProjectPage(QWidget):
|
||||||
|
|
||||||
cancelNewProject = pyqtSignal()
|
cancelNewProject = pyqtSignal()
|
||||||
|
|||||||
Reference in New Issue
Block a user