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