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: |
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 | |
75 | private: |
76 | KateColorItem m_colorItem; |
77 | }; |
78 | // END KateColorTreeItem |
79 | |
80 | // BEGIN KateColorTreeDelegate |
81 | class KateColorTreeDelegate : public QStyledItemDelegate |
82 | { |
83 | public: |
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 | painter->setClipRect(option.rect); |
173 | |
174 | // BEGIN: draw color button |
175 | if (index.column() == 1) { |
176 | KateColorTreeItem *item = static_cast<KateColorTreeItem *>(m_tree->itemFromIndex(index)); |
177 | QColor color = item->useDefaultColor() ? item->defaultColor() : item->color(); |
178 | |
179 | QStyleOptionButton opt; |
180 | opt.rect = option.rect; |
181 | opt.palette = m_tree->palette(); |
182 | |
183 | m_tree->style()->drawControl(element: QStyle::CE_PushButton, opt: &opt, p: painter, w: m_tree); |
184 | opt.rect = m_tree->style()->subElementRect(subElement: QStyle::SE_PushButtonContents, option: &opt, widget: m_tree); |
185 | opt.rect.adjust(dx1: 1, dy1: 1, dx2: -1, dy2: -1); |
186 | painter->fillRect(opt.rect, color); |
187 | |
188 | qDrawShadePanel(p: painter, r: opt.rect, pal: opt.palette, sunken: true, lineWidth: 1, fill: nullptr); |
189 | } |
190 | // END: draw color button |
191 | |
192 | // BEGIN: draw reset icon |
193 | if (index.column() == 2 && !static_cast<KateColorTreeItem *>(m_tree->itemFromIndex(index))->useDefaultColor()) { |
194 | // get right pixmap |
195 | const bool enabled = (option.state & QStyle::State_MouseOver || option.state & QStyle::State_HasFocus); |
196 | const QPixmap p = QIcon::fromTheme(QStringLiteral("edit-undo" )).pixmap(w: 16, h: 16, mode: enabled ? QIcon::Normal : QIcon::Disabled); |
197 | |
198 | // compute rect with scaled sizes |
199 | const QRect rect(option.rect.left() + 10, |
200 | option.rect.top() + (option.rect.height() - p.height() / p.devicePixelRatio() + 1) / 2, |
201 | p.width() / p.devicePixelRatio(), |
202 | p.height() / p.devicePixelRatio()); |
203 | painter->drawPixmap(r: rect, pm: p); |
204 | } |
205 | // END: draw reset icon |
206 | |
207 | // paint the text |
208 | QStyledItemDelegate::paint(painter, option, index); |
209 | } |
210 | |
211 | private: |
212 | KateColorTreeWidget *m_tree; |
213 | KateCategoryDrawer m_categoryDrawer; |
214 | }; |
215 | // END KateColorTreeDelegate |
216 | |
217 | KateColorTreeWidget::KateColorTreeWidget(QWidget *parent) |
218 | : QTreeWidget(parent) |
219 | { |
220 | setItemDelegate(new KateColorTreeDelegate(this)); |
221 | |
222 | QStringList ; |
223 | headers << QString() // i18nc("@title:column the color name", "Color Role") |
224 | << QString() // i18nc("@title:column a color button", "Color") |
225 | << QString(); // i18nc("@title:column use default color", "Reset") |
226 | setHeaderLabels(headers); |
227 | setHeaderHidden(true); |
228 | setRootIsDecorated(false); |
229 | setIndentation(25); |
230 | setSelectionMode(QAbstractItemView::NoSelection); |
231 | } |
232 | |
233 | bool KateColorTreeWidget::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) |
234 | { |
235 | if (m_readOnly) { |
236 | return false; |
237 | } |
238 | |
239 | // accept edit only for color buttons in column 1 and reset in column 2 |
240 | if (!index.parent().isValid() || index.column() < 1) { |
241 | return QTreeWidget::edit(index, trigger, event); |
242 | } |
243 | |
244 | bool accept = false; |
245 | if (event && event->type() == QEvent::KeyPress) { |
246 | QKeyEvent *ke = static_cast<QKeyEvent *>(event); |
247 | accept = (ke->key() == Qt::Key_Space); // allow Space to edit |
248 | } |
249 | |
250 | switch (trigger) { |
251 | case QAbstractItemView::DoubleClicked: |
252 | case QAbstractItemView::SelectedClicked: |
253 | case QAbstractItemView::EditKeyPressed: // = F2 |
254 | accept = true; |
255 | break; |
256 | default: |
257 | break; |
258 | } |
259 | |
260 | if (accept) { |
261 | KateColorTreeItem *item = static_cast<KateColorTreeItem *>(itemFromIndex(index)); |
262 | const QColor color = item->useDefaultColor() ? item->defaultColor() : item->color(); |
263 | |
264 | if (index.column() == 1) { |
265 | const QColor selectedColor = QColorDialog::getColor(initial: color, parent: this, title: QString(), options: QColorDialog::ShowAlphaChannel); |
266 | |
267 | if (selectedColor.isValid()) { |
268 | item->setUseDefaultColor(false); |
269 | item->setColor(selectedColor); |
270 | viewport()->update(); |
271 | Q_EMIT changed(); |
272 | } |
273 | } else if (index.column() == 2 && !item->useDefaultColor()) { |
274 | item->setUseDefaultColor(true); |
275 | viewport()->update(); |
276 | Q_EMIT changed(); |
277 | } |
278 | |
279 | return false; |
280 | } |
281 | return QTreeWidget::edit(index, trigger, event); |
282 | } |
283 | |
284 | void KateColorTreeWidget::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const |
285 | { |
286 | Q_UNUSED(painter) |
287 | Q_UNUSED(rect) |
288 | Q_UNUSED(index) |
289 | } |
290 | |
291 | void KateColorTreeWidget::selectDefaults() |
292 | { |
293 | bool somethingChanged = false; |
294 | |
295 | // use default colors for all selected items |
296 | for (int a = 0; a < topLevelItemCount(); ++a) { |
297 | QTreeWidgetItem *top = topLevelItem(index: a); |
298 | for (int b = 0; b < top->childCount(); ++b) { |
299 | KateColorTreeItem *it = static_cast<KateColorTreeItem *>(top->child(index: b)); |
300 | Q_ASSERT(it); |
301 | if (!it->useDefaultColor()) { |
302 | it->setUseDefaultColor(true); |
303 | somethingChanged = true; |
304 | } |
305 | } |
306 | } |
307 | |
308 | if (somethingChanged) { |
309 | viewport()->update(); |
310 | Q_EMIT changed(); |
311 | } |
312 | } |
313 | |
314 | void KateColorTreeWidget::addColorItem(const KateColorItem &colorItem) |
315 | { |
316 | QTreeWidgetItem *categoryItem = nullptr; |
317 | for (int i = 0; i < topLevelItemCount(); ++i) { |
318 | if (topLevelItem(index: i)->text(column: 0) == colorItem.category) { |
319 | categoryItem = topLevelItem(index: i); |
320 | break; |
321 | } |
322 | } |
323 | |
324 | if (!categoryItem) { |
325 | categoryItem = new QTreeWidgetItem(); |
326 | categoryItem->setText(column: 0, atext: colorItem.category); |
327 | addTopLevelItem(item: categoryItem); |
328 | expandItem(item: categoryItem); |
329 | } |
330 | |
331 | new KateColorTreeItem(colorItem, categoryItem); |
332 | |
333 | resizeColumnToContents(column: 0); |
334 | } |
335 | |
336 | void KateColorTreeWidget::addColorItems(const QList<KateColorItem> &colorItems) |
337 | { |
338 | for (const KateColorItem &item : colorItems) { |
339 | addColorItem(colorItem: item); |
340 | } |
341 | } |
342 | |
343 | QList<KateColorItem> KateColorTreeWidget::colorItems() const |
344 | { |
345 | QList<KateColorItem> items; |
346 | for (int a = 0; a < topLevelItemCount(); ++a) { |
347 | QTreeWidgetItem *top = topLevelItem(index: a); |
348 | for (int b = 0; b < top->childCount(); ++b) { |
349 | KateColorTreeItem *item = static_cast<KateColorTreeItem *>(top->child(index: b)); |
350 | Q_ASSERT(item); |
351 | items.append(t: item->colorItem()); |
352 | } |
353 | } |
354 | return items; |
355 | } |
356 | |
357 | QColor KateColorTreeWidget::findColor(const QString &key) const |
358 | { |
359 | for (int a = 0; a < topLevelItemCount(); ++a) { |
360 | QTreeWidgetItem *top = topLevelItem(index: a); |
361 | for (int b = 0; b < top->childCount(); ++b) { |
362 | KateColorTreeItem *item = static_cast<KateColorTreeItem *>(top->child(index: b)); |
363 | if (item->key() == key) { |
364 | if (item->useDefaultColor()) { |
365 | return item->defaultColor(); |
366 | } else { |
367 | return item->color(); |
368 | } |
369 | } |
370 | } |
371 | } |
372 | return QColor(); |
373 | } |
374 | |
375 | bool KateColorTreeWidget::readOnly() const |
376 | { |
377 | return m_readOnly; |
378 | } |
379 | |
380 | void KateColorTreeWidget::setReadOnly(bool readOnly) |
381 | { |
382 | m_readOnly = readOnly; |
383 | } |
384 | |
385 | #include "moc_katecolortreewidget.cpp" |
386 | |