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