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 setColorHistoryModel(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{
165 }
166 if (colorHistoryModel) {
167 connect(colorHistoryModel, SIGNAL(sigColorAdded(int)), m_contentWidget, SLOT(update()));
168 connect(colorHistoryModel, SIGNAL(sigColorMoved(int,int)), m_contentWidget, SLOT(update()));
169 connect(colorHistoryModel, SIGNAL(sigColorRemoved(int)), m_contentWidget, SLOT(update()));
170 connect(colorHistoryModel, SIGNAL(sigReset()), m_contentWidget, SLOT(update()));
171 m_scrollValue = 0;
172 }
175 m_contentWidget->update();
176}
177
179{
180 if (m_buttonList.isEmpty() || m_preset == None) {
181 return;
182 }
183 if (m_preset == History) {
184 m_buttonList.first()->setIcon(KisIconUtils::loadIcon("edit-clear-16"));
185 }
186 else if (m_preset == CommonColors) {
187 m_buttonList.first()->setIcon(KisIconUtils::loadIcon("reload-preset-16"));
188 }
189}
190
191bool WGColorPatches::event(QEvent *event)
192{
193 switch (event->type()) {
194 case QEvent::Paint:
195 // TODO: remove?
196 // this widget doesn't paint anything on itself, instead the viewport's paint event
197 // is redirected to this paintEvent() handler
198 return true;
199 case QEvent::ScrollPrepare:
200 {
201 QScrollPrepareEvent *se = static_cast<QScrollPrepareEvent *>(event);
202 if (m_allowScrolling && m_maxScroll > 0) {
203
204 se->setViewportSize(size());
205 if ((m_orientation == Qt::Horizontal && m_scrollInline) ||
206 (m_orientation == Qt::Vertical && !m_scrollInline)) {
207 se->setContentPosRange(QRectF(0, 0, m_maxScroll, 0));
208 se->setContentPos(QPointF(m_scrollValue, 0));
209 }
210 else {
211 se->setContentPosRange(QRectF(0, 0, 0, m_maxScroll));
212 se->setContentPos(QPointF(0, m_scrollValue));
213 }
214 se->accept();
215 return true;
216 }
217 return false;
218 }
219 case QEvent::Scroll:
220 {
221 QScrollEvent *se = static_cast<QScrollEvent *>(event);
222
223 if ((m_orientation == Qt::Horizontal && m_scrollInline) ||
224 (m_orientation == Qt::Vertical && !m_scrollInline)) {
225 m_scrollValue = qRound(se->contentPos().x() + se->overshootDistance().x());
226 }
227 else {
228 m_scrollValue = qRound(se->contentPos().y() + se->overshootDistance().y());
229 }
230
231 // TODO: keep overshoot seperately
232
234 return true;
235 }
236 default:
237 return WGSelectorWidgetBase::event(event);
238 }
239}
240
241bool WGColorPatches::eventFilter(QObject *watched, QEvent *e)
242{
243 if (watched == m_viewport) {
244 // this is basically a stripped down version of QAbstractScrollArea::viewportEvent()
245 switch (e->type()) {
246 // redirect to base class, as this event() implementation does not care
247 case QEvent::ContextMenu:
248 case QEvent::Wheel:
249 case QEvent::Drop:
250 case QEvent::DragEnter:
251 case QEvent::DragMove:
252 case QEvent::DragLeave:
253 return WGSelectorWidgetBase::event(e);
254
255 // these are handled in WGColorPatches::event()
256 case QEvent::ScrollPrepare:
257 case QEvent::Scroll:
258 return event(e);
259
260 default: break;
261 }
262 }
263 else if (watched == m_contentWidget) {
264 switch (e->type()) {
265 // redirect to base class and handle them in the specialized handlers
266 case QEvent::MouseButtonPress:
267 case QEvent::MouseButtonRelease:
268 case QEvent::MouseButtonDblClick:
269 case QEvent::TouchBegin:
270 case QEvent::TouchUpdate:
271 case QEvent::TouchEnd:
272 case QEvent::MouseMove:
273 return WGSelectorWidgetBase::event(e);
274
275 case QEvent::Paint:
276 {
277 QPaintEvent *pe = static_cast<QPaintEvent*>(e);
278 this->contentPaintEvent(pe);
279 return true;
280 }
281 default:
282 break;
283 }
284 }
285 return false;
286}
287
288void WGColorPatches::mouseMoveEvent(QMouseEvent *event)
289{
290 if (event->buttons() & Qt::LeftButton) {
291 int index = indexAt(event->pos());
292 if (index >= 0 && index != m_mouseIndex) {
293 Q_EMIT sigColorChanged(m_colorHistoryModel->color(index));
294 m_mouseIndex = index;
295 }
296 }
297}
298
299void WGColorPatches::mousePressEvent(QMouseEvent *event)
300{
301 if (event->button() == Qt::LeftButton) {
302 Q_EMIT sigColorInteraction(true);
303 m_mouseIndex = indexAt(event->pos());
304 if (m_mouseIndex >= 0) {
306 }
307 }
308}
309
310void WGColorPatches::mouseReleaseEvent(QMouseEvent *event)
311{
312 if (event->button() == Qt::LeftButton) {
313 Q_EMIT sigColorInteraction(false);
314 }
315}
316
317void WGColorPatches::wheelEvent(QWheelEvent *event)
318{
319 if (!m_allowScrolling) {
320 return;
321 }
322
323 int oldScroll = m_scrollValue;
324
325 if (m_scrollInline) {
326 // scroll two patches per "tick"
327 int scrollAmount = 2 * m_patchWidth;
328 scrollAmount = (event->angleDelta().y() * scrollAmount) / QWheelEvent::DefaultDeltasPerStep;
329 m_scrollValue = qBound(0, m_scrollValue - scrollAmount, m_maxScroll);
330 }
331 else {
332 // scroll one row per "tick"
333 int scrollAmount = (event->angleDelta().y() * m_patchHeight) / QWheelEvent::DefaultDeltasPerStep;
334 m_scrollValue = qBound(0, m_scrollValue - scrollAmount, m_maxScroll);
335 }
336
337 if (oldScroll != m_scrollValue) {
339 }
340 event->accept();
341}
342
343void WGColorPatches::contentPaintEvent(QPaintEvent *event)
344{
345 QRect updateRect = event->rect();
346 //qDebug() << "WGColorPatches::conentPaintEvent region:" << event->region();
347 int numColors = m_colorHistoryModel ? m_colorHistoryModel->size() : 0;
348 if (numColors <= 0) {
349 return;
350 }
351
352 QPainter painter(m_contentWidget);
353 const KisDisplayColorConverter *converter = displayConverter();
354
355 // this could be optimized a bit more...
356 for (int i = 0; i < qMin(m_patchCount, m_colorHistoryModel->size()); i++) {
357 QRect patch = patchRect(i);
358 if (patch.intersects(updateRect)) {
359 QColor qcolor = converter->toQColor(m_colorHistoryModel->color(i));
360
361 painter.fillRect(patch, qcolor);
362 }
363 }
364}
365
366void WGColorPatches::resizeEvent(QResizeEvent *event)
367{
368 Q_UNUSED(event)
369 int oldLineLength = m_patchesPerLine;
371 m_viewport->resize(size());
373 if (oldLineLength != m_patchesPerLine) {
375 m_contentWidget->resize(m_orientation == Qt::Horizontal ? conentSize : conentSize.transposed());
376 // notify the layout system that sizeHint() in the fixed policy dimension changed
377 updateGeometry();
378 }
379 for (int i = 0; i < m_buttonList.size(); i++) {
380 QRect buttonRect = patchRect(i);
381 // mirror the rect around the center
382 buttonRect.moveBottomRight(rect().bottomRight() - buttonRect.topLeft());
383 m_buttonList[i]->setGeometry(buttonRect);
384 }
385}
386
388{
389 if (m_orientation == Qt::Vertical) {
391 } else {
393 }
394}
395
396int WGColorPatches::indexAt(const QPoint &widgetPos) const
397{
398 if(!m_colorHistoryModel || !m_contentWidget->rect().contains(widgetPos))
399 return -1;
400
401 QPoint pos = (m_orientation == Qt::Horizontal) ? widgetPos : transposed(widgetPos);
402
403 int col = pos.x() / m_patchWidth;
404 int row = pos.y() / m_patchHeight;
405
406 if (col > m_patchesPerLine || row > m_totalLines) {
407 return -1;
408 }
409
410 int patchNr = m_scrollInline ? col * m_numLines + row : row * m_patchesPerLine + col;
411
412 //patchNr -= m_buttonList.size();
413
414 if (patchNr >= 0 && patchNr < qMin(m_patchCount, m_colorHistoryModel->size())) {
415 return patchNr;
416 }
417 return -1;
418}
419
420QRect WGColorPatches::patchRect(int gridIndex) const
421{
422 int row, col;
423 if (m_scrollInline) {
424 row = gridIndex % m_numLines;
425 col = gridIndex / m_numLines;
426 }
427 else {
428 row = gridIndex / m_patchesPerLine;
429 col = gridIndex % m_patchesPerLine;
430 }
431
432 QSize patchSize(m_patchWidth, m_patchHeight);
433 QPoint pos(col * m_patchWidth, row * m_patchHeight);
434
435 return (m_orientation == Qt::Horizontal) ? QRect(pos, patchSize)
436 : QRect(transposed(pos), patchSize.transposed());
437}
438
440{
441 if (!m_allowScrolling) {
442 return QPoint(0, 0);
443 }
444 QPoint offset(0, 0);
445 if (m_orientation == Qt::Horizontal) {
446 if (m_scrollInline) {
447 offset.rx() += m_scrollValue;
448 } else {
449 offset.ry() += m_scrollValue;
450 }
451 } else {
452 if (m_scrollInline) {
453 offset.ry() += m_scrollValue;
454 } else {
455 offset.rx() += m_scrollValue;
456 }
457 }
458 return offset;
459}
460
462{
463 if (m_scrollInline) {
466 }
467 else {
468 // in this mode, the line length and count depends on widget size
469 int availableLength = (m_orientation == Qt::Horizontal) ? width() : height();
470 m_patchesPerLine = qMax(1, availableLength / m_patchWidth);
471
472 if (m_allowScrolling) {
473 // with only one line, we need to subtract the buttons because we can't scroll past them
474 if (m_numLines == 1) {
477 } else {
479 }
480 } else {
483 m_maxScroll = 0;
484 }
485 }
486 // scroll limit
487 if (m_allowScrolling) {
488 if (m_scrollInline) {
489 int available = (m_orientation == Qt::Horizontal) ? width() : height();
490 int required = m_patchesPerLine * m_patchWidth;
491 m_maxScroll = qMax(0, required - available);
492 }
493 else {
494 int available = (m_orientation == Qt::Horizontal) ? height() : width();
495 int required = m_totalLines * m_patchHeight;
496 m_maxScroll = qMax(0, required - available);
497 }
498 }
499}
500
502{
503 if (recycleList.size() > 0) {
504 return recycleList.takeLast();
505 }
506 QToolButton *button = new QToolButton(this);
507 button->setAutoRaise(true);
508 button->show();
509 return button;
510}
511
513{
514 if (m_preset == History && !m_buttonList.isEmpty()) {
515 QToolButton *clearButton = m_buttonList.first();
516 if (oldSet) {
517 clearButton->disconnect(oldSet);
518 }
519 if (newSet) {
520 connect(clearButton, SIGNAL(clicked(bool)), newSet, SLOT(clear()));
521 }
522 }
523 else if (m_preset == CommonColors && !m_buttonList.isEmpty()) {
524 QToolButton *reloadButton = m_buttonList.first();
525 if (oldSet) {
526 reloadButton->disconnect(oldSet);
527 }
528 WGCommonColorSet *ccSet = qobject_cast<WGCommonColorSet *>(newSet);
529 if (ccSet) {
530 connect(reloadButton, SIGNAL(clicked(bool)), ccSet, SLOT(slotUpdateColors()));
531 }
532 }
533}
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
QPointer< KisUniqueColorSet > m_colorHistoryModel
int indexAt(const QPoint &widgetPos) const
void resizeEvent(QResizeEvent *event) override
QWidget * m_contentWidget
KisUniqueColorSet * colorHistoryModel() const
void setColorHistoryModel(KisUniqueColorSet *history)
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
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
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)
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:241
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