1 /* This file is part of the KDE project
2    Copyright 2009 Vera Lukman <shicmap@gmail.com>
3    Copyright 2011 Sven Langkamp <sven.langkamp@gmail.com>
4    Copyright 2016 Scott Petrovic <scottpetrovic@gmail.com>
5 
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License version 2 as published by the Free Software Foundation.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301, USA.
19 
20 */
21 
22 #include "kis_canvas2.h"
23 #include "kis_config.h"
24 #include "kis_popup_palette.h"
25 #include "kis_favorite_resource_manager.h"
26 #include "kis_icon_utils.h"
27 #include "KisResourceServerProvider.h"
28 #include <kis_canvas_resource_provider.h>
29 #include <KoTriangleColorSelector.h>
30 #include "KoColorDisplayRendererInterface.h"
31 #include <KisVisualColorSelector.h>
32 #include <kis_config_notifier.h>
33 #include <QtGui>
34 #include <QMenu>
35 #include <QWhatsThis>
36 #include <QVBoxLayout>
37 #include <QElapsedTimer>
38 #include "kis_signal_compressor.h"
39 #include "brushhud/kis_brush_hud.h"
40 #include "brushhud/kis_round_hud_button.h"
41 #include "kis_signals_blocker.h"
42 #include "kis_canvas_controller.h"
43 #include "kis_acyclic_signal_connector.h"
44 #include "KisMouseClickEater.h"
45 
46 class PopupColorTriangle : public KoTriangleColorSelector
47 {
48 public:
PopupColorTriangle(const KoColorDisplayRendererInterface * displayRenderer,QWidget * parent)49     PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent)
50         : KoTriangleColorSelector(displayRenderer, parent)
51         , m_dragging(false)
52     {
53     }
54 
~PopupColorTriangle()55     ~PopupColorTriangle() override {}
56 
tabletEvent(QTabletEvent * event)57     void tabletEvent(QTabletEvent* event) override {
58         event->accept();
59         QMouseEvent* mouseEvent = 0;
60 
61         // this will tell the pop-up palette widget to close
62         if(event->button() == Qt::RightButton) {
63             emit requestCloseContainer();
64         }
65 
66         // ignore any tablet events that are done with the right click
67         // Tablet move events don't return a "button", so catch that too
68         if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove)
69         {
70             switch (event->type()) {
71                 case QEvent::TabletPress:
72                     mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(),
73                                                  Qt::LeftButton, Qt::LeftButton, event->modifiers());
74                     m_dragging = true;
75                     mousePressEvent(mouseEvent);
76                     break;
77                 case QEvent::TabletMove:
78                     mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(),
79                                                  (m_dragging) ? Qt::LeftButton : Qt::NoButton,
80                                                  (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers());
81                     mouseMoveEvent(mouseEvent);
82                     break;
83                 case QEvent::TabletRelease:
84                     mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(),
85                                                  Qt::LeftButton,
86                                                  Qt::LeftButton,
87                                                  event->modifiers());
88                     m_dragging = false;
89                     mouseReleaseEvent(mouseEvent);
90                     break;
91                 default: break;
92             }
93         }
94 
95         delete mouseEvent;
96     }
97 
98 private:
99     bool m_dragging;
100 };
101 
KisPopupPalette(KisViewManager * viewManager,KisCoordinatesConverter * coordinatesConverter,KisFavoriteResourceManager * manager,const KoColorDisplayRendererInterface * displayRenderer,KisCanvasResourceProvider * provider,QWidget * parent)102 KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager,
103                                  const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent)
104     : QWidget(parent, Qt::FramelessWindowHint)
105     , m_coordinatesConverter(coordinatesConverter)
106     , m_viewManager(viewManager)
107     , m_actionManager(viewManager->actionManager())
108     , m_resourceManager(manager)
109     , m_displayRenderer(displayRenderer)
110     , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE))
111     , m_actionCollection(viewManager->actionCollection())
112     , m_acyclicConnector(new KisAcyclicSignalConnector(this))
113     , m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this))
114 {
115     // some UI controls are defined and created based off these variables
116 
117     const int borderWidth = 3;
118 
119     if (KisConfig(true).readEntry<bool>("popuppalette/usevisualcolorselector", false)) {
120         KisVisualColorSelector *selector = new KisVisualColorSelector(this);
121         selector->setAcceptTabletEvents(true);
122         m_triangleColorSelector = selector;
123     }
124     else {
125         m_triangleColorSelector  = new PopupColorTriangle(displayRenderer, this);
126         connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide()));
127     }
128     m_triangleColorSelector->setDisplayRenderer(displayRenderer);
129     m_triangleColorSelector->setConfig(true,false);
130     m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth);
131     m_triangleColorSelector->resize(m_popupPaletteSize - 2*m_triangleColorSelector->x(), m_popupPaletteSize - 2*m_triangleColorSelector->y());
132     m_triangleColorSelector->setVisible(true);
133     KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8());
134     if (m_resourceManager) {
135         fgcolor = provider->fgColor();
136     }
137     m_triangleColorSelector->slotSetColor(fgcolor);
138 
139 
140     /**
141      * Tablet support code generates a spurious right-click right after opening
142      * the window, so we should ignore it. Next right-click will be used for
143      * closing the popup palette
144      */
145     this->installEventFilter(m_clicksEater);
146     m_triangleColorSelector->installEventFilter(m_clicksEater);
147 
148     // ellipse - to make sure the widget doesn't eat events meant for recent colors or brushes
149     //         - needs to be +2 pixels on every side for anti-aliasing to look nice on high dpi displays
150     // rectange - to make sure the area doesn't extend outside of the widget
151     QRegion maskedEllipse(-2, -2, m_triangleColorSelector->width() + 4, m_triangleColorSelector->height() + 4, QRegion::Ellipse );
152     QRegion maskedRectange(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Rectangle);
153     QRegion maskedRegion = maskedEllipse.intersected(maskedRectange);
154     m_triangleColorSelector->setMask(maskedRegion);
155 
156     //setAttribute(Qt::WA_TranslucentBackground, true);
157 
158     connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)),
159             m_colorChangeCompressor.data(), SLOT(start()));
160     connect(m_colorChangeCompressor.data(), SIGNAL(timeout()),
161             SLOT(slotEmitColorChanged()));
162 
163     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged()));
164     connect(m_displayRenderer,  SIGNAL(displayConfigurationChanged()), this, SLOT(slotDisplayConfigurationChanged()));
165 
166     m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)),
167                                               this, SLOT(slotExternalFgColorChanged(KoColor)));
168 
169     m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)),
170                                                m_resourceManager, SIGNAL(sigSetFGColor(KoColor)));
171 
172     connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int)));
173     connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int)));
174 
175     connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int)));
176     connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate()));
177     connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide()));
178 
179     setCursor(Qt::ArrowCursor);
180     setMouseTracking(true);
181     setHoveredPreset(-1);
182     setHoveredColor(-1);
183     setSelectedColor(-1);
184 
185     m_brushHud = new KisBrushHud(provider, parent);
186     m_brushHud->setFixedHeight(int(m_popupPaletteSize));
187     m_brushHud->setVisible(false);
188 
189     const int auxButtonSize = 35;
190 
191     m_settingsButton = new KisRoundHudButton(this);
192 
193     m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize,
194                                   auxButtonSize, auxButtonSize);
195 
196     connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup()));
197 
198     KisConfig cfg(true);
199     m_brushHudButton = new KisRoundHudButton(this);
200     m_brushHudButton->setCheckable(true);
201 
202     m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize,
203                                   auxButtonSize, auxButtonSize);
204     connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool)));
205     m_brushHudButton->setChecked(cfg.showBrushHud());
206 
207 
208     // add some stuff below the pop-up palette that will make it easier to use for tablet people
209     QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout
210 
211     QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
212     vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom
213 
214 
215     QHBoxLayout* hLayout = new QHBoxLayout();
216 
217     vLayout->addLayout(hLayout);
218 
219     mirrorMode = new KisHighlightedToolButton(this);
220     mirrorMode->setFixedSize(35, 35);
221 
222     mirrorMode->setToolTip(i18n("Mirror Canvas"));
223     mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas"));
224 
225     canvasOnlyButton = new KisHighlightedToolButton(this);
226     canvasOnlyButton->setFixedSize(35, 35);
227 
228     canvasOnlyButton->setToolTip(i18n("Canvas Only"));
229     canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only"));
230 
231     zoomToOneHundredPercentButton = new QPushButton(this);
232     zoomToOneHundredPercentButton->setText(i18n("100%"));
233     zoomToOneHundredPercentButton->setFixedHeight(35);
234 
235     zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%"));
236     connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked()));
237 
238     zoomCanvasSlider = new QSlider(Qt::Horizontal, this);
239     zoomSliderMinValue = 10; // set in %
240     zoomSliderMaxValue = 200; // set in %
241 
242     zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue);
243     zoomCanvasSlider->setFixedHeight(35);
244     zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent());
245 
246     zoomCanvasSlider->setSingleStep(1);
247     zoomCanvasSlider->setPageStep(1);
248 
249     connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int)));
250     connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed()));
251     connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased()));
252 
253     clearHistoryButton = new QPushButton(this);
254     clearHistoryButton->setFixedHeight(35);
255 
256     clearHistoryButton->setText(i18nc("verb, to clear", "Clear colors"));
257     clearHistoryButton->setToolTip(i18n("Clear the colors of the popup palette"));
258 
259     connect(clearHistoryButton, SIGNAL(clicked(bool)), m_resourceManager, SLOT(slotClearHistory()));
260     //Otherwise the colors won't disappear until the cursor moves away from the button:
261     connect(clearHistoryButton, SIGNAL(released()), this, SLOT(slotUpdate()));
262 
263     slotUpdateIcons();
264 
265     hLayout->addWidget(mirrorMode);
266     hLayout->addWidget(canvasOnlyButton);
267     hLayout->addWidget(zoomToOneHundredPercentButton);
268     hLayout->addWidget(zoomCanvasSlider);
269     hLayout->addWidget(clearHistoryButton);
270 
271     setVisible(true);
272     setVisible(false);
273 
274     opacityChange = new QGraphicsOpacityEffect(this);
275     setGraphicsEffect(opacityChange);
276 
277     // Prevent tablet events from being captured by the canvas
278     setAttribute(Qt::WA_NoMousePropagation, true);
279 }
280 
slotDisplayConfigurationChanged()281 void KisPopupPalette::slotDisplayConfigurationChanged()
282 {
283     // Visual Color Selector picks up color space from input
284     KoColor col = m_viewManager->canvasResourceProvider()->fgColor();
285     const KoColorSpace *paintingCS = m_displayRenderer->getPaintingColorSpace();
286     //hack to get around cmyk for now.
287     if (paintingCS->colorChannelCount()>3) {
288         paintingCS = KoColorSpaceRegistry::instance()->rgb8();
289     }
290     m_triangleColorSelector->slotSetColorSpace(paintingCS);
291     m_triangleColorSelector->slotSetColor(col);
292 }
293 
slotExternalFgColorChanged(const KoColor & color)294 void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color)
295 {
296     m_triangleColorSelector->slotSetColor(color);
297 }
298 
slotEmitColorChanged()299 void KisPopupPalette::slotEmitColorChanged()
300 {
301     if (isVisible()) {
302         update();
303         emit sigChangefGColor(m_triangleColorSelector->getCurrentColor());
304     }
305 }
306 
307 //setting KisPopupPalette properties
hoveredPreset() const308 int KisPopupPalette::hoveredPreset() const
309 {
310     return m_hoveredPreset;
311 }
312 
setHoveredPreset(int x)313 void KisPopupPalette::setHoveredPreset(int x)
314 {
315     m_hoveredPreset = x;
316 }
317 
hoveredColor() const318 int KisPopupPalette::hoveredColor() const
319 {
320     return m_hoveredColor;
321 }
322 
setHoveredColor(int x)323 void KisPopupPalette::setHoveredColor(int x)
324 {
325     m_hoveredColor = x;
326 }
327 
selectedColor() const328 int KisPopupPalette::selectedColor() const
329 {
330     return m_selectedColor;
331 }
332 
setSelectedColor(int x)333 void KisPopupPalette::setSelectedColor(int x)
334 {
335     m_selectedColor = x;
336 }
337 
slotZoomSliderChanged(int zoom)338 void KisPopupPalette::slotZoomSliderChanged(int zoom) {
339     emit zoomLevelChanged(zoom);
340 }
341 
slotZoomSliderPressed()342 void KisPopupPalette::slotZoomSliderPressed()
343 {
344    m_isZoomingCanvas = true;
345 }
346 
slotZoomSliderReleased()347 void KisPopupPalette::slotZoomSliderReleased()
348 {
349     m_isZoomingCanvas = false;
350 }
351 
adjustLayout(const QPoint & p)352 void KisPopupPalette::adjustLayout(const QPoint &p)
353 {
354     KIS_ASSERT_RECOVER_RETURN(m_brushHud);
355     if (isVisible() && parentWidget())  {
356 
357         float hudMargin = 30.0;
358         const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin
359         const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
360         QRect paletteRect = rect();
361         paletteRect.moveTo(p - paletteCenterOffset);
362         if (m_brushHudButton->isChecked()) {
363             m_brushHud->updateGeometry();
364             paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0);
365         }
366 
367         paletteRect = kisEnsureInRect(paletteRect, fitRect);
368         move(paletteRect.topLeft());
369         m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0));
370         m_lastCenterPoint = p;
371     }
372 }
373 
slotUpdateIcons()374 void KisPopupPalette::slotUpdateIcons()
375 {
376     this->setPalette(qApp->palette());
377 
378     for(int i=0; i<this->children().size(); i++) {
379         QWidget *w = qobject_cast<QWidget*>(this->children().at(i));
380         if (w) {
381             w->setPalette(qApp->palette());
382         }
383     }
384     zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon());
385     m_brushHud->updateIcons();
386     m_settingsButton->setIcon(KisIconUtils::loadIcon("tag"));
387     m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right"));
388 }
389 
showHudWidget(bool visible)390 void KisPopupPalette::showHudWidget(bool visible)
391 {
392     KIS_ASSERT_RECOVER_RETURN(m_brushHud);
393 
394     const bool reallyVisible = visible && m_brushHudButton->isChecked();
395 
396     if (reallyVisible) {
397         m_brushHud->updateProperties();
398     }
399 
400     m_brushHud->setVisible(reallyVisible);
401     adjustLayout(m_lastCenterPoint);
402 
403     KisConfig cfg(false);
404     cfg.setShowBrushHud(visible);
405 }
406 
showPopupPalette(const QPoint & p)407 void KisPopupPalette::showPopupPalette(const QPoint &p)
408 {
409     showPopupPalette(!isVisible());
410     adjustLayout(p);
411 }
412 
showPopupPalette(bool show)413 void KisPopupPalette::showPopupPalette(bool show)
414 {
415     if (show) {
416         // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within
417         // the bounds and cause the canvas to jump between the slider's min and max
418         if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue &&
419                 m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue  ){
420 
421             KisSignalsBlocker b(zoomCanvasSlider);
422             zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider
423         }
424     }
425     setVisible(show);
426     m_brushHud->setVisible(show && m_brushHudButton->isChecked());
427 }
428 
429 //redefinition of setVariable function to change the scope to private
setVisible(bool b)430 void KisPopupPalette::setVisible(bool b)
431 {
432     QWidget::setVisible(b);
433 }
434 
setParent(QWidget * parent)435 void KisPopupPalette::setParent(QWidget *parent) {
436     m_brushHud->setParent(parent);
437     QWidget::setParent(parent);
438 }
439 
sizeHint() const440 QSize KisPopupPalette::sizeHint() const
441 {
442     return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below
443 }
444 
resizeEvent(QResizeEvent *)445 void KisPopupPalette::resizeEvent(QResizeEvent*)
446 {
447 }
448 
paintEvent(QPaintEvent * e)449 void KisPopupPalette::paintEvent(QPaintEvent* e)
450 {
451     Q_UNUSED(e);
452 
453     QPainter painter(this);
454 
455     QPen pen(palette().color(QPalette::Text));
456     pen.setWidth(3);
457     painter.setPen(pen);
458 
459     painter.setRenderHint(QPainter::Antialiasing);
460     painter.setRenderHint(QPainter::SmoothPixmapTransform);
461 
462     // painting background color indicator
463     QPainterPath bgColor;
464     bgColor.addEllipse(QPoint( 50, 80), 30, 30);
465     painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor()));
466     painter.drawPath(bgColor);
467 
468     // painting foreground color indicator
469     QPainterPath fgColor;
470     fgColor.addEllipse(QPoint( 60, 50), 30, 30);
471     painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor()));
472     painter.drawPath(fgColor);
473 
474     // create a circle background that everything else will go into
475     QPainterPath backgroundContainer;
476     float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it
477 
478     QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount,
479                       m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2);
480 
481 
482     backgroundContainer.addEllipse( circleRect );
483     painter.fillPath(backgroundContainer,palette().brush(QPalette::Background));
484 
485     painter.drawPath(backgroundContainer);
486 
487     // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas
488     // with the indicator
489     QPainterPath rotationTrackPath;
490     shrinkCircleAmount = 18;
491     QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount,
492                        m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2);
493 
494     rotationTrackPath.addEllipse( circleRect2 );
495     pen.setWidth(1);
496     painter.setPen(pen);
497     painter.drawPath(rotationTrackPath);
498 
499     // this thing will help indicate where the starting brush preset is at.
500     // also what direction they go to give sor order to the presets populated
501     /*
502     pen.setWidth(6);
503     pen.setCapStyle(Qt::RoundCap);
504     painter.setPen(pen);
505     painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees
506 
507     QPainterPath brushDir;
508     brushDir.arcMoveTo(circleRect, 60);
509     brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14);
510     painter.drawPath(brushDir);
511 
512     brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6);
513     painter.drawPath(brushDir);
514     */
515 
516     // the following things needs to be based off the center, so let's translate the painter
517     painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
518 
519     // create the canvas rotation handle
520     QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true);
521 
522     painter.fillPath(rotationIndicator,palette().brush(QPalette::Text));
523 
524     // hover indicator for the canvas rotation
525     if (m_isOverCanvasRotationIndicator == true) {
526         painter.save();
527 
528         QPen pen(palette().color(QPalette::Highlight));
529         pen.setWidth(2);
530         painter.setPen(pen);
531         painter.drawPath(rotationIndicator);
532 
533         painter.restore();
534     }
535 
536     // create a reset canvas rotation indicator to bring the canvas back to 0 degrees
537     QPainterPath resetRotationIndicator = drawRotationIndicator(0, false);
538 
539     QPen resetPen(palette().color(QPalette::Text));
540     resetPen.setWidth(1);
541     painter.save();
542     painter.setPen(resetPen);
543     painter.drawPath(resetRotationIndicator);
544 
545     painter.restore();
546 
547     // painting favorite brushes
548     QList<QImage> images(m_resourceManager->favoritePresetImages());
549 
550     // painting favorite brushes pixmap/icon
551     QPainterPath presetPath;
552     for (int pos = 0; pos < numSlots(); pos++) {
553         painter.save();
554 
555         presetPath = createPathFromPresetIndex(pos);
556 
557         if (pos < images.size()) {
558             painter.setClipPath(presetPath);
559 
560             QRect bounds = presetPath.boundingRect().toAlignedRect();
561             QImage previewHighDPI = images.at(pos).scaled(bounds.size()*devicePixelRatioF() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
562             previewHighDPI.setDevicePixelRatio(devicePixelRatioF());
563             painter.drawImage(bounds.topLeft(), previewHighDPI);
564         }
565         else {
566             painter.fillPath(presetPath, palette().brush(QPalette::Window));  // brush slot that has no brush in it
567         }
568         QPen pen = painter.pen();
569         pen.setWidth(1);
570         painter.setPen(pen);
571         painter.drawPath(presetPath);
572 
573         painter.restore();
574     }
575     if (hoveredPreset() > -1) {
576         presetPath = createPathFromPresetIndex(hoveredPreset());
577         QPen pen(palette().color(QPalette::Highlight));
578         pen.setWidth(3);
579         painter.setPen(pen);
580         painter.drawPath(presetPath);
581     }
582 
583     // paint recent colors area.
584     painter.setPen(Qt::NoPen);
585     float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal();
586 
587     // there might be no recent colors at the start, so paint a placeholder
588     if (m_resourceManager->recentColorsTotal() == 0) {
589         painter.setBrush(Qt::transparent);
590 
591         QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
592         painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
593         painter.drawPath(emptyRecentColorsPath);
594     } else {
595 
596         for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) {
597             QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
598 
599             //accessing recent color of index pos
600             painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) ));
601             painter.drawPath(recentColorsPath);
602             painter.rotate(rotationAngle);
603         }
604     }
605 
606     // painting hovered color
607     if (hoveredColor() > -1) {
608         painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
609 
610         if (m_resourceManager->recentColorsTotal() == 1) {
611             QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
612             painter.drawPath(path_ColorDonut);
613         } else {
614             painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle);
615             QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
616             painter.drawPath(path);
617             painter.rotate(hoveredColor() * -1 * rotationAngle);
618         }
619     }
620 
621     // painting selected color
622     if (selectedColor() > -1) {
623         painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
624 
625         if (m_resourceManager->recentColorsTotal() == 1) {
626             QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
627             painter.drawPath(path_ColorDonut);
628         } else {
629             painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle);
630             QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
631             painter.drawPath(path);
632             painter.rotate(selectedColor() * -1 * rotationAngle);
633         }
634     }
635 
636 
637     // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better
638     if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) {
639         opacityChange->setOpacity(0.4);
640     } else {
641         opacityChange->setOpacity(1.0);
642     }
643 
644 }
645 
drawDonutPathFull(int x,int y,int inner_radius,int outer_radius)646 QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius)
647 {
648     QPainterPath path;
649     path.addEllipse(QPointF(x, y), outer_radius, outer_radius);
650     path.addEllipse(QPointF(x, y), inner_radius, inner_radius);
651     path.setFillRule(Qt::OddEvenFill);
652 
653     return path;
654 }
655 
drawDonutPathAngle(int inner_radius,int outer_radius,int limit)656 QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit)
657 {
658     QPainterPath path;
659     path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit));
660     path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit,
661                360.0 / limit);
662     path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit,
663                - 360.0 / limit);
664     path.closeSubpath();
665 
666     return path;
667 }
668 
drawRotationIndicator(qreal rotationAngle,bool canDrag)669 QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag)
670 {
671     // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator,
672     // and another time by the reset canvas position
673 
674     float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90);  // -90 will make 0 degrees be at the top
675     float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2  = radius
676     float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10);
677 
678     QPainterPath canvasRotationIndicator;
679     int canvasIndicatorSize = 15;
680     int canvasIndicatorMiddle = canvasIndicatorSize / 2;
681     QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle,
682                                       canvasIndicatorSize, canvasIndicatorSize );
683 
684     if (canDrag) {
685         m_canvasRotationIndicatorRect = indicatorRectangle;
686     } else {
687         m_resetCanvasRotationIndicatorRect = indicatorRectangle;
688     }
689 
690     canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(),
691                                        indicatorRectangle.width(), indicatorRectangle.height() );
692 
693     return canvasRotationIndicator;
694 }
695 
696 
mouseMoveEvent(QMouseEvent * event)697 void KisPopupPalette::mouseMoveEvent(QMouseEvent *event)
698 {
699     QPointF point = event->localPos();
700     event->accept();
701 
702     setToolTip(QString());
703     setHoveredPreset(-1);
704     setHoveredColor(-1);
705 
706     // calculate if we are over the canvas rotation knob
707     // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to
708     // correct them first before looking for a click event intersection
709 
710     float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2);
711     float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2);
712     QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos,
713                                                    m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height());
714 
715     if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) {
716         m_isOverCanvasRotationIndicator = true;
717     } else {
718         m_isOverCanvasRotationIndicator = false;
719     }
720 
721     if (m_isRotatingCanvasIndicator) {
722         // we are rotating the canvas, so calculate the rotation angle based off the center
723         // calculate the angle we are at first
724         QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2);
725 
726         float dX = point.x() - widgetCenterPoint.x();
727         float dY = point.y() - widgetCenterPoint.y();
728 
729 
730         float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle
731         finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up
732         float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out
733 
734         KisCanvasController *canvasController =
735             dynamic_cast<KisCanvasController*>(m_viewManager->canvasBase()->canvasController());
736         canvasController->rotateCanvas(angleDifference);
737 
738 
739         emit sigUpdateCanvas();
740     }
741 
742     // don't highlight the presets if we are in the middle of rotating the canvas
743     if (m_isRotatingCanvasIndicator == false) {
744         QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
745         {
746             int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets());
747 
748             if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) {
749                 setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name());
750                 setHoveredPreset(pos);
751             }
752         }
753         if (pathColor.contains(point)) {
754             int pos = calculateIndex(point, m_resourceManager->recentColorsTotal());
755 
756             if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) {
757                 setHoveredColor(pos);
758             }
759         }
760     }
761 
762     update();
763 }
764 
mousePressEvent(QMouseEvent * event)765 void KisPopupPalette::mousePressEvent(QMouseEvent *event)
766 {
767     QPointF point = event->localPos();
768     event->accept();
769 
770     if (event->button() == Qt::LeftButton) {
771 
772         //in favorite brushes area
773         int pos = calculateIndex(point, m_resourceManager->numFavoritePresets());
774         if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()
775                 && isPointInPixmap(point, pos)) {
776             //setSelectedBrush(pos);
777             update();
778         }
779 
780         if (m_isOverCanvasRotationIndicator) {
781             m_isRotatingCanvasIndicator = true;
782         }
783 
784         // reset the canvas if we are over the reset canvas rotation indicator
785         float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2);
786         float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2);
787         QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos,
788                                                             m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height());
789 
790         if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) {
791             float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs
792             KisCanvasController *canvasController =
793                     dynamic_cast<KisCanvasController*>(m_viewManager->canvasBase()->canvasController());
794             canvasController->rotateCanvas(angleDifference);
795 
796             emit sigUpdateCanvas();
797         }
798     }
799 }
800 
slotShowTagsPopup()801 void KisPopupPalette::slotShowTagsPopup()
802 {
803     KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer();
804     QStringList tags = rServer->tagNamesList();
805     std::sort(tags.begin(), tags.end());
806 
807     if (!tags.isEmpty()) {
808         QMenu menu;
809         Q_FOREACH (const QString& tag, tags) {
810             menu.addAction(tag);
811         }
812 
813         QAction *action = menu.exec(QCursor::pos());
814         if (action) {
815             m_resourceManager->setCurrentTag(KLocalizedString::removeAcceleratorMarker(action->text()));
816         }
817     } else {
818         QWhatsThis::showText(QCursor::pos(),
819                              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."));
820     }
821 }
822 
slotZoomToOneHundredPercentClicked()823 void KisPopupPalette::slotZoomToOneHundredPercentClicked() {
824     QAction *action = m_actionCollection->action("zoom_to_100pct");
825 
826     if (action) {
827         action->trigger();
828     }
829 
830     // also move the zoom slider to 100% position so they are in sync
831     zoomCanvasSlider->setValue(100);
832 }
833 
tabletEvent(QTabletEvent * event)834 void KisPopupPalette::tabletEvent(QTabletEvent *event) {
835     event->ignore();
836 }
837 
showEvent(QShowEvent * event)838 void KisPopupPalette::showEvent(QShowEvent *event)
839 {
840     m_clicksEater->reset();
841     QWidget::showEvent(event);
842 }
843 
mouseReleaseEvent(QMouseEvent * event)844 void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event)
845 {
846     QPointF point = event->localPos();
847     event->accept();
848 
849     if (event->buttons() == Qt::NoButton &&
850         event->button() == Qt::RightButton) {
851 
852         showPopupPalette(false);
853         return;
854     }
855 
856     m_isOverCanvasRotationIndicator = false;
857     m_isRotatingCanvasIndicator = false;
858 
859     if (event->button() == Qt::LeftButton) {
860         QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
861 
862         //in favorite brushes area
863         if (hoveredPreset() > -1) {
864             //setSelectedBrush(hoveredBrush());
865             emit sigChangeActivePaintop(hoveredPreset());
866         }
867         if (pathColor.contains(point)) {
868             int pos = calculateIndex(point, m_resourceManager->recentColorsTotal());
869 
870             if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) {
871                 emit sigUpdateRecentColor(pos);
872             }
873         }
874     }
875 }
876 
calculateIndex(QPointF point,int n)877 int KisPopupPalette::calculateIndex(QPointF point, int n)
878 {
879     calculatePresetIndex(point, n);
880     //translate to (0,0)
881     point.setX(point.x() - m_popupPaletteSize / 2);
882     point.setY(point.y() - m_popupPaletteSize / 2);
883 
884     //rotate
885     float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x());
886     float radius = sqrt((float)point.x() * point.x() + point.y() * point.y());
887     point.setX(radius * cos(smallerAngle));
888     point.setY(radius * sin(smallerAngle));
889 
890     //calculate brush index
891     int pos = floor(acos(point.x() / radius) * n / (2 * M_PI));
892     if (point.y() < 0) pos =  n - pos - 1;
893 
894     return pos;
895 }
896 
isPointInPixmap(QPointF & point,int pos)897 bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos)
898 {
899     if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) {
900         return true;
901     }
902     return false;
903 }
904 
~KisPopupPalette()905 KisPopupPalette::~KisPopupPalette()
906 {
907 }
908 
createPathFromPresetIndex(int index)909 QPainterPath KisPopupPalette::createPathFromPresetIndex(int index)
910 {
911     qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get
912 
913     // the starting angle of the slice we need to draw. the negative sign makes us go clockwise.
914     // adding 90 degrees makes us start at the top. otherwise we would start at the right
915     qreal startingAngle = -(index * angleSlice) + 90;
916 
917     // the radius will get smaller as the amount of presets shown increases. 10 slots == 41
918     qreal radians = qDegreesToRadians((360.0/10)/2);
919     qreal maxRadius = (m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)))-2;
920 
921     radians = qDegreesToRadians(angleSlice/2);
922     qreal presetRadius = m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians));
923     //If we assume that circles will mesh like a hexagonal grid, then 3.5r is the size of two hexagons interlocking.
924 
925     qreal length = m_colorHistoryOuterRadius + presetRadius;
926     // can we can fit in a second row? We don't want the preset icons to get too tiny.
927     if (maxRadius > presetRadius) {
928         //redo all calculations assuming a second row.
929         if (numSlots() % 2) {
930             angleSlice = 360.0/(numSlots()+1);
931             startingAngle = -(index * angleSlice) + 90;
932         }
933         if (numSlots() != m_cachedNumSlots){
934             qreal tempRadius = presetRadius;
935             qreal distance = 0;
936             do{
937                 tempRadius+=0.1;
938 
939                 // Calculate the XY of two adjectant circles using this tempRadius.
940                 qreal length1 = m_colorHistoryOuterRadius + tempRadius;
941                 qreal length2 = m_colorHistoryOuterRadius + ((maxRadius*2)-tempRadius);
942                 qreal pathX1 = length1 * qCos(qDegreesToRadians(startingAngle)) - tempRadius;
943                 qreal pathY1 = -(length1) * qSin(qDegreesToRadians(startingAngle)) - tempRadius;
944                 qreal startingAngle2 = -(index+1 * angleSlice) + 90;
945                 qreal pathX2 = length2 * qCos(qDegreesToRadians(startingAngle2)) - tempRadius;
946                 qreal pathY2 = -(length2) * qSin(qDegreesToRadians(startingAngle2)) - tempRadius;
947 
948                 // Use Pythagorean Theorem to calculate the distance between these two values.
949                 qreal m1 = pathX2-pathX1;
950                 qreal m2 = pathY2-pathY1;
951 
952                 distance = sqrt((m1*m1)+(m2*m2));
953             }
954             //As long at there's more distance than the radius of the two presets, continue increasing the radius.
955             while((tempRadius+1)*2 < distance);
956             m_cachedRadius = tempRadius;
957         }
958         m_cachedNumSlots = numSlots();
959         presetRadius = m_cachedRadius;
960         length = m_colorHistoryOuterRadius + presetRadius;
961         if (index % 2) {
962             length = m_colorHistoryOuterRadius + ((maxRadius*2)-presetRadius);
963         }
964     }
965     QPainterPath path;
966     qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - presetRadius;
967     qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - presetRadius;
968     qreal pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size
969     path.addEllipse(pathX, pathY, pathDiameter, pathDiameter);
970     return path;
971 }
972 
calculatePresetIndex(QPointF point,int)973 int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/)
974 {
975     for(int i = 0; i < numSlots(); i++)
976     {
977         QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2);
978         if(createPathFromPresetIndex(i).contains(adujustedPoint))
979         {
980             return i;
981         }
982     }
983     return -1;
984 }
985 
numSlots()986 int KisPopupPalette::numSlots()
987 {
988     KisConfig config(true);
989     return qMax(config.favoritePresets(), 10);
990 }
991