Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_popup_palette.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2009 Vera Lukman <shicmap@gmail.com>
3 SPDX-FileCopyrightText: 2011 Sven Langkamp <sven.langkamp@gmail.com>
4 SPDX-FileCopyrightText: 2016 Scott Petrovic <scottpetrovic@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7
8*/
9#include <QtGui>
10#include <QMenu>
11#include <QWhatsThis>
12#include <QVBoxLayout>
13
14#include <KisTagModel.h>
15
16#include "kis_canvas2.h"
17#include "kis_config.h"
18#include "kis_popup_palette.h"
20#include "kis_icon_utils.h"
25#include <kis_config_notifier.h>
28#include "KisDockerHud.h"
29#include "kis_signals_blocker.h"
32#include <kis_paintop_preset.h>
33#include "KisMouseClickEater.h"
35
36static const int WIDGET_MARGIN = 16;
37static const qreal BORDER_WIDTH = 3.0;
38
40{
41public:
47
48 ~PopupColorTriangle() override {}
49
50 void tabletEvent(QTabletEvent* event) override {
51 QMouseEvent* mouseEvent = 0;
52
53 // ignore any tablet events that are done with the right click
54 // Tablet move events don't return a "button", so catch that too
55 if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove)
56 {
57 switch (event->type()) {
58 case QEvent::TabletPress:
59 mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(),
60 Qt::LeftButton, Qt::LeftButton, event->modifiers());
61 m_dragging = true;
62 mousePressEvent(mouseEvent);
63 event->accept();
64 break;
65 case QEvent::TabletMove:
66 mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(),
67 (m_dragging) ? Qt::LeftButton : Qt::NoButton,
68 (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers());
69 mouseMoveEvent(mouseEvent);
70 event->accept();
71 break;
72 case QEvent::TabletRelease:
73 mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(),
74 Qt::LeftButton,
75 Qt::LeftButton,
76 event->modifiers());
77 m_dragging = false;
78 mouseReleaseEvent(mouseEvent);
79 event->accept();
80 break;
81 default: break;
82 }
83 }
84
85 delete mouseEvent;
86 }
87
88private:
90};
91
93 const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent)
94 : QWidget(parent,
95
111 KisPlatformPluginInterfaceFactory::instance()->surfaceColorManagedByOS() ?
112 Qt::Popup :
113 Qt::FramelessWindowHint)
114 , m_coordinatesConverter(coordinatesConverter)
115 , m_viewManager(viewManager)
116 , m_actionManager(viewManager->actionManager())
117 , m_resourceManager(manager)
118 , m_displayRenderer(displayRenderer)
119 , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::FIRST_ACTIVE))
120 , m_actionCollection(viewManager->actionCollection())
121 , m_acyclicConnector(new KisAcyclicSignalConnector(this))
122 , m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this))
123{
124 setAttribute(Qt::WA_TranslucentBackground);
125
126 connect(m_colorChangeCompressor.data(), SIGNAL(timeout()),
127 SLOT(slotEmitColorChanged()));
128
129 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(slotConfigurationChanged()));
130 connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(slotDisplayConfigurationChanged()));
131
132 m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)),
134
136 m_resourceManager, SIGNAL(sigSetFGColor(KoColor)));
137 // just update() to repaint color labels on external background color change
138 connect(viewManager->canvasResourceProvider(), SIGNAL(sigBGColorChanged(KoColor)), SLOT(update()));
139
140 connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int)));
141 connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int)));
142
143 connect(m_resourceManager, SIGNAL(setSelectedColor(int)), this, SLOT(slotSetSelectedColor(int)));
144 connect(m_resourceManager, SIGNAL(updatePalettes()), this, SLOT(slotUpdate()));
145 connect(m_resourceManager, SIGNAL(hidePalettes()), this, SIGNAL(finished()));
146
147 // Instances of `this` rely on finished() to be detached and its lifetime is associated with `parent`
148 connect(parent, SIGNAL(destroyed(QObject *)), this, SIGNAL(finished()), Qt::DirectConnection);
149
150 setCursor(Qt::ArrowCursor);
151 setMouseTracking(true);
153 setHoveredColor(-1);
155
156 m_dockerHud = new KisDockerHud(i18n("Popup Palette"), "popuppalette");
157
159
160 connect(m_tagsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup()));
161
163 m_dockerHudButton->setCheckable(true);
164
165 connect(m_dockerHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool)));
166
167 m_bottomBarWidget = new QWidget(this);
168
170 m_bottomBarButton->setCheckable(true);
171
172 connect( m_bottomBarButton, SIGNAL(toggled(bool)), SLOT(showBottomBarWidget(bool)));
173
175 m_clearColorHistoryButton->setToolTip(i18n("Clear color history"));
176
177 connect(m_clearColorHistoryButton, SIGNAL(clicked(bool)), m_resourceManager, SLOT(slotClearHistory()));
178 //Otherwise the colors won't disappear until the cursor moves away from the button:
179 connect(m_clearColorHistoryButton, SIGNAL(released()), this, SLOT(slotUpdate()));
180
181 // add some stuff below the pop-up palette that will make it easier to use for tablet people
182 QGridLayout* gLayout = new QGridLayout(this);
183 gLayout->setSizeConstraint(QLayout::SetFixedSize);
184 gLayout->setSpacing(0);
185 gLayout->setContentsMargins(QMargins());
187 gLayout->addItem(m_mainArea, 0, 0); // this should push the box to the bottom
188 gLayout->setColumnMinimumWidth(1, WIDGET_MARGIN);
189 gLayout->addWidget(m_dockerHud, 0, 2);
190 gLayout->setRowMinimumHeight(1, WIDGET_MARGIN);
191 gLayout->addWidget(m_bottomBarWidget, 2, 0);
192
193 QHBoxLayout* hLayout = new QHBoxLayout(m_bottomBarWidget);
194
196 mirrorMode->setFixedSize(35, 35);
197
198 mirrorMode->setToolTip(i18n("Mirror Canvas"));
199 mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas_around_cursor"));
200 connect(mirrorMode, SIGNAL(clicked(bool)), this, SLOT(slotUpdate()));
201 connect(mirrorMode, SIGNAL(pressed()), this, SLOT(slotSetMirrorPos()));
202 connect(mirrorMode, SIGNAL(clicked()), this, SLOT(slotRemoveMirrorPos()));
203
205 canvasOnlyButton->setFixedSize(35, 35);
206
207 canvasOnlyButton->setToolTip(i18n("Canvas Only"));
208 canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only"));
209
210 zoomToOneHundredPercentButton = new QPushButton(this);
211 zoomToOneHundredPercentButton->setText(i18n("100%"));
212 zoomToOneHundredPercentButton->setFixedHeight(35);
213
214 zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%"));
215 connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked()));
216
217 fitToViewButton = new QPushButton(this);
218 fitToViewButton->setFixedHeight(35);
219
220 fitToViewButton->setToolTip(i18n("Fit Canvas to View"));
221 connect(fitToViewButton, SIGNAL(clicked(bool)), this, SLOT(slotFitToViewClicked()));
222
223 zoomCanvasSlider = new QSlider(Qt::Horizontal, this);
224 zoomSliderMinValue = 10; // set in %
225 zoomSliderMaxValue = 200; // set in %
226
228 zoomCanvasSlider->setFixedHeight(35);
230
231 zoomCanvasSlider->setSingleStep(1);
232 zoomCanvasSlider->setPageStep(1);
233
234 connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int)));
235 connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed()));
236 connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased()));
237
239
240 hLayout->setSpacing(2);
241 hLayout->setContentsMargins(0, 6, 0, 0);
242 hLayout->addWidget(mirrorMode);
243 hLayout->addWidget(canvasOnlyButton);
244 hLayout->addWidget(zoomCanvasSlider);
245 hLayout->addWidget(zoomToOneHundredPercentButton);
246 hLayout->addWidget(fitToViewButton);
247
248 setVisible(false);
249 reconfigure();
250
251 opacityChange = new QGraphicsOpacityEffect(this);
252 opacityChange->setOpacity(1);
253 setGraphicsEffect(opacityChange);
254
260 installEventFilter(m_clicksEater);
261
262 // Prevent tablet events from being captured by the canvas
263 setAttribute(Qt::WA_NoMousePropagation, true);
264
265 // Because we can create a popup in canvas widget, synthetic events sometimes arrive here (see mousePressEvent()).
266 setAttribute(Qt::WA_AcceptTouchEvents, true);
267 installEventFilter(this);
268 const QList<QWidget *> childrenWidgets = findChildren<QWidget *>();
269 for (const auto &child: childrenWidgets) {
270 child->setAttribute(Qt::WA_AcceptTouchEvents, true);
271 child->installEventFilter(this);
272 }
273
274 // No context menu here. Setting this avoids long-presses from delaying
275 // inputs or dismissing the palette, see KisLongPressEventFilter.cpp.
276 setContextMenuPolicy(Qt::PreventContextMenu);
277
278 // Load configuration..
279 KisConfig cfg(true);
280 m_dockerHudButton->setChecked(cfg.showBrushHud());
281 m_bottomBarButton->setChecked(cfg.showPaletteBottomBar());
282
283 m_dockerHud->setVisible(m_dockerHudButton->isChecked());
284 m_bottomBarWidget->setVisible(m_bottomBarButton->isChecked());
285}
286
290
292{
293 reconfigure();
294 layout()->invalidate();
295}
296
298{
299 KisConfig config(true);
300 m_useDynamicSlotCount = config.readEntry("popuppalette/useDynamicSlotCount", true);
303 int presetCount = m_resourceManager->numFavoritePresets();
304 // if there are no presets because the tag is empty
305 // show the maximum number allowed (they will be painted as empty slots)
306 m_presetSlotCount = presetCount == 0
309 } else {
311 }
312 m_popupPaletteSize = config.readEntry("popuppalette/size", 385);
313 qreal selectorRadius = config.readEntry("popuppalette/selectorSize", 140) / 2.0;
314
315 m_showColorHistory = config.readEntry("popuppalette/showColorHistory", true);
316 m_showRotationTrack = config.readEntry("popuppalette/showRotationTrack", true);
317
320 if (m_showColorHistory) {
322 m_clearColorHistoryButton->setVisible(true);
323 } else {
324 m_clearColorHistoryButton->setVisible(false);
325 }
326
328
329 bool useVisualSelector = config.readEntry<bool>("popuppalette/usevisualcolorselector", false);
330 if (m_colorSelector) {
331 // if the selector type changed, delete it
332 bool haveVisualSelector = qobject_cast<KisVisualColorSelector*>(m_colorSelector) != 0;
333 if (useVisualSelector != haveVisualSelector) {
334 delete m_colorSelector;
335 m_colorSelector = 0;
336 }
337 }
338 if (!m_colorSelector) {
339 if (useVisualSelector) {
341 selector->setAcceptTabletEvents(true);
342 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()),
343 selector, SLOT(slotConfigurationChanged()));
344 m_colorSelector = selector;
345 }
346 else {
348 connect(m_colorSelector, SIGNAL(requestCloseContainer()), this, SIGNAL(finished()));
349 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()),
350 m_colorSelector, SLOT(configurationChanged()));
351 }
353 m_colorSelector->setConfig(true,false);
354 m_colorSelector->setVisible(true);
356 connect(m_colorSelector, SIGNAL(sigNewColor(KoColor)),
357 m_colorChangeCompressor.data(), SLOT(start()));
358
359 }
360
361 if (KoTriangleColorSelector *srgbColorSelector = dynamic_cast<KoTriangleColorSelector*>(m_colorSelector)) {
362 bool fixTriangleRotation = config.readEntry("popuppalette/fixTriangleRotation", false);
363 if (fixTriangleRotation) {
364 int triangleRotationAngle = config.readEntry("popuppalette/triangleRotationAngle", 0);
365 srgbColorSelector->setRotation(triangleRotationAngle);
366 }
367 else srgbColorSelector->setFollowHue(true);
368 }
369
370 const int auxButtonSize = 35;
371 m_colorSelector->move(m_popupPaletteSize/2 - selectorRadius, m_popupPaletteSize/2 - selectorRadius);
373
374 // ellipse - to make sure the widget doesn't eat events meant for recent colors or brushes
375 // - needs to be +2 pixels on every side for anti-aliasing to look nice on high dpi displays
376 // rectangle - to make sure the area doesn't extend outside of the widget
377 QRegion maskedEllipse(-2, -2, m_colorSelector->width() + 4, m_colorSelector->height() + 4, QRegion::Ellipse );
378 QRegion maskedRectangle(0, 0, m_colorSelector->width(), m_colorSelector->height(), QRegion::Rectangle);
379 QRegion maskedRegion = maskedEllipse.intersected(maskedRectangle);
380
381 m_colorSelector->setMask(maskedRegion);
382
383 m_dockerHud->setFixedHeight(int(m_popupPaletteSize));
384
385 // arranges the buttons around the popup palette
386 // buttons are spread out from the center of the set arc length
387
388 // the margin in degrees between buttons
389 qreal margin = 10.0;
390 // visual center
391 qreal center = m_popupPaletteSize/2 - auxButtonSize/2.0;
392 qreal length = m_popupPaletteSize/2 + auxButtonSize/2.0 + 5;
393 {
394 int buttonCount = 2;
395 int arcLength = 90;
396 // note the int buttonCount/2 is on purpose
397 qreal start = arcLength/2 - (buttonCount/2) * margin;
398 if (buttonCount % 2 == 0) start += margin / 2;
399 int place = 0;
400 m_dockerHudButton->setGeometry(
401 center + qCos(qDegreesToRadians(start + place*margin))*length,
402 center + qSin(qDegreesToRadians(start + place*margin))*length,
403 auxButtonSize, auxButtonSize
404 );
405 place++;
406 m_bottomBarButton->setGeometry (
407 center + qCos(qDegreesToRadians(start + place*margin))*length,
408 center + qSin(qDegreesToRadians(start + place*margin))*length,
409 auxButtonSize, auxButtonSize
410 );
411 }
412 {
413 int buttonCount = m_showColorHistory ? 2 : 1 ;
414 int arcLength = 90;
415 int shiftArc = 90;
416 // note the int buttonCount/2 is on purpose
417 qreal start = shiftArc + arcLength / 2 - (buttonCount/2) * margin;
418 if (buttonCount % 2 == 0) start += margin / 2;
419 int place = 0;
420 if (m_showColorHistory) {
421 m_clearColorHistoryButton->setGeometry(
422 center + qCos(qDegreesToRadians(start + place * margin)) * length,
423 center + qSin(qDegreesToRadians(start + place * margin)) * length,
424 auxButtonSize, auxButtonSize);
425 place++;
426 }
427 m_tagsButton->setGeometry(
428 center + qCos(qDegreesToRadians(start + place*margin))*length,
429 center + qSin(qDegreesToRadians(start + place*margin))*length,
430 auxButtonSize, auxButtonSize
431 );
432 }
434}
435
437{
438 // Visual Color Selector picks up color space from input
441 //hack to get around cmyk for now.
442 if (paintingCS->colorChannelCount()>3) {
443 paintingCS = KoColorSpaceRegistry::instance()->rgb8();
444 }
447}
448
455
457{
458 if (isVisible()) {
459 update();
461 }
462}
463
475
476//setting KisPopupPalette properties
478{
479 return m_hoveredPreset;
480}
481
486
488{
489 return m_hoveredColor;
490}
491
496
498{
499 return m_selectedColor;
500}
501
506
508 Q_EMIT zoomLevelChanged(zoom);
509}
510
515
520
522{
523 this->setPalette(qApp->palette());
524
525 for(int i=0; i<this->children().size(); i++) {
526 QWidget *w = qobject_cast<QWidget*>(this->children().at(i));
527 if (w) {
528 w->setPalette(qApp->palette());
529 }
530 }
531 zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon());
532 fitToViewButton->setIcon(m_actionCollection->action("zoom_to_fit")->icon());
534 m_tagsButton->setIcon(KisIconUtils::loadIcon("tag"));
535 m_clearColorHistoryButton->setIcon(KisIconUtils::loadIcon("reload-preset-16"));
538}
539
541{
543 const bool reallyVisible = visible && m_dockerHudButton->isChecked();
544 m_dockerHud->setVisible(reallyVisible);
545
546 KisConfig cfg(false);
547 cfg.setShowBrushHud(visible);
548}
549
551{
552 const bool reallyVisible = visible && m_bottomBarButton->isChecked();
553
554 m_bottomBarWidget->setVisible(reallyVisible);
555
556 KisConfig cfg(false);
557 cfg.setShowPaletteBottomBar(visible);
558}
559
560void KisPopupPalette::setParent(QWidget *parent) {
561 QWidget::setParent(parent);
562}
563
564
566{
567 // Note: the canvas popup widget system "abuses" the sizeHint to determine
568 // the position to show the widget; this does not reflect the true size.
570}
571
572void KisPopupPalette::paintEvent(QPaintEvent* e)
573{
574 Q_UNUSED(e);
575
576 QPainter painter(this);
577
578 QPen pen(palette().color(QPalette::Text), BORDER_WIDTH);
579 painter.setPen(pen);
580
581 painter.setRenderHint(QPainter::Antialiasing);
582 painter.setRenderHint(QPainter::SmoothPixmapTransform);
583
584 if (m_isOverFgBgColors) {
585 painter.save();
586 painter.setPen(QPen(palette().color(QPalette::Highlight), BORDER_WIDTH));
587 }
588
589 // painting background color indicator
590 QPainterPath bgColor(drawFgBgColorIndicator(0));
591 painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor()));
592 painter.drawPath(bgColor);
593
594 // painting foreground color indicator
595 QPainterPath fgColor(drawFgBgColorIndicator(1));
596 painter.fillPath(fgColor, m_displayRenderer->toQColor(m_colorSelector->getCurrentColor()));
597 painter.drawPath(fgColor);
598
599 if (m_isOverFgBgColors) painter.restore();
600
601
602 // create a circle background that everything else will go into
603 QPainterPath backgroundContainer;
604
605 // draws the circle halfway into the border so that the border never goes past the bounds of the popup
607 backgroundContainer.addEllipse(circleRect);
608 painter.fillPath(backgroundContainer, palette().brush(QPalette::Window));
609 painter.drawPath(backgroundContainer);
610
612 painter.save();
613 QPen pen(palette().color(QPalette::Window).lighter(150), 2);
614 painter.setPen(pen);
615
616 // draw rotation snap lines
618 for (QLineF &line: m_snapLines) {
619 painter.drawLine(line);
620 }
621 }
622 // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas
623 // with the indicator
624 QPainterPath rotationTrackPath;
625 QRectF circleRect2(m_rotationTrackSize, m_rotationTrackSize,
627
628 rotationTrackPath.addEllipse(circleRect2);
629 painter.drawPath(rotationTrackPath);
630
631 // create a reset canvas rotation indicator to bring the canvas back to 0 degrees
632 QRectF resetRotationIndicator = m_resetCanvasRotationIndicatorRect;
633
635 ? palette().color(QPalette::Highlight)
636 : palette().color(QPalette::Text));
637 // cover the first snap line
638 painter.setBrush(palette().brush(QPalette::Window));
639 painter.setPen(pen);
640 painter.drawEllipse(resetRotationIndicator);
641
642 // create the canvas rotation handle
643 // highlight if either just hovering or currently rotating
645 ? palette().color(QPalette::Highlight)
646 : palette().color(QPalette::Text));
647 painter.setPen(pen);
648
649 // fill with highlight if snapping
651 ? palette().brush(QPalette::Highlight)
652 : palette().brush(QPalette::Text));
653
654 // gotta update the rect, see bug 459801
655 // (note: it can't just update when it shows up because then rotating when popup palette is on the screen wouldn't make an effect)
657 painter.drawEllipse(m_canvasRotationIndicatorRect);
658
659 painter.restore();
660 }
661
662 // the following things needs to be based off the center, so let's translate the painter
663 painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
664
665 // painting favorite brushes
667
668 // painting favorite brushes pixmap/icon
669 QPainterPath presetPath;
670 int presetCount = images.size();
671 bool isTagEmpty = presetCount == 0;
672 for (int pos = 0; pos < m_presetSlotCount; pos++) {
673 painter.save();
674 presetPath = createPathFromPresetIndex(pos);
675
676 if (pos < presetCount) {
677 painter.setClipPath(presetPath);
678
679 QRect bounds = presetPath.boundingRect().toAlignedRect();
680 if (!images.at(pos).isNull()) {
681 QImage previewHighDPI = images.at(pos).scaled(bounds.size()*devicePixelRatioF() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
682 previewHighDPI.setDevicePixelRatio(devicePixelRatioF());
683 painter.drawImage(bounds.topLeft(), previewHighDPI);
684 }
685 } else {
686 painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it
687 }
688 // needs to be called here so that the clipping is removed
689 painter.restore();
690 // if the slot is empty, stroke it slightly darker
691 QColor color = isTagEmpty || pos >= presetCount
692 ? palette().color(QPalette::Window).lighter(150)
693 : palette().color(QPalette::Text);
694 painter.setPen(QPen(color, 1));
695 painter.drawPath(presetPath);
696 }
697 if (hoveredPreset() > -1) {
699 painter.setPen(QPen(palette().color(QPalette::Highlight), BORDER_WIDTH));
700 painter.drawPath(presetPath);
701 }
702
703 if (m_showColorHistory) {
704 // paint recent colors area.
705 painter.setPen(Qt::NoPen);
706 const qreal rotationAngle = 360.0 / m_resourceManager->recentColorsTotal();
707 const qreal rotationOffset = 180.0;
708
709 painter.rotate(rotationOffset);
710
711 // there might be no recent colors at the start, so paint a placeholder
713 painter.setBrush(Qt::transparent);
714
715 QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
716 painter.setPen(QPen(palette().color(QPalette::Window).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
717 painter.drawPath(emptyRecentColorsPath);
718 } else {
719 for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) {
721
722 //accessing recent color of index pos
723 painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) ));
724 painter.drawPath(recentColorsPath);
725 painter.rotate(rotationAngle);
726 }
727 }
728
729 // painting hovered color
730 if (hoveredColor() > -1) {
731 painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
732
734 QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
735 painter.drawPath(path_ColorDonut);
736 } else {
737 painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) * rotationAngle);
739 painter.drawPath(path);
740 painter.rotate(hoveredColor() * -1 * rotationAngle);
741 }
742 }
743
744 // painting selected color
745 if (selectedColor() > -1) {
746 painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
747
749 QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
750 painter.drawPath(path_ColorDonut);
751 } else {
752 painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) * rotationAngle);
754 painter.drawPath(path);
755 painter.rotate(selectedColor() * -1 * rotationAngle);
756 }
757 }
758 }
759
760
761 // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better
763 opacityChange->setOpacity(0.4);
764 } else {
765 opacityChange->setOpacity(1.0);
766 }
767
768}
769
770void KisPopupPalette::resizeEvent(QResizeEvent* resizeEvent) {
771 Q_UNUSED(resizeEvent);
775 // Ensure that the resized geometry fits within the desired rect...
776
782 const QPoint globalTopLeft = windowHandle() ?
783 geometry().topLeft() :
784 parentWidget()->mapToGlobal(geometry().topLeft());
785 ensureWithinParent(globalTopLeft, true);
786}
787
788QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius)
789{
790 QPainterPath path;
791 path.addEllipse(QPointF(x, y), outer_radius, outer_radius);
792 path.addEllipse(QPointF(x, y), inner_radius, inner_radius);
793 path.setFillRule(Qt::OddEvenFill);
794
795 return path;
796}
797
798QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit)
799{
800 QPainterPath path;
801 path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit));
802 path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit,
803 360.0 / limit);
804 path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit,
805 - 360.0 / limit);
806 path.closeSubpath();
807
808 return path;
809}
810
811QPainterPath KisPopupPalette::drawFgBgColorIndicator(int type) const
812{
813 QPointF edgePoint = QPointF(0.14645, 0.14645) * (m_popupPaletteSize);
814
815 // the points are really (-5, 15) and (5, 15) shifted right 1px
816 // this is so that where the circles meet the circle of the palette, the space occupied is exactly half to either side of the -45deg line
817 QPainterPath indicator;
818 switch (type) {
819 case 0: { // background
820 indicator.addEllipse(edgePoint + QPointF(-4, 15), 30, 30);
821 break;
822 }
823 case 1: { //foreground
824 indicator.addEllipse(edgePoint + QPointF(6, -15), 30, 30);
825 break;
826 }
827 }
828 return indicator;
829}
830
831QRectF KisPopupPalette::rotationIndicatorRect(qreal rotationAngle) const
832{
833 qreal paletteRadius = 0.5 * m_popupPaletteSize;
834 QPointF rotationDialPosition(drawPointOnAngle(rotationAngle, paletteRadius - 10));
835 rotationDialPosition += QPointF(paletteRadius, paletteRadius);
836
837 QPointF indicatorDiagonal(7.5, 7.5);
838 return QRectF(rotationDialPosition - indicatorDiagonal, rotationDialPosition + indicatorDiagonal);
839}
840
841void KisPopupPalette::mouseMoveEvent(QMouseEvent *event)
842{
843 QPointF point = event->localPos();
844 event->accept();
845
847 // check if mouse is over the canvas rotation knob
848 bool wasOverRotationIndicator = m_isOverCanvasRotationIndicator;
850 bool wasOverResetRotationIndicator = m_isOverResetCanvasRotationIndicator;
852
853 if (
854 wasOverRotationIndicator != m_isOverCanvasRotationIndicator ||
855 wasOverResetRotationIndicator != m_isOverResetCanvasRotationIndicator
856 ) {
857 update();
858 }
859
861 m_snapRotation = false;
862 int i = 0;
863 for (QRect &rect: m_snapRects) {
864 QPainterPath circle;
865 circle.addEllipse(rect);
866 if (circle.contains(point)) {
867 m_snapRotation = true;
868 m_rotationSnapAngle = i * 15;
869 break;
870 }
871 i++;
872 }
873 qreal finalAngle = 0.0;
874 if (m_snapRotation) {
875 finalAngle = m_rotationSnapAngle;
876 // to match the numbers displayed when rotating without snapping
877 if (finalAngle >= 270) {
878 finalAngle = finalAngle - 360;
879 }
880 } else {
881 // we are rotating the canvas, so calculate the rotation angle based off the center
882 // calculate the angle we are at first
883 QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2);
884
885 qreal dX = point.x() - widgetCenterPoint.x();
886 qreal dY = point.y() - widgetCenterPoint.y();
887
888
889 finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle
890 finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up
891 }
892 qreal angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out
893
894 KisCanvasController *canvasController =
896 KIS_ASSERT_RECOVER_RETURN(canvasController);
897 canvasController->rotateCanvas(angleDifference);
899
900 update();
901 Q_EMIT sigUpdateCanvas();
902 }
903 }
904
905 if (m_isRotatingCanvasIndicator == false) {
906 QPainterPath bgColor(drawFgBgColorIndicator(0));
907 QPainterPath fgColor(drawFgBgColorIndicator(1));
908 QPainterPath backgroundContainer;
910 backgroundContainer.addEllipse(circleRect);
911
912 QPainterPath fgBgColors = (fgColor + bgColor) - backgroundContainer;
913
914 if (fgBgColors.contains(point)) {
915 if (!m_isOverFgBgColors) {
916 m_isOverFgBgColors = true;
917 setToolTip(i18n("Click to swap foreground and background colors.\nRight click to set to black and white."));
918 update();
919 }
920 } else {
921 if (m_isOverFgBgColors) {
922 m_isOverFgBgColors = false;
923 setToolTip(QString());
924 update();
925 }
926 }
927
929 if (colorHistoryPath.contains(point)) {
930 if (hoveredPreset() >= 0) {
931 setToolTip(QString());
933 }
934
936
937 if (pos != hoveredColor()) {
938 setHoveredColor(pos);
939 update();
940 }
941 }
942 else {
943 if (hoveredColor() >= 0) {
944 setHoveredColor(-1);
945 update();
946 }
947
948 int pos = findPresetSlot(point);
949
950 if (pos != hoveredPreset()) {
951
952 if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) {
953 setToolTip(m_resourceManager->favoritePresetNamesList().at(pos));
954 setHoveredPreset(pos);
955 }
956 else {
957 setToolTip(QString());
959 }
960
961 update();
962 }
963 }
964 }
965}
966
967void KisPopupPalette::mousePressEvent(QMouseEvent *event)
968{
969 event->accept();
970
971 if (event->button() == Qt::LeftButton) {
975 update();
976 }
977
979 qreal angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs
980 KisCanvasController *canvasController =
982 KIS_ASSERT_RECOVER_RETURN(canvasController);
983 canvasController->rotateCanvas(angleDifference);
985
986 Q_EMIT sigUpdateCanvas();
987 }
988 }
989 }
990}
991
992bool KisPopupPalette::eventFilter(QObject *, QEvent *event)
993{
994 switch (event->type()) {
995 case QEvent::TouchBegin:
997 break;
998 case QEvent::MouseButtonPress:
999 case QEvent::MouseMove:
1000 // HACK(sh_zam): Let's say the tap gesture is used by the canvas to launch the popup. Following that, a
1001 // synthesized mousePress is sent and this arrives in our event filter here. But, this event was meant for the
1002 // canvas (even though it blocks it), so we only act on the event if we got a TouchBegin on it first.
1003 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedBySystem && !m_touchBeginReceived) {
1004 event->accept();
1005 return true;
1006 }
1007 break;
1008 case QEvent::MouseButtonRelease:
1009 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedBySystem && !m_touchBeginReceived) {
1010 event->accept();
1011 return true;
1012 }
1013 // fallthrough
1014 case QEvent::Show:
1015 case QEvent::FocusOut:
1016 m_touchBeginReceived = false;
1017 break;
1018 default:
1019 break;
1020 }
1021 return false;
1022}
1023
1025{
1027 QVector<QString> tags;
1028 for (int i = 0; i < model.rowCount(); ++i) {
1029 QModelIndex idx = model.index(i, 0);
1030 tags << model.data(idx, Qt::DisplayRole).toString();
1031 }
1032
1033 //std::sort(tags.begin(), tags.end());
1034
1035 if (!tags.isEmpty()) {
1036 QMenu menu;
1037 Q_FOREACH (const QString& tag, tags) {
1038 menu.addAction(tag)->setData(tag);
1039 }
1040
1041 QAction *action = menu.exec(QCursor::pos());
1042 if (action) {
1043
1044 for (int i = 0; i < model.rowCount(); ++i) {
1045 QModelIndex idx = model.index(i, 0);
1046 if (model.data(idx, Qt::DisplayRole).toString() == action->data()) {
1048 reconfigure();
1049 break;
1050 }
1051 }
1052 }
1053 } else {
1054 QWhatsThis::showText(QCursor::pos(),
1055 i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here."));
1056 }
1057
1058}
1059
1061 QAction *action = m_actionCollection->action("zoom_to_100pct");
1062
1063 if (action) {
1064 action->trigger();
1065 }
1066
1067 // also move the zoom slider to 100% position so they are in sync
1068 zoomCanvasSlider->setValue(100);
1069}
1070
1072 QAction *action = m_actionCollection->action("zoom_to_fit");
1073
1074 if (action) {
1075 action->trigger();
1076 }
1077
1078 // sync zoom slider
1080}
1081
1083 m_actionCollection->action("mirror_canvas_around_cursor")->setProperty("customPosition", QVariant(m_mirrorPos));
1084}
1086 m_actionCollection->action("mirror_canvas_around_cursor")->setProperty("customPosition", QVariant());
1087}
1088
1089void KisPopupPalette::popup(const QPoint &position) {
1090 setVisible(true);
1091 KIS_SAFE_ASSERT_RECOVER_RETURN(parentWidget());
1092 const QPoint globalPos = parentWidget()->mapToGlobal(position);
1093 ensureWithinParent(globalPos, false);
1094 m_mirrorPos = globalPos;
1095}
1096
1098{
1099 setVisible(false);
1100}
1101
1103{
1104 return isVisible();
1105}
1106
1107void KisPopupPalette::ensureWithinParent(const QPoint& globalPos, bool useUpperLeft) {
1108 if (isVisible()) {
1109 const QSize paletteSize = geometry().size();
1110 const QPoint paletteCenterOffset(paletteSize.width() / 2, paletteSize.height() / 2);
1111
1112 QPoint paletteGlobalPos = globalPos;
1113 if (!useUpperLeft) {
1114 paletteGlobalPos -= paletteCenterOffset;
1115 }
1116
1117 if (parentWidget()) {
1118 const qreal widgetMargin = -20.0;
1119
1120 const QPoint paletteParentPos = parentWidget()->mapFromGlobal(paletteGlobalPos);
1121 QRect paletteParentRect(paletteParentPos, paletteSize);
1122
1123 const QRect fitRect = kisGrowRect(parentWidget()->rect(), widgetMargin);
1124 paletteParentRect = kisEnsureInRect(paletteParentRect, fitRect);
1125 paletteGlobalPos = parentWidget()->mapToGlobal(paletteParentRect.topLeft());
1126 }
1127
1128
1129 const QPoint moveToPoint = this->windowHandle() ? paletteGlobalPos : parentWidget()->mapFromGlobal(paletteGlobalPos);
1130
1131 move(moveToPoint);
1132 }
1133}
1134
1135void KisPopupPalette::showEvent(QShowEvent *event)
1136{
1138
1139 // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within
1140 // the bounds and cause the canvas to jump between the slider's min and max
1144 zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider
1145 }
1146
1147 QWidget::showEvent(event);
1148}
1149
1150void KisPopupPalette::tabletEvent(QTabletEvent *event)
1151{
1152 event->ignore();
1153
1154 if (this->windowHandle()) {
1155#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1156 const QPointF localPos = event->posF();
1157#else
1158 const QPointF localPos = event->position();
1159#endif
1160
1161 if (event->type() == QEvent::TabletRelease && !QRectF(rect()).contains(localPos)) {
1162 event->accept();
1163 Q_EMIT finished();
1164 return;
1165 }
1166 }
1167}
1168
1170{
1171 QPointF point = event->localPos();
1172 event->accept();
1173
1177#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1178 const QPointF localPos = event->localPos();
1179#else
1180 const QPointF localPos = event->position();
1181#endif
1182
1183 if (this->windowHandle() && !QRectF(rect()).contains(localPos)) {
1184 Q_EMIT finished();
1185 return;
1186 }
1187
1189 update();
1190 }
1191
1193
1194 if (event->button() == Qt::LeftButton) {
1195 if (m_isOverFgBgColors) {
1197 }
1198
1199 //in favorite brushes area
1200 if (hoveredPreset() > -1) {
1201 //setSelectedBrush(hoveredBrush());
1203 }
1204
1205 if (m_showColorHistory) {
1207 if (pathColor.contains(point)) {
1209
1210 if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) {
1211 Q_EMIT sigUpdateRecentColor(pos);
1212 }
1213 }
1214 }
1215 } else if (event->button() == Qt::RightButton) {
1216 Q_EMIT finished();
1217 }
1218}
1219
1220int KisPopupPalette::calculateColorIndex(QPointF position, int numColors) const
1221{
1222 if (numColors < 1) {
1223 return -1;
1224 }
1225 // relative to palette center
1226 QPointF relPosition = position - QPointF(0.5 * m_popupPaletteSize, 0.5 * m_popupPaletteSize);
1227
1228 qreal angle = M_PI - qAtan2(relPosition.x(), relPosition.y()) + M_PI / numColors;
1229 angle = normalizeAngle(angle);
1230
1231 int index = floor(angle * numColors / (2 * M_PI));
1232 return qBound(0, index, numColors - 1);
1233}
1234
1235bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos)
1236{
1237 if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) {
1238 return true;
1239 }
1240 return false;
1241}
1242
1243QPointF KisPopupPalette::drawPointOnAngle(qreal angle, qreal radius) const
1244{
1245 QPointF p(
1246 // -90 so it starts at the top since this is mainly used by calculatePresetLayout
1247 radius * qCos(qDegreesToRadians(angle - 90)),
1248 radius * qSin(qDegreesToRadians(angle - 90))
1249 );
1250 return p;
1251}
1252
1254{
1255 if (m_presetSlotCount == 0) {
1257 return;
1258 }
1259 // how many degrees each slice will get
1260 // if the slot count is 1, we must divide by 2 to get 180 for the algorithm to work
1261 qreal angleSlice = 360.0 / qMax(m_presetSlotCount, 2);
1262 qreal outerRadius = m_popupPaletteSize/2 - m_presetRingMargin - (m_showRotationTrack ? m_rotationTrackSize + 1 /* half of stroke */ : BORDER_WIDTH);
1263 qreal innerRadius = m_colorHistoryOuterRadius +
1265 ? 1 /* half of stroke */ + m_presetRingMargin
1266 : 0 /* preset margin is already included in either color history radius when it's not showing */
1267 );
1268
1269 qreal ringWidth = outerRadius - innerRadius;
1270 qreal halfRingWidth = 0.5 * ringWidth;
1271 qreal ringMidRadius = innerRadius + halfRingWidth;
1272
1273 // reset the cached layout
1276
1277 // note: adding the margin the way it's done
1278 // (calculating the radiuses without taking it into account then subtracting it after)
1279 // is not particularly accurate, but it looks fine since it's so small
1280 int margin = 2;
1281
1282 // assume one row and get the max radius until the circles would touch
1283 qreal oneRowAngleSlice = angleSlice / 2;
1284 qreal oneRowMaxRadius = ringMidRadius * qSin(qDegreesToRadians(oneRowAngleSlice));
1285
1286 // if the circles are bigger than the ring we're limited by the
1287 // ring's width instead and only one row would fit
1288
1289 // oneRowMaxRadius * 0.2 is to make sure that we still do one row if
1290 // there isn't that much of a difference and two would just look weirder
1291 if (oneRowMaxRadius - margin > halfRingWidth - oneRowMaxRadius * 0.2) {
1292 c.ringCount = 1;
1293 c.firstRowRadius = qMin(halfRingWidth, oneRowMaxRadius - margin);
1294 c.firstRowPos = ringMidRadius;
1295 return;
1296 }
1297
1298 // otherwise 2 or 3 rows always fit
1299 qreal tempRadius = halfRingWidth;
1300 {
1301 // for two rows, the first row is tangent to the inner radius
1302 // and the second row to the outer one
1303 qreal twoRowInnerCount = ceil(qMax(m_presetSlotCount, 2) / 2.0);
1304 qreal twoRowAngleSlice = 360.0 / twoRowInnerCount;
1305
1306 // we can start at half the ring width and shrink the radius until nothing is overlapping
1307 while (tempRadius >= 0) {
1308 tempRadius -= 0.2;
1309 QPointF r1p1(drawPointOnAngle(twoRowAngleSlice / 2, innerRadius + tempRadius));
1310 QPointF r1p2(drawPointOnAngle(twoRowAngleSlice / 2 * 3, innerRadius + tempRadius));
1311 QPointF r2p(drawPointOnAngle(twoRowAngleSlice, outerRadius - tempRadius));
1312 qreal row1SiblingDistance = kisDistance(r1p1, r1p2);
1313 qreal row1To2Distance = kisDistance(r1p1, r2p);
1314 if (row1To2Distance >= (tempRadius + margin) * 2) {
1315 // the previous radius is the one that's guaranteed not to be overlapping
1316 if (row1SiblingDistance < (tempRadius + margin) * 2) {
1317 // the inner row still overlaps, attempt 3 rows instead
1318 break;
1319 }
1320 c.ringCount = 2;
1321 c.secondRowRadius = tempRadius;
1322 c.secondRowPos = outerRadius - tempRadius;
1323 c.firstRowRadius = tempRadius;
1324 c.firstRowPos = innerRadius + tempRadius;
1325 return;
1326 }
1327 }
1328 }
1329
1330 // for three rows, we initially arrange them like so:
1331 // the first row tangent to the inner radius
1332 // the second row in the middle
1333 // the third row tangent to the outer radius
1334
1335 qreal threeRowInnerCount = ceil(qMax(m_presetSlotCount, 2) / 3.0);
1336 qreal threeRowAngleSlice = 360.0 / threeRowInnerCount;
1337
1338 // then we decrease the radius until no row is overlapping each other or itself
1339 while (tempRadius >= 0) {
1340 QPointF r1p1(drawPointOnAngle(threeRowAngleSlice / 2, innerRadius + tempRadius));
1341 QPointF r1p2(drawPointOnAngle(threeRowAngleSlice / 2 * 3, innerRadius + tempRadius));
1342 QPointF r2p1(drawPointOnAngle(threeRowAngleSlice, ringMidRadius));
1343 QPointF r2p2(drawPointOnAngle(threeRowAngleSlice * 2, ringMidRadius));
1344 QPointF r3p(drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempRadius));
1345
1346 qreal row1SiblingDistance = kisDistance(r1p1, r1p2);
1347 qreal row1to2Distance = kisDistance(r1p1, r2p1);
1348 qreal row2to3Distance = kisDistance(r2p1, r3p);
1349 qreal row1to3Distance = kisDistance(r1p1, r3p);
1350
1351 if (
1352 row1to2Distance >= tempRadius * 2 &&
1353 row2to3Distance >= tempRadius * 2 &&
1354 row1to3Distance >= tempRadius * 2 &&
1355 row1SiblingDistance >= tempRadius * 2
1356 ) {
1357
1358 qreal row2SiblingDistance = kisDistance(r2p1, r2p2);
1359
1360 qreal firstRowRadius = tempRadius;
1361 qreal thirdRowRadius = tempRadius;
1362 qreal secondRowRadius = tempRadius;
1363
1364 bool firstRowTouching = row1SiblingDistance - firstRowRadius * 2 < 1;
1365 if (firstRowTouching) {
1366 // attempt to expand the second row
1367 // and expand + move the third row inwards
1368 QPointF tempR3p = r3p;
1369 qreal tempSecondThirdRowRadius = secondRowRadius;
1370 qreal tempRow2to3Distance = row2to3Distance;
1371 qreal tempRow1to3Distance = row1to3Distance;
1372 while (
1373 tempSecondThirdRowRadius * 2 < tempRow2to3Distance &&
1374 tempSecondThirdRowRadius * 2 < row2SiblingDistance &&
1375 tempSecondThirdRowRadius * 2 < tempRow1to3Distance &&
1376 tempSecondThirdRowRadius + firstRowRadius < row1to2Distance
1377 ) {
1378 // the previous temp variables are within limits
1379 r3p = tempR3p;
1380 row2to3Distance = tempRow2to3Distance;
1381 row1to3Distance = tempRow1to3Distance;
1382 secondRowRadius = tempSecondThirdRowRadius;
1383
1384 tempSecondThirdRowRadius += 1;
1385
1386 tempR3p = drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempSecondThirdRowRadius);
1387
1388 tempRow2to3Distance = kisDistance(r2p1, tempR3p);
1389 tempRow1to3Distance = kisDistance(r1p1, tempR3p);
1390 }
1391 thirdRowRadius = secondRowRadius;
1392 }
1393
1394 {
1395 // the third row can sometimes be expanded + moved a bit more
1396 qreal tempThirdRowRadius = thirdRowRadius;
1397 QPointF tempR3p = r3p;
1398 qreal tempRow2to3Distance = row2to3Distance;
1399 qreal tempRow1to3Distance = row1to3Distance;
1400 while (
1401 tempThirdRowRadius < halfRingWidth &&
1402 secondRowRadius + tempThirdRowRadius < tempRow2to3Distance &&
1403 firstRowRadius + tempThirdRowRadius < tempRow1to3Distance
1404 ) {
1405 r3p = tempR3p;
1406 row2to3Distance = tempRow2to3Distance;
1407 row1to3Distance = tempRow1to3Distance;
1408 thirdRowRadius = tempThirdRowRadius;
1409
1410 tempThirdRowRadius += 1;
1411
1412 tempR3p = drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempThirdRowRadius);
1413 tempRow2to3Distance = kisDistance(r2p1, tempR3p);
1414 tempRow1to3Distance = kisDistance(r1p1, tempR3p);
1415 }
1416 }
1417 // the third row is no longer moved
1418 qreal thirdRowPos = outerRadius - thirdRowRadius;
1419
1420 // many times, e.g. when the second row is touching
1421 // the first row can be moved outwards and expanded
1422 // sometimes it will even detach from the inner radius if the ringwidth is large enough
1423 // and there's a lot of presets
1424 qreal firstRowPos = innerRadius + tempRadius;
1425 {
1426 qreal tempFirstRowPos = firstRowPos;
1427 qreal tempFirstRowRadius = firstRowRadius;
1428 qreal tempRow1SiblingDistance = row1SiblingDistance;
1429 qreal tempRow1to3Distance = row1to3Distance;
1430 qreal tempRow1to2Distance = row1to2Distance;
1431 QPointF tempR1p1 = r1p1;
1432 QPointF tempR1p2 = r1p2;
1433
1434 while (
1435 tempFirstRowPos < ringMidRadius &&
1436 tempFirstRowRadius + secondRowRadius < tempRow1to2Distance &&
1437 tempFirstRowRadius + thirdRowRadius < tempRow1to3Distance
1438 ) {
1439 firstRowPos = tempFirstRowPos;
1440 firstRowRadius = tempFirstRowRadius;
1441 row1to2Distance = tempRow1to2Distance;
1442 r1p1 = tempR1p1;
1443 // these are unused after so it's not necessary to update them
1444 // row1to3Distance = tempRow1to3Distance;
1445 // row1SiblingDistance = tempRow1SiblingDistance;
1446 // r1p2 = tempR1p2;
1447
1448 tempFirstRowPos += 1;
1449
1450 tempR1p1 = drawPointOnAngle(threeRowAngleSlice / 2, tempFirstRowPos);
1451 tempR1p2 = drawPointOnAngle(threeRowAngleSlice / 2 * 3, tempFirstRowPos);
1452 tempRow1SiblingDistance = kisDistance(tempR1p1, tempR1p2);
1453 // expand it to the max size
1454 tempFirstRowRadius = tempRow1SiblingDistance / 2;
1455 tempRow1to2Distance = kisDistance(tempR1p2, r2p1);
1456 tempRow1to3Distance = kisDistance(tempR1p1, r3p);
1457 }
1458 }
1459
1460 // finally it's rare, but sometimes possible to also move + expand the second row
1461 qreal secondRowPos = ringMidRadius;
1462 bool row2touching1 = row1to2Distance - (firstRowRadius + secondRowRadius) < 1;
1463 bool row2touching3 = row2to3Distance - (thirdRowRadius + secondRowRadius) < 1;
1464 if (!row2touching1 && !row2touching3) {
1465 // move the second row in until it's touching the first row
1466 qreal knownAngleRatio = qSin(qDegreesToRadians(threeRowAngleSlice / 2)) /
1467 (firstRowRadius + secondRowRadius);
1468 qreal angleRow1Row2Center = qAsin(knownAngleRatio * firstRowPos);
1469 qreal angleCenterRow2Row1 = 180 - threeRowAngleSlice / 2 - qRadiansToDegrees(angleRow1Row2Center);
1470 secondRowPos = qSin(qDegreesToRadians(angleCenterRow2Row1)) / knownAngleRatio;
1471 }
1472 if (!row2touching3) {
1473 QPointF tempR2p1 = r2p1;
1474 qreal tempRadius = secondRowRadius;
1475 qreal tempRow1to2Distance = row1to2Distance;
1476 qreal tempRow2to3Distance = row2to3Distance;
1477 qreal tempSecondRowPos = secondRowPos;
1478 while (
1479 tempSecondRowPos < thirdRowPos &&
1480 tempRadius + thirdRowRadius < tempRow2to3Distance &&
1481 // this is an artificial limit, it could get bigger but looks weird
1482 tempRadius < thirdRowRadius
1483 ) {
1484 secondRowRadius = tempRadius;
1485 secondRowPos = tempSecondRowPos;
1486 // these are unused after so it's not necessary to update them
1487 // r2p1 = tempR2p1;
1488 // row1to2Distance = tempRow1to2Distance;
1489 // row2to3Distance = tempRow2to3Distance;
1490
1491 tempSecondRowPos += 1;
1492
1493 tempR2p1 = drawPointOnAngle(threeRowAngleSlice, secondRowPos + 1);
1494 tempRow1to2Distance = kisDistance(tempR2p1, r1p1);
1495 tempRow2to3Distance = kisDistance(tempR2p1, r3p);
1496 tempRadius = tempRow1to2Distance - firstRowRadius;
1497 }
1498 }
1499 c = {
1500 3, //ringCount
1501 firstRowRadius - margin,
1502 secondRowRadius - margin,
1503 thirdRowRadius - margin,
1504 firstRowPos,
1505 secondRowPos,
1506 thirdRowPos
1507 };
1508 return;
1509 }
1510 tempRadius -= 0.2;
1511 }
1512}
1513
1515{
1516 // how many degrees each slice will get
1517 // if the slot count is 1, we must divide by 2 to get 180 for the algorithm to work
1518 qreal angleSlice = 360.0 / qMax(m_presetSlotCount, 2);
1519 // the starting angle of the slice we need to draw. the negative sign makes us go clockwise.
1520 // adding 90 degrees makes us start at the top. otherwise we would start at the right
1521 qreal startingAngle = -(index * angleSlice) + 90;
1523 qreal radius = m_cachedPresetLayout.firstRowRadius;
1525 case 1: break;
1526 case 2: {
1527 angleSlice = 180.0/((m_presetSlotCount+1) / 2);
1528 startingAngle = -(index * angleSlice) + 90;
1529
1530 if (index % 2) {
1533 }
1534 break;
1535 }
1536 case 3: {
1537 int triplet = index / 3;
1538 angleSlice = 180.0 / ((m_presetSlotCount + 2) / 3);
1539 switch (index % 3) {
1540 case 0:
1541 startingAngle = -(triplet * 2 * angleSlice) + 90;
1544 break;
1545 case 1:
1546 startingAngle = -(triplet * 2 * angleSlice) + 90;
1549 break;
1550 case 2:
1551 startingAngle = -((triplet * 2 + 1) * angleSlice) + 90;
1554 break;
1555 default:
1556 KIS_ASSERT(false);
1557 }
1558 break;
1559 }
1560 default:
1562 }
1563 QPainterPath path;
1564 qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - radius;
1565 qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - radius;
1566 qreal pathDiameter = 2 * radius; // distance is used to calculate the X/Y in addition to the preset circle size
1567 path.addEllipse(pathX, pathY, pathDiameter, pathDiameter);
1568 return path;
1569}
1570
1572 int i = 0;
1573 for (QRect &rect: m_snapRects) {
1574 QPointF point(drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - BORDER_WIDTH - m_snapRadius/2));
1575 point += QPointF(m_popupPaletteSize / 2 - m_snapRadius, m_popupPaletteSize / 2 - m_snapRadius);
1576 rect = QRect(point.x(), point.y(), m_snapRadius*2, m_snapRadius*2);
1577 i++;
1578 }
1579 i = 0;
1580 for (QLineF &line: m_snapLines) {
1581 qreal penWidth = BORDER_WIDTH / 2;
1582 QPointF point1 = drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - m_rotationTrackSize + penWidth);
1583 point1 += QPointF(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
1584 QPointF point2 = drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - BORDER_WIDTH - penWidth);
1585 point2 += QPointF(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
1586 line = QLineF(point1, point2);
1587 i++;
1588 }
1589}
1590
1591int KisPopupPalette::findPresetSlot(QPointF position) const
1592{
1593 QPointF adjustedPoint = position - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2);
1594 for (int i = 0; i < m_presetSlotCount; i++) {
1595 if (createPathFromPresetIndex(i).contains(adjustedPoint)) {
1596 return i;
1597 }
1598 }
1599 return -1;
1600}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
const Params2D p
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
PythonPluginManager * instance
void connectBackwardKoColor(QObject *sender, const char *signal, QObject *receiver, const char *method)
void connectForwardKoColor(QObject *sender, const char *signal, QObject *receiver, const char *method)
void rotateCanvas(qreal angle, const std::optional< KoViewTransformStillPoint > &stillPoint, bool isNativeGesture=false)
virtual void slotSetColor(const KoColor &c)=0
virtual void slotSetColorSpace(const KoColorSpace *cs)
slotSetColorSpace Set the color space the selector should cover
virtual void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
virtual void setConfig(bool forceCircular, bool forceSelfUpdate)
virtual KoColor getCurrentColor() const =0
static KisConfigNotifier * instance()
void setShowBrushHud(bool value)
void setShowPaletteBottomBar(bool value)
bool showPaletteBottomBar(bool defaultValue=false) const
int favoritePresets(bool defaultValue=false) const
bool showBrushHud(bool defaultValue=false) const
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:832
void setIsShown(bool isShown)
void slotUpdateIcons()
QAction * action(int index) const
QPainterPath drawDonutPathAngle(int, int, int)
QPainterPath drawFgBgColorIndicator(int type) const
void zoomLevelChanged(int)
void sigUpdateCanvas()
bool isPointInPixmap(QPointF &, int pos)
int findPresetSlot(QPointF position) const
/ find the index of the brush preset slot containing @position.
bool onScreen() override
Returns whether the widget is active (on screen) or not.
KisHighlightedToolButton * canvasOnlyButton
QSize sizeHint() const override
std::array< QRect, 24 > m_snapRects
void tabletEvent(QTabletEvent *event) override
KisRoundHudButton * m_dockerHudButton
KisViewManager * m_viewManager
void finished() override
QPointF drawPointOnAngle(qreal angle, qreal radius) const
void ensureWithinParent(const QPoint &globalPos, bool useUpperLeft)
KisKActionCollection * m_actionCollection
KisRoundHudButton * m_tagsButton
QPushButton * zoomToOneHundredPercentButton
void slotExternalFgColorChanged(const KoColor &color)
void resizeEvent(QResizeEvent *) override
QRectF m_resetCanvasRotationIndicatorRect
void calculatePresetLayout()
Determine the number of rings to distribute the presets and calculate the radius of the brush preset ...
void slotSetSelectedColor(int x)
KisRoundHudButton * m_bottomBarButton
void slotZoomSliderChanged(int zoom)
CachedPresetLayout m_cachedPresetLayout
QPainterPath drawDonutPathFull(int, int, int, int)
void sigChangefGColor(const KoColor &)
KisHighlightedToolButton * mirrorMode
std::array< QLineF, 24 > m_snapLines
int calculateColorIndex(QPointF position, int numColors) const
Calculate index of recent color in array.
KisCoordinatesConverter * m_coordinatesConverter
void slotDisplayConfigurationChanged()
QRectF rotationIndicatorRect(qreal rotationAngle) const
void sigChangeActivePaintop(int)
void showBottomBarWidget(bool visible)
void dismiss() override
Called when you want to dismiss a popup widget.
QSpacerItem * m_mainArea
void setParent(QWidget *parent)
KisMouseClickEater * m_clicksEater
void showEvent(QShowEvent *event) override
void showHudWidget(bool visible)
QWidget * m_bottomBarWidget
QScopedPointer< KisSignalCompressor > m_colorChangeCompressor
KisColorSelectorInterface * m_colorSelector
KisFavoriteResourceManager * m_resourceManager
KisRoundHudButton * m_clearColorHistoryButton
KisPopupPalette(KisViewManager *, KisCoordinatesConverter *, KisFavoriteResourceManager *, const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent=0)
KisDockerHud * m_dockerHud
void setHoveredPreset(int x)
void mouseMoveEvent(QMouseEvent *) override
void mouseReleaseEvent(QMouseEvent *) override
QPainterPath createPathFromPresetIndex(int index) const
KisAcyclicSignalConnector * m_acyclicConnector
QGraphicsOpacityEffect * opacityChange
bool m_isOverResetCanvasRotationIndicator
void paintEvent(QPaintEvent *) override
void setSelectedColor(int x)
void mousePressEvent(QMouseEvent *) override
QRectF m_canvasRotationIndicatorRect
void slotZoomToOneHundredPercentClicked()
void popup(const QPoint &position) override
Called when and where you want a widget to popup.
const KoColorDisplayRendererInterface * m_displayRenderer
void setHoveredColor(int x)
void sigUpdateRecentColor(int)
bool eventFilter(QObject *, QEvent *) override
QPushButton * fitToViewButton
void setOnOffIcons(const QIcon &on, const QIcon &off)
KisTagSP tagForIndex(QModelIndex index=QModelIndex()) const override
KisCanvas2 * canvasBase() const
Return the canvas base class.
KisCanvasResourceProvider * canvasResourceProvider()
The KisVisualColorSelector class.
KoCanvasController * canvasController() const
virtual QColor toQColor(const KoColor &c, bool proofToPaintColors=false) const =0
virtual const KoColorSpace * getPaintingColorSpace() const =0
getColorSpace
virtual quint32 colorChannelCount() const =0
int zoomInPercent() const
void tabletEvent(QTabletEvent *event) override
PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define bounds(x, a, b)
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
std::enable_if< std::is_floating_point< T >::value, T >::type normalizeAngle(T a)
Definition kis_global.h:121
QRect kisEnsureInRect(QRect rc, const QRect &bounds)
Definition kis_global.h:291
#define M_PI
Definition kis_global.h:111
static const qreal BORDER_WIDTH
static const int WIDGET_MARGIN
QIcon loadIcon(const QString &name)
const QString PaintOpPresets
unsigned paletteSize
Definition palette.c:34
rgba palette[MAX_PALETTE]
Definition palette.c:35
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())
void mousePressEvent(QMouseEvent *event) override
void mouseMoveEvent(QMouseEvent *event) override
void mouseReleaseEvent(QMouseEvent *event) override
const KoColorDisplayRendererInterface * displayRenderer