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
12const int ELLIPSIS_GRADIENT_WIDTH = 16;
13
14TaskDelegate::TaskDelegate(QObject *parent) :
15 QStyledItemDelegate(parent)
16{ }
17
18TaskDelegate::~TaskDelegate() = default;
19
20QSize 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
76void TaskDelegate::emitSizeHintChanged(const QModelIndex &index)
77{
78 emit sizeHintChanged(index);
79}
80
81void TaskDelegate::currentChanged(const QModelIndex &current, const QModelIndex &previous)
82{
83 emit sizeHintChanged(current);
84 emit sizeHintChanged(previous);
85}
86
87void 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