File indexing completed on 2024-06-16 04:17:45

0001 /*
0002  *  SPDX-FileCopyrightText: 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
0003  *  SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 #include "kis_tool_multihand.h"
0008 
0009 #include <QTransform>
0010 
0011 #include <QPushButton>
0012 #include <QComboBox>
0013 #include <QFormLayout>
0014 #include <QStackedWidget>
0015 #include <kis_slider_spin_box.h>
0016 #include <QLabel>
0017 #include "kis_aspect_ratio_locker.h"
0018 #include "kis_canvas2.h"
0019 #include "kis_cursor.h"
0020 #include "KisViewManager.h"
0021 #include "kis_selection.h"
0022 
0023 #include "kis_tool_multihand_helper.h"
0024 
0025 
0026 static const int MAXIMUM_BRUSHES = 50;
0027 
0028 #include <QtGlobal>
0029 #ifdef Q_OS_WIN
0030 // quoting DRAND48(3) man-page:
0031 // These functions are declared obsolete by  SVID  3,
0032 // which  states  that rand(3) should be used instead.
0033 #define drand48() (static_cast<double>(qrand()) / static_cast<double>(RAND_MAX))
0034 #endif
0035 
0036 
0037 KisToolMultihand::KisToolMultihand(KoCanvasBase *canvas)
0038     : KisToolBrush(canvas),
0039       m_transformMode(SYMMETRY),
0040       m_angle(0),
0041       m_handsCount(6),
0042       m_mirrorVertically(false),
0043       m_mirrorHorizontally(false),
0044       m_showAxes(false),
0045       m_translateRadius(100),
0046       m_setupAxesFlag(false),
0047       m_addSubbrushesMode(false),
0048       m_intervalX(0),
0049       m_intervalY(0)
0050     , customUI(0)
0051 {
0052 
0053 
0054     m_helper =
0055         new KisToolMultihandHelper(paintingInformationBuilder(),
0056                                    canvas->resourceManager(),
0057                                    kundo2_i18n("Multibrush Stroke"));
0058     resetHelper(m_helper);
0059     if (image()) {
0060         m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height());
0061     }
0062 
0063 }
0064 
0065 KisToolMultihand::~KisToolMultihand()
0066 {
0067 }
0068 
0069 void KisToolMultihand::beginPrimaryAction(KoPointerEvent *event)
0070 {
0071     if(m_setupAxesFlag) {
0072         setMode(KisTool::OTHER);
0073         m_axesPoint = convertToPixelCoord(event->point);
0074         requestUpdateOutline(event->point, 0);
0075         updateCanvas();
0076     }
0077     else if (m_addSubbrushesMode){
0078         QPointF newPoint = convertToPixelCoord(event->point);
0079         m_subbrOriginalLocations << newPoint;
0080         requestUpdateOutline(event->point, 0);
0081         updateCanvas();
0082     }
0083     else if (m_transformMode == COPYTRANSLATEINTERVALS) {
0084         m_axesPoint = convertToPixelCoord(event->point);
0085         initTransformations();
0086         KisToolFreehand::beginPrimaryAction(event);
0087     }
0088     else {
0089         initTransformations();
0090         KisToolFreehand::beginPrimaryAction(event);
0091     }
0092 }
0093 
0094 void KisToolMultihand::continuePrimaryAction(KoPointerEvent *event)
0095 {
0096     if(mode() == KisTool::OTHER) {
0097         m_axesPoint = convertToPixelCoord(event->point);
0098         requestUpdateOutline(event->point, 0);
0099         updateCanvas();
0100     }
0101     else {
0102         requestUpdateOutline(event->point, 0);
0103         KisToolFreehand::continuePrimaryAction(event);
0104     }
0105 }
0106 
0107 void KisToolMultihand::endPrimaryAction(KoPointerEvent *event)
0108 {
0109     if(mode() == KisTool::OTHER) {
0110         setMode(KisTool::HOVER_MODE);
0111         requestUpdateOutline(event->point, 0);
0112         finishAxesSetup();
0113     }
0114     else {
0115         KisToolFreehand::endPrimaryAction(event);
0116     }
0117 }
0118 
0119 void KisToolMultihand::beginAlternateAction(KoPointerEvent* event, AlternateAction action)
0120 {
0121     if ((action != ChangeSize && action != ChangeSizeSnap) || m_transformMode != COPYTRANSLATE ||
0122          m_transformMode != COPYTRANSLATEINTERVALS || !m_addSubbrushesMode) {
0123         KisToolBrush::beginAlternateAction(event, action);
0124         return;
0125     }
0126     setMode(KisTool::OTHER_1);
0127     m_axesPoint = convertToPixelCoord(event->point);
0128     requestUpdateOutline(event->point, 0);
0129     updateCanvas();
0130 }
0131 
0132 void KisToolMultihand::continueAlternateAction(KoPointerEvent* event, AlternateAction action)
0133 {
0134     if ((action != ChangeSize && action != ChangeSizeSnap) || m_transformMode != COPYTRANSLATE ||
0135          m_transformMode != COPYTRANSLATEINTERVALS || !m_addSubbrushesMode) {
0136         KisToolBrush::continueAlternateAction(event, action);
0137         return;
0138     }
0139     if (mode() == KisTool::OTHER_1) {
0140         m_axesPoint = convertToPixelCoord(event->point);
0141         requestUpdateOutline(event->point, 0);
0142         updateCanvas();
0143     }
0144 }
0145 
0146 void KisToolMultihand::endAlternateAction(KoPointerEvent* event, AlternateAction action)
0147 {
0148     if ((action != ChangeSize && action != ChangeSizeSnap) || m_transformMode != COPYTRANSLATE ||
0149          m_transformMode != COPYTRANSLATEINTERVALS || !m_addSubbrushesMode) {
0150         KisToolBrush::endAlternateAction(event, action);
0151         return;
0152     }
0153     if (mode() == KisTool::OTHER_1) {
0154         setMode(KisTool::HOVER_MODE);
0155     }
0156 }
0157 
0158 void KisToolMultihand::mouseMoveEvent(KoPointerEvent* event)
0159 {
0160     if (mode() == HOVER_MODE) {
0161         m_lastToolPos=convertToPixelCoord(event->point);
0162     }
0163     KisToolBrush::mouseMoveEvent(event);
0164 }
0165 
0166 void KisToolMultihand::paint(QPainter& gc, const KoViewConverter &converter)
0167 {
0168     QPainterPath path;
0169 
0170     if (m_showAxes) {
0171         const int axisLength = currentImage()->height() + currentImage()->width();
0172 
0173         // add division guide lines if using multiple brushes
0174         if ((m_handsCount > 1 && m_transformMode == SYMMETRY) ||
0175             (m_handsCount > 1 && m_transformMode == SNOWFLAKE) ) {
0176             int axesCount;
0177             if (m_transformMode == SYMMETRY){
0178                  axesCount = m_handsCount;
0179             }
0180             else {
0181                 axesCount = m_handsCount*2;
0182             }
0183             const qreal axesAngle = 360.0 / float(axesCount);
0184             float currentAngle = 0.0;
0185             const float startingInsetLength = 20; // don't start each line at the origin so we can see better when all points converge
0186 
0187             // draw lines radiating from the origin
0188             for( int i=0; i < axesCount; i++) {
0189 
0190                 currentAngle = i*axesAngle;
0191 
0192                 // convert angles to radians since cos and sin need that
0193                 currentAngle = currentAngle * 0.017453 + m_angle; // m_angle is current rotation set on UI
0194 
0195                 const QPoint startingSpot = QPoint(m_axesPoint.x()+ (sin(currentAngle)*startingInsetLength), m_axesPoint.y()- (cos(currentAngle))*startingInsetLength );
0196                 path.moveTo(startingSpot.x(), startingSpot.y());
0197                 QPointF symmetryLinePoint(m_axesPoint.x()+ (sin(currentAngle)*axisLength), m_axesPoint.y()- (cos(currentAngle))*axisLength );
0198                 path.lineTo(symmetryLinePoint);
0199             }
0200 
0201         }
0202         else if(m_transformMode == MIRROR) {
0203 
0204             if (m_mirrorHorizontally) {
0205                 path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2));
0206                 path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2));
0207             }
0208 
0209             if(m_mirrorVertically) {
0210                 path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle));
0211                 path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle));
0212             }
0213         }
0214         else if (m_transformMode == COPYTRANSLATE) {
0215 
0216             const int ellipsePreviewSize = 10;
0217             // draw ellipse at origin to emphasize this is a drawing point
0218             path.addEllipse(m_axesPoint.x()-(ellipsePreviewSize),
0219                             m_axesPoint.y()-(ellipsePreviewSize),
0220                             ellipsePreviewSize*2,
0221                             ellipsePreviewSize*2);
0222 
0223             Q_FOREACH (QPointF dPos, m_subbrOriginalLocations) {
0224                 path.addEllipse(dPos, ellipsePreviewSize, ellipsePreviewSize);  // Show subbrush reference locations while in add mode
0225             }
0226 
0227             // draw the horiz/vertical line for axis  origin
0228             path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle));
0229             path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle));
0230             path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2));
0231             path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2));
0232 
0233         }
0234         else if (m_transformMode == COPYTRANSLATEINTERVALS) {
0235             const int ellipsePreviewSize = 10;
0236 
0237             Q_FOREACH (QPointF dPos, intervalLocations()) {
0238                 path.addEllipse(dPos, ellipsePreviewSize, ellipsePreviewSize);
0239             }
0240         }
0241         else {
0242 
0243             // draw the horiz/vertical line for axis  origin
0244             path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle));
0245             path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle));
0246             path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2));
0247             path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2));
0248         }
0249 
0250     } else {
0251 
0252         // not showing axis
0253         if (m_transformMode == COPYTRANSLATE) {
0254 
0255             Q_FOREACH (QPointF dPos, m_subbrOriginalLocations) {
0256                 // Show subbrush reference locations while in add mode
0257                 if (m_addSubbrushesMode) {
0258                     path.addEllipse(dPos, 10, 10);
0259                 }
0260             }
0261         }
0262     }
0263 
0264     KisToolFreehand::paint(gc, converter);
0265 
0266     // origin point preview line/s
0267     gc.save();
0268     QPen outlinePen;
0269     outlinePen.setColor(QColor(100,100,100,150));
0270     outlinePen.setStyle(Qt::PenStyle::SolidLine);
0271     gc.setPen(outlinePen);
0272     paintToolOutline(&gc, pixelToView(path));
0273     gc.restore();
0274 
0275 
0276     // fill in a dot for the origin if showing axis
0277     if (m_showAxes && m_transformMode != COPYTRANSLATEINTERVALS) {
0278         // draw a dot at the origin point to help with precisely moving
0279         QPainterPath dotPath;
0280         const int dotRadius = 4;
0281         dotPath.moveTo(m_axesPoint.x(), m_axesPoint.y());
0282         dotPath.addEllipse(m_axesPoint.x()- dotRadius*0.25, m_axesPoint.y()- dotRadius*0.25, dotRadius, dotRadius); // last 2 parameters are dot's size
0283 
0284         QBrush fillBrush;
0285         fillBrush.setColor(QColor(255, 255, 255, 255));
0286         fillBrush.setStyle(Qt::SolidPattern);
0287         gc.fillPath(pixelToView(dotPath), fillBrush);
0288 
0289 
0290         // add slight offset circle for contrast to help show it on
0291         dotPath = QPainterPath(); // resets path
0292         dotPath.addEllipse(m_axesPoint.x() - dotRadius*0.75, m_axesPoint.y()- dotRadius*0.75, dotRadius, dotRadius); // last 2 parameters are dot's size
0293         fillBrush.setColor(QColor(120, 120, 120, 255));
0294         gc.fillPath(pixelToView(dotPath), fillBrush);
0295     }
0296 
0297 }
0298 
0299 void KisToolMultihand::initTransformations()
0300 {
0301     QVector<QTransform> transformations;
0302     QTransform m;
0303 
0304     if(m_transformMode == SYMMETRY) {
0305         qreal angle = 0;
0306         const qreal angleStep = (2 * M_PI) / m_handsCount;
0307 
0308         for(int i = 0; i < m_handsCount; i++) {
0309             m.translate(m_axesPoint.x(), m_axesPoint.y());
0310             m.rotateRadians(angle);
0311             m.translate(-m_axesPoint.x(), -m_axesPoint.y());
0312 
0313             transformations << m;
0314             m.reset();
0315             angle += angleStep;
0316         }
0317     }
0318     else if(m_transformMode == MIRROR) {
0319         transformations << m;
0320 
0321         if (m_mirrorHorizontally) {
0322             m.translate(m_axesPoint.x(),m_axesPoint.y());
0323             m.rotateRadians(m_angle);
0324             m.scale(-1,1);
0325             m.rotateRadians(-m_angle);
0326             m.translate(-m_axesPoint.x(), -m_axesPoint.y());
0327             transformations << m;
0328             m.reset();
0329         }
0330 
0331         if (m_mirrorVertically) {
0332             m.translate(m_axesPoint.x(),m_axesPoint.y());
0333             m.rotateRadians(m_angle);
0334             m.scale(1,-1);
0335             m.rotateRadians(-m_angle);
0336             m.translate(-m_axesPoint.x(), -m_axesPoint.y());
0337             transformations << m;
0338             m.reset();
0339         }
0340 
0341         if (m_mirrorVertically && m_mirrorHorizontally){
0342             m.translate(m_axesPoint.x(),m_axesPoint.y());
0343             m.rotateRadians(m_angle);
0344             m.scale(-1,-1);
0345             m.rotateRadians(-m_angle);
0346             m.translate(-m_axesPoint.x(), -m_axesPoint.y());
0347             transformations << m;
0348             m.reset();
0349         }
0350 
0351     }
0352     else if(m_transformMode == SNOWFLAKE) {
0353         qreal angle = 0;
0354         const qreal angleStep = (2 * M_PI) / m_handsCount/4;
0355 
0356         for(int i = 0; i < m_handsCount*4; i++) {
0357            if ((i%2)==1) {
0358 
0359                m.translate(m_axesPoint.x(), m_axesPoint.y());
0360                m.rotateRadians(m_angle-angleStep);
0361                m.rotateRadians(angle);
0362                m.scale(-1,1);
0363                m.rotateRadians(-m_angle+angleStep);
0364                m.translate(-m_axesPoint.x(), -m_axesPoint.y());
0365 
0366                transformations << m;
0367                m.reset();
0368                angle += angleStep*2;
0369            } else {
0370                m.translate(m_axesPoint.x(), m_axesPoint.y());
0371                m.rotateRadians(m_angle-angleStep);
0372                m.rotateRadians(angle);
0373                m.rotateRadians(-m_angle+angleStep);
0374                m.translate(-m_axesPoint.x(), -m_axesPoint.y());
0375 
0376                transformations << m;
0377                m.reset();
0378                angle += angleStep*2;
0379             }
0380         }
0381     }
0382     else if(m_transformMode == TRANSLATE) {
0383         /**
0384          * TODO: currently, the seed is the same for all the
0385          * strokes
0386          */
0387         for (int i = 0; i < m_handsCount; i++){
0388             const qreal angle = drand48() * M_PI * 2;
0389             const qreal length = drand48();
0390 
0391             // convert the Polar coordinates to Cartesian coordinates
0392             qreal nx = (m_translateRadius * cos(angle) * length);
0393             qreal ny = (m_translateRadius * sin(angle) * length);
0394 
0395             m.translate(m_axesPoint.x(),m_axesPoint.y());
0396             m.rotateRadians(m_angle);
0397             m.translate(nx,ny);
0398             m.rotateRadians(-m_angle);
0399             m.translate(-m_axesPoint.x(), -m_axesPoint.y());
0400             transformations << m;
0401             m.reset();
0402         }
0403     } else if (m_transformMode == COPYTRANSLATE) {
0404         transformations << m;
0405         Q_FOREACH (QPointF dPos, m_subbrOriginalLocations) {
0406             const QPointF resPos = dPos-m_axesPoint; // Calculate the difference between subbrush reference position and "origin" reference
0407             m.translate(resPos.x(), resPos.y());
0408             transformations << m;
0409             m.reset();
0410         }
0411     } else if (m_transformMode == COPYTRANSLATEINTERVALS) {
0412         KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0413         Q_ASSERT(kisCanvas);
0414         const QRect bounds = kisCanvas->viewManager()->selection() ?
0415             kisCanvas->viewManager()->selection()->selectedExactRect() :
0416             kisCanvas->currentImage()->bounds();
0417         const QPoint dPos = bounds.topLeft() +
0418                       QPoint(m_intervalX ? m_intervalX * floor((m_axesPoint.x() - bounds.left()) / m_intervalX) : 0,
0419                              m_intervalY ? m_intervalY * floor((m_axesPoint.y() - bounds.top()) / m_intervalY) : 0);
0420 
0421         Q_FOREACH (QPoint pos, intervalLocations()) {
0422                 const QPointF resPos = pos - dPos;
0423                 m.translate(resPos.x(), resPos.y());
0424                 transformations << m;
0425                 m.reset();
0426         }
0427     }
0428 
0429     m_helper->setupTransformations(transformations);
0430 }
0431 
0432 QWidget* KisToolMultihand::createOptionWidget()
0433 {
0434     QWidget *widget = KisToolBrush::createOptionWidget();
0435 
0436     customUI = new KisToolMultiHandConfigWidget();
0437 
0438     // brush smoothing option.
0439     //customUI->layout()->addWidget(widget);
0440     customUI->smoothingOptionsLayout->addWidget(widget);
0441 
0442 
0443     // setup common parameters that all of the modes will see
0444     connect(customUI->showAxesCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetAxesVisible(bool)));
0445     customUI->showAxesCheckbox->setChecked((bool)m_configGroup.readEntry("showAxes", false));
0446 
0447     connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(resetAxes()));
0448 
0449     customUI->moveOriginButton->setCheckable(true);
0450     connect(customUI->moveOriginButton, SIGNAL(clicked(bool)),this, SLOT(activateAxesPointModeSetup()));
0451 
0452     connect(customUI->resetOriginButton, SIGNAL(released()), this, SLOT(resetAxes()));
0453 
0454     customUI->multihandTypeCombobox->addItem(i18n("Symmetry"),int(SYMMETRY));  // axis mode
0455     customUI->multihandTypeCombobox->addItem(i18nc("Label of Mirror in Multihand brush tool options", "Mirror"),int(MIRROR));
0456     customUI->multihandTypeCombobox->addItem(i18n("Translate"),int(TRANSLATE));
0457     customUI->multihandTypeCombobox->addItem(i18n("Snowflake"),int(SNOWFLAKE));
0458     customUI->multihandTypeCombobox->addItem(i18n("Copy Translate"),int(COPYTRANSLATE));
0459     customUI->multihandTypeCombobox->addItem(i18n("Copy Translate at Intervals"),int(COPYTRANSLATEINTERVALS));
0460     connect(customUI->multihandTypeCombobox,SIGNAL(currentIndexChanged(int)),this, SLOT(slotSetTransformMode(int)));
0461     customUI->multihandTypeCombobox->setCurrentIndex(m_configGroup.readEntry("transformMode", 0));
0462 
0463 
0464     customUI->axisRotationAngleSelector->setRange(0.0, 90.0);
0465     customUI->axisRotationAngleSelector->setDecimals(1);
0466     customUI->axisRotationAngleSelector->setWrapping(false);
0467     customUI->axisRotationAngleSelector->setFlipOptionsMode(KisAngleSelector::FlipOptionsMode_NoFlipOptions);
0468     connect(customUI->axisRotationAngleSelector, SIGNAL(angleChanged(qreal)), this, SLOT(slotSetAxesAngle(qreal)));
0469     customUI->axisRotationAngleSelector->setAngle(m_configGroup.readEntry("axesAngle", 0.0));
0470 
0471 
0472     // symmetry mode options
0473     customUI->brushCountSpinBox->setRange(1, MAXIMUM_BRUSHES);
0474     connect(customUI->brushCountSpinBox, SIGNAL(valueChanged(int)),this, SLOT(slotSetHandsCount(int)));
0475     customUI->brushCountSpinBox->setValue(m_configGroup.readEntry("handsCount", 4));
0476 
0477     // mirror mode specific options
0478     connect(customUI->horizontalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorHorizontally(bool)));
0479     customUI->horizontalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorHorizontally", false));
0480 
0481     connect(customUI->verticalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorVertically(bool)));
0482     customUI->verticalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorVertically", false));
0483 
0484     // translate mode options
0485     customUI->translationRadiusSpinbox->setRange(0, 200);
0486     customUI->translationRadiusSpinbox->setSuffix(i18n(" px"));
0487 
0488     connect(customUI->translationRadiusSpinbox,SIGNAL(valueChanged(int)),this,SLOT(slotSetTranslateRadius(int)));
0489     customUI->translationRadiusSpinbox->setValue(m_configGroup.readEntry("translateRadius", 0));
0490 
0491     // Copy translate mode options and actions
0492     connect(customUI->addSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotAddSubbrushesMode);
0493     connect(customUI->removeSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotRemoveAllSubbrushes);
0494 
0495     // Copy translate at intervals mode options and actions
0496     customUI->intervalXSpinBox->setRange(0, 2000);
0497     customUI->intervalXSpinBox->setSuffix(i18n(" px"));
0498     customUI->intervalYSpinBox->setRange(0, 2000);
0499     customUI->intervalYSpinBox->setSuffix(i18n(" px"));
0500 
0501     KisAspectRatioLocker *intervalAspectLocker = new KisAspectRatioLocker(this);
0502     intervalAspectLocker->connectSpinBoxes(customUI->intervalXSpinBox, customUI->intervalYSpinBox, customUI->intervalAspectButton);
0503 
0504     customUI->intervalXSpinBox->setValue(m_configGroup.readEntry("intervalX", 0));
0505     customUI->intervalYSpinBox->setValue(m_configGroup.readEntry("intervalY", 0));
0506     connect(intervalAspectLocker, SIGNAL(sliderValueChanged()), this, SLOT(slotSetIntervals()));
0507     slotSetIntervals(); // X and Y need to be set at the same time.
0508     connect(intervalAspectLocker, SIGNAL(aspectButtonChanged()), this, SLOT(slotSetKeepAspect()));
0509     customUI->intervalAspectButton->setKeepAspectRatio(m_configGroup.readEntry("intervalKeepAspect", false));
0510 
0511     // snowflake re-uses the existing options, so there is no special parameters for that...
0512 
0513 
0514     return static_cast<QWidget*>(customUI); // keeping it in the native class until the end allows us to access the UI components
0515 }
0516 
0517 void KisToolMultihand::activateAxesPointModeSetup()
0518 {
0519     if (customUI->moveOriginButton->isChecked()){
0520         m_setupAxesFlag = true;
0521         useCursor(KisCursor::crossCursor());
0522         updateCanvas();
0523     } else {
0524         finishAxesSetup();
0525     }
0526 }
0527 
0528 void KisToolMultihand::resetAxes()
0529 {
0530     m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height());
0531     finishAxesSetup();
0532 }
0533 
0534 
0535 void KisToolMultihand::finishAxesSetup()
0536 {
0537     m_setupAxesFlag = false;
0538     customUI->moveOriginButton->setChecked(false);
0539     resetCursorStyle();
0540     updateCanvas();
0541 }
0542 
0543 void KisToolMultihand::updateCanvas()
0544 {
0545     KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0546     Q_ASSERT(kisCanvas);
0547     kisCanvas->updateCanvas();
0548     if(customUI->moveOriginButton->isChecked())
0549     {
0550         kisCanvas->viewManager()->showFloatingMessage(i18n("X: %1 px\nY: %2 px"
0551                 , QString::number(this->m_axesPoint.x(),'f',1),QString::number(this->m_axesPoint.y(),'f',1))
0552                 , QIcon(), 1000, KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
0553     }
0554 }
0555 
0556 QVector<QPoint> KisToolMultihand::intervalLocations()
0557 {
0558     QVector<QPoint> intervalLocations;
0559 
0560     KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0561     Q_ASSERT(kisCanvas);
0562     const QRect bounds = kisCanvas->viewManager()->selection() ?
0563         kisCanvas->viewManager()->selection()->selectedExactRect() :
0564         kisCanvas->currentImage()->bounds();
0565 
0566     const int intervals = m_intervalX ? (bounds.width() / m_intervalX) : 0 +
0567                     m_intervalY ? (bounds.height() / m_intervalY) : 0;
0568     if (intervals > MAXIMUM_BRUSHES) {
0569         kisCanvas->viewManager()->showFloatingMessage(
0570             i18n("Multibrush Tool does not support more than %1 brushes; use a larger interval.",
0571             QString::number(MAXIMUM_BRUSHES)), QIcon());
0572         return intervalLocations;
0573     }
0574 
0575     for (int x = bounds.left(); x <= bounds.right(); x += m_intervalX) {
0576         for (int y = bounds.top(); y <= bounds.bottom(); y += m_intervalY) {
0577             intervalLocations << QPoint(x,y);
0578 
0579             if (m_intervalY == 0) { break; }
0580         }
0581         if (m_intervalX == 0) { break; }
0582     }
0583 
0584     return intervalLocations;
0585 }
0586 
0587 void KisToolMultihand::slotSetHandsCount(int count)
0588 {
0589     m_handsCount = count;
0590     m_configGroup.writeEntry("handsCount", count);
0591     updateCanvas();
0592 }
0593 
0594 void KisToolMultihand::slotSetAxesAngle(qreal angle)
0595 {
0596     //negative so axes rotates counter clockwise
0597     m_angle = -angle*M_PI/180;
0598     updateCanvas();
0599     m_configGroup.writeEntry("axesAngle", angle);
0600 }
0601 
0602 void KisToolMultihand::slotSetTransformMode(int index)
0603 {
0604     m_transformMode = enumTransformModes(customUI->multihandTypeCombobox->itemData(index).toInt());
0605     m_configGroup.writeEntry("transformMode", index);
0606 
0607     // turn on or off what we need
0608 
0609     bool vis = index == MIRROR;
0610     customUI->horizontalCheckbox->setVisible(vis);
0611     customUI->verticalCheckbox->setVisible(vis);
0612 
0613     vis = index == TRANSLATE;
0614     customUI->translationRadiusSpinbox->setVisible(vis);
0615     customUI->radiusLabel->setVisible(vis);
0616     customUI->brushCountSpinBox->setVisible(vis);
0617     customUI->brushesLabel->setVisible(vis);
0618 
0619     vis = index == SYMMETRY || index == SNOWFLAKE || index == TRANSLATE;
0620     customUI->brushCountSpinBox->setVisible(vis);
0621     customUI->brushesLabel->setVisible(vis);
0622     
0623     vis = index == COPYTRANSLATE;
0624     customUI->subbrushLabel->setVisible(vis);
0625     customUI->addSubbrushButton->setVisible(vis);
0626     customUI->addSubbrushButton->setChecked(false);
0627     customUI->removeSubbrushButton->setVisible(vis);
0628 
0629     vis = index == COPYTRANSLATEINTERVALS;
0630     customUI->intervalXLabel->setVisible(vis);
0631     customUI->intervalYLabel->setVisible(vis);
0632     customUI->intervalXSpinBox->setVisible(vis);
0633     customUI->intervalYSpinBox->setVisible(vis);
0634     customUI->intervalAspectButton->setVisible(vis);
0635 
0636     vis = index != COPYTRANSLATEINTERVALS;
0637     customUI->label->setVisible(vis); // the origin label
0638     customUI->moveOriginButton->setVisible(vis);
0639     customUI->resetOriginButton->setVisible(vis);
0640     customUI->axisRotationLabel->setVisible(vis);
0641     customUI->axisRotationAngleSelector->setVisible(vis);
0642 
0643 }
0644 
0645 void KisToolMultihand::slotSetAxesVisible(bool vis)
0646 {
0647     m_showAxes = vis;
0648     updateCanvas();
0649     m_configGroup.writeEntry("showAxes", vis);
0650 }
0651 
0652 
0653 void KisToolMultihand::slotSetMirrorVertically(bool mirror)
0654 {
0655     m_mirrorVertically = mirror;
0656     updateCanvas();
0657     m_configGroup.writeEntry("mirrorVertically", mirror);
0658 }
0659 
0660 void KisToolMultihand::slotSetMirrorHorizontally(bool mirror)
0661 {
0662     m_mirrorHorizontally = mirror;
0663     updateCanvas();
0664     m_configGroup.writeEntry("mirrorHorizontally", mirror);
0665 }
0666 
0667 void KisToolMultihand::slotSetTranslateRadius(int radius)
0668 {
0669     m_translateRadius = radius;
0670     m_configGroup.writeEntry("translateRadius", radius);
0671 }
0672 
0673 void KisToolMultihand::slotAddSubbrushesMode(bool checked)
0674 {
0675     m_addSubbrushesMode = checked;
0676     updateCanvas();
0677 }
0678 
0679 void KisToolMultihand::slotRemoveAllSubbrushes()
0680 {
0681     m_subbrOriginalLocations.clear();
0682     updateCanvas();
0683 }
0684 
0685 void KisToolMultihand::slotSetIntervals()
0686 {
0687     m_intervalX = customUI->intervalXSpinBox->value();
0688     m_configGroup.writeEntry("intervalX", m_intervalX);
0689 
0690     m_intervalY = customUI->intervalYSpinBox->value();
0691     m_configGroup.writeEntry("intervalY", m_intervalY);
0692 
0693     updateCanvas();
0694 }
0695 
0696 void KisToolMultihand::slotSetKeepAspect()
0697 {
0698     m_configGroup.writeEntry("intervalKeepAspect", customUI->intervalAspectButton->keepAspectRatio());
0699 }