1 /*
2 SPDX-FileCopyrightText: 2012-2018 Dominik Haumann <dhaumann@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "katecolortreewidget.h"
8
9 #include "katecategorydrawer.h"
10
11 #include <QPainter>
12 #include <QStyledItemDelegate>
13
14 #include <KLocalizedString>
15
16 #include <QColorDialog>
17 #include <QEvent>
18 #include <QKeyEvent>
19 #include <qdrawutil.h>
20
21 // BEGIN KateColorTreeItem
22 class KateColorTreeItem : public QTreeWidgetItem
23 {
24 public:
KateColorTreeItem(const KateColorItem & colorItem,QTreeWidgetItem * parent=nullptr)25 KateColorTreeItem(const KateColorItem &colorItem, QTreeWidgetItem *parent = nullptr)
26 : QTreeWidgetItem(parent)
27 , m_colorItem(colorItem)
28 {
29 setText(0, m_colorItem.name);
30 if (!colorItem.whatsThis.isEmpty()) {
31 setData(1, Qt::WhatsThisRole, colorItem.whatsThis);
32 }
33 if (!colorItem.useDefault) {
34 setData(2, Qt::ToolTipRole, i18n("Use default color from the color theme"));
35 }
36 }
37
color() const38 QColor color() const
39 {
40 return m_colorItem.color;
41 }
42
setColor(const QColor & c)43 void setColor(const QColor &c)
44 {
45 m_colorItem.color = c;
46 }
47
defaultColor() const48 QColor defaultColor() const
49 {
50 return m_colorItem.defaultColor;
51 }
52
useDefaultColor() const53 bool useDefaultColor() const
54 {
55 return m_colorItem.useDefault;
56 }
57
setUseDefaultColor(bool useDefault)58 void setUseDefaultColor(bool useDefault)
59 {
60 m_colorItem.useDefault = useDefault;
61 QString tooltip = useDefault ? QString() : i18n("Use default color from the color theme");
62 setData(2, Qt::ToolTipRole, tooltip);
63 }
64
key()65 QString key()
66 {
67 return m_colorItem.key;
68 }
69
colorItem() const70 KateColorItem colorItem() const
71 {
72 return m_colorItem;
73 }
74
75 private:
76 KateColorItem m_colorItem;
77 };
78 // END KateColorTreeItem
79
80 // BEGIN KateColorTreeDelegate
81 class KateColorTreeDelegate : public QStyledItemDelegate
82 {
83 public:
KateColorTreeDelegate(KateColorTreeWidget * widget)84 KateColorTreeDelegate(KateColorTreeWidget *widget)
85 : QStyledItemDelegate(widget)
86 , m_tree(widget)
87 {
88 }
89
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const90 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
91 {
92 QSize sh = QStyledItemDelegate::sizeHint(option, index);
93 if (!index.parent().isValid()) {
94 sh.rheight() += 2 * m_categoryDrawer.leftMargin();
95 } else {
96 sh.rheight() += m_categoryDrawer.leftMargin();
97 }
98 if (index.column() == 0) {
99 sh.rwidth() += m_categoryDrawer.leftMargin();
100 } else if (index.column() == 1) {
101 sh.rwidth() = 150;
102 } else {
103 sh.rwidth() += m_categoryDrawer.leftMargin();
104 }
105
106 return sh;
107 }
108
fullCategoryRect(const QStyleOptionViewItem & option,const QModelIndex & index) const109 QRect fullCategoryRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
110 {
111 QModelIndex i = index;
112 if (i.parent().isValid()) {
113 i = i.parent();
114 }
115
116 QTreeWidgetItem *item = m_tree->itemFromIndex(i);
117 QRect r = m_tree->visualItemRect(item);
118
119 // adapt width
120 r.setLeft(m_categoryDrawer.leftMargin());
121 r.setWidth(m_tree->viewport()->width() - m_categoryDrawer.leftMargin() - m_categoryDrawer.rightMargin());
122
123 // adapt height
124 if (item->isExpanded() && item->childCount() > 0) {
125 const int childCount = item->childCount();
126 const int h = sizeHint(option, index.model()->index(0, 0, index)).height();
127 r.setHeight(r.height() + childCount * h);
128 }
129
130 r.setTop(r.top() + m_categoryDrawer.leftMargin());
131
132 return r;
133 }
134
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const135 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
136 {
137 Q_ASSERT(index.isValid());
138 Q_ASSERT(index.column() >= 0 && index.column() <= 2);
139
140 // BEGIN: draw toplevel items
141 if (!index.parent().isValid()) {
142 QStyleOptionViewItem opt(option);
143 const QRegion cl = painter->clipRegion();
144 painter->setClipRect(opt.rect);
145 opt.rect = fullCategoryRect(option, index);
146 m_categoryDrawer.drawCategory(index, 0, opt, painter);
147 painter->setClipRegion(cl);
148 return;
149 }
150 // END: draw toplevel items
151
152 // BEGIN: draw background of category for all other items
153 {
154 QStyleOptionViewItem opt(option);
155 opt.rect = fullCategoryRect(option, index);
156 const QRegion cl = painter->clipRegion();
157 QRect cr = option.rect;
158 if (index.column() == 0) {
159 if (m_tree->layoutDirection() == Qt::LeftToRight) {
160 cr.setLeft(5);
161 } else {
162 cr.setRight(opt.rect.right());
163 }
164 }
165 painter->setClipRect(cr);
166 m_categoryDrawer.drawCategory(index, 0, opt, painter);
167 painter->setClipRegion(cl);
168 painter->setRenderHint(QPainter::Antialiasing, false);
169 }
170 // END: draw background of category for all other items
171
172 // paint the text
173 QStyledItemDelegate::paint(painter, option, index);
174 if (index.column() == 0) {
175 return;
176 }
177
178 painter->setClipRect(option.rect);
179 KateColorTreeItem *item = dynamic_cast<KateColorTreeItem *>(m_tree->itemFromIndex(index));
180
181 // BEGIN: draw color button
182 if (index.column() == 1) {
183 QColor color = item->useDefaultColor() ? item->defaultColor() : item->color();
184
185 QStyleOptionButton opt;
186 opt.rect = option.rect;
187 opt.palette = m_tree->palette();
188
189 m_tree->style()->drawControl(QStyle::CE_PushButton, &opt, painter, m_tree);
190 opt.rect = m_tree->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, m_tree);
191 opt.rect.adjust(1, 1, -1, -1);
192 painter->fillRect(opt.rect, color);
193
194 qDrawShadePanel(painter, opt.rect, opt.palette, true, 1, nullptr);
195 }
196 // END: draw color button
197
198 // BEGIN: draw reset icon
199 if (index.column() == 2 && !item->useDefaultColor()) {
200 // get right pixmap
201 const bool enabled = (option.state & QStyle::State_MouseOver || option.state & QStyle::State_HasFocus);
202 const QPixmap p = QIcon::fromTheme(QStringLiteral("edit-undo")).pixmap(16, 16, enabled ? QIcon::Normal : QIcon::Disabled);
203
204 // compute rect with scaled sizes
205 const QRect rect(option.rect.left() + 10,
206 option.rect.top() + (option.rect.height() - p.height() / p.devicePixelRatio() + 1) / 2,
207 p.width() / p.devicePixelRatio(),
208 p.height() / p.devicePixelRatio());
209 painter->drawPixmap(rect, p);
210 }
211 // END: draw reset icon
212 }
213
214 private:
215 KateColorTreeWidget *m_tree;
216 KateCategoryDrawer m_categoryDrawer;
217 };
218 // END KateColorTreeDelegate
219
KateColorTreeWidget(QWidget * parent)220 KateColorTreeWidget::KateColorTreeWidget(QWidget *parent)
221 : QTreeWidget(parent)
222 {
223 setItemDelegate(new KateColorTreeDelegate(this));
224
225 QStringList headers;
226 headers << QString() // i18nc("@title:column the color name", "Color Role")
227 << QString() // i18nc("@title:column a color button", "Color")
228 << QString(); // i18nc("@title:column use default color", "Reset")
229 setHeaderLabels(headers);
230 setHeaderHidden(true);
231 setRootIsDecorated(false);
232 setIndentation(25);
233 }
234
edit(const QModelIndex & index,EditTrigger trigger,QEvent * event)235 bool KateColorTreeWidget::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
236 {
237 // accept edit only for color buttons in column 1 and reset in column 2
238 if (!index.parent().isValid() || index.column() < 1) {
239 return QTreeWidget::edit(index, trigger, event);
240 }
241
242 bool accept = false;
243 if (event && event->type() == QEvent::KeyPress) {
244 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
245 accept = (ke->key() == Qt::Key_Space); // allow Space to edit
246 }
247
248 switch (trigger) {
249 case QAbstractItemView::DoubleClicked:
250 case QAbstractItemView::SelectedClicked:
251 case QAbstractItemView::EditKeyPressed: // = F2
252 accept = true;
253 break;
254 default:
255 break;
256 }
257
258 if (accept) {
259 KateColorTreeItem *item = dynamic_cast<KateColorTreeItem *>(itemFromIndex(index));
260 const QColor color = item->useDefaultColor() ? item->defaultColor() : item->color();
261
262 if (index.column() == 1) {
263 const QColor selectedColor = QColorDialog::getColor(color, this, QString(), QColorDialog::ShowAlphaChannel);
264
265 if (selectedColor.isValid()) {
266 item->setUseDefaultColor(false);
267 item->setColor(selectedColor);
268 viewport()->update();
269 Q_EMIT changed();
270 }
271 } else if (index.column() == 2 && !item->useDefaultColor()) {
272 item->setUseDefaultColor(true);
273 viewport()->update();
274 Q_EMIT changed();
275 }
276
277 return false;
278 }
279 return QTreeWidget::edit(index, trigger, event);
280 }
281
drawBranches(QPainter * painter,const QRect & rect,const QModelIndex & index) const282 void KateColorTreeWidget::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const
283 {
284 Q_UNUSED(painter)
285 Q_UNUSED(rect)
286 Q_UNUSED(index)
287 }
288
selectDefaults()289 void KateColorTreeWidget::selectDefaults()
290 {
291 bool somethingChanged = false;
292
293 // use default colors for all selected items
294 for (int a = 0; a < topLevelItemCount(); ++a) {
295 QTreeWidgetItem *top = topLevelItem(a);
296 for (int b = 0; b < top->childCount(); ++b) {
297 KateColorTreeItem *it = dynamic_cast<KateColorTreeItem *>(top->child(b));
298 Q_ASSERT(it);
299 if (!it->useDefaultColor()) {
300 it->setUseDefaultColor(true);
301 somethingChanged = true;
302 }
303 }
304 }
305
306 if (somethingChanged) {
307 viewport()->update();
308 Q_EMIT changed();
309 }
310 }
311
addColorItem(const KateColorItem & colorItem)312 void KateColorTreeWidget::addColorItem(const KateColorItem &colorItem)
313 {
314 QTreeWidgetItem *categoryItem = nullptr;
315 for (int i = 0; i < topLevelItemCount(); ++i) {
316 if (topLevelItem(i)->text(0) == colorItem.category) {
317 categoryItem = topLevelItem(i);
318 break;
319 }
320 }
321
322 if (!categoryItem) {
323 categoryItem = new QTreeWidgetItem();
324 categoryItem->setText(0, colorItem.category);
325 addTopLevelItem(categoryItem);
326 expandItem(categoryItem);
327 }
328
329 new KateColorTreeItem(colorItem, categoryItem);
330
331 resizeColumnToContents(0);
332 }
333
addColorItems(const QVector<KateColorItem> & colorItems)334 void KateColorTreeWidget::addColorItems(const QVector<KateColorItem> &colorItems)
335 {
336 for (const KateColorItem &item : colorItems) {
337 addColorItem(item);
338 }
339 }
340
colorItems() const341 QVector<KateColorItem> KateColorTreeWidget::colorItems() const
342 {
343 QVector<KateColorItem> items;
344 for (int a = 0; a < topLevelItemCount(); ++a) {
345 QTreeWidgetItem *top = topLevelItem(a);
346 for (int b = 0; b < top->childCount(); ++b) {
347 KateColorTreeItem *item = dynamic_cast<KateColorTreeItem *>(top->child(b));
348 Q_ASSERT(item);
349 items.append(item->colorItem());
350 }
351 }
352 return items;
353 }
354
findColor(const QString & key) const355 QColor KateColorTreeWidget::findColor(const QString &key) const
356 {
357 for (int a = 0; a < topLevelItemCount(); ++a) {
358 QTreeWidgetItem *top = topLevelItem(a);
359 for (int b = 0; b < top->childCount(); ++b) {
360 KateColorTreeItem *item = dynamic_cast<KateColorTreeItem *>(top->child(b));
361 if (item->key() == key) {
362 if (item->useDefaultColor()) {
363 return item->defaultColor();
364 } else {
365 return item->color();
366 }
367 }
368 }
369 }
370 return QColor();
371 }
372