1 #include "./paletteeditor.h"
2 #include "./colorbutton.h"
3 
4 #include "ui_paletteeditor.h"
5 
6 #include <QHeaderView>
7 #include <QLabel>
8 #include <QMetaProperty>
9 #include <QPainter>
10 #include <QStyle>
11 #include <QToolButton>
12 
13 namespace QtUtilities {
14 
15 enum { BrushRole = 33 };
16 
PaletteEditor(QWidget * parent)17 PaletteEditor::PaletteEditor(QWidget *parent)
18     : QDialog(parent)
19     , m_ui(new Ui::PaletteEditor)
20     , m_currentColorGroup(QPalette::Active)
21     , m_paletteModel(new PaletteModel(this))
22     , m_modelUpdated(false)
23     , m_paletteUpdated(false)
24     , m_compute(true)
25 {
26     m_ui->setupUi(this);
27     m_ui->paletteView->setModel(m_paletteModel);
28     updatePreviewPalette();
29     updateStyledButton();
30     m_ui->paletteView->setModel(m_paletteModel);
31     auto *const delegate = new ColorDelegate(this);
32     m_ui->paletteView->setItemDelegate(delegate);
33     m_ui->paletteView->setEditTriggers(QAbstractItemView::AllEditTriggers);
34     connect(m_paletteModel, &PaletteModel::paletteChanged, this, &PaletteEditor::paletteChanged);
35     m_ui->paletteView->setSelectionBehavior(QAbstractItemView::SelectRows);
36     m_ui->paletteView->setDragEnabled(true);
37     m_ui->paletteView->setDropIndicatorShown(true);
38     m_ui->paletteView->setRootIsDecorated(false);
39     m_ui->paletteView->setColumnHidden(2, true);
40     m_ui->paletteView->setColumnHidden(3, true);
41 }
42 
~PaletteEditor()43 PaletteEditor::~PaletteEditor()
44 {
45 }
46 
palette() const47 QPalette PaletteEditor::palette() const
48 {
49     return m_editPalette;
50 }
51 
setPalette(const QPalette & palette)52 void PaletteEditor::setPalette(const QPalette &palette)
53 {
54     m_editPalette = palette;
55     const auto mask = palette.
56 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
57                       resolveMask()
58 #else
59                       resolve()
60 #endif
61         ;
62     for (int i = 0; i < static_cast<int>(QPalette::NColorRoles); ++i) {
63         if (mask & (1 << i)) {
64             continue;
65         }
66         m_editPalette.setBrush(
67             QPalette::Active, static_cast<QPalette::ColorRole>(i), m_parentPalette.brush(QPalette::Active, static_cast<QPalette::ColorRole>(i)));
68         m_editPalette.setBrush(
69             QPalette::Inactive, static_cast<QPalette::ColorRole>(i), m_parentPalette.brush(QPalette::Inactive, static_cast<QPalette::ColorRole>(i)));
70         m_editPalette.setBrush(
71             QPalette::Disabled, static_cast<QPalette::ColorRole>(i), m_parentPalette.brush(QPalette::Disabled, static_cast<QPalette::ColorRole>(i)));
72     }
73     m_editPalette.
74 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
75         setResolveMask(mask);
76     m_editPalette = m_editPalette.resolve(m_editPalette)
77 #else
78         resolve(mask)
79 #endif
80         ;
81     updatePreviewPalette();
82     updateStyledButton();
83     m_paletteUpdated = true;
84     if (!m_modelUpdated) {
85         m_paletteModel->setPalette(m_editPalette, m_parentPalette);
86     }
87     m_paletteUpdated = false;
88 }
89 
setPalette(const QPalette & palette,const QPalette & parentPalette)90 void PaletteEditor::setPalette(const QPalette &palette, const QPalette &parentPalette)
91 {
92     m_parentPalette = parentPalette;
93     setPalette(palette);
94 }
95 
handleBuildButtonColorChanged(const QColor &)96 void PaletteEditor::handleBuildButtonColorChanged(const QColor &)
97 {
98     buildPalette();
99 }
100 
handleActiveRadioClicked()101 void PaletteEditor::handleActiveRadioClicked()
102 {
103     m_currentColorGroup = QPalette::Active;
104     updatePreviewPalette();
105 }
106 
handleInactiveRadioClicked()107 void PaletteEditor::handleInactiveRadioClicked()
108 {
109     m_currentColorGroup = QPalette::Inactive;
110     updatePreviewPalette();
111 }
112 
handleDisabledRadioClicked()113 void PaletteEditor::handleDisabledRadioClicked()
114 {
115     m_currentColorGroup = QPalette::Disabled;
116     updatePreviewPalette();
117 }
118 
handleComputeRadioClicked()119 void PaletteEditor::handleComputeRadioClicked()
120 {
121     if (m_compute) {
122         return;
123     }
124     m_ui->paletteView->setColumnHidden(2, true);
125     m_ui->paletteView->setColumnHidden(3, true);
126     m_compute = true;
127     m_paletteModel->setCompute(true);
128 }
129 
handleDetailsRadioClicked()130 void PaletteEditor::handleDetailsRadioClicked()
131 {
132     if (!m_compute) {
133         return;
134     }
135     const int w = m_ui->paletteView->columnWidth(1);
136     m_ui->paletteView->setColumnHidden(2, false);
137     m_ui->paletteView->setColumnHidden(3, false);
138     auto *const header = m_ui->paletteView->header();
139     header->resizeSection(1, w / 3);
140     header->resizeSection(2, w / 3);
141     header->resizeSection(3, w / 3);
142     m_compute = false;
143     m_paletteModel->setCompute(false);
144 }
145 
paletteChanged(const QPalette & palette)146 void PaletteEditor::paletteChanged(const QPalette &palette)
147 {
148     m_modelUpdated = true;
149     if (!m_paletteUpdated) {
150         setPalette(palette);
151     }
152     m_modelUpdated = false;
153 }
154 
buildPalette()155 void PaletteEditor::buildPalette()
156 {
157     const QColor btn(m_ui->buildButton->color());
158     const QPalette temp(btn);
159     setPalette(temp);
160 }
161 
updatePreviewPalette()162 void PaletteEditor::updatePreviewPalette()
163 {
164     const QPalette::ColorGroup g = currentColorGroup();
165     // build the preview palette
166     const QPalette currentPalette = palette();
167     QPalette previewPalette;
168     for (int i = QPalette::WindowText; i < QPalette::NColorRoles; ++i) {
169         const QPalette::ColorRole r = static_cast<QPalette::ColorRole>(i);
170         const QBrush br = currentPalette.brush(g, r);
171         previewPalette.setBrush(QPalette::Active, r, br);
172         previewPalette.setBrush(QPalette::Inactive, r, br);
173         previewPalette.setBrush(QPalette::Disabled, r, br);
174     }
175 }
176 
updateStyledButton()177 void PaletteEditor::updateStyledButton()
178 {
179     m_ui->buildButton->setColor(palette().color(QPalette::Active, QPalette::Button));
180 }
181 
getPalette(QWidget * parent,const QPalette & init,const QPalette & parentPal,int * ok)182 QPalette PaletteEditor::getPalette(QWidget *parent, const QPalette &init, const QPalette &parentPal, int *ok)
183 {
184     PaletteEditor dlg(parent);
185     auto parentPalette(parentPal);
186     const auto mask = init.
187 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
188                       resolveMask()
189 #else
190                       resolve()
191 #endif
192         ;
193     for (int i = 0; i < static_cast<int>(QPalette::NColorRoles); ++i) {
194         if (mask & (1 << i)) {
195             continue;
196         }
197         parentPalette.setBrush(
198             QPalette::Active, static_cast<QPalette::ColorRole>(i), init.brush(QPalette::Active, static_cast<QPalette::ColorRole>(i)));
199         parentPalette.setBrush(
200             QPalette::Inactive, static_cast<QPalette::ColorRole>(i), init.brush(QPalette::Inactive, static_cast<QPalette::ColorRole>(i)));
201         parentPalette.setBrush(
202             QPalette::Disabled, static_cast<QPalette::ColorRole>(i), init.brush(QPalette::Disabled, static_cast<QPalette::ColorRole>(i)));
203     }
204     dlg.setPalette(init, parentPalette);
205 
206     const int result = dlg.exec();
207     if (ok) {
208         *ok = result;
209     }
210     return result == QDialog::Accepted ? dlg.palette() : init;
211 }
212 
PaletteModel(QObject * parent)213 PaletteModel::PaletteModel(QObject *parent)
214     : QAbstractTableModel(parent)
215     , m_compute(true)
216 {
217     const QMetaObject *meta = metaObject();
218     const QMetaProperty property = meta->property(meta->indexOfProperty("colorRole"));
219     const QMetaEnum enumerator = property.enumerator();
220     for (int r = QPalette::WindowText; r < QPalette::NColorRoles; ++r) {
221         m_roleNames[static_cast<QPalette::ColorRole>(r)] = QLatin1String(enumerator.key(r));
222     }
223 }
224 
rowCount(const QModelIndex &) const225 int PaletteModel::rowCount(const QModelIndex &) const
226 {
227     return m_roleNames.count();
228 }
229 
columnCount(const QModelIndex &) const230 int PaletteModel::columnCount(const QModelIndex &) const
231 {
232     return 4;
233 }
234 
data(const QModelIndex & index,int role) const235 QVariant PaletteModel::data(const QModelIndex &index, int role) const
236 {
237     if (!index.isValid() || index.row() < 0 || index.row() >= QPalette::NColorRoles || index.column() < 0 || index.column() >= 4) {
238         return QVariant();
239     }
240 
241     if (index.column() == 0) {
242         if (role == Qt::DisplayRole) {
243             return m_roleNames[static_cast<QPalette::ColorRole>(index.row())];
244         }
245         if (role == Qt::EditRole) {
246             const auto mask = m_palette.
247 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
248                               resolveMask()
249 #else
250                               resolve()
251 #endif
252                 ;
253             if (mask & (1 << index.row()))
254                 return true;
255             return false;
256         }
257         return QVariant();
258     }
259     if (role == BrushRole) {
260         return m_palette.brush(columnToGroup(index.column()), static_cast<QPalette::ColorRole>(index.row()));
261     }
262     return QVariant();
263 }
264 
setData(const QModelIndex & index,const QVariant & value,int role)265 bool PaletteModel::setData(const QModelIndex &index, const QVariant &value, int role)
266 {
267     if (!index.isValid()) {
268         return false;
269     }
270 
271     if (index.column() != 0 && role == BrushRole) {
272         const QBrush br = qvariant_cast<QBrush>(value);
273         const QPalette::ColorRole r = static_cast<QPalette::ColorRole>(index.row());
274         const QPalette::ColorGroup g = columnToGroup(index.column());
275         m_palette.setBrush(g, r, br);
276 
277         QModelIndex idxBegin = PaletteModel::index(r, 0);
278         QModelIndex idxEnd = PaletteModel::index(r, 3);
279         if (m_compute) {
280             m_palette.setBrush(QPalette::Inactive, r, br);
281             switch (r) {
282             case QPalette::WindowText:
283             case QPalette::Text:
284             case QPalette::ButtonText:
285             case QPalette::Base:
286                 break;
287             case QPalette::Dark:
288                 m_palette.setBrush(QPalette::Disabled, QPalette::WindowText, br);
289                 m_palette.setBrush(QPalette::Disabled, QPalette::Dark, br);
290                 m_palette.setBrush(QPalette::Disabled, QPalette::Text, br);
291                 m_palette.setBrush(QPalette::Disabled, QPalette::ButtonText, br);
292                 idxBegin = PaletteModel::index(0, 0);
293                 idxEnd = PaletteModel::index(m_roleNames.count() - 1, 3);
294                 break;
295             case QPalette::Window:
296                 m_palette.setBrush(QPalette::Disabled, QPalette::Base, br);
297                 m_palette.setBrush(QPalette::Disabled, QPalette::Window, br);
298                 idxBegin = PaletteModel::index(QPalette::Base, 0);
299                 break;
300             case QPalette::Highlight:
301                 break;
302             default:
303                 m_palette.setBrush(QPalette::Disabled, r, br);
304                 break;
305             }
306         }
307         emit paletteChanged(m_palette);
308         emit dataChanged(idxBegin, idxEnd);
309         return true;
310     }
311     if (index.column() == 0 && role == Qt::EditRole) {
312         auto mask = m_palette.
313 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
314                     resolveMask()
315 #else
316                     resolve()
317 #endif
318             ;
319         const bool isMask = qvariant_cast<bool>(value);
320         const int r = index.row();
321         if (isMask) {
322             mask |= (1 << r);
323         } else {
324             m_palette.setBrush(
325                 QPalette::Active, static_cast<QPalette::ColorRole>(r), m_parentPalette.brush(QPalette::Active, static_cast<QPalette::ColorRole>(r)));
326             m_palette.setBrush(QPalette::Inactive, static_cast<QPalette::ColorRole>(r),
327                 m_parentPalette.brush(QPalette::Inactive, static_cast<QPalette::ColorRole>(r)));
328             m_palette.setBrush(QPalette::Disabled, static_cast<QPalette::ColorRole>(r),
329                 m_parentPalette.brush(QPalette::Disabled, static_cast<QPalette::ColorRole>(r)));
330             mask &= ~static_cast<decltype(mask)>(1 << index.row());
331         }
332         m_palette.
333 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
334             setResolveMask(mask);
335         m_palette = m_palette.resolve(m_palette)
336 #else
337             resolve(mask)
338 #endif
339             ;
340         emit paletteChanged(m_palette);
341         const QModelIndex idxEnd = PaletteModel::index(r, 3);
342         emit dataChanged(index, idxEnd);
343         return true;
344     }
345     return false;
346 }
347 
flags(const QModelIndex & index) const348 Qt::ItemFlags PaletteModel::flags(const QModelIndex &index) const
349 {
350     if (!index.isValid())
351         return Qt::ItemIsEnabled;
352     return Qt::ItemIsEditable | Qt::ItemIsEnabled;
353 }
354 
headerData(int section,Qt::Orientation orientation,int role) const355 QVariant PaletteModel::headerData(int section, Qt::Orientation orientation, int role) const
356 {
357     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
358         if (section == 0)
359             return tr("Color Role");
360         if (section == groupToColumn(QPalette::Active))
361             return tr("Active");
362         if (section == groupToColumn(QPalette::Inactive))
363             return tr("Inactive");
364         if (section == groupToColumn(QPalette::Disabled))
365             return tr("Disabled");
366     }
367     return QVariant();
368 }
369 
getPalette() const370 QPalette PaletteModel::getPalette() const
371 {
372     return m_palette;
373 }
374 
setPalette(const QPalette & palette,const QPalette & parentPalette)375 void PaletteModel::setPalette(const QPalette &palette, const QPalette &parentPalette)
376 {
377     m_parentPalette = parentPalette;
378     m_palette = palette;
379     const QModelIndex idxBegin = index(0, 0);
380     const QModelIndex idxEnd = index(m_roleNames.count() - 1, 3);
381     emit dataChanged(idxBegin, idxEnd);
382 }
383 
columnToGroup(int index) const384 QPalette::ColorGroup PaletteModel::columnToGroup(int index) const
385 {
386     if (index == 1)
387         return QPalette::Active;
388     if (index == 2)
389         return QPalette::Inactive;
390     return QPalette::Disabled;
391 }
392 
groupToColumn(QPalette::ColorGroup group) const393 int PaletteModel::groupToColumn(QPalette::ColorGroup group) const
394 {
395     if (group == QPalette::Active)
396         return 1;
397     if (group == QPalette::Inactive)
398         return 2;
399     return 3;
400 }
401 
BrushEditor(QWidget * parent)402 BrushEditor::BrushEditor(QWidget *parent)
403     : QWidget(parent)
404     , m_button(new ColorButton(this))
405     , m_changed(false)
406 {
407     auto *const layout = new QHBoxLayout(this);
408     layout->setContentsMargins(0, 0, 0, 0);
409     layout->addWidget(m_button);
410     connect(m_button, &ColorButton::colorChanged, this, &BrushEditor::brushChanged);
411     setFocusProxy(m_button);
412 }
413 
setBrush(const QBrush & brush)414 void BrushEditor::setBrush(const QBrush &brush)
415 {
416     m_button->setColor(brush.color());
417     m_changed = false;
418 }
419 
brush() const420 QBrush BrushEditor::brush() const
421 {
422     return QBrush(m_button->color());
423 }
424 
brushChanged()425 void BrushEditor::brushChanged()
426 {
427     m_changed = true;
428     emit changed(this);
429 }
430 
changed() const431 bool BrushEditor::changed() const
432 {
433     return m_changed;
434 }
435 
RoleEditor(QWidget * parent)436 RoleEditor::RoleEditor(QWidget *parent)
437     : QWidget(parent)
438     , m_label(new QLabel(this))
439     , m_edited(false)
440 {
441     QHBoxLayout *layout = new QHBoxLayout(this);
442     layout->setContentsMargins(0, 0, 0, 0);
443     layout->setSpacing(0);
444 
445     layout->addWidget(m_label);
446     m_label->setAutoFillBackground(true);
447     m_label->setIndent(3); // same value as textMargin in QItemDelegate
448     setFocusProxy(m_label);
449 
450     auto *const button = new QToolButton(this);
451     button->setToolButtonStyle(Qt::ToolButtonIconOnly);
452     button->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear")));
453     button->setIconSize(QSize(8, 8));
454     button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding));
455     layout->addWidget(button);
456     connect(button, &QAbstractButton::clicked, this, &RoleEditor::emitResetProperty);
457 }
458 
setLabel(const QString & label)459 void RoleEditor::setLabel(const QString &label)
460 {
461     m_label->setText(label);
462 }
463 
setEdited(bool on)464 void RoleEditor::setEdited(bool on)
465 {
466     QFont font;
467     if (on == true) {
468         font.setBold(on);
469     }
470     m_label->setFont(font);
471     m_edited = on;
472 }
473 
edited() const474 bool RoleEditor::edited() const
475 {
476     return m_edited;
477 }
478 
emitResetProperty()479 void RoleEditor::emitResetProperty()
480 {
481     setEdited(false);
482     emit changed(this);
483 }
484 
ColorDelegate(QObject * parent)485 ColorDelegate::ColorDelegate(QObject *parent)
486     : QItemDelegate(parent)
487 {
488 }
489 
createEditor(QWidget * parent,const QStyleOptionViewItem &,const QModelIndex & index) const490 QWidget *ColorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
491 {
492     if (index.column() == 0) {
493         auto *const editor = new RoleEditor(parent);
494         connect(editor, &RoleEditor::changed, this, &ColorDelegate::commitData);
495         return editor;
496     }
497 
498     using BrushEditorWidgetSignal = void (BrushEditor::*)(QWidget *);
499 
500     auto *const editor = new BrushEditor(parent);
501     connect(editor, static_cast<BrushEditorWidgetSignal>(&BrushEditor::changed), this, &ColorDelegate::commitData);
502     editor->setFocusPolicy(Qt::NoFocus);
503     editor->installEventFilter(const_cast<ColorDelegate *>(this));
504     return editor;
505 }
506 
setEditorData(QWidget * ed,const QModelIndex & index) const507 void ColorDelegate::setEditorData(QWidget *ed, const QModelIndex &index) const
508 {
509     if (index.column() == 0) {
510         const auto mask = qvariant_cast<bool>(index.model()->data(index, Qt::EditRole));
511         auto *const editor = static_cast<RoleEditor *>(ed);
512         editor->setEdited(mask);
513         const auto colorName = qvariant_cast<QString>(index.model()->data(index, Qt::DisplayRole));
514         editor->setLabel(colorName);
515     } else {
516         const auto br = qvariant_cast<QBrush>(index.model()->data(index, BrushRole));
517         auto *const editor = static_cast<BrushEditor *>(ed);
518         editor->setBrush(br);
519     }
520 }
521 
setModelData(QWidget * ed,QAbstractItemModel * model,const QModelIndex & index) const522 void ColorDelegate::setModelData(QWidget *ed, QAbstractItemModel *model, const QModelIndex &index) const
523 {
524     if (index.column() == 0) {
525         const auto *const editor = static_cast<RoleEditor *>(ed);
526         const auto mask = editor->edited();
527         model->setData(index, mask, Qt::EditRole);
528     } else {
529         const auto *const editor = static_cast<BrushEditor *>(ed);
530         if (editor->changed()) {
531             QBrush br = editor->brush();
532             model->setData(index, br, BrushRole);
533         }
534     }
535 }
536 
updateEditorGeometry(QWidget * ed,const QStyleOptionViewItem & option,const QModelIndex & index) const537 void ColorDelegate::updateEditorGeometry(QWidget *ed, const QStyleOptionViewItem &option, const QModelIndex &index) const
538 {
539     QItemDelegate::updateEditorGeometry(ed, option, index);
540     ed->setGeometry(ed->geometry().adjusted(0, 0, -1, -1));
541 }
542 
paint(QPainter * painter,const QStyleOptionViewItem & opt,const QModelIndex & index) const543 void ColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const
544 {
545     QStyleOptionViewItem option = opt;
546     const auto mask = qvariant_cast<bool>(index.model()->data(index, Qt::EditRole));
547     if (index.column() == 0 && mask) {
548         option.font.setBold(true);
549     }
550     auto br = qvariant_cast<QBrush>(index.model()->data(index, BrushRole));
551     if (br.style() == Qt::LinearGradientPattern || br.style() == Qt::RadialGradientPattern || br.style() == Qt::ConicalGradientPattern) {
552         painter->save();
553         painter->translate(option.rect.x(), option.rect.y());
554         painter->scale(option.rect.width(), option.rect.height());
555         QGradient gr = *(br.gradient());
556         gr.setCoordinateMode(QGradient::LogicalMode);
557         br = QBrush(gr);
558         painter->fillRect(0, 0, 1, 1, br);
559         painter->restore();
560     } else {
561         painter->save();
562         painter->setBrushOrigin(option.rect.x(), option.rect.y());
563         painter->fillRect(option.rect, br);
564         painter->restore();
565     }
566     QItemDelegate::paint(painter, option, index);
567 
568     const QColor color = static_cast<QRgb>(QApplication::style()->styleHint(QStyle::SH_Table_GridLineColor, &option));
569     const QPen oldPen = painter->pen();
570     painter->setPen(QPen(color));
571 
572     painter->drawLine(option.rect.right(), option.rect.y(), option.rect.right(), option.rect.bottom());
573     painter->drawLine(option.rect.x(), option.rect.bottom(), option.rect.right(), option.rect.bottom());
574     painter->setPen(oldPen);
575 }
576 
sizeHint(const QStyleOptionViewItem & opt,const QModelIndex & index) const577 QSize ColorDelegate::sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &index) const
578 {
579     return QItemDelegate::sizeHint(opt, index) + QSize(4, 4);
580 }
581 
582 } // namespace QtUtilities
583