File indexing completed on 2024-05-12 16:01:41
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2009 Vera Lukman <shicmap@gmail.com> 0003 SPDX-FileCopyrightText: 2011 Sven Langkamp <sven.langkamp@gmail.com> 0004 SPDX-FileCopyrightText: 2016 Scott Petrovic <scottpetrovic@gmail.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-only 0007 0008 */ 0009 #include <QtGui> 0010 #include <QMenu> 0011 #include <QWhatsThis> 0012 #include <QVBoxLayout> 0013 0014 #include <KisTagModel.h> 0015 0016 #include "kis_canvas2.h" 0017 #include "kis_config.h" 0018 #include "kis_popup_palette.h" 0019 #include "kis_favorite_resource_manager.h" 0020 #include "kis_icon_utils.h" 0021 #include <kis_canvas_resource_provider.h> 0022 #include <KoTriangleColorSelector.h> 0023 #include "KoColorDisplayRendererInterface.h" 0024 #include <KisVisualColorSelector.h> 0025 #include <kis_config_notifier.h> 0026 #include "kis_signal_compressor.h" 0027 #include "brushhud/kis_brush_hud.h" 0028 #include "brushhud/kis_round_hud_button.h" 0029 #include "kis_signals_blocker.h" 0030 #include "kis_canvas_controller.h" 0031 #include "kis_acyclic_signal_connector.h" 0032 #include <kis_paintop_preset.h> 0033 #include "KisMouseClickEater.h" 0034 0035 static const int WIDGET_MARGIN = 16; 0036 static const qreal BORDER_WIDTH = 3.0; 0037 0038 class PopupColorTriangle : public KoTriangleColorSelector 0039 { 0040 public: 0041 PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) 0042 : KoTriangleColorSelector(displayRenderer, parent) 0043 , m_dragging(false) 0044 { 0045 } 0046 0047 ~PopupColorTriangle() override {} 0048 0049 void tabletEvent(QTabletEvent* event) override { 0050 QMouseEvent* mouseEvent = 0; 0051 0052 // ignore any tablet events that are done with the right click 0053 // Tablet move events don't return a "button", so catch that too 0054 if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove) 0055 { 0056 switch (event->type()) { 0057 case QEvent::TabletPress: 0058 mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), 0059 Qt::LeftButton, Qt::LeftButton, event->modifiers()); 0060 m_dragging = true; 0061 mousePressEvent(mouseEvent); 0062 event->accept(); 0063 break; 0064 case QEvent::TabletMove: 0065 mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), 0066 (m_dragging) ? Qt::LeftButton : Qt::NoButton, 0067 (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); 0068 mouseMoveEvent(mouseEvent); 0069 event->accept(); 0070 break; 0071 case QEvent::TabletRelease: 0072 mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), 0073 Qt::LeftButton, 0074 Qt::LeftButton, 0075 event->modifiers()); 0076 m_dragging = false; 0077 mouseReleaseEvent(mouseEvent); 0078 event->accept(); 0079 break; 0080 default: break; 0081 } 0082 } 0083 0084 delete mouseEvent; 0085 } 0086 0087 private: 0088 bool m_dragging; 0089 }; 0090 0091 KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, 0092 const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) 0093 : QWidget(parent, Qt::FramelessWindowHint) 0094 , m_coordinatesConverter(coordinatesConverter) 0095 , m_viewManager(viewManager) 0096 , m_actionManager(viewManager->actionManager()) 0097 , m_resourceManager(manager) 0098 , m_displayRenderer(displayRenderer) 0099 , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) 0100 , m_actionCollection(viewManager->actionCollection()) 0101 , m_acyclicConnector(new KisAcyclicSignalConnector(this)) 0102 , m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this)) 0103 { 0104 connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), 0105 SLOT(slotEmitColorChanged())); 0106 0107 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(slotConfigurationChanged())); 0108 connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(slotDisplayConfigurationChanged())); 0109 0110 m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), 0111 this, SLOT(slotExternalFgColorChanged(KoColor))); 0112 0113 m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)), 0114 m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); 0115 // just update() to repaint color labels on external background color change 0116 connect(viewManager->canvasResourceProvider(), SIGNAL(sigBGColorChanged(KoColor)), SLOT(update())); 0117 0118 connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); 0119 connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); 0120 0121 connect(m_resourceManager, SIGNAL(setSelectedColor(int)), this, SLOT(slotSetSelectedColor(int))); 0122 connect(m_resourceManager, SIGNAL(updatePalettes()), this, SLOT(slotUpdate())); 0123 connect(m_resourceManager, SIGNAL(hidePalettes()), this, SIGNAL(finished())); 0124 0125 // Instances of `this` rely on finished() to be detached and its lifetime is associated with `parent` 0126 connect(parent, SIGNAL(destroyed(QObject *)), this, SIGNAL(finished()), Qt::DirectConnection); 0127 0128 setCursor(Qt::ArrowCursor); 0129 setMouseTracking(true); 0130 setHoveredPreset(-1); 0131 setHoveredColor(-1); 0132 setSelectedColor(-1); 0133 0134 m_brushHud = new KisBrushHud(provider, this); 0135 0136 m_tagsButton = new KisRoundHudButton(this); 0137 0138 connect(m_tagsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); 0139 0140 m_brushHudButton = new KisRoundHudButton(this); 0141 m_brushHudButton->setCheckable(true); 0142 0143 connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); 0144 0145 m_bottomBarWidget = new QWidget(this); 0146 0147 m_bottomBarButton = new KisRoundHudButton(this); 0148 m_bottomBarButton->setCheckable(true); 0149 0150 connect( m_bottomBarButton, SIGNAL(toggled(bool)), SLOT(showBottomBarWidget(bool))); 0151 0152 m_clearColorHistoryButton = new KisRoundHudButton(this); 0153 m_clearColorHistoryButton->setToolTip(i18n("Clear color history")); 0154 0155 connect(m_clearColorHistoryButton, SIGNAL(clicked(bool)), m_resourceManager, SLOT(slotClearHistory())); 0156 //Otherwise the colors won't disappear until the cursor moves away from the button: 0157 connect(m_clearColorHistoryButton, SIGNAL(released()), this, SLOT(slotUpdate())); 0158 0159 // add some stuff below the pop-up palette that will make it easier to use for tablet people 0160 QGridLayout* gLayout = new QGridLayout(this); 0161 gLayout->setSizeConstraint(QLayout::SetFixedSize); 0162 gLayout->setSpacing(0); 0163 gLayout->setContentsMargins(QMargins()); 0164 m_mainArea = new QSpacerItem(m_popupPaletteSize, m_popupPaletteSize); 0165 gLayout->addItem(m_mainArea, 0, 0); // this should push the box to the bottom 0166 gLayout->setColumnMinimumWidth(1, WIDGET_MARGIN); 0167 gLayout->addWidget(m_brushHud, 0, 2); 0168 gLayout->setRowMinimumHeight(1, WIDGET_MARGIN); 0169 gLayout->addWidget(m_bottomBarWidget, 2, 0); 0170 0171 QHBoxLayout* hLayout = new QHBoxLayout(m_bottomBarWidget); 0172 0173 mirrorMode = new KisHighlightedToolButton(this); 0174 mirrorMode->setFixedSize(35, 35); 0175 0176 mirrorMode->setToolTip(i18n("Mirror Canvas")); 0177 mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas_around_cursor")); 0178 connect(mirrorMode, SIGNAL(clicked(bool)), this, SLOT(slotUpdate())); 0179 connect(mirrorMode, SIGNAL(pressed()), this, SLOT(slotSetMirrorPos())); 0180 connect(mirrorMode, SIGNAL(clicked()), this, SLOT(slotRemoveMirrorPos())); 0181 0182 canvasOnlyButton = new KisHighlightedToolButton(this); 0183 canvasOnlyButton->setFixedSize(35, 35); 0184 0185 canvasOnlyButton->setToolTip(i18n("Canvas Only")); 0186 canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only")); 0187 0188 zoomToOneHundredPercentButton = new QPushButton(this); 0189 zoomToOneHundredPercentButton->setText(i18n("100%")); 0190 zoomToOneHundredPercentButton->setFixedHeight(35); 0191 0192 zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); 0193 connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); 0194 0195 zoomCanvasSlider = new QSlider(Qt::Horizontal, this); 0196 zoomSliderMinValue = 10; // set in % 0197 zoomSliderMaxValue = 200; // set in % 0198 0199 zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); 0200 zoomCanvasSlider->setFixedHeight(35); 0201 zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); 0202 0203 zoomCanvasSlider->setSingleStep(1); 0204 zoomCanvasSlider->setPageStep(1); 0205 0206 connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); 0207 connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed())); 0208 connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased())); 0209 0210 slotUpdateIcons(); 0211 0212 hLayout->setSpacing(2); 0213 hLayout->setContentsMargins(0, 6, 0, 0); 0214 hLayout->addWidget(mirrorMode); 0215 hLayout->addWidget(canvasOnlyButton); 0216 hLayout->addWidget(zoomCanvasSlider); 0217 hLayout->addWidget(zoomToOneHundredPercentButton); 0218 0219 setVisible(false); 0220 reconfigure(); 0221 0222 opacityChange = new QGraphicsOpacityEffect(this); 0223 setGraphicsEffect(opacityChange); 0224 0225 /** 0226 * Tablet support code generates a spurious right-click right after opening 0227 * the window, so we should ignore it. Next right-click will be used for 0228 * closing the popup palette 0229 */ 0230 installEventFilter(m_clicksEater); 0231 0232 // Prevent tablet events from being captured by the canvas 0233 setAttribute(Qt::WA_NoMousePropagation, true); 0234 0235 // Because we can create a popup in canvas widget, synthetic events sometimes arrive here (see mousePressEvent()). 0236 setAttribute(Qt::WA_AcceptTouchEvents, true); 0237 installEventFilter(this); 0238 const QList<QWidget *> childrenWidgets = findChildren<QWidget *>(); 0239 for (const auto &child: childrenWidgets) { 0240 child->setAttribute(Qt::WA_AcceptTouchEvents, true); 0241 child->installEventFilter(this); 0242 } 0243 0244 // Load configuration.. 0245 KisConfig cfg(true); 0246 m_brushHudButton->setChecked(cfg.showBrushHud()); 0247 m_bottomBarButton->setChecked(cfg.showPaletteBottomBar()); 0248 } 0249 0250 KisPopupPalette::~KisPopupPalette() 0251 { 0252 } 0253 0254 void KisPopupPalette::slotConfigurationChanged() 0255 { 0256 reconfigure(); 0257 layout()->invalidate(); 0258 } 0259 0260 void KisPopupPalette::reconfigure() 0261 { 0262 KisConfig config(true); 0263 m_useDynamicSlotCount = config.readEntry("popuppalette/useDynamicSlotCount", true); 0264 m_maxPresetSlotCount = config.favoritePresets(); 0265 if (m_useDynamicSlotCount) { 0266 int presetCount = m_resourceManager->numFavoritePresets(); 0267 // if there are no presets because the tag is empty 0268 // show the maximum number allowed (they will be painted as empty slots) 0269 m_presetSlotCount = presetCount == 0 0270 ? m_maxPresetSlotCount 0271 : qMin(m_maxPresetSlotCount, m_resourceManager->numFavoritePresets()); 0272 } else { 0273 m_presetSlotCount = m_maxPresetSlotCount; 0274 } 0275 m_popupPaletteSize = config.readEntry("popuppalette/size", 385); 0276 qreal selectorRadius = config.readEntry("popuppalette/selectorSize", 140) / 2; 0277 0278 m_showColorHistory = config.readEntry("popuppalette/showColorHistory", true); 0279 m_showRotationTrack = config.readEntry("popuppalette/showRotationTrack", true); 0280 0281 m_colorHistoryInnerRadius = selectorRadius + m_presetRingMargin; 0282 m_colorHistoryOuterRadius = m_colorHistoryInnerRadius; 0283 if (m_showColorHistory) { 0284 m_colorHistoryOuterRadius += 20; 0285 m_clearColorHistoryButton->setVisible(true); 0286 } else { 0287 m_clearColorHistoryButton->setVisible(false); 0288 } 0289 0290 m_mainArea->changeSize(m_popupPaletteSize, m_popupPaletteSize); 0291 0292 bool useVisualSelector = config.readEntry<bool>("popuppalette/usevisualcolorselector", false); 0293 if (m_colorSelector) { 0294 // if the selector type changed, delete it 0295 bool haveVisualSelector = qobject_cast<KisVisualColorSelector*>(m_colorSelector) != 0; 0296 if (useVisualSelector != haveVisualSelector) { 0297 delete m_colorSelector; 0298 m_colorSelector = 0; 0299 } 0300 } 0301 if (!m_colorSelector) { 0302 if (useVisualSelector) { 0303 KisVisualColorSelector *selector = new KisVisualColorSelector(this); 0304 selector->setAcceptTabletEvents(true); 0305 m_colorSelector = selector; 0306 } 0307 else { 0308 m_colorSelector = new PopupColorTriangle(m_displayRenderer, this); 0309 connect(m_colorSelector, SIGNAL(requestCloseContainer()), this, SIGNAL(finished())); 0310 } 0311 m_colorSelector->setDisplayRenderer(m_displayRenderer); 0312 m_colorSelector->setConfig(true,false); 0313 m_colorSelector->setVisible(true); 0314 slotDisplayConfigurationChanged(); 0315 connect(m_colorSelector, SIGNAL(sigNewColor(KoColor)), 0316 m_colorChangeCompressor.data(), SLOT(start())); 0317 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), 0318 m_colorSelector, SLOT(configurationChanged())); 0319 } 0320 0321 0322 const int auxButtonSize = 35; 0323 m_colorSelector->move(m_popupPaletteSize/2 - selectorRadius, m_popupPaletteSize/2 - selectorRadius); 0324 m_colorSelector->resize(m_popupPaletteSize - 2*m_colorSelector->x(), m_popupPaletteSize - 2*m_colorSelector->y()); 0325 0326 // ellipse - to make sure the widget doesn't eat events meant for recent colors or brushes 0327 // - needs to be +2 pixels on every side for anti-aliasing to look nice on high dpi displays 0328 // rectangle - to make sure the area doesn't extend outside of the widget 0329 QRegion maskedEllipse(-2, -2, m_colorSelector->width() + 4, m_colorSelector->height() + 4, QRegion::Ellipse ); 0330 QRegion maskedRectangle(0, 0, m_colorSelector->width(), m_colorSelector->height(), QRegion::Rectangle); 0331 QRegion maskedRegion = maskedEllipse.intersected(maskedRectangle); 0332 0333 m_colorSelector->setMask(maskedRegion); 0334 0335 m_brushHud->setFixedHeight(int(m_popupPaletteSize)); 0336 0337 // arranges the buttons around the popup palette 0338 // buttons are spread out from the center of the set arc length 0339 0340 // the margin in degrees between buttons 0341 qreal margin = 10.0; 0342 // visual center 0343 qreal center = m_popupPaletteSize/2 - auxButtonSize/2; 0344 qreal length = m_popupPaletteSize/2 + auxButtonSize/2 + 5; 0345 { 0346 int buttonCount = 2; 0347 int arcLength = 90; 0348 // note the int buttonCount/2 is on purpose 0349 qreal start = arcLength/2 - (buttonCount/2) * margin; 0350 if (buttonCount % 2 == 0) start += margin / 2; 0351 int place = 0; 0352 m_brushHudButton->setGeometry( 0353 center + qCos(qDegreesToRadians(start + place*margin))*length, 0354 center + qSin(qDegreesToRadians(start + place*margin))*length, 0355 auxButtonSize, auxButtonSize 0356 ); 0357 place++; 0358 m_bottomBarButton->setGeometry ( 0359 center + qCos(qDegreesToRadians(start + place*margin))*length, 0360 center + qSin(qDegreesToRadians(start + place*margin))*length, 0361 auxButtonSize, auxButtonSize 0362 ); 0363 } 0364 { 0365 int buttonCount = m_showColorHistory ? 2 : 1 ; 0366 int arcLength = 90; 0367 int shiftArc = 90; 0368 // note the int buttonCount/2 is on purpose 0369 qreal start = shiftArc + arcLength / 2 - (buttonCount/2) * margin; 0370 if (buttonCount % 2 == 0) start += margin / 2; 0371 int place = 0; 0372 if (m_showColorHistory) { 0373 m_clearColorHistoryButton->setGeometry( 0374 center + qCos(qDegreesToRadians(start + place * margin)) * length, 0375 center + qSin(qDegreesToRadians(start + place * margin)) * length, 0376 auxButtonSize, auxButtonSize); 0377 place++; 0378 } 0379 m_tagsButton->setGeometry( 0380 center + qCos(qDegreesToRadians(start + place*margin))*length, 0381 center + qSin(qDegreesToRadians(start + place*margin))*length, 0382 auxButtonSize, auxButtonSize 0383 ); 0384 } 0385 calculatePresetLayout(); 0386 } 0387 0388 void KisPopupPalette::slotDisplayConfigurationChanged() 0389 { 0390 // Visual Color Selector picks up color space from input 0391 KoColor col = m_viewManager->canvasResourceProvider()->fgColor(); 0392 const KoColorSpace *paintingCS = m_displayRenderer->getPaintingColorSpace(); 0393 //hack to get around cmyk for now. 0394 if (paintingCS->colorChannelCount()>3) { 0395 paintingCS = KoColorSpaceRegistry::instance()->rgb8(); 0396 } 0397 m_colorSelector->slotSetColorSpace(paintingCS); 0398 m_colorSelector->slotSetColor(col); 0399 } 0400 0401 void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) 0402 { 0403 KisSignalsBlocker b(m_colorSelector); 0404 m_colorSelector->slotSetColor(color); 0405 update(); 0406 } 0407 0408 void KisPopupPalette::slotEmitColorChanged() 0409 { 0410 if (isVisible()) { 0411 update(); 0412 emit sigChangefGColor(m_colorSelector->getCurrentColor()); 0413 } 0414 } 0415 0416 void KisPopupPalette::slotUpdate() { 0417 int presetCount = m_resourceManager->numFavoritePresets(); 0418 if (m_useDynamicSlotCount && presetCount != m_presetSlotCount) { 0419 m_presetSlotCount = presetCount == 0 0420 ? m_maxPresetSlotCount 0421 : qMin(m_maxPresetSlotCount, presetCount); 0422 calculatePresetLayout(); 0423 } 0424 m_canvasRotationIndicatorRect = rotationIndicatorRect(m_coordinatesConverter->rotationAngle()); 0425 update(); 0426 } 0427 0428 //setting KisPopupPalette properties 0429 int KisPopupPalette::hoveredPreset() const 0430 { 0431 return m_hoveredPreset; 0432 } 0433 0434 void KisPopupPalette::setHoveredPreset(int x) 0435 { 0436 m_hoveredPreset = x; 0437 } 0438 0439 int KisPopupPalette::hoveredColor() const 0440 { 0441 return m_hoveredColor; 0442 } 0443 0444 void KisPopupPalette::setHoveredColor(int x) 0445 { 0446 m_hoveredColor = x; 0447 } 0448 0449 int KisPopupPalette::selectedColor() const 0450 { 0451 return m_selectedColor; 0452 } 0453 0454 void KisPopupPalette::setSelectedColor(int x) 0455 { 0456 m_selectedColor = x; 0457 } 0458 0459 void KisPopupPalette::slotZoomSliderChanged(int zoom) { 0460 emit zoomLevelChanged(zoom); 0461 } 0462 0463 void KisPopupPalette::slotZoomSliderPressed() 0464 { 0465 m_isZoomingCanvas = true; 0466 } 0467 0468 void KisPopupPalette::slotZoomSliderReleased() 0469 { 0470 m_isZoomingCanvas = false; 0471 } 0472 0473 void KisPopupPalette::slotUpdateIcons() 0474 { 0475 this->setPalette(qApp->palette()); 0476 0477 for(int i=0; i<this->children().size(); i++) { 0478 QWidget *w = qobject_cast<QWidget*>(this->children().at(i)); 0479 if (w) { 0480 w->setPalette(qApp->palette()); 0481 } 0482 } 0483 zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon()); 0484 m_brushHud->updateIcons(); 0485 m_tagsButton->setIcon(KisIconUtils::loadIcon("tag")); 0486 m_clearColorHistoryButton->setIcon(KisIconUtils::loadIcon("reload-preset-16")); 0487 m_bottomBarButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-up"), KisIconUtils::loadIcon("arrow-down")); 0488 m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); 0489 } 0490 0491 void KisPopupPalette::showHudWidget(bool visible) 0492 { 0493 const bool reallyVisible = visible && m_brushHudButton->isChecked(); 0494 0495 if (reallyVisible) { 0496 m_brushHud->updateProperties(); 0497 } 0498 0499 m_brushHud->setVisible(reallyVisible); 0500 0501 KisConfig cfg(false); 0502 cfg.setShowBrushHud(visible); 0503 } 0504 0505 void KisPopupPalette::showBottomBarWidget(bool visible) 0506 { 0507 const bool reallyVisible = visible && m_bottomBarButton->isChecked(); 0508 0509 m_bottomBarWidget->setVisible(reallyVisible); 0510 0511 KisConfig cfg(false); 0512 cfg.setShowPaletteBottomBar(visible); 0513 } 0514 0515 void KisPopupPalette::setParent(QWidget *parent) { 0516 QWidget::setParent(parent); 0517 } 0518 0519 0520 QSize KisPopupPalette::sizeHint() const 0521 { 0522 // Note: the canvas popup widget system "abuses" the sizeHint to determine 0523 // the position to show the widget; this does not reflect the true size. 0524 return QSize(m_popupPaletteSize, m_popupPaletteSize); 0525 } 0526 0527 void KisPopupPalette::paintEvent(QPaintEvent* e) 0528 { 0529 Q_UNUSED(e); 0530 0531 QPainter painter(this); 0532 0533 QPen pen(palette().color(QPalette::Text), BORDER_WIDTH); 0534 painter.setPen(pen); 0535 0536 painter.setRenderHint(QPainter::Antialiasing); 0537 painter.setRenderHint(QPainter::SmoothPixmapTransform); 0538 0539 if (m_isOverFgBgColors) { 0540 painter.save(); 0541 painter.setPen(QPen(palette().color(QPalette::Highlight), BORDER_WIDTH)); 0542 } 0543 0544 // painting background color indicator 0545 QPainterPath bgColor(drawFgBgColorIndicator(0)); 0546 painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); 0547 painter.drawPath(bgColor); 0548 0549 // painting foreground color indicator 0550 QPainterPath fgColor(drawFgBgColorIndicator(1)); 0551 painter.fillPath(fgColor, m_displayRenderer->toQColor(m_colorSelector->getCurrentColor())); 0552 painter.drawPath(fgColor); 0553 0554 if (m_isOverFgBgColors) painter.restore(); 0555 0556 0557 // create a circle background that everything else will go into 0558 QPainterPath backgroundContainer; 0559 0560 // draws the circle halfway into the border so that the border never goes past the bounds of the popup 0561 QRectF circleRect(BORDER_WIDTH/2, BORDER_WIDTH/2, m_popupPaletteSize - BORDER_WIDTH, m_popupPaletteSize - BORDER_WIDTH); 0562 backgroundContainer.addEllipse(circleRect); 0563 painter.fillPath(backgroundContainer, palette().brush(QPalette::Background)); 0564 painter.drawPath(backgroundContainer); 0565 0566 if (m_showRotationTrack) { 0567 painter.save(); 0568 QPen pen(palette().color(QPalette::Background).lighter(150), 2); 0569 painter.setPen(pen); 0570 0571 // draw rotation snap lines 0572 if (m_isRotatingCanvasIndicator) { 0573 for (QLineF &line: m_snapLines) { 0574 painter.drawLine(line); 0575 } 0576 } 0577 // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas 0578 // with the indicator 0579 QPainterPath rotationTrackPath; 0580 QRectF circleRect2(m_rotationTrackSize, m_rotationTrackSize, 0581 m_popupPaletteSize - m_rotationTrackSize * 2, m_popupPaletteSize - m_rotationTrackSize * 2); 0582 0583 rotationTrackPath.addEllipse(circleRect2); 0584 painter.drawPath(rotationTrackPath); 0585 0586 // create a reset canvas rotation indicator to bring the canvas back to 0 degrees 0587 QRectF resetRotationIndicator = m_resetCanvasRotationIndicatorRect; 0588 0589 pen.setColor(m_isOverResetCanvasRotationIndicator 0590 ? palette().color(QPalette::Highlight) 0591 : palette().color(QPalette::Text)); 0592 // cover the first snap line 0593 painter.setBrush(palette().brush(QPalette::Background)); 0594 painter.setPen(pen); 0595 painter.drawEllipse(resetRotationIndicator); 0596 0597 // create the canvas rotation handle 0598 // highlight if either just hovering or currently rotating 0599 pen.setColor(m_isOverCanvasRotationIndicator || m_isRotatingCanvasIndicator 0600 ? palette().color(QPalette::Highlight) 0601 : palette().color(QPalette::Text)); 0602 painter.setPen(pen); 0603 0604 // fill with highlight if snapping 0605 painter.setBrush(m_isRotatingCanvasIndicator && m_snapRotation 0606 ? palette().brush(QPalette::Highlight) 0607 : palette().brush(QPalette::Text)); 0608 0609 painter.drawEllipse(m_canvasRotationIndicatorRect); 0610 0611 painter.restore(); 0612 } 0613 0614 // the following things needs to be based off the center, so let's translate the painter 0615 painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); 0616 0617 // painting favorite brushes 0618 QList<QImage> images(m_resourceManager->favoritePresetImages()); 0619 0620 // painting favorite brushes pixmap/icon 0621 QPainterPath presetPath; 0622 int presetCount = images.size(); 0623 bool isTagEmpty = presetCount == 0; 0624 for (int pos = 0; pos < m_presetSlotCount; pos++) { 0625 painter.save(); 0626 presetPath = createPathFromPresetIndex(pos); 0627 0628 if (pos < presetCount) { 0629 painter.setClipPath(presetPath); 0630 0631 QRect bounds = presetPath.boundingRect().toAlignedRect(); 0632 if (!images.at(pos).isNull()) { 0633 QImage previewHighDPI = images.at(pos).scaled(bounds.size()*devicePixelRatioF() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); 0634 previewHighDPI.setDevicePixelRatio(devicePixelRatioF()); 0635 painter.drawImage(bounds.topLeft(), previewHighDPI); 0636 } 0637 } else { 0638 painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it 0639 } 0640 // needs to be called here so that the clipping is removed 0641 painter.restore(); 0642 // if the slot is empty, stroke it slightly darker 0643 QColor color = isTagEmpty || pos >= presetCount 0644 ? palette().color(QPalette::Background).lighter(150) 0645 : palette().color(QPalette::Text); 0646 painter.setPen(QPen(color, 1)); 0647 painter.drawPath(presetPath); 0648 } 0649 if (hoveredPreset() > -1) { 0650 presetPath = createPathFromPresetIndex(hoveredPreset()); 0651 painter.setPen(QPen(palette().color(QPalette::Highlight), BORDER_WIDTH)); 0652 painter.drawPath(presetPath); 0653 } 0654 0655 if (m_showColorHistory) { 0656 // paint recent colors area. 0657 painter.setPen(Qt::NoPen); 0658 qreal rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); 0659 0660 // there might be no recent colors at the start, so paint a placeholder 0661 if (m_resourceManager->recentColorsTotal() == 0) { 0662 painter.setBrush(Qt::transparent); 0663 0664 QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); 0665 painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); 0666 painter.drawPath(emptyRecentColorsPath); 0667 } else { 0668 0669 for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { 0670 QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); 0671 0672 //accessing recent color of index pos 0673 painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); 0674 painter.drawPath(recentColorsPath); 0675 painter.rotate(rotationAngle); 0676 } 0677 } 0678 0679 // painting hovered color 0680 if (hoveredColor() > -1) { 0681 painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); 0682 0683 if (m_resourceManager->recentColorsTotal() == 1) { 0684 QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); 0685 painter.drawPath(path_ColorDonut); 0686 } else { 0687 painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); 0688 QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); 0689 painter.drawPath(path); 0690 painter.rotate(hoveredColor() * -1 * rotationAngle); 0691 } 0692 } 0693 0694 // painting selected color 0695 if (selectedColor() > -1) { 0696 painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); 0697 0698 if (m_resourceManager->recentColorsTotal() == 1) { 0699 QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); 0700 painter.drawPath(path_ColorDonut); 0701 } else { 0702 painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); 0703 QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); 0704 painter.drawPath(path); 0705 painter.rotate(selectedColor() * -1 * rotationAngle); 0706 } 0707 } 0708 } 0709 0710 0711 // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better 0712 if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) { 0713 opacityChange->setOpacity(0.4); 0714 } else { 0715 opacityChange->setOpacity(1.0); 0716 } 0717 0718 } 0719 0720 void KisPopupPalette::resizeEvent(QResizeEvent* resizeEvent) { 0721 Q_UNUSED(resizeEvent); 0722 calculateRotationSnapAreas(); 0723 m_resetCanvasRotationIndicatorRect = rotationIndicatorRect(0); 0724 m_canvasRotationIndicatorRect = rotationIndicatorRect(m_coordinatesConverter->rotationAngle()); 0725 // Ensure that the resized geometry fits within the desired rect... 0726 QRect tempGeo = rect(); 0727 tempGeo.translate(pos()); 0728 ensureWithinParent(tempGeo.topLeft(), true); 0729 } 0730 0731 QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) 0732 { 0733 QPainterPath path; 0734 path.addEllipse(QPointF(x, y), outer_radius, outer_radius); 0735 path.addEllipse(QPointF(x, y), inner_radius, inner_radius); 0736 path.setFillRule(Qt::OddEvenFill); 0737 0738 return path; 0739 } 0740 0741 QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) 0742 { 0743 QPainterPath path; 0744 path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); 0745 path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 0746 360.0 / limit); 0747 path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, 0748 - 360.0 / limit); 0749 path.closeSubpath(); 0750 0751 return path; 0752 } 0753 0754 QPainterPath KisPopupPalette::drawFgBgColorIndicator(int type) const 0755 { 0756 QPointF edgePoint = QPointF(0.14645, 0.14645) * (m_popupPaletteSize); 0757 0758 // the points are really (-5, 15) and (5, 15) shifted right 1px 0759 // 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 0760 QPainterPath indicator; 0761 switch (type) { 0762 case 0: { // background 0763 indicator.addEllipse(edgePoint + QPointF(-4, 15), 30, 30); 0764 break; 0765 } 0766 case 1: { //foreground 0767 indicator.addEllipse(edgePoint + QPointF(6, -15), 30, 30); 0768 break; 0769 } 0770 } 0771 return indicator; 0772 } 0773 0774 QRectF KisPopupPalette::rotationIndicatorRect(qreal rotationAngle) const 0775 { 0776 qreal paletteRadius = 0.5 * m_popupPaletteSize; 0777 QPointF rotationDialPosition(drawPointOnAngle(rotationAngle, paletteRadius - 10)); 0778 rotationDialPosition += QPointF(paletteRadius, paletteRadius); 0779 0780 QPointF indicatorDiagonal(7.5, 7.5); 0781 return QRectF(rotationDialPosition - indicatorDiagonal, rotationDialPosition + indicatorDiagonal); 0782 } 0783 0784 void KisPopupPalette::mouseMoveEvent(QMouseEvent *event) 0785 { 0786 QPointF point = event->localPos(); 0787 event->accept(); 0788 0789 if (m_showRotationTrack) { 0790 // check if mouse is over the canvas rotation knob 0791 bool wasOverRotationIndicator = m_isOverCanvasRotationIndicator; 0792 m_isOverCanvasRotationIndicator = m_canvasRotationIndicatorRect.contains(point); 0793 bool wasOverResetRotationIndicator = m_isOverResetCanvasRotationIndicator; 0794 m_isOverResetCanvasRotationIndicator = m_resetCanvasRotationIndicatorRect.contains(point); 0795 0796 if ( 0797 wasOverRotationIndicator != m_isOverCanvasRotationIndicator || 0798 wasOverResetRotationIndicator != m_isOverResetCanvasRotationIndicator 0799 ) { 0800 update(); 0801 } 0802 0803 if (m_isRotatingCanvasIndicator) { 0804 m_snapRotation = false; 0805 int i = 0; 0806 for (QRect &rect: m_snapRects) { 0807 QPainterPath circle; 0808 circle.addEllipse(rect); 0809 if (circle.contains(point)) { 0810 m_snapRotation = true; 0811 m_rotationSnapAngle = i * 15; 0812 break; 0813 } 0814 i++; 0815 } 0816 qreal finalAngle = 0.0; 0817 if (m_snapRotation) { 0818 finalAngle = m_rotationSnapAngle; 0819 // to match the numbers displayed when rotating without snapping 0820 if (finalAngle >= 270) { 0821 finalAngle = finalAngle - 360; 0822 } 0823 } else { 0824 // we are rotating the canvas, so calculate the rotation angle based off the center 0825 // calculate the angle we are at first 0826 QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); 0827 0828 qreal dX = point.x() - widgetCenterPoint.x(); 0829 qreal dY = point.y() - widgetCenterPoint.y(); 0830 0831 0832 finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle 0833 finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up 0834 } 0835 qreal angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out 0836 0837 KisCanvasController *canvasController = 0838 dynamic_cast<KisCanvasController*>(m_viewManager->canvasBase()->canvasController()); 0839 canvasController->rotateCanvas(angleDifference); 0840 m_canvasRotationIndicatorRect = rotationIndicatorRect(finalAngle); 0841 0842 update(); 0843 emit sigUpdateCanvas(); 0844 } 0845 } 0846 0847 if (m_isRotatingCanvasIndicator == false) { 0848 QPainterPath bgColor(drawFgBgColorIndicator(0)); 0849 QPainterPath fgColor(drawFgBgColorIndicator(1)); 0850 QPainterPath backgroundContainer; 0851 QRectF circleRect(BORDER_WIDTH / 2, BORDER_WIDTH / 2, m_popupPaletteSize - BORDER_WIDTH, m_popupPaletteSize - BORDER_WIDTH); 0852 backgroundContainer.addEllipse(circleRect); 0853 0854 QPainterPath fgBgColors = (fgColor + bgColor) - backgroundContainer; 0855 0856 if (fgBgColors.contains(point)) { 0857 if (!m_isOverFgBgColors) { 0858 m_isOverFgBgColors = true; 0859 setToolTip(i18n("Click to swap foreground and background colors.\nRight click to set to black and white.")); 0860 update(); 0861 } 0862 } else { 0863 if (m_isOverFgBgColors) { 0864 m_isOverFgBgColors = false; 0865 setToolTip(QString()); 0866 update(); 0867 } 0868 } 0869 0870 QPainterPath colorHistoryPath(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); 0871 if (colorHistoryPath.contains(point)) { 0872 if (hoveredPreset() >= 0) { 0873 setToolTip(QString()); 0874 setHoveredPreset(-1); 0875 } 0876 0877 int pos = calculateColorIndex(point, m_resourceManager->recentColorsTotal()); 0878 0879 if (pos != hoveredColor()) { 0880 setHoveredColor(pos); 0881 update(); 0882 } 0883 } 0884 else { 0885 if (hoveredColor() >= 0) { 0886 setHoveredColor(-1); 0887 update(); 0888 } 0889 0890 int pos = findPresetSlot(point); 0891 0892 if (pos != hoveredPreset()) { 0893 0894 if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { 0895 setToolTip(m_resourceManager->favoritePresetNamesList().at(pos)); 0896 setHoveredPreset(pos); 0897 } 0898 else { 0899 setToolTip(QString()); 0900 setHoveredPreset(-1); 0901 } 0902 0903 update(); 0904 } 0905 } 0906 } 0907 } 0908 0909 void KisPopupPalette::mousePressEvent(QMouseEvent *event) 0910 { 0911 event->accept(); 0912 0913 if (event->button() == Qt::LeftButton) { 0914 if (m_showRotationTrack) { 0915 if (m_isOverCanvasRotationIndicator) { 0916 m_isRotatingCanvasIndicator = true; 0917 update(); 0918 } 0919 0920 if (m_isOverResetCanvasRotationIndicator) { 0921 qreal angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs 0922 KisCanvasController *canvasController = 0923 dynamic_cast<KisCanvasController*>(m_viewManager->canvasBase()->canvasController()); 0924 canvasController->rotateCanvas(angleDifference); 0925 m_canvasRotationIndicatorRect = rotationIndicatorRect(0); 0926 0927 emit sigUpdateCanvas(); 0928 } 0929 } 0930 } 0931 } 0932 0933 bool KisPopupPalette::eventFilter(QObject *, QEvent *event) 0934 { 0935 switch (event->type()) { 0936 case QEvent::TouchBegin: 0937 m_touchBeginReceived = true; 0938 break; 0939 case QEvent::MouseButtonPress: 0940 case QEvent::MouseMove: 0941 // HACK(sh_zam): Let's say the tap gesture is used by the canvas to launch the popup. Following that, a 0942 // synthesized mousePress is sent and this arrives in our event filter here. But, this event was meant for the 0943 // canvas (even though it blocks it), so we only act on the event if we got a TouchBegin on it first. 0944 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedBySystem && !m_touchBeginReceived) { 0945 event->accept(); 0946 return true; 0947 } 0948 break; 0949 case QEvent::MouseButtonRelease: 0950 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedBySystem && !m_touchBeginReceived) { 0951 event->accept(); 0952 return true; 0953 } 0954 // fallthrough 0955 case QEvent::Show: 0956 case QEvent::FocusOut: 0957 m_touchBeginReceived = false; 0958 break; 0959 default: 0960 break; 0961 } 0962 return false; 0963 } 0964 0965 void KisPopupPalette::slotShowTagsPopup() 0966 { 0967 KisTagModel model (ResourceType::PaintOpPresets); 0968 QVector<QString> tags; 0969 for (int i = 0; i < model.rowCount(); ++i) { 0970 QModelIndex idx = model.index(i, 0); 0971 tags << model.data(idx, Qt::DisplayRole).toString(); 0972 } 0973 0974 //std::sort(tags.begin(), tags.end()); 0975 0976 if (!tags.isEmpty()) { 0977 QMenu menu; 0978 Q_FOREACH (const QString& tag, tags) { 0979 menu.addAction(tag); 0980 } 0981 0982 QAction *action = menu.exec(QCursor::pos()); 0983 if (action) { 0984 0985 for (int i = 0; i < model.rowCount(); ++i) { 0986 QModelIndex idx = model.index(i, 0); 0987 if (model.data(idx, Qt::DisplayRole).toString() == KLocalizedString::removeAcceleratorMarker(action->text())) { 0988 m_resourceManager->setCurrentTag(model.tagForIndex(idx)); 0989 reconfigure(); 0990 break; 0991 } 0992 } 0993 } 0994 } else { 0995 QWhatsThis::showText(QCursor::pos(), 0996 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.")); 0997 } 0998 0999 } 1000 1001 void KisPopupPalette::slotZoomToOneHundredPercentClicked() { 1002 QAction *action = m_actionCollection->action("zoom_to_100pct"); 1003 1004 if (action) { 1005 action->trigger(); 1006 } 1007 1008 // also move the zoom slider to 100% position so they are in sync 1009 zoomCanvasSlider->setValue(100); 1010 } 1011 1012 void KisPopupPalette::slotSetMirrorPos() { 1013 m_actionCollection->action("mirror_canvas_around_cursor")->setProperty("customPosition", QVariant(m_mirrorPos)); 1014 } 1015 void KisPopupPalette::slotRemoveMirrorPos() { 1016 m_actionCollection->action("mirror_canvas_around_cursor")->setProperty("customPosition", QVariant()); 1017 } 1018 1019 void KisPopupPalette::popup(const QPoint &position) { 1020 setVisible(true); 1021 ensureWithinParent(position, false); 1022 m_mirrorPos = QCursor::pos(); 1023 } 1024 1025 void KisPopupPalette::dismiss() 1026 { 1027 setVisible(false); 1028 } 1029 1030 bool KisPopupPalette::onScreen() 1031 { 1032 return isVisible(); 1033 } 1034 1035 void KisPopupPalette::ensureWithinParent(const QPoint& position, bool useUpperLeft) { 1036 if (isVisible() && parentWidget()) { 1037 const qreal widgetMargin = -20.0; 1038 const QRect fitRect = kisGrowRect(parentWidget()->rect(), widgetMargin); 1039 const QPoint paletteCenterOffset(sizeHint().width() / 2, sizeHint().height() / 2); 1040 1041 QRect paletteRect = rect(); 1042 1043 if (!useUpperLeft) { 1044 paletteRect.moveTo(position - paletteCenterOffset); 1045 } else { 1046 paletteRect.moveTopLeft(position); 1047 } 1048 1049 paletteRect = kisEnsureInRect(paletteRect, fitRect); 1050 move(paletteRect.topLeft()); 1051 } 1052 } 1053 1054 void KisPopupPalette::showEvent(QShowEvent *event) 1055 { 1056 m_clicksEater->reset(); 1057 1058 // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within 1059 // the bounds and cause the canvas to jump between the slider's min and max 1060 if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && 1061 m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue){ 1062 KisSignalsBlocker b(zoomCanvasSlider); 1063 zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider 1064 } 1065 1066 m_brushHud->setVisible(m_brushHudButton->isChecked()); 1067 m_bottomBarWidget->setVisible(m_bottomBarButton->isChecked()); 1068 1069 QWidget::showEvent(event); 1070 } 1071 1072 void KisPopupPalette::tabletEvent(QTabletEvent *event) 1073 { 1074 if (event->button() == Qt::RightButton && event->type() == QEvent::TabletPress) { 1075 m_tabletRightClickPressed = true; 1076 } 1077 1078 event->ignore(); 1079 } 1080 1081 void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event) 1082 { 1083 QPointF point = event->localPos(); 1084 event->accept(); 1085 1086 if (m_isRotatingCanvasIndicator) { 1087 update(); 1088 } 1089 1090 m_isRotatingCanvasIndicator = false; 1091 1092 if (event->button() == Qt::LeftButton) { 1093 if (m_isOverFgBgColors) { 1094 m_viewManager->slotToggleFgBg(); 1095 } 1096 1097 //in favorite brushes area 1098 if (hoveredPreset() > -1) { 1099 //setSelectedBrush(hoveredBrush()); 1100 emit sigChangeActivePaintop(hoveredPreset()); 1101 } 1102 1103 if (m_showColorHistory) { 1104 QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); 1105 if (pathColor.contains(point)) { 1106 int pos = calculateColorIndex(point, m_resourceManager->recentColorsTotal()); 1107 1108 if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { 1109 emit sigUpdateRecentColor(pos); 1110 } 1111 } 1112 } 1113 } else if (event->button() == Qt::RightButton) { 1114 emit finished(); 1115 } 1116 } 1117 1118 int KisPopupPalette::calculateColorIndex(QPointF position, int numColors) const 1119 { 1120 if (numColors < 1) { 1121 return -1; 1122 } 1123 // relative to palette center 1124 QPointF relPosition = position - QPointF(0.5 * m_popupPaletteSize, 0.5 * m_popupPaletteSize); 1125 1126 qreal angle = qAtan2(relPosition.x(), relPosition.y()) + M_PI/numColors; 1127 if (angle < 0) { 1128 angle += 2 * M_PI; 1129 } 1130 1131 int index = floor(angle * numColors / (2 * M_PI)); 1132 1133 return qBound(0, index, numColors - 1); 1134 } 1135 1136 bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos) 1137 { 1138 if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { 1139 return true; 1140 } 1141 return false; 1142 } 1143 1144 QPointF KisPopupPalette::drawPointOnAngle(qreal angle, qreal radius) const 1145 { 1146 QPointF p( 1147 // -90 so it starts at the top since this is mainly used by calculatePresetLayout 1148 radius * qCos(qDegreesToRadians(angle - 90)), 1149 radius * qSin(qDegreesToRadians(angle - 90)) 1150 ); 1151 return p; 1152 } 1153 1154 void KisPopupPalette::calculatePresetLayout() 1155 { 1156 if (m_presetSlotCount == 0) { 1157 m_cachedPresetLayout = {}; 1158 return; 1159 } 1160 // how many degrees each slice will get 1161 // if the slot count is 1, we must divide by 2 to get 180 for the algorithm to work 1162 qreal angleSlice = 360.0 / qMax(m_presetSlotCount, 2); 1163 qreal outerRadius = m_popupPaletteSize/2 - m_presetRingMargin - (m_showRotationTrack ? m_rotationTrackSize + 1 /* half of stroke */ : BORDER_WIDTH); 1164 qreal innerRadius = m_colorHistoryOuterRadius + 1165 (m_showColorHistory 1166 ? 1 /* half of stroke */ + m_presetRingMargin 1167 : 0 /* preset margin is already included in either color history radius when it's not showing */ 1168 ); 1169 1170 qreal ringWidth = outerRadius - innerRadius; 1171 qreal halfRingWidth = 0.5 * ringWidth; 1172 qreal ringMidRadius = innerRadius + halfRingWidth; 1173 1174 // reset the cached layout 1175 m_cachedPresetLayout = {}; 1176 CachedPresetLayout& c = m_cachedPresetLayout; 1177 1178 // note: adding the margin the way it's done 1179 // (calculating the radiuses without taking it into account then subtracting it after) 1180 // is not particularly accurate, but it looks fine since it's so small 1181 int margin = 2; 1182 1183 // assume one row and get the max radius until the circles would touch 1184 qreal oneRowAngleSlice = angleSlice / 2; 1185 qreal oneRowMaxRadius = ringMidRadius * qSin(qDegreesToRadians(oneRowAngleSlice)); 1186 1187 // if the circles are bigger than the ring we're limited by the 1188 // ring's width instead and only one row would fit 1189 1190 // oneRowMaxRadius * 0.2 is to make sure that we still do one row if 1191 // there isn't that much of a difference and two would just look weirder 1192 if (oneRowMaxRadius - margin > halfRingWidth - oneRowMaxRadius * 0.2) { 1193 c.ringCount = 1; 1194 c.firstRowRadius = qMin(halfRingWidth, oneRowMaxRadius - margin); 1195 c.firstRowPos = ringMidRadius; 1196 return; 1197 } 1198 1199 // otherwise 2 or 3 rows always fit 1200 qreal tempRadius = halfRingWidth; 1201 { 1202 // for two rows, the first row is tangent to the inner radius 1203 // and the second row to the outer one 1204 qreal twoRowInnerCount = ceil(qMax(m_presetSlotCount, 2) / 2.0); 1205 qreal twoRowAngleSlice = 360.0 / twoRowInnerCount; 1206 1207 // we can start at half the ring width and shrink the radius until nothing is overlapping 1208 while (tempRadius >= 0) { 1209 tempRadius -= 0.2; 1210 QPointF r1p1(drawPointOnAngle(twoRowAngleSlice / 2, innerRadius + tempRadius)); 1211 QPointF r1p2(drawPointOnAngle(twoRowAngleSlice / 2 * 3, innerRadius + tempRadius)); 1212 QPointF r2p(drawPointOnAngle(twoRowAngleSlice, outerRadius - tempRadius)); 1213 qreal row1SiblingDistance = kisDistance(r1p1, r1p2); 1214 qreal row1To2Distance = kisDistance(r1p1, r2p); 1215 if (row1To2Distance >= (tempRadius + margin) * 2) { 1216 // the previous radius is the one that's guaranteed not to be overlapping 1217 if (row1SiblingDistance < (tempRadius + margin) * 2) { 1218 // the inner row still overlaps, attempt 3 rows instead 1219 break; 1220 } 1221 c.ringCount = 2; 1222 c.secondRowRadius = tempRadius; 1223 c.secondRowPos = outerRadius - tempRadius; 1224 c.firstRowRadius = tempRadius; 1225 c.firstRowPos = innerRadius + tempRadius; 1226 return; 1227 } 1228 } 1229 } 1230 1231 // for three rows, we initially arrange them like so: 1232 // the first row tangent to the inner radius 1233 // the second row in the middle 1234 // the third row tangent to the outer radius 1235 1236 qreal threeRowInnerCount = ceil(qMax(m_presetSlotCount, 2) / 3.0); 1237 qreal threeRowAngleSlice = 360.0 / threeRowInnerCount; 1238 1239 // then we decrease the radius until no row is overlapping eachother or itself 1240 while (tempRadius >= 0) { 1241 QPointF r1p1(drawPointOnAngle(threeRowAngleSlice / 2, innerRadius + tempRadius)); 1242 QPointF r1p2(drawPointOnAngle(threeRowAngleSlice / 2 * 3, innerRadius + tempRadius)); 1243 QPointF r2p1(drawPointOnAngle(threeRowAngleSlice, ringMidRadius)); 1244 QPointF r2p2(drawPointOnAngle(threeRowAngleSlice * 2, ringMidRadius)); 1245 QPointF r3p(drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempRadius)); 1246 1247 qreal row1SiblingDistance = kisDistance(r1p1, r1p2); 1248 qreal row1to2Distance = kisDistance(r1p1, r2p1); 1249 qreal row2to3Distance = kisDistance(r2p1, r3p); 1250 qreal row1to3Distance = kisDistance(r1p1, r3p); 1251 1252 if ( 1253 row1to2Distance >= tempRadius * 2 && 1254 row2to3Distance >= tempRadius * 2 && 1255 row1to3Distance >= tempRadius * 2 && 1256 row1SiblingDistance >= tempRadius * 2 1257 ) { 1258 1259 qreal row2SiblingDistance = kisDistance(r2p1, r2p2); 1260 1261 qreal firstRowRadius = tempRadius; 1262 qreal thirdRowRadius = tempRadius; 1263 qreal secondRowRadius = tempRadius; 1264 1265 bool firstRowTouching = row1SiblingDistance - firstRowRadius * 2 < 1; 1266 if (firstRowTouching) { 1267 // attempt to expand the second row 1268 // and expand + move the third row inwards 1269 QPointF tempR3p = r3p; 1270 qreal tempSecondThirdRowRadius = secondRowRadius; 1271 qreal tempRow2to3Distance = row2to3Distance; 1272 qreal tempRow1to3Distance = row1to3Distance; 1273 while ( 1274 tempSecondThirdRowRadius * 2 < tempRow2to3Distance && 1275 tempSecondThirdRowRadius * 2 < row2SiblingDistance && 1276 tempSecondThirdRowRadius * 2 < tempRow1to3Distance && 1277 tempSecondThirdRowRadius + firstRowRadius < row1to2Distance 1278 ) { 1279 // the previous temp variables are within limits 1280 r3p = tempR3p; 1281 row2to3Distance = tempRow2to3Distance; 1282 row1to3Distance = tempRow1to3Distance; 1283 secondRowRadius = tempSecondThirdRowRadius; 1284 1285 tempSecondThirdRowRadius += 1; 1286 1287 tempR3p = drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempSecondThirdRowRadius); 1288 1289 tempRow2to3Distance = kisDistance(r2p1, tempR3p); 1290 tempRow1to3Distance = kisDistance(r1p1, tempR3p); 1291 } 1292 thirdRowRadius = secondRowRadius; 1293 } 1294 1295 { 1296 // the third row can sometimes be expanded + moved a bit more 1297 qreal tempThirdRowRadius = thirdRowRadius; 1298 QPointF tempR3p = r3p; 1299 qreal tempRow2to3Distance = row2to3Distance; 1300 qreal tempRow1to3Distance = row1to3Distance; 1301 while ( 1302 tempThirdRowRadius < halfRingWidth && 1303 secondRowRadius + tempThirdRowRadius < tempRow2to3Distance && 1304 firstRowRadius + tempThirdRowRadius < tempRow1to3Distance 1305 ) { 1306 r3p = tempR3p; 1307 row2to3Distance = tempRow2to3Distance; 1308 row1to3Distance = tempRow1to3Distance; 1309 thirdRowRadius = tempThirdRowRadius; 1310 1311 tempThirdRowRadius += 1; 1312 1313 tempR3p = drawPointOnAngle(threeRowAngleSlice / 2, outerRadius - tempThirdRowRadius); 1314 tempRow2to3Distance = kisDistance(r2p1, tempR3p); 1315 tempRow1to3Distance = kisDistance(r1p1, tempR3p); 1316 } 1317 } 1318 // the third row is no longer moved 1319 qreal thirdRowPos = outerRadius - thirdRowRadius; 1320 1321 // many times, e.g. when the second row is touching 1322 // the first row can be moved outwards and expanded 1323 // sometimes it will even detach from the inner radius if the ringwidth is large enough 1324 // and there's a lot of presets 1325 qreal firstRowPos = innerRadius + tempRadius; 1326 { 1327 qreal tempFirstRowPos = firstRowPos; 1328 qreal tempFirstRowRadius = firstRowRadius; 1329 qreal tempRow1SiblingDistance = row1SiblingDistance; 1330 qreal tempRow1to3Distance = row1to3Distance; 1331 qreal tempRow1to2Distance = row1to2Distance; 1332 QPointF tempR1p1 = r1p1; 1333 QPointF tempR1p2 = r1p2; 1334 1335 while ( 1336 tempFirstRowPos < ringMidRadius && 1337 tempFirstRowRadius + secondRowRadius < tempRow1to2Distance && 1338 tempFirstRowRadius + thirdRowRadius < tempRow1to3Distance 1339 ) { 1340 firstRowPos = tempFirstRowPos; 1341 firstRowRadius = tempFirstRowRadius; 1342 row1to2Distance = tempRow1to2Distance; 1343 r1p1 = tempR1p1; 1344 // these are unused after so it's not neccesary to update them 1345 // row1to3Distance = tempRow1to3Distance; 1346 // row1SiblingDistance = tempRow1SiblingDistance; 1347 // r1p2 = tempR1p2; 1348 1349 tempFirstRowPos += 1; 1350 1351 tempR1p1 = drawPointOnAngle(threeRowAngleSlice / 2, tempFirstRowPos); 1352 tempR1p2 = drawPointOnAngle(threeRowAngleSlice / 2 * 3, tempFirstRowPos); 1353 tempRow1SiblingDistance = kisDistance(tempR1p1, tempR1p2); 1354 // expand it to the max size 1355 tempFirstRowRadius = tempRow1SiblingDistance / 2; 1356 tempRow1to2Distance = kisDistance(tempR1p2, r2p1); 1357 tempRow1to3Distance = kisDistance(tempR1p1, r3p); 1358 } 1359 } 1360 1361 // finally it's rare, but sometimes possible to also move + expand the second row 1362 qreal secondRowPos = ringMidRadius; 1363 bool row2touching1 = row1to2Distance - (firstRowRadius + secondRowRadius) < 1; 1364 bool row2touching3 = row2to3Distance - (thirdRowRadius + secondRowRadius) < 1; 1365 if (!row2touching1 && !row2touching3) { 1366 // move the second row in until it's touching the first row 1367 qreal knownAngleRatio = qSin(qDegreesToRadians(threeRowAngleSlice / 2)) / 1368 (firstRowRadius + secondRowRadius); 1369 qreal angleRow1Row2Center = qAsin(knownAngleRatio * firstRowPos); 1370 qreal angleCenterRow2Row1 = 180 - threeRowAngleSlice / 2 - qRadiansToDegrees(angleRow1Row2Center); 1371 secondRowPos = qSin(qDegreesToRadians(angleCenterRow2Row1)) / knownAngleRatio; 1372 } 1373 if (!row2touching3) { 1374 QPointF tempR2p1 = r2p1; 1375 qreal tempRadius = secondRowRadius; 1376 qreal tempRow1to2Distance = row1to2Distance; 1377 qreal tempRow2to3Distance = row2to3Distance; 1378 qreal tempSecondRowPos = secondRowPos; 1379 while ( 1380 tempSecondRowPos < thirdRowPos && 1381 tempRadius + thirdRowRadius < tempRow2to3Distance && 1382 // this is an artificial limit, it could get bigger but looks weird 1383 tempRadius < thirdRowRadius 1384 ) { 1385 secondRowRadius = tempRadius; 1386 secondRowPos = tempSecondRowPos; 1387 // these are unused after so it's not neccesary to update them 1388 // r2p1 = tempR2p1; 1389 // row1to2Distance = tempRow1to2Distance; 1390 // row2to3Distance = tempRow2to3Distance; 1391 1392 tempSecondRowPos += 1; 1393 1394 tempR2p1 = drawPointOnAngle(threeRowAngleSlice, secondRowPos + 1); 1395 tempRow1to2Distance = kisDistance(tempR2p1, r1p1); 1396 tempRow2to3Distance = kisDistance(tempR2p1, r3p); 1397 tempRadius = tempRow1to2Distance - firstRowRadius; 1398 } 1399 } 1400 c = { 1401 3, //ringCount 1402 firstRowRadius - margin, 1403 secondRowRadius - margin, 1404 thirdRowRadius - margin, 1405 firstRowPos, 1406 secondRowPos, 1407 thirdRowPos 1408 }; 1409 return; 1410 } 1411 tempRadius -= 0.2; 1412 } 1413 } 1414 1415 QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) const 1416 { 1417 // how many degrees each slice will get 1418 // if the slot count is 1, we must divide by 2 to get 180 for the algorithm to work 1419 qreal angleSlice = 360.0 / qMax(m_presetSlotCount, 2); 1420 // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. 1421 // adding 90 degrees makes us start at the top. otherwise we would start at the right 1422 qreal startingAngle = -(index * angleSlice) + 90; 1423 qreal length = m_cachedPresetLayout.firstRowPos; 1424 qreal radius = m_cachedPresetLayout.firstRowRadius; 1425 switch (m_cachedPresetLayout.ringCount) { 1426 case 1: break; 1427 case 2: { 1428 angleSlice = 180.0/((m_presetSlotCount+1) / 2); 1429 startingAngle = -(index * angleSlice) + 90; 1430 1431 if (index % 2) { 1432 length = m_cachedPresetLayout.secondRowPos; 1433 radius = m_cachedPresetLayout.secondRowRadius; 1434 } 1435 break; 1436 } 1437 case 3: { 1438 int triplet = index / 3; 1439 angleSlice = 180.0 / ((m_presetSlotCount + 2) / 3); 1440 switch (index % 3) { 1441 case 0: 1442 startingAngle = -(triplet * 2 * angleSlice) + 90; 1443 length = m_cachedPresetLayout.firstRowPos; 1444 radius = m_cachedPresetLayout.firstRowRadius; 1445 break; 1446 case 1: 1447 startingAngle = -(triplet * 2 * angleSlice) + 90; 1448 length = m_cachedPresetLayout.thirdRowPos; 1449 radius = m_cachedPresetLayout.thirdRowRadius; 1450 break; 1451 case 2: 1452 startingAngle = -((triplet * 2 + 1) * angleSlice) + 90; 1453 length = m_cachedPresetLayout.secondRowPos; 1454 radius = m_cachedPresetLayout.secondRowRadius; 1455 break; 1456 default: 1457 KIS_ASSERT(false); 1458 } 1459 break; 1460 } 1461 default: 1462 KIS_ASSERT_RECOVER_NOOP(false); 1463 } 1464 QPainterPath path; 1465 qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - radius; 1466 qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - radius; 1467 qreal pathDiameter = 2 * radius; // distance is used to calculate the X/Y in addition to the preset circle size 1468 path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); 1469 return path; 1470 } 1471 1472 void KisPopupPalette::calculateRotationSnapAreas() { 1473 int i = 0; 1474 for (QRect &rect: m_snapRects) { 1475 QPointF point(drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - BORDER_WIDTH - m_snapRadius/2)); 1476 point += QPointF(m_popupPaletteSize / 2 - m_snapRadius, m_popupPaletteSize / 2 - m_snapRadius); 1477 rect = QRect(point.x(), point.y(), m_snapRadius*2, m_snapRadius*2); 1478 i++; 1479 } 1480 i = 0; 1481 for (QLineF &line: m_snapLines) { 1482 qreal penWidth = BORDER_WIDTH / 2; 1483 QPointF point1 = drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - m_rotationTrackSize + penWidth); 1484 point1 += QPointF(m_popupPaletteSize / 2, m_popupPaletteSize / 2); 1485 QPointF point2 = drawPointOnAngle(i * 15, m_popupPaletteSize / 2 - BORDER_WIDTH - penWidth); 1486 point2 += QPointF(m_popupPaletteSize / 2, m_popupPaletteSize / 2); 1487 line = QLineF(point1, point2); 1488 i++; 1489 } 1490 } 1491 1492 int KisPopupPalette::findPresetSlot(QPointF position) const 1493 { 1494 QPointF adujustedPoint = position - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); 1495 for (int i = 0; i < m_presetSlotCount; i++) { 1496 if (createPathFromPresetIndex(i).contains(adujustedPoint)) { 1497 return i; 1498 } 1499 } 1500 return -1; 1501 }