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