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 // Load configuration..
275 KisConfig cfg(true);
276 m_dockerHudButton->setChecked(cfg.showBrushHud());
277 m_bottomBarButton->setChecked(cfg.showPaletteBottomBar());
278
279 m_dockerHud->setVisible(m_dockerHudButton->isChecked());
280 m_bottomBarWidget->setVisible(m_bottomBarButton->isChecked());
281}
282
286
288{
289 reconfigure();
290 layout()->invalidate();
291}
292
294{
295 KisConfig config(true);
296 m_useDynamicSlotCount = config.readEntry("popuppalette/useDynamicSlotCount", true);
299 int presetCount = m_resourceManager->numFavoritePresets();
300 // if there are no presets because the tag is empty
301 // show the maximum number allowed (they will be painted as empty slots)
302 m_presetSlotCount = presetCount == 0
305 } else {
307 }
308 m_popupPaletteSize = config.readEntry("popuppalette/size", 385);
309 qreal selectorRadius = config.readEntry("popuppalette/selectorSize", 140) / 2.0;
310
311 m_showColorHistory = config.readEntry("popuppalette/showColorHistory", true);
312 m_showRotationTrack = config.readEntry("popuppalette/showRotationTrack", true);
313
316 if (m_showColorHistory) {
318 m_clearColorHistoryButton->setVisible(true);
319 } else {
320 m_clearColorHistoryButton->setVisible(false);
321 }
322
324
325 bool useVisualSelector = config.readEntry<bool>("popuppalette/usevisualcolorselector", false);
326 if (m_colorSelector) {
327 // if the selector type changed, delete it
328 bool haveVisualSelector = qobject_cast<KisVisualColorSelector*>(m_colorSelector) != 0;
329 if (useVisualSelector != haveVisualSelector) {
330 delete m_colorSelector;
331 m_colorSelector = 0;
332 }
333 }
334 if (!m_colorSelector) {
335 if (useVisualSelector) {
337 selector->setAcceptTabletEvents(true);
338 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()),
339 selector, SLOT(slotConfigurationChanged()));
340 m_colorSelector = selector;
341 }
342 else {
344 connect(m_colorSelector, SIGNAL(requestCloseContainer()), this, SIGNAL(finished()));
345 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()),
346 m_colorSelector, SLOT(configurationChanged()));
347 }
349 m_colorSelector->setConfig(true,false);
350 m_colorSelector->setVisible(true);
352 connect(m_colorSelector, SIGNAL(sigNewColor(KoColor)),
353 m_colorChangeCompressor.data(), SLOT(start()));
354
355 }
356
357
358 const int auxButtonSize = 35;
359 m_colorSelector->move(m_popupPaletteSize/2 - selectorRadius, m_popupPaletteSize/2 - selectorRadius);
361
362 // ellipse - to make sure the widget doesn't eat events meant for recent colors or brushes
363 // - needs to be +2 pixels on every side for anti-aliasing to look nice on high dpi displays
364 // rectangle - to make sure the area doesn't extend outside of the widget
365 QRegion maskedEllipse(-2, -2, m_colorSelector->width() + 4, m_colorSelector->height() + 4, QRegion::Ellipse );
366 QRegion maskedRectangle(0, 0, m_colorSelector->width(), m_colorSelector->height(), QRegion::Rectangle);
367 QRegion maskedRegion = maskedEllipse.intersected(maskedRectangle);
368
369 m_colorSelector->setMask(maskedRegion);
370
371 m_dockerHud->setFixedHeight(int(m_popupPaletteSize));
372
373 // arranges the buttons around the popup palette
374 // buttons are spread out from the center of the set arc length
375
376 // the margin in degrees between buttons
377 qreal margin = 10.0;
378 // visual center
379 qreal center = m_popupPaletteSize/2 - auxButtonSize/2.0;
380 qreal length = m_popupPaletteSize/2 + auxButtonSize/2.0 + 5;
381 {
382 int buttonCount = 2;
383 int arcLength = 90;
384 // note the int buttonCount/2 is on purpose
385 qreal start = arcLength/2 - (buttonCount/2) * margin;
386 if (buttonCount % 2 == 0) start += margin / 2;
387 int place = 0;
388 m_dockerHudButton->setGeometry(
389 center + qCos(qDegreesToRadians(start + place*margin))*length,
390 center + qSin(qDegreesToRadians(start + place*margin))*length,
391 auxButtonSize, auxButtonSize
392 );
393 place++;
394 m_bottomBarButton->setGeometry (
395 center + qCos(qDegreesToRadians(start + place*margin))*length,
396 center + qSin(qDegreesToRadians(start + place*margin))*length,
397 auxButtonSize, auxButtonSize
398 );
399 }
400 {
401 int buttonCount = m_showColorHistory ? 2 : 1 ;
402 int arcLength = 90;
403 int shiftArc = 90;
404 // note the int buttonCount/2 is on purpose
405 qreal start = shiftArc + arcLength / 2 - (buttonCount/2) * margin;
406 if (buttonCount % 2 == 0) start += margin / 2;
407 int place = 0;
408 if (m_showColorHistory) {
409 m_clearColorHistoryButton->setGeometry(
410 center + qCos(qDegreesToRadians(start + place * margin)) * length,
411 center + qSin(qDegreesToRadians(start + place * margin)) * length,
412 auxButtonSize, auxButtonSize);
413 place++;
414 }
415 m_tagsButton->setGeometry(
416 center + qCos(qDegreesToRadians(start + place*margin))*length,
417 center + qSin(qDegreesToRadians(start + place*margin))*length,
418 auxButtonSize, auxButtonSize
419 );
420 }
422}
423
425{
426 // Visual Color Selector picks up color space from input
429 //hack to get around cmyk for now.
430 if (paintingCS->colorChannelCount()>3) {
431 paintingCS = KoColorSpaceRegistry::instance()->rgb8();
432 }
435}
436
443
445{
446 if (isVisible()) {
447 update();
449 }
450}
451
463
464//setting KisPopupPalette properties
466{
467 return m_hoveredPreset;
468}
469
474
476{
477 return m_hoveredColor;
478}
479
484
486{
487 return m_selectedColor;
488}
489
494
496 Q_EMIT zoomLevelChanged(zoom);
497}
498
503
508
510{
511 this->setPalette(qApp->palette());
512
513 for(int i=0; i<this->children().size(); i++) {
514 QWidget *w = qobject_cast<QWidget*>(this->children().at(i));
515 if (w) {
516 w->setPalette(qApp->palette());
517 }
518 }
519 zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon());
520 fitToViewButton->setIcon(m_actionCollection->action("zoom_to_fit")->icon());
522 m_tagsButton->setIcon(KisIconUtils::loadIcon("tag"));
523 m_clearColorHistoryButton->setIcon(KisIconUtils::loadIcon("reload-preset-16"));
526}
527
529{
531 const bool reallyVisible = visible && m_dockerHudButton->isChecked();
532 m_dockerHud->setVisible(reallyVisible);
533
534 KisConfig cfg(false);
535 cfg.setShowBrushHud(visible);
536}
537
539{
540 const bool reallyVisible = visible && m_bottomBarButton->isChecked();
541
542 m_bottomBarWidget->setVisible(reallyVisible);
543
544 KisConfig cfg(false);
545 cfg.setShowPaletteBottomBar(visible);
546}
547
548void KisPopupPalette::setParent(QWidget *parent) {
549 QWidget::setParent(parent);
550}
551
552
554{
555 // Note: the canvas popup widget system "abuses" the sizeHint to determine
556 // the position to show the widget; this does not reflect the true size.
558}
559
560void KisPopupPalette::paintEvent(QPaintEvent* e)
561{
562 Q_UNUSED(e);
563
564 QPainter painter(this);
565
566 QPen pen(palette().color(QPalette::Text), BORDER_WIDTH);
567 painter.setPen(pen);
568
569 painter.setRenderHint(QPainter::Antialiasing);
570 painter.setRenderHint(QPainter::SmoothPixmapTransform);
571
572 if (m_isOverFgBgColors) {
573 painter.save();
574 painter.setPen(QPen(palette().color(QPalette::Highlight), BORDER_WIDTH));
575 }
576
577 // painting background color indicator
578 QPainterPath bgColor(drawFgBgColorIndicator(0));
579 painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor()));
580 painter.drawPath(bgColor);
581
582 // painting foreground color indicator
583 QPainterPath fgColor(drawFgBgColorIndicator(1));
584 painter.fillPath(fgColor, m_displayRenderer->toQColor(m_colorSelector->getCurrentColor()));
585 painter.drawPath(fgColor);
586
587 if (m_isOverFgBgColors) painter.restore();
588
589
590 // create a circle background that everything else will go into
591 QPainterPath backgroundContainer;
592
593 // draws the circle halfway into the border so that the border never goes past the bounds of the popup
595 backgroundContainer.addEllipse(circleRect);
596 painter.fillPath(backgroundContainer, palette().brush(QPalette::Window));
597 painter.drawPath(backgroundContainer);
598
600 painter.save();
601 QPen pen(palette().color(QPalette::Window).lighter(150), 2);
602 painter.setPen(pen);
603
604 // draw rotation snap lines
606 for (QLineF &line: m_snapLines) {
607 painter.drawLine(line);
608 }
609 }
610 // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas
611 // with the indicator
612 QPainterPath rotationTrackPath;
613 QRectF circleRect2(m_rotationTrackSize, m_rotationTrackSize,
615
616 rotationTrackPath.addEllipse(circleRect2);
617 painter.drawPath(rotationTrackPath);
618
619 // create a reset canvas rotation indicator to bring the canvas back to 0 degrees
620 QRectF resetRotationIndicator = m_resetCanvasRotationIndicatorRect;
621
623 ? palette().color(QPalette::Highlight)
624 : palette().color(QPalette::Text));
625 // cover the first snap line
626 painter.setBrush(palette().brush(QPalette::Window));
627 painter.setPen(pen);
628 painter.drawEllipse(resetRotationIndicator);
629
630 // create the canvas rotation handle
631 // highlight if either just hovering or currently rotating
633 ? palette().color(QPalette::Highlight)
634 : palette().color(QPalette::Text));
635 painter.setPen(pen);
636
637 // fill with highlight if snapping
639 ? palette().brush(QPalette::Highlight)
640 : palette().brush(QPalette::Text));
641
642 // gotta update the rect, see bug 459801
643 // (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)
645 painter.drawEllipse(m_canvasRotationIndicatorRect);
646
647 painter.restore();
648 }
649
650 // the following things needs to be based off the center, so let's translate the painter
651 painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
652
653 // painting favorite brushes
655
656 // painting favorite brushes pixmap/icon
657 QPainterPath presetPath;
658 int presetCount = images.size();
659 bool isTagEmpty = presetCount == 0;
660 for (int pos = 0; pos < m_presetSlotCount; pos++) {
661 painter.save();
662 presetPath = createPathFromPresetIndex(pos);
663
664 if (pos < presetCount) {
665 painter.setClipPath(presetPath);
666
667 QRect bounds = presetPath.boundingRect().toAlignedRect();
668 if (!images.at(pos).isNull()) {
669 QImage previewHighDPI = images.at(pos).scaled(bounds.size()*devicePixelRatioF() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
670 previewHighDPI.setDevicePixelRatio(devicePixelRatioF());
671 painter.drawImage(bounds.topLeft(), previewHighDPI);
672 }
673 } else {
674 painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it
675 }
676 // needs to be called here so that the clipping is removed
677 painter.restore();
678 // if the slot is empty, stroke it slightly darker
679 QColor color = isTagEmpty || pos >= presetCount
680 ? palette().color(QPalette::Window).lighter(150)
681 : palette().color(QPalette::Text);
682 painter.setPen(QPen(color, 1));
683 painter.drawPath(presetPath);
684 }
685 if (hoveredPreset() > -1) {
687 painter.setPen(QPen(palette().color(QPalette::Highlight), BORDER_WIDTH));
688 painter.drawPath(presetPath);
689 }
690
691 if (m_showColorHistory) {
692 // paint recent colors area.
693 painter.setPen(Qt::NoPen);
694 const qreal rotationAngle = 360.0 / m_resourceManager->recentColorsTotal();
695 const qreal rotationOffset = 180.0;
696
697 painter.rotate(rotationOffset);
698
699 // there might be no recent colors at the start, so paint a placeholder
701 painter.setBrush(Qt::transparent);
702
703 QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
704 painter.setPen(QPen(palette().color(QPalette::Window).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
705 painter.drawPath(emptyRecentColorsPath);
706 } else {
707 for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) {
709
710 //accessing recent color of index pos
711 painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) ));
712 painter.drawPath(recentColorsPath);
713 painter.rotate(rotationAngle);
714 }
715 }
716
717 // painting hovered color
718 if (hoveredColor() > -1) {
719 painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
720
722 QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
723 painter.drawPath(path_ColorDonut);
724 } else {
725 painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) * rotationAngle);
727 painter.drawPath(path);
728 painter.rotate(hoveredColor() * -1 * rotationAngle);
729 }
730 }
731
732 // painting selected color
733 if (selectedColor() > -1) {
734 painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
735
737 QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
738 painter.drawPath(path_ColorDonut);
739 } else {
740 painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) * rotationAngle);
742 painter.drawPath(path);
743 painter.rotate(selectedColor() * -1 * rotationAngle);
744 }
745 }
746 }
747
748
749 // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better
751 opacityChange->setOpacity(0.4);
752 } else {
753 opacityChange->setOpacity(1.0);
754 }
755
756}
757
758void KisPopupPalette::resizeEvent(QResizeEvent* resizeEvent) {
759 Q_UNUSED(resizeEvent);
763 // Ensure that the resized geometry fits within the desired rect...
764
770 const QPoint globalTopLeft = windowHandle() ?
771 geometry().topLeft() :
772 parentWidget()->mapToGlobal(geometry().topLeft());
773 ensureWithinParent(globalTopLeft, true);
774}
775
776QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius)
777{
778 QPainterPath path;
779 path.addEllipse(QPointF(x, y), outer_radius, outer_radius);
780 path.addEllipse(QPointF(x, y), inner_radius, inner_radius);
781 path.setFillRule(Qt::OddEvenFill);
782
783 return path;
784}
785
786QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit)
787{
788 QPainterPath path;
789 path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit));
790 path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit,
791 360.0 / limit);
792 path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit,
793 - 360.0 / limit);
794 path.closeSubpath();
795
796 return path;
797}
798
799QPainterPath KisPopupPalette::drawFgBgColorIndicator(int type) const
800{
801 QPointF edgePoint = QPointF(0.14645, 0.14645) * (m_popupPaletteSize);
802
803 // the points are really (-5, 15) and (5, 15) shifted right 1px
804 // 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
805 QPainterPath indicator;
806 switch (type) {
807 case 0: { // background
808 indicator.addEllipse(edgePoint + QPointF(-4, 15), 30, 30);
809 break;
810 }
811 case 1: { //foreground
812 indicator.addEllipse(edgePoint + QPointF(6, -15), 30, 30);
813 break;
814 }
815 }
816 return indicator;
817}
818
819QRectF KisPopupPalette::rotationIndicatorRect(qreal rotationAngle) const
820{
821 qreal paletteRadius = 0.5 * m_popupPaletteSize;
822 QPointF rotationDialPosition(drawPointOnAngle(rotationAngle, paletteRadius - 10));
823 rotationDialPosition += QPointF(paletteRadius, paletteRadius);
824
825 QPointF indicatorDiagonal(7.5, 7.5);
826 return QRectF(rotationDialPosition - indicatorDiagonal, rotationDialPosition + indicatorDiagonal);
827}
828
829void KisPopupPalette::mouseMoveEvent(QMouseEvent *event)
830{
831 QPointF point = event->localPos();
832 event->accept();
833
835 // check if mouse is over the canvas rotation knob
836 bool wasOverRotationIndicator = m_isOverCanvasRotationIndicator;
838 bool wasOverResetRotationIndicator = m_isOverResetCanvasRotationIndicator;
840
841 if (
842 wasOverRotationIndicator != m_isOverCanvasRotationIndicator ||
843 wasOverResetRotationIndicator != m_isOverResetCanvasRotationIndicator
844 ) {
845 update();
846 }
847
849 m_snapRotation = false;
850 int i = 0;
851 for (QRect &rect: m_snapRects) {
852 QPainterPath circle;
853 circle.addEllipse(rect);
854 if (circle.contains(point)) {
855 m_snapRotation = true;
856 m_rotationSnapAngle = i * 15;
857 break;
858 }
859 i++;
860 }
861 qreal finalAngle = 0.0;
862 if (m_snapRotation) {
863 finalAngle = m_rotationSnapAngle;
864 // to match the numbers displayed when rotating without snapping
865 if (finalAngle >= 270) {
866 finalAngle = finalAngle - 360;
867 }
868 } else {
869 // we are rotating the canvas, so calculate the rotation angle based off the center
870 // calculate the angle we are at first
871 QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2);
872
873 qreal dX = point.x() - widgetCenterPoint.x();
874 qreal dY = point.y() - widgetCenterPoint.y();
875
876
877 finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle
878 finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up
879 }
880 qreal angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out
881
882 KisCanvasController *canvasController =
884 KIS_ASSERT_RECOVER_RETURN(canvasController);
885 canvasController->rotateCanvas(angleDifference);
887
888 update();
889 Q_EMIT sigUpdateCanvas();
890 }
891 }
892
893 if (m_isRotatingCanvasIndicator == false) {
894 QPainterPath bgColor(drawFgBgColorIndicator(0));
895 QPainterPath fgColor(drawFgBgColorIndicator(1));
896 QPainterPath backgroundContainer;
898 backgroundContainer.addEllipse(circleRect);
899
900 QPainterPath fgBgColors = (fgColor + bgColor) - backgroundContainer;
901
902 if (fgBgColors.contains(point)) {
903 if (!m_isOverFgBgColors) {
904 m_isOverFgBgColors = true;
905 setToolTip(i18n("Click to swap foreground and background colors.\nRight click to set to black and white."));
906 update();
907 }
908 } else {
909 if (m_isOverFgBgColors) {
910 m_isOverFgBgColors = false;
911 setToolTip(QString());
912 update();
913 }
914 }
915
917 if (colorHistoryPath.contains(point)) {
918 if (hoveredPreset() >= 0) {
919 setToolTip(QString());
921 }
922
924
925 if (pos != hoveredColor()) {
926 setHoveredColor(pos);
927 update();
928 }
929 }
930 else {
931 if (hoveredColor() >= 0) {
932 setHoveredColor(-1);
933 update();
934 }
935
936 int pos = findPresetSlot(point);
937
938 if (pos != hoveredPreset()) {
939
940 if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) {
941 setToolTip(m_resourceManager->favoritePresetNamesList().at(pos));
942 setHoveredPreset(pos);
943 }
944 else {
945 setToolTip(QString());
947 }
948
949 update();
950 }
951 }
952 }
953}
954
955void KisPopupPalette::mousePressEvent(QMouseEvent *event)
956{
957 event->accept();
958
959 if (event->button() == Qt::LeftButton) {
963 update();
964 }
965
967 qreal angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs
968 KisCanvasController *canvasController =
970 KIS_ASSERT_RECOVER_RETURN(canvasController);
971 canvasController->rotateCanvas(angleDifference);
973
974 Q_EMIT sigUpdateCanvas();
975 }
976 }
977 }
978}
979
980bool KisPopupPalette::eventFilter(QObject *, QEvent *event)
981{
982 switch (event->type()) {
983 case QEvent::TouchBegin:
985 break;
986 case QEvent::MouseButtonPress:
987 case QEvent::MouseMove:
988 // HACK(sh_zam): Let's say the tap gesture is used by the canvas to launch the popup. Following that, a
989 // synthesized mousePress is sent and this arrives in our event filter here. But, this event was meant for the
990 // canvas (even though it blocks it), so we only act on the event if we got a TouchBegin on it first.
991 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedBySystem && !m_touchBeginReceived) {
992 event->accept();
993 return true;
994 }
995 break;
996 case QEvent::MouseButtonRelease:
997 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedBySystem && !m_touchBeginReceived) {
998 event->accept();
999 return true;
1000 }
1001 // fallthrough
1002 case QEvent::Show:
1003 case QEvent::FocusOut:
1004 m_touchBeginReceived = false;
1005 break;
1006 default:
1007 break;
1008 }
1009 return false;
1010}
1011
1013{
1015 QVector<QString> tags;
1016 for (int i = 0; i < model.rowCount(); ++i) {
1017 QModelIndex idx = model.index(i, 0);
1018 tags << model.data(idx, Qt::DisplayRole).toString();
1019 }
1020
1021 //std::sort(tags.begin(), tags.end());
1022
1023 if (!tags.isEmpty()) {
1024 QMenu menu;
1025 Q_FOREACH (const QString& tag, tags) {
1026 menu.addAction(tag)->setData(tag);
1027 }
1028
1029 QAction *action = menu.exec(QCursor::pos());
1030 if (action) {
1031
1032 for (int i = 0; i < model.rowCount(); ++i) {
1033 QModelIndex idx = model.index(i, 0);
1034 if (model.data(idx, Qt::DisplayRole).toString() == action->data()) {
1036 reconfigure();
1037 break;
1038 }
1039 }
1040 }
1041 } else {
1042 QWhatsThis::showText(QCursor::pos(),
1043 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."));
1044 }
1045
1046}
1047
1049 QAction *action = m_actionCollection->action("zoom_to_100pct");
1050
1051 if (action) {
1052 action->trigger();
1053 }
1054
1055 // also move the zoom slider to 100% position so they are in sync
1056 zoomCanvasSlider->setValue(100);
1057}
1058
1060 QAction *action = m_actionCollection->action("zoom_to_fit");
1061
1062 if (action) {
1063 action->trigger();
1064 }
1065
1066 // sync zoom slider
1068}
1069
1071 m_actionCollection->action("mirror_canvas_around_cursor")->setProperty("customPosition", QVariant(m_mirrorPos));
1072}
1074 m_actionCollection->action("mirror_canvas_around_cursor")->setProperty("customPosition", QVariant());
1075}
1076
1077void KisPopupPalette::popup(const QPoint &position) {
1078 setVisible(true);
1079 KIS_SAFE_ASSERT_RECOVER_RETURN(parentWidget());
1080 const QPoint globalPos = parentWidget()->mapToGlobal(position);
1081 ensureWithinParent(globalPos, false);
1082 m_mirrorPos = QCursor::pos();
1083}
1084
1086{
1087 setVisible(false);
1088}
1089
1091{
1092 return isVisible();
1093}
1094
1095void KisPopupPalette::ensureWithinParent(const QPoint& globalPos, bool useUpperLeft) {
1096 if (isVisible()) {
1097 const QSize paletteSize = geometry().size();
1098 const QPoint paletteCenterOffset(paletteSize.width() / 2, paletteSize.height() / 2);
1099
1100 QPoint paletteGlobalPos = globalPos;
1101 if (!useUpperLeft) {
1102 paletteGlobalPos -= paletteCenterOffset;
1103 }
1104
1105 if (parentWidget()) {
1106 const qreal widgetMargin = -20.0;
1107
1108 const QPoint paletteParentPos = parentWidget()->mapFromGlobal(paletteGlobalPos);
1109 QRect paletteParentRect(paletteParentPos, paletteSize);
1110
1111 const QRect fitRect = kisGrowRect(parentWidget()->rect(), widgetMargin);
1112 paletteParentRect = kisEnsureInRect(paletteParentRect, fitRect);
1113 paletteGlobalPos = parentWidget()->mapToGlobal(paletteParentRect.topLeft());
1114 }
1115
1116
1117 const QPoint moveToPoint = this->windowHandle() ? paletteGlobalPos : parentWidget()->mapFromGlobal(paletteGlobalPos);
1118
1119 move(moveToPoint);
1120 }
1121}
1122
1123void KisPopupPalette::showEvent(QShowEvent *event)
1124{
1126
1127 // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within
1128 // the bounds and cause the canvas to jump between the slider's min and max
1132 zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider
1133 }
1134
1135 QWidget::showEvent(event);
1136}
1137
1138void KisPopupPalette::tabletEvent(QTabletEvent *event)
1139{
1140 if (event->button() == Qt::RightButton && event->type() == QEvent::TabletPress) {
1142 }
1143
1144 event->ignore();
1145}
1146
1148{
1149 QPointF point = event->localPos();
1150 event->accept();
1151
1155#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1156 const QPointF localPos = event->localPos();
1157#else
1158 const QPointF localPos = event->position();
1159#endif
1160
1161 if (this->windowHandle() && !QRectF(rect()).contains(localPos)) {
1162 Q_EMIT finished();
1163 return;
1164 }
1165
1167 update();
1168 }
1169
1171
1172 if (event->button() == Qt::LeftButton) {
1173 if (m_isOverFgBgColors) {
1175 }
1176
1177 //in favorite brushes area
1178 if (hoveredPreset() > -1) {
1179 //setSelectedBrush(hoveredBrush());
1181 }
1182
1183 if (m_showColorHistory) {
1185 if (pathColor.contains(point)) {
1187
1188 if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) {
1189 Q_EMIT sigUpdateRecentColor(pos);
1190 }
1191 }
1192 }
1193 } else if (event->button() == Qt::RightButton) {
1194 Q_EMIT finished();
1195 }
1196}
1197
1198int KisPopupPalette::calculateColorIndex(QPointF position, int numColors) const
1199{
1200 if (numColors < 1) {
1201 return -1;
1202 }
1203 // relative to palette center
1204 QPointF relPosition = position - QPointF(0.5 * m_popupPaletteSize, 0.5 * m_popupPaletteSize);
1205
1206 qreal angle = M_PI - qAtan2(relPosition.x(), relPosition.y()) + M_PI / numColors;
1207 angle = normalizeAngle(angle);
1208
1209 int index = floor(angle * numColors / (2 * M_PI));
1210 return qBound(0, index, numColors - 1);
1211}
1212
1213bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos)
1214{
1215 if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) {
1216 return true;
1217 }
1218 return false;
1219}
1220
1221QPointF KisPopupPalette::drawPointOnAngle(qreal angle, qreal radius) const
1222{
1223 QPointF p(
1224 // -90 so it starts at the top since this is mainly used by calculatePresetLayout
1225 radius * qCos(qDegreesToRadians(angle - 90)),
1226 radius * qSin(qDegreesToRadians(angle - 90))
1227 );
1228 return p;
1229}
1230
1232{
1233 if (m_presetSlotCount == 0) {
1235 return;
1236 }
1237 // how many degrees each slice will get
1238 // if the slot count is 1, we must divide by 2 to get 180 for the algorithm to work
1239 qreal angleSlice = 360.0 / qMax(m_presetSlotCount, 2);
1240 qreal outerRadius = m_popupPaletteSize/2 - m_presetRingMargin - (m_showRotationTrack ? m_rotationTrackSize + 1 /* half of stroke */ : BORDER_WIDTH);
1241 qreal innerRadius = m_colorHistoryOuterRadius +
1243 ? 1 /* half of stroke */ + m_presetRingMargin
1244 : 0 /* preset margin is already included in either color history radius when it's not showing */
1245 );
1246
1247 qreal ringWidth = outerRadius - innerRadius;
1248 qreal halfRingWidth = 0.5 * ringWidth;
1249 qreal ringMidRadius = innerRadius + halfRingWidth;
1250
1251 // reset the cached layout
1254
1255 // note: adding the margin the way it's done
1256 // (calculating the radiuses without taking it into account then subtracting it after)
1257 // is not particularly accurate, but it looks fine since it's so small
1258 int margin = 2;
1259
1260 // assume one row and get the max radius until the circles would touch
1261 qreal oneRowAngleSlice = angleSlice / 2;
1262 qreal oneRowMaxRadius = ringMidRadius * qSin(qDegreesToRadians(oneRowAngleSlice));
1263
1264 // if the circles are bigger than the ring we're limited by the
1265 // ring's width instead and only one row would fit
1266
1267 // oneRowMaxRadius * 0.2 is to make sure that we still do one row if
1268 // there isn't that much of a difference and two would just look weirder
1269 if (oneRowMaxRadius - margin > halfRingWidth - oneRowMaxRadius * 0.2) {
1270 c.ringCount = 1;
1271 c.firstRowRadius = qMin(halfRingWidth, oneRowMaxRadius - margin);
1272 c.firstRowPos = ringMidRadius;
1273 return;
1274 }
1275
1276 // otherwise 2 or 3 rows always fit
1277 qreal tempRadius = halfRingWidth;
1278 {
1279 // for two rows, the first row is tangent to the inner radius
1280 // and the second row to the outer one
1281 qreal twoRowInnerCount = ceil(qMax(m_presetSlotCount, 2) / 2.0);
1282 qreal twoRowAngleSlice = 360.0 / twoRowInnerCount;
1283
1284 // we can start at half the ring width and shrink the radius until nothing is overlapping
1285 while (tempRadius >= 0) {
1286 tempRadius -= 0.2;
1287 QPointF r1p1(drawPointOnAngle(twoRowAngleSlice / 2, innerRadius + tempRadius));
1288 QPointF r1p2(drawPointOnAngle(twoRowAngleSlice / 2 * 3, innerRadius + tempRadius));
1289 QPointF r2p(drawPointOnAngle(twoRowAngleSlice, outerRadius - tempRadius));
1290 qreal row1SiblingDistance = kisDistance(r1p1, r1p2);
1291 qreal row1To2Distance = kisDistance(r1p1, r2p);
1292 if (row1To2Distance >= (tempRadius + margin) * 2) {
1293 // the previous radius is the one that's guaranteed not to be overlapping
1294 if (row1SiblingDistance < (tempRadius + margin) * 2) {
1295 // the inner row still overlaps, attempt 3 rows instead
1296 break;
1297 }
1298 c.ringCount = 2;
1299 c.secondRowRadius = tempRadius;
1300 c.secondRowPos = outerRadius - tempRadius;
1301 c.firstRowRadius = tempRadius;
1302 c.firstRowPos = innerRadius + tempRadius;
1303 return;
1304 }
1305 }
1306 }
1307
1308 // for three rows, we initially arrange them like so:
1309 // the first row tangent to the inner radius
1310 // the second row in the middle
1311 // the third row tangent to the outer radius
1312
1313 qreal threeRowInnerCount = ceil(qMax(m_presetSlotCount, 2) / 3.0);
1314 qreal threeRowAngleSlice = 360.0 / threeRowInnerCount;
1315
1316 // then we decrease the radius until no row is overlapping each other or itself
1317 while (tempRadius >= 0) {
1318 QPointF r1p1(drawPointOnAngle(threeRowAngleSlice / 2, innerRadius + tempRadius));
1319 QPointF r1p2(drawPointOnAngle(threeRowAngleSlice / 2 * 3, innerRadius + tempRadius));
1320 QPointF r2p1(drawPointOnAngle(threeRowAngleSlice, ringMidRadius));
1321 QPointF r2p2(drawPointOnAngle(threeRowAngleSlice * 2, ringMidRadius));
1322 QPointF r3p(drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempRadius));
1323
1324 qreal row1SiblingDistance = kisDistance(r1p1, r1p2);
1325 qreal row1to2Distance = kisDistance(r1p1, r2p1);
1326 qreal row2to3Distance = kisDistance(r2p1, r3p);
1327 qreal row1to3Distance = kisDistance(r1p1, r3p);
1328
1329 if (
1330 row1to2Distance >= tempRadius * 2 &&
1331 row2to3Distance >= tempRadius * 2 &&
1332 row1to3Distance >= tempRadius * 2 &&
1333 row1SiblingDistance >= tempRadius * 2
1334 ) {
1335
1336 qreal row2SiblingDistance = kisDistance(r2p1, r2p2);
1337
1338 qreal firstRowRadius = tempRadius;
1339 qreal thirdRowRadius = tempRadius;
1340 qreal secondRowRadius = tempRadius;
1341
1342 bool firstRowTouching = row1SiblingDistance - firstRowRadius * 2 < 1;
1343 if (firstRowTouching) {
1344 // attempt to expand the second row
1345 // and expand + move the third row inwards
1346 QPointF tempR3p = r3p;
1347 qreal tempSecondThirdRowRadius = secondRowRadius;
1348 qreal tempRow2to3Distance = row2to3Distance;
1349 qreal tempRow1to3Distance = row1to3Distance;
1350 while (
1351 tempSecondThirdRowRadius * 2 < tempRow2to3Distance &&
1352 tempSecondThirdRowRadius * 2 < row2SiblingDistance &&
1353 tempSecondThirdRowRadius * 2 < tempRow1to3Distance &&
1354 tempSecondThirdRowRadius + firstRowRadius < row1to2Distance
1355 ) {
1356 // the previous temp variables are within limits
1357 r3p = tempR3p;
1358 row2to3Distance = tempRow2to3Distance;
1359 row1to3Distance = tempRow1to3Distance;
1360 secondRowRadius = tempSecondThirdRowRadius;
1361
1362 tempSecondThirdRowRadius += 1;
1363
1364 tempR3p = drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempSecondThirdRowRadius);
1365
1366 tempRow2to3Distance = kisDistance(r2p1, tempR3p);
1367 tempRow1to3Distance = kisDistance(r1p1, tempR3p);
1368 }
1369 thirdRowRadius = secondRowRadius;
1370 }
1371
1372 {
1373 // the third row can sometimes be expanded + moved a bit more
1374 qreal tempThirdRowRadius = thirdRowRadius;
1375 QPointF tempR3p = r3p;
1376 qreal tempRow2to3Distance = row2to3Distance;
1377 qreal tempRow1to3Distance = row1to3Distance;
1378 while (
1379 tempThirdRowRadius < halfRingWidth &&
1380 secondRowRadius + tempThirdRowRadius < tempRow2to3Distance &&
1381 firstRowRadius + tempThirdRowRadius < tempRow1to3Distance
1382 ) {
1383 r3p = tempR3p;
1384 row2to3Distance = tempRow2to3Distance;
1385 row1to3Distance = tempRow1to3Distance;
1386 thirdRowRadius = tempThirdRowRadius;
1387
1388 tempThirdRowRadius += 1;
1389
1390 tempR3p = drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempThirdRowRadius);
1391 tempRow2to3Distance = kisDistance(r2p1, tempR3p);
1392 tempRow1to3Distance = kisDistance(r1p1, tempR3p);
1393 }
1394 }
1395 // the third row is no longer moved
1396 qreal thirdRowPos = outerRadius - thirdRowRadius;
1397
1398 // many times, e.g. when the second row is touching
1399 // the first row can be moved outwards and expanded
1400 // sometimes it will even detach from the inner radius if the ringwidth is large enough
1401 // and there's a lot of presets
1402 qreal firstRowPos = innerRadius + tempRadius;
1403 {
1404 qreal tempFirstRowPos = firstRowPos;
1405 qreal tempFirstRowRadius = firstRowRadius;
1406 qreal tempRow1SiblingDistance = row1SiblingDistance;
1407 qreal tempRow1to3Distance = row1to3Distance;
1408 qreal tempRow1to2Distance = row1to2Distance;
1409 QPointF tempR1p1 = r1p1;
1410 QPointF tempR1p2 = r1p2;
1411
1412 while (
1413 tempFirstRowPos < ringMidRadius &&
1414 tempFirstRowRadius + secondRowRadius < tempRow1to2Distance &&
1415 tempFirstRowRadius + thirdRowRadius < tempRow1to3Distance
1416 ) {
1417 firstRowPos = tempFirstRowPos;
1418 firstRowRadius = tempFirstRowRadius;
1419 row1to2Distance = tempRow1to2Distance;
1420 r1p1 = tempR1p1;
1421 // these are unused after so it's not necessary to update them
1422 // row1to3Distance = tempRow1to3Distance;
1423 // row1SiblingDistance = tempRow1SiblingDistance;
1424 // r1p2 = tempR1p2;
1425
1426 tempFirstRowPos += 1;
1427
1428 tempR1p1 = drawPointOnAngle(threeRowAngleSlice / 2, tempFirstRowPos);
1429 tempR1p2 = drawPointOnAngle(threeRowAngleSlice / 2 * 3, tempFirstRowPos);
1430 tempRow1SiblingDistance = kisDistance(tempR1p1, tempR1p2);
1431 // expand it to the max size
1432 tempFirstRowRadius = tempRow1SiblingDistance / 2;
1433 tempRow1to2Distance = kisDistance(tempR1p2, r2p1);
1434 tempRow1to3Distance = kisDistance(tempR1p1, r3p);
1435 }
1436 }
1437
1438 // finally it's rare, but sometimes possible to also move + expand the second row
1439 qreal secondRowPos = ringMidRadius;
1440 bool row2touching1 = row1to2Distance - (firstRowRadius + secondRowRadius) < 1;
1441 bool row2touching3 = row2to3Distance - (thirdRowRadius + secondRowRadius) < 1;
1442 if (!row2touching1 && !row2touching3) {
1443 // move the second row in until it's touching the first row
1444 qreal knownAngleRatio = qSin(qDegreesToRadians(threeRowAngleSlice / 2)) /
1445 (firstRowRadius + secondRowRadius);
1446 qreal angleRow1Row2Center = qAsin(knownAngleRatio * firstRowPos);
1447 qreal angleCenterRow2Row1 = 180 - threeRowAngleSlice / 2 - qRadiansToDegrees(angleRow1Row2Center);
1448 secondRowPos = qSin(qDegreesToRadians(angleCenterRow2Row1)) / knownAngleRatio;
1449 }
1450 if (!row2touching3) {
1451 QPointF tempR2p1 = r2p1;
1452 qreal tempRadius = secondRowRadius;
1453 qreal tempRow1to2Distance = row1to2Distance;
1454 qreal tempRow2to3Distance = row2to3Distance;
1455 qreal tempSecondRowPos = secondRowPos;
1456 while (
1457 tempSecondRowPos < thirdRowPos &&
1458 tempRadius + thirdRowRadius < tempRow2to3Distance &&
1459 // this is an artificial limit, it could get bigger but looks weird
1460 tempRadius < thirdRowRadius
1461 ) {
1462 secondRowRadius = tempRadius;
1463 secondRowPos = tempSecondRowPos;
1464 // these are unused after so it's not necessary to update them
1465 // r2p1 = tempR2p1;
1466 // row1to2Distance = tempRow1to2Distance;
1467 // row2to3Distance = tempRow2to3Distance;
1468
1469 tempSecondRowPos += 1;
1470
1471 tempR2p1 = drawPointOnAngle(threeRowAngleSlice, secondRowPos + 1);
1472 tempRow1to2Distance = kisDistance(tempR2p1, r1p1);
1473 tempRow2to3Distance = kisDistance(tempR2p1, r3p);
1474 tempRadius = tempRow1to2Distance - firstRowRadius;
1475 }
1476 }
1477 c = {
1478 3, //ringCount
1479 firstRowRadius - margin,
1480 secondRowRadius - margin,
1481 thirdRowRadius - margin,
1482 firstRowPos,
1483 secondRowPos,
1484 thirdRowPos
1485 };
1486 return;
1487 }
1488 tempRadius -= 0.2;
1489 }
1490}
1491
1493{
1494 // how many degrees each slice will get
1495 // if the slot count is 1, we must divide by 2 to get 180 for the algorithm to work
1496 qreal angleSlice = 360.0 / qMax(m_presetSlotCount, 2);
1497 // the starting angle of the slice we need to draw. the negative sign makes us go clockwise.
1498 // adding 90 degrees makes us start at the top. otherwise we would start at the right
1499 qreal startingAngle = -(index * angleSlice) + 90;
1501 qreal radius = m_cachedPresetLayout.firstRowRadius;
1503 case 1: break;
1504 case 2: {
1505 angleSlice = 180.0/((m_presetSlotCount+1) / 2);
1506 startingAngle = -(index * angleSlice) + 90;
1507
1508 if (index % 2) {
1511 }
1512 break;
1513 }
1514 case 3: {
1515 int triplet = index / 3;
1516 angleSlice = 180.0 / ((m_presetSlotCount + 2) / 3);
1517 switch (index % 3) {
1518 case 0:
1519 startingAngle = -(triplet * 2 * angleSlice) + 90;
1522 break;
1523 case 1:
1524 startingAngle = -(triplet * 2 * angleSlice) + 90;
1527 break;
1528 case 2:
1529 startingAngle = -((triplet * 2 + 1) * angleSlice) + 90;
1532 break;
1533 default:
1534 KIS_ASSERT(false);
1535 }
1536 break;
1537 }
1538 default:
1540 }
1541 QPainterPath path;
1542 qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - radius;
1543 qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - radius;
1544 qreal pathDiameter = 2 * radius; // distance is used to calculate the X/Y in addition to the preset circle size
1545 path.addEllipse(pathX, pathY, pathDiameter, pathDiameter);
1546 return path;
1547}
1548
1550 int i = 0;
1551 for (QRect &rect: m_snapRects) {
1552 QPointF point(drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - BORDER_WIDTH - m_snapRadius/2));
1553 point += QPointF(m_popupPaletteSize / 2 - m_snapRadius, m_popupPaletteSize / 2 - m_snapRadius);
1554 rect = QRect(point.x(), point.y(), m_snapRadius*2, m_snapRadius*2);
1555 i++;
1556 }
1557 i = 0;
1558 for (QLineF &line: m_snapLines) {
1559 qreal penWidth = BORDER_WIDTH / 2;
1560 QPointF point1 = drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - m_rotationTrackSize + penWidth);
1561 point1 += QPointF(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
1562 QPointF point2 = drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - BORDER_WIDTH - penWidth);
1563 point2 += QPointF(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
1564 line = QLineF(point1, point2);
1565 i++;
1566 }
1567}
1568
1569int KisPopupPalette::findPresetSlot(QPointF position) const
1570{
1571 QPointF adjustedPoint = position - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2);
1572 for (int i = 0; i < m_presetSlotCount; i++) {
1573 if (createPathFromPresetIndex(i).contains(adjustedPoint)) {
1574 return i;
1575 }
1576 }
1577 return -1;
1578}
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
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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:789
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:267
#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