1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
2 | // |
3 | // SPDX-License-Identifier: GPL-3.0-or-later |
4 | |
5 | #include "taskdelegate.h" |
6 | |
7 | #include <QListView> |
8 | #include <QTextLayout> |
9 | #include <QPainter> |
10 | #include <QDir> |
11 | |
12 | const int ELLIPSIS_GRADIENT_WIDTH = 16; |
13 | |
14 | TaskDelegate::TaskDelegate(QObject *parent) : |
15 | QStyledItemDelegate(parent) |
16 | { } |
17 | |
18 | TaskDelegate::~TaskDelegate() = default; |
19 | |
20 | QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const |
21 | { |
22 | QStyleOptionViewItem opt = option; |
23 | initStyleOption(&opt, index); |
24 | |
25 | auto view = qobject_cast<const QAbstractItemView *>(opt.widget); |
26 | const bool selected = (view->selectionModel()->currentIndex() == index); |
27 | QSize s; |
28 | s.setWidth(option.rect.width()); |
29 | |
30 | if (!selected && option.font == cachedFont && cachedHeight > 0) { |
31 | s.setHeight(cachedHeight); |
32 | return s; |
33 | } |
34 | |
35 | QFontMetrics fm(option.font); |
36 | int fontHeight = fm.height(); |
37 | int fontLeading = fm.leading(); |
38 | |
39 | auto model = static_cast<TaskModel *>(view->model()); |
40 | Positions positions(option, model); |
41 | |
42 | if (selected) { |
43 | QString description = index.data(TaskModel::Description).toString(); |
44 | // Layout the description |
45 | int leading = fontLeading; |
46 | int height = 0; |
47 | description.replace(QLatin1Char('\n'), QChar::LineSeparator); |
48 | QTextLayout tl(description); |
49 | tl.beginLayout(); |
50 | while (true) { |
51 | QTextLine line = tl.createLine(); |
52 | if (!line.isValid()) |
53 | break; |
54 | line.setLineWidth(positions.textAreaWidth()); |
55 | height += leading; |
56 | line.setPosition(QPoint(0, height)); |
57 | height += static_cast<int>(line.height()); |
58 | } |
59 | tl.endLayout(); |
60 | |
61 | s.setHeight(height + leading + fontHeight + 3); |
62 | } else { |
63 | s.setHeight(fontHeight + 3); |
64 | } |
65 | if (s.height() < positions.minimumHeight()) |
66 | s.setHeight(positions.minimumHeight()); |
67 | |
68 | if (!selected) { |
69 | cachedHeight = s.height(); |
70 | cachedFont = option.font; |
71 | } |
72 | |
73 | return s; |
74 | } |
75 | |
76 | void TaskDelegate::emitSizeHintChanged(const QModelIndex &index) |
77 | { |
78 | emit sizeHintChanged(index); |
79 | } |
80 | |
81 | void TaskDelegate::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
82 | { |
83 | emit sizeHintChanged(current); |
84 | emit sizeHintChanged(previous); |
85 | } |
86 | |
87 | void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const |
88 | { |
89 | QStyleOptionViewItem opt = option; |
90 | initStyleOption(&opt, index); |
91 | painter->save(); |
92 | |
93 | QFontMetrics fm(opt.font); |
94 | QColor backgroundColor; |
95 | QColor textColor; |
96 | |
97 | auto view = qobject_cast<const QAbstractItemView *>(opt.widget); |
98 | bool selected = view->selectionModel()->currentIndex() == index; |
99 | |
100 | if (selected) { |
101 | painter->setBrush(opt.palette.highlight().color()); |
102 | backgroundColor = opt.palette.highlight().color(); |
103 | } else { |
104 | painter->setBrush(opt.palette.window().color()); |
105 | backgroundColor = opt.palette.window().color(); |
106 | } |
107 | painter->setPen(Qt::NoPen); |
108 | painter->drawRect(opt.rect); |
109 | |
110 | // Set Text Color |
111 | if (selected) |
112 | textColor = opt.palette.highlightedText().color(); |
113 | else |
114 | textColor = opt.palette.text().color(); |
115 | |
116 | painter->setPen(textColor); |
117 | |
118 | auto model = static_cast<TaskModel *>(view->model()); |
119 | Positions positions(opt, model); |
120 | |
121 | // Paint TaskIconArea: |
122 | QIcon icon = index.data(TaskModel::Icon).value<QIcon>(); |
123 | painter->drawPixmap(positions.left(), positions.getTop(), |
124 | icon.pixmap(positions.taskIconWidth(), positions.taskIconHeight())); |
125 | |
126 | // Paint TextArea: |
127 | if (!selected) { |
128 | // in small mode we lay out differently |
129 | QString bottom = index.data(TaskModel::Description).toString().split(QLatin1Char('\n')).first(); |
130 | painter->setClipRect(positions.textArea()); |
131 | painter->drawText(positions.textAreaLeft(), positions.getTop() + fm.ascent(), bottom); |
132 | if (fm.horizontalAdvance(bottom) > positions.textAreaWidth()) { |
133 | // draw a gradient to mask the text |
134 | int gradientStart = positions.textAreaRight() - ELLIPSIS_GRADIENT_WIDTH + 1; |
135 | QLinearGradient lg(gradientStart, 0, gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0); |
136 | lg.setColorAt(0, Qt::transparent); |
137 | lg.setColorAt(1, backgroundColor); |
138 | painter->fillRect(gradientStart, positions.getTop(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg); |
139 | } |
140 | } else { |
141 | // Description |
142 | QString description = index.data(TaskModel::Description).toString(); |
143 | // Layout the description |
144 | int leading = fm.leading(); |
145 | int height = 0; |
146 | description.replace(QLatin1Char('\n'), QChar::LineSeparator); |
147 | QTextLayout tl(description); |
148 | tl.beginLayout(); |
149 | while (true) { |
150 | QTextLine line = tl.createLine(); |
151 | if (!line.isValid()) |
152 | break; |
153 | line.setLineWidth(positions.textAreaWidth()); |
154 | height += leading; |
155 | line.setPosition(QPoint(0, height)); |
156 | height += static_cast<int>(line.height()); |
157 | } |
158 | tl.endLayout(); |
159 | tl.draw(painter, QPoint(positions.textAreaLeft(), positions.getTop())); |
160 | |
161 | QColor mix; |
162 | mix.setRgb( static_cast<int>(0.7 * textColor.red() + 0.3 * backgroundColor.red()), |
163 | static_cast<int>(0.7 * textColor.green() + 0.3 * backgroundColor.green()), |
164 | static_cast<int>(0.7 * textColor.blue() + 0.3 * backgroundColor.blue())); |
165 | painter->setPen(mix); |
166 | |
167 | const QString directory = QDir::toNativeSeparators(index.data(TaskModel::File).toString()); |
168 | int secondBaseLine = positions.getTop() + fm.ascent() + height + leading; |
169 | if (index.data(TaskModel::FileNotFound).toBool() |
170 | && !directory.isEmpty()) { |
171 | QString fileNotFound = tr("File not found: %1" ).arg(directory); |
172 | painter->setPen(Qt::red); |
173 | painter->drawText(positions.textAreaLeft(), secondBaseLine, fileNotFound); |
174 | } else { |
175 | painter->drawText(positions.textAreaLeft(), secondBaseLine, directory); |
176 | } |
177 | } |
178 | painter->setPen(textColor); |
179 | |
180 | // Paint FileArea |
181 | QString file = index.data(TaskModel::File).toString(); |
182 | const int pos = file.lastIndexOf(QLatin1Char('/')); |
183 | if (pos != -1) |
184 | file = file.mid(pos +1); |
185 | const int realFileWidth = fm.horizontalAdvance(file); |
186 | painter->setClipRect(positions.fileArea()); |
187 | painter->drawText(qMin(positions.fileAreaLeft(), positions.fileAreaRight() - realFileWidth), |
188 | positions.getTop() + fm.ascent(), file); |
189 | if (realFileWidth > positions.fileAreaWidth()) { |
190 | // draw a gradient to mask the text |
191 | int gradientStart = positions.fileAreaLeft() - 1; |
192 | QLinearGradient lg(gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0, gradientStart, 0); |
193 | lg.setColorAt(0, Qt::transparent); |
194 | lg.setColorAt(1, backgroundColor); |
195 | painter->fillRect(gradientStart, positions.getTop(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg); |
196 | } |
197 | |
198 | // Paint LineArea |
199 | int line = index.data(TaskModel::Line).toInt(); |
200 | int movedLine = index.data(TaskModel::MovedLine).toInt(); |
201 | QString lineText; |
202 | |
203 | if (line == -1) { |
204 | // No line information at all |
205 | } else if (movedLine == -1) { |
206 | // removed the line, but we had line information, show the line in () |
207 | QFont f = painter->font(); |
208 | f.setItalic(true); |
209 | painter->setFont(f); |
210 | lineText = QLatin1Char('(') + QString::number(line) + QLatin1Char(')'); |
211 | } else if (movedLine != line) { |
212 | // The line was moved |
213 | QFont f = painter->font(); |
214 | f.setItalic(true); |
215 | painter->setFont(f); |
216 | lineText = QString::number(movedLine); |
217 | } else { |
218 | lineText = QString::number(line); |
219 | } |
220 | |
221 | painter->setClipRect(positions.lineArea()); |
222 | const int realLineWidth = fm.horizontalAdvance(lineText); |
223 | painter->drawText(positions.lineAreaRight() - realLineWidth, positions.getTop() + fm.ascent(), lineText); |
224 | painter->setClipRect(opt.rect); |
225 | |
226 | // Separator lines |
227 | painter->setPen(QColor::fromRgb(150,150,150)); |
228 | const QRectF borderRect = QRectF(opt.rect).adjusted(0.5, 0.5, -0.5, -0.5); |
229 | painter->drawLine(borderRect.bottomLeft(), borderRect.bottomRight()); |
230 | painter->restore(); |
231 | } |
232 | |