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