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
22class KateColorTreeItem : public QTreeWidgetItem
23{
24public:
25 KateColorTreeItem(const KateColorItem &colorItem, QTreeWidgetItem *parent = nullptr)
26 : QTreeWidgetItem(parent)
27 , m_colorItem(colorItem)
28 {
29 setText(column: 0, atext: m_colorItem.name);
30 if (!colorItem.whatsThis.isEmpty()) {
31 setData(column: 1, role: Qt::WhatsThisRole, value: colorItem.whatsThis);
32 }
33 if (!colorItem.useDefault) {
34 setData(column: 2, role: Qt::ToolTipRole, i18n("Use default color from the color theme"));
35 }
36 }
37
38 QColor color() const
39 {
40 return m_colorItem.color;
41 }
42
43 void setColor(const QColor &c)
44 {
45 m_colorItem.color = c;
46 }
47
48 QColor defaultColor() const
49 {
50 return m_colorItem.defaultColor;
51 }
52
53 bool useDefaultColor() const
54 {
55 return m_colorItem.useDefault;
56 }
57
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(column: 2, role: Qt::ToolTipRole, value: tooltip);
63 }
64
65 QString key() const
66 {
67 return m_colorItem.key;
68 }
69
70 KateColorItem colorItem() const
71 {
72 return m_colorItem;
73 }
74
75private:
76 KateColorItem m_colorItem;
77};
78// END KateColorTreeItem
79
80// BEGIN KateColorTreeDelegate
81class KateColorTreeDelegate : public QStyledItemDelegate
82{
83public:
84 KateColorTreeDelegate(KateColorTreeWidget *widget)
85 : QStyledItemDelegate(widget)
86 , m_tree(widget)
87 {
88 }
89
90 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
109 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(index: 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: index.model()->index(row: 0, column: 0, parent: 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
135 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, sortRole: 0, option: 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, sortRole: 0, option: opt, painter);
167 painter->setClipRegion(cl);
168 painter->setRenderHint(hint: QPainter::Antialiasing, on: 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 = static_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(element: QStyle::CE_PushButton, opt: &opt, p: painter, w: m_tree);
190 opt.rect = m_tree->style()->subElementRect(subElement: QStyle::SE_PushButtonContents, option: &opt, widget: m_tree);
191 opt.rect.adjust(dx1: 1, dy1: 1, dx2: -1, dy2: -1);
192 painter->fillRect(opt.rect, color);
193
194 qDrawShadePanel(p: painter, r: opt.rect, pal: opt.palette, sunken: true, lineWidth: 1, fill: 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(w: 16, h: 16, mode: 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(r: rect, pm: p);
210 }
211 // END: draw reset icon
212 }
213
214private:
215 KateColorTreeWidget *m_tree;
216 KateCategoryDrawer m_categoryDrawer;
217};
218// END KateColorTreeDelegate
219
220KateColorTreeWidget::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
235bool KateColorTreeWidget::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
236{
237 if (m_readOnly) {
238 return false;
239 }
240
241 // accept edit only for color buttons in column 1 and reset in column 2
242 if (!index.parent().isValid() || index.column() < 1) {
243 return QTreeWidget::edit(index, trigger, event);
244 }
245
246 bool accept = false;
247 if (event && event->type() == QEvent::KeyPress) {
248 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
249 accept = (ke->key() == Qt::Key_Space); // allow Space to edit
250 }
251
252 switch (trigger) {
253 case QAbstractItemView::DoubleClicked:
254 case QAbstractItemView::SelectedClicked:
255 case QAbstractItemView::EditKeyPressed: // = F2
256 accept = true;
257 break;
258 default:
259 break;
260 }
261
262 if (accept) {
263 KateColorTreeItem *item = static_cast<KateColorTreeItem *>(itemFromIndex(index));
264 const QColor color = item->useDefaultColor() ? item->defaultColor() : item->color();
265
266 if (index.column() == 1) {
267 const QColor selectedColor = QColorDialog::getColor(initial: color, parent: this, title: QString(), options: QColorDialog::ShowAlphaChannel);
268
269 if (selectedColor.isValid()) {
270 item->setUseDefaultColor(false);
271 item->setColor(selectedColor);
272 viewport()->update();
273 Q_EMIT changed();
274 }
275 } else if (index.column() == 2 && !item->useDefaultColor()) {
276 item->setUseDefaultColor(true);
277 viewport()->update();
278 Q_EMIT changed();
279 }
280
281 return false;
282 }
283 return QTreeWidget::edit(index, trigger, event);
284}
285
286void KateColorTreeWidget::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const
287{
288 Q_UNUSED(painter)
289 Q_UNUSED(rect)
290 Q_UNUSED(index)
291}
292
293void KateColorTreeWidget::selectDefaults()
294{
295 bool somethingChanged = false;
296
297 // use default colors for all selected items
298 for (int a = 0; a < topLevelItemCount(); ++a) {
299 QTreeWidgetItem *top = topLevelItem(index: a);
300 for (int b = 0; b < top->childCount(); ++b) {
301 KateColorTreeItem *it = static_cast<KateColorTreeItem *>(top->child(index: b));
302 Q_ASSERT(it);
303 if (!it->useDefaultColor()) {
304 it->setUseDefaultColor(true);
305 somethingChanged = true;
306 }
307 }
308 }
309
310 if (somethingChanged) {
311 viewport()->update();
312 Q_EMIT changed();
313 }
314}
315
316void KateColorTreeWidget::addColorItem(const KateColorItem &colorItem)
317{
318 QTreeWidgetItem *categoryItem = nullptr;
319 for (int i = 0; i < topLevelItemCount(); ++i) {
320 if (topLevelItem(index: i)->text(column: 0) == colorItem.category) {
321 categoryItem = topLevelItem(index: i);
322 break;
323 }
324 }
325
326 if (!categoryItem) {
327 categoryItem = new QTreeWidgetItem();
328 categoryItem->setText(column: 0, atext: colorItem.category);
329 addTopLevelItem(item: categoryItem);
330 expandItem(item: categoryItem);
331 }
332
333 new KateColorTreeItem(colorItem, categoryItem);
334
335 resizeColumnToContents(column: 0);
336}
337
338void KateColorTreeWidget::addColorItems(const QList<KateColorItem> &colorItems)
339{
340 for (const KateColorItem &item : colorItems) {
341 addColorItem(colorItem: item);
342 }
343}
344
345QList<KateColorItem> KateColorTreeWidget::colorItems() const
346{
347 QList<KateColorItem> items;
348 for (int a = 0; a < topLevelItemCount(); ++a) {
349 QTreeWidgetItem *top = topLevelItem(index: a);
350 for (int b = 0; b < top->childCount(); ++b) {
351 KateColorTreeItem *item = static_cast<KateColorTreeItem *>(top->child(index: b));
352 Q_ASSERT(item);
353 items.append(t: item->colorItem());
354 }
355 }
356 return items;
357}
358
359QColor KateColorTreeWidget::findColor(const QString &key) const
360{
361 for (int a = 0; a < topLevelItemCount(); ++a) {
362 QTreeWidgetItem *top = topLevelItem(index: a);
363 for (int b = 0; b < top->childCount(); ++b) {
364 KateColorTreeItem *item = static_cast<KateColorTreeItem *>(top->child(index: b));
365 if (item->key() == key) {
366 if (item->useDefaultColor()) {
367 return item->defaultColor();
368 } else {
369 return item->color();
370 }
371 }
372 }
373 }
374 return QColor();
375}
376
377bool KateColorTreeWidget::readOnly() const
378{
379 return m_readOnly;
380}
381
382void KateColorTreeWidget::setReadOnly(bool readOnly)
383{
384 m_readOnly = readOnly;
385}
386
387#include "moc_katecolortreewidget.cpp"
388

source code of ktexteditor/src/syntax/katecolortreewidget.cpp