Krita Source Code Documentation
Loading...
Searching...
No Matches
WGColorPatches.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Mathias Wein <lynx.mw+kde@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7#include "WGColorPatches.h"
8#include "WGCommonColorSet.h"
9#include "WGConfig.h"
10
12#include <kis_icon_utils.h>
13#include <KisUniqueColorSet.h>
14
15#include <QMouseEvent>
16#include <QPainter>
17#include <QScroller>
18#include <QScrollEvent>
19#include <QToolButton>
20
21namespace {
22 inline QPoint transposed(QPoint point) {
23 return point.transposed();
24 }
25}
27 : WGSelectorWidgetBase(displayConfig, parent)
28{
29 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
30 m_viewport = new QWidget(this);
31 m_viewport->installEventFilter(this);
32 m_viewport->setFocusProxy(this);
33 m_contentWidget = new QWidget(m_viewport);
34 m_contentWidget->installEventFilter(this);
35 m_contentWidget->setAttribute(Qt::WA_StaticContents);
36 // this prevents repainting the entire content widget when scrolling:
37 m_contentWidget->setAutoFillBackground(true);
38 setColorHistory(history);
39}
40
45
47{
48 if (!m_configSource) {
49 return;
50 }
51
53
54 QSize patchSize = cfg.get(m_configSource->patchSize);
55 m_patchWidth = patchSize.width();
56 m_patchHeight = patchSize.height();
58 m_numLines = cfg.get(m_configSource->rows);
60
61 WGConfig::Scrolling scrolling = cfg.get(m_configSource->scrolling);
64
65 if (m_orientation == Qt::Vertical) {
66 setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
67 } else {
68 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
69 }
70
71 // redo buttons
73 if (m_preset == History) {
74 bool showClearButton = cfg.get(WGConfig::colorHistoryShowClearButton);
75 if (showClearButton) {
77 }
78 }
79 else if (m_preset == CommonColors) {
80 if (uiMode() == PopupMode) {
81 // Small workaround: override patch limit because it's basically a
82 // property of WGCommonColorSet.
83 m_patchCount = cfg.get(WGConfig::commonColors.maxCount);
84 }
86 }
87 // clear leftover buttons (if any) and set new list
88 while (m_buttonList.size() > 0) {
89 delete m_buttonList.takeLast();
90 }
94
95 // recalc metrics and resize content widget
96 m_patchesPerLine = -1; // ensure resizeEvent() sets new content dimensions
97 QResizeEvent dummyEvent(size(), size());
98 resizeEvent(&dummyEvent);
99
100 if (QScroller::hasScroller(m_viewport)) {
101 QScroller *scroller = QScroller::scroller(m_viewport);
102 if (m_orientation == Qt::Horizontal) {
103 scroller->setSnapPositionsX(0.0, m_patchWidth);
104 scroller->setSnapPositionsY(0.0, m_patchHeight);
105 } else {
106 scroller->setSnapPositionsX(0.0, m_patchHeight);
107 scroller->setSnapPositionsY(0.0, m_patchWidth);
108 }
109 }
110
111 m_contentWidget->update();
112}
113
115{
116 if (preset == m_preset) {
117 return;
118 }
119
120 m_preset = preset;
121
122 if (uiMode() == PopupMode) {
124 }
125 else {
126 switch (preset) {
127 case History:
129 break;
130 case CommonColors:
132 break;
133 case None:
134 default:
135 m_configSource = nullptr;
136 }
137 }
138
140}
141
143{
144 return patchRect(m_buttonList.size()).center();
145}
146
148{
149 for (int i = 0; i < buttonList.size(); i++) {
150 buttonList[i]->setParent(this);
151 //buttonList[i]->setAutoFillBackground(true);
152 buttonList[i]->raise();
153 }
154 m_buttonList = buttonList;
155 // recalc metrics and resize content widget
156 m_patchesPerLine = -1; // ensure resizeEvent() sets new content dimensions
157 QResizeEvent dummyEvent(size(), size());
158 resizeEvent(&dummyEvent);
159}
160
162{
163 if (m_colors) {
164 m_colors->disconnect(m_contentWidget);
165 }
166 if (history) {
167 connect(history, SIGNAL(sigColorAdded(int)), m_contentWidget, SLOT(update()));
168 connect(history, SIGNAL(sigColorMoved(int,int)), m_contentWidget, SLOT(update()));
169 connect(history, SIGNAL(sigColorRemoved(int)), m_contentWidget, SLOT(update()));
170 connect(history, SIGNAL(sigReset()), m_contentWidget, SLOT(update()));
171 m_scrollValue = 0;
172 }
173 reconnectButtons(m_colors, history);
174 m_colors = history;
175}
176
178{
179 if (m_buttonList.isEmpty() || m_preset == None) {
180 return;
181 }
182 if (m_preset == History) {
183 m_buttonList.first()->setIcon(KisIconUtils::loadIcon("edit-clear-16"));
184 }
185 else if (m_preset == CommonColors) {
186 m_buttonList.first()->setIcon(KisIconUtils::loadIcon("reload-preset-16"));
187 }
188}
189
190bool WGColorPatches::event(QEvent *event)
191{
192 switch (event->type()) {
193 case QEvent::Paint:
194 // TODO: remove?
195 // this widget doesn't paint anything on itself, instead the viewport's paint event
196 // is redirected to this paintEvent() handler
197 return true;
198 case QEvent::ScrollPrepare:
199 {
200 QScrollPrepareEvent *se = static_cast<QScrollPrepareEvent *>(event);
201 if (m_allowScrolling && m_maxScroll > 0) {
202
203 se->setViewportSize(size());
204 if ((m_orientation == Qt::Horizontal && m_scrollInline) ||
205 (m_orientation == Qt::Vertical && !m_scrollInline)) {
206 se->setContentPosRange(QRectF(0, 0, m_maxScroll, 0));
207 se->setContentPos(QPointF(m_scrollValue, 0));
208 }
209 else {
210 se->setContentPosRange(QRectF(0, 0, 0, m_maxScroll));
211 se->setContentPos(QPointF(0, m_scrollValue));
212 }
213 se->accept();
214 return true;
215 }
216 return false;
217 }
218 case QEvent::Scroll:
219 {
220 QScrollEvent *se = static_cast<QScrollEvent *>(event);
221
222 if ((m_orientation == Qt::Horizontal && m_scrollInline) ||
223 (m_orientation == Qt::Vertical && !m_scrollInline)) {
224 m_scrollValue = qRound(se->contentPos().x() + se->overshootDistance().x());
225 }
226 else {
227 m_scrollValue = qRound(se->contentPos().y() + se->overshootDistance().y());
228 }
229
230 // TODO: keep overshoot seperately
231
233 return true;
234 }
235 default:
236 return WGSelectorWidgetBase::event(event);
237 }
238}
239
240bool WGColorPatches::eventFilter(QObject *watched, QEvent *e)
241{
242 if (watched == m_viewport) {
243 // this is basically a stripped down version of QAbstractScrollArea::viewportEvent()
244 switch (e->type()) {
245 // redirect to base class, as this event() implementation does not care
246 case QEvent::ContextMenu:
247 case QEvent::Wheel:
248 case QEvent::Drop:
249 case QEvent::DragEnter:
250 case QEvent::DragMove:
251 case QEvent::DragLeave:
252 return WGSelectorWidgetBase::event(e);
253
254 // these are handled in WGColorPatches::event()
255 case QEvent::ScrollPrepare:
256 case QEvent::Scroll:
257 return event(e);
258
259 default: break;
260 }
261 }
262 else if (watched == m_contentWidget) {
263 switch (e->type()) {
264 // redirect to base class and handle them in the specialized handlers
265 case QEvent::MouseButtonPress:
266 case QEvent::MouseButtonRelease:
267 case QEvent::MouseButtonDblClick:
268 case QEvent::TouchBegin:
269 case QEvent::TouchUpdate:
270 case QEvent::TouchEnd:
271 case QEvent::MouseMove:
272 return WGSelectorWidgetBase::event(e);
273
274 case QEvent::Paint:
275 {
276 QPaintEvent *pe = static_cast<QPaintEvent*>(e);
277 this->contentPaintEvent(pe);
278 return true;
279 }
280 default:
281 break;
282 }
283 }
284 return false;
285}
286
287void WGColorPatches::mouseMoveEvent(QMouseEvent *event)
288{
289 if (event->buttons() & Qt::LeftButton) {
290 int index = indexAt(event->pos());
291 if (index >= 0 && index != m_mouseIndex) {
292 Q_EMIT sigColorChanged(m_colors->color(index));
293 m_mouseIndex = index;
294 }
295 }
296}
297
298void WGColorPatches::mousePressEvent(QMouseEvent *event)
299{
300 if (event->button() == Qt::LeftButton) {
301 Q_EMIT sigColorInteraction(true);
302 m_mouseIndex = indexAt(event->pos());
303 if (m_mouseIndex >= 0) {
304 Q_EMIT sigColorChanged(m_colors->color(m_mouseIndex));
305 }
306 }
307}
308
309void WGColorPatches::mouseReleaseEvent(QMouseEvent *event)
310{
311 if (event->button() == Qt::LeftButton) {
312 Q_EMIT sigColorInteraction(false);
313 }
314}
315
316void WGColorPatches::wheelEvent(QWheelEvent *event)
317{
318 if (!m_allowScrolling) {
319 return;
320 }
321
322 int oldScroll = m_scrollValue;
323
324 if (m_scrollInline) {
325 // scroll two patches per "tick"
326 int scrollAmount = 2 * m_patchWidth;
327 scrollAmount = (event->angleDelta().y() * scrollAmount) / QWheelEvent::DefaultDeltasPerStep;
328 m_scrollValue = qBound(0, m_scrollValue - scrollAmount, m_maxScroll);
329 }
330 else {
331 // scroll one row per "tick"
332 int scrollAmount = (event->angleDelta().y() * m_patchHeight) / QWheelEvent::DefaultDeltasPerStep;
333 m_scrollValue = qBound(0, m_scrollValue - scrollAmount, m_maxScroll);
334 }
335
336 if (oldScroll != m_scrollValue) {
338 }
339 event->accept();
340}
341
342void WGColorPatches::contentPaintEvent(QPaintEvent *event)
343{
344 QRect updateRect = event->rect();
345 //qDebug() << "WGColorPatches::conentPaintEvent region:" << event->region();
346 int numColors = m_colors ? m_colors->size() : 0;
347 if (numColors <= 0) {
348 return;
349 }
350
351 QPainter painter(m_contentWidget);
352 const KisDisplayColorConverter *converter = displayConverter();
353
354 // this could be optimized a bit more...
355 for (int i = 0; i < qMin(m_patchCount, m_colors->size()); i++) {
356 QRect patch = patchRect(i);
357 if (patch.intersects(updateRect)) {
358 QColor qcolor = converter->toQColor(m_colors->color(i));
359
360 painter.fillRect(patch, qcolor);
361 }
362 }
363}
364
365void WGColorPatches::resizeEvent(QResizeEvent *event)
366{
367 Q_UNUSED(event)
368 int oldLineLength = m_patchesPerLine;
370 m_viewport->resize(size());
372 if (oldLineLength != m_patchesPerLine) {
374 m_contentWidget->resize(m_orientation == Qt::Horizontal ? conentSize : conentSize.transposed());
375 // notify the layout system that sizeHint() in the fixed policy dimension changed
376 updateGeometry();
377 }
378 for (int i = 0; i < m_buttonList.size(); i++) {
379 QRect buttonRect = patchRect(i);
380 // mirror the rect around the center
381 buttonRect.moveBottomRight(rect().bottomRight() - buttonRect.topLeft());
382 m_buttonList[i]->setGeometry(buttonRect);
383 }
384}
385
387{
388 if (m_orientation == Qt::Vertical) {
390 } else {
392 }
393}
394
395int WGColorPatches::indexAt(const QPoint &widgetPos) const
396{
397 if(!m_colors || !m_contentWidget->rect().contains(widgetPos))
398 return -1;
399
400 QPoint pos = (m_orientation == Qt::Horizontal) ? widgetPos : transposed(widgetPos);
401
402 int col = pos.x() / m_patchWidth;
403 int row = pos.y() / m_patchHeight;
404
405 if (col > m_patchesPerLine || row > m_totalLines) {
406 return -1;
407 }
408
409 int patchNr = m_scrollInline ? col * m_numLines + row : row * m_patchesPerLine + col;
410
411 //patchNr -= m_buttonList.size();
412
413 if (patchNr >= 0 && patchNr < qMin(m_patchCount, m_colors->size())) {
414 return patchNr;
415 }
416 return -1;
417}
418
419QRect WGColorPatches::patchRect(int gridIndex) const
420{
421 int row, col;
422 if (m_scrollInline) {
423 row = gridIndex % m_numLines;
424 col = gridIndex / m_numLines;
425 }
426 else {
427 row = gridIndex / m_patchesPerLine;
428 col = gridIndex % m_patchesPerLine;
429 }
430
431 QSize patchSize(m_patchWidth, m_patchHeight);
432 QPoint pos(col * m_patchWidth, row * m_patchHeight);
433
434 return (m_orientation == Qt::Horizontal) ? QRect(pos, patchSize)
435 : QRect(transposed(pos), patchSize.transposed());
436}
437
439{
440 if (!m_allowScrolling) {
441 return QPoint(0, 0);
442 }
443 QPoint offset(0, 0);
444 if (m_orientation == Qt::Horizontal) {
445 if (m_scrollInline) {
446 offset.rx() += m_scrollValue;
447 } else {
448 offset.ry() += m_scrollValue;
449 }
450 } else {
451 if (m_scrollInline) {
452 offset.ry() += m_scrollValue;
453 } else {
454 offset.rx() += m_scrollValue;
455 }
456 }
457 return offset;
458}
459
461{
462 if (m_scrollInline) {
465 }
466 else {
467 // in this mode, the line length and count depends on widget size
468 int availableLength = (m_orientation == Qt::Horizontal) ? width() : height();
469 m_patchesPerLine = qMax(1, availableLength / m_patchWidth);
470
471 if (m_allowScrolling) {
472 // with only one line, we need to subtract the buttons because we can't scroll past them
473 if (m_numLines == 1) {
476 } else {
478 }
479 } else {
482 m_maxScroll = 0;
483 }
484 }
485 // scroll limit
486 if (m_allowScrolling) {
487 if (m_scrollInline) {
488 int available = (m_orientation == Qt::Horizontal) ? width() : height();
489 int required = m_patchesPerLine * m_patchWidth;
490 m_maxScroll = qMax(0, required - available);
491 }
492 else {
493 int available = (m_orientation == Qt::Horizontal) ? height() : width();
494 int required = m_totalLines * m_patchHeight;
495 m_maxScroll = qMax(0, required - available);
496 }
497 }
498}
499
501{
502 if (recycleList.size() > 0) {
503 return recycleList.takeLast();
504 }
505 QToolButton *button = new QToolButton(this);
506 button->setAutoRaise(true);
507 button->show();
508 return button;
509}
510
512{
513 if (m_preset == History && !m_buttonList.isEmpty()) {
514 QToolButton *clearButton = m_buttonList.first();
515 if (oldSet) {
516 clearButton->disconnect(oldSet);
517 }
518 connect(clearButton, SIGNAL(clicked(bool)), newSet, SLOT(clear()));
519 }
520 else if (m_preset == CommonColors && !m_buttonList.isEmpty()) {
521 QToolButton *reloadButton = m_buttonList.first();
522 if (oldSet) {
523 reloadButton->disconnect(oldSet);
524 }
525 WGCommonColorSet *ccSet = qobject_cast<WGCommonColorSet *>(newSet);
526 if (ccSet) {
527 connect(reloadButton, SIGNAL(clicked(bool)), ccSet, SLOT(slotUpdateColors()));
528 }
529 }
530}
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
QColor toQColor(const KoColor &c, bool proofToPaintColors=false) const
QPoint popupOffset() const override
The position, relative to the top left corner, where the cursor of the cursor shall be when showing t...
QSize sizeHint() const override
void mousePressEvent(QMouseEvent *event) override
void setAdditionalButtons(QList< QToolButton * > buttonList)
QRect patchRect(int gridIndex) const
int indexAt(const QPoint &widgetPos) const
void resizeEvent(QResizeEvent *event) override
QWidget * m_contentWidget
QToolButton * fetchButton(QList< QToolButton * > &recycleList)
void sigColorChanged(const KoColor &color)
void setPreset(Preset preset)
bool eventFilter(QObject *watched, QEvent *e) override
bool event(QEvent *event) override
QPointer< KisUniqueColorSet > m_colors
QPoint scrollOffset() const
QList< QToolButton * > m_buttonList
void mouseMoveEvent(QMouseEvent *event) override
void contentPaintEvent(QPaintEvent *event)
QWidget * m_viewport
void updateSettings() override
void mouseReleaseEvent(QMouseEvent *event) override
void setColorHistory(KisUniqueColorSet *history)
WGColorPatches(WGSelectorDisplayConfigSP displayConfig, KisUniqueColorSet *history, QWidget *parent=nullptr)
Qt::Orientation m_orientation
const WGConfig::ColorPatches * m_configSource
void wheelEvent(QWheelEvent *event) override
void reconnectButtons(KisUniqueColorSet *oldSet, KisUniqueColorSet *newSet)
KisUniqueColorSet * colorHistory() const
const KisDisplayColorConverter * displayConverter() const
void sigColorInteraction(bool active)
QString button(const QWheelEvent &ev)
QString buttons(const T &ev)
QIcon loadIcon(const QString &name)
const ColorPatches commonColors
Definition WGConfig.cpp:221
const ColorPatches popupPatches
Definition WGConfig.cpp:229
const GenericSetting< bool > colorHistoryShowClearButton
Definition WGConfig.cpp:240
class WGConfig Accessor
@ ScrollLongitudinal
Definition WGConfig.h:190
@ ScrollNone
Definition WGConfig.h:189
const ColorPatches colorHistory
Definition WGConfig.cpp:213
NumericSetting< Scrolling > scrolling
Definition WGConfig.h:200
NumericSetting< Qt::Orientation > orientation
Definition WGConfig.h:196
NumericSetting< int > rows
Definition WGConfig.h:199
NumericSetting< int > maxCount
Definition WGConfig.h:198
NumericSetting< QSize > patchSize
Definition WGConfig.h:197