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 }