diff --git a/libs/ui/tool/kis_tool.h b/libs/ui/tool/kis_tool.h index df338dbdd3..cf9cb45b28 100644 --- a/libs/ui/tool/kis_tool.h +++ b/libs/ui/tool/kis_tool.h @@ -1,318 +1,319 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_H_ #define KIS_TOOL_H_ #include #include #include #include #include #include #include #ifdef __GNUC__ #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come to" << __func__ << "while being mode" << _mode << "!" #else #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come while being mode" << _mode << "!" #endif #define CHECK_MODE_SANITY_OR_RETURN(_mode) if (mode() != _mode) { WARN_WRONG_MODE(mode()); return; } class KoCanvasBase; class KoPattern; class KoAbstractGradient; class KisFilterConfiguration; class QPainter; class QPainterPath; class QPolygonF; /// Definitions of the toolgroups of Krita static const QString TOOL_TYPE_SHAPE = "0 Krita/Shape"; // Geometric shapes like ellipses and lines static const QString TOOL_TYPE_TRANSFORM = "2 Krita/Transform"; // Tools that transform the layer; static const QString TOOL_TYPE_FILL = "3 Krita/Fill"; // Tools that fill parts of the canvas static const QString TOOL_TYPE_VIEW = "4 Krita/View"; // Tools that affect the canvas: pan, zoom, etc. static const QString TOOL_TYPE_SELECTION = "5 Krita/Select"; // Tools that select pixels //activation id for Krita tools, Krita tools are always active and handle locked and invisible layers by themself static const QString KRITA_TOOL_ACTIVATION_ID = "flake/always"; class KRITAUI_EXPORT KisTool : public KoToolBase { Q_OBJECT Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged) public: enum { FLAG_USES_CUSTOM_PRESET=0x01, FLAG_USES_CUSTOM_COMPOSITEOP=0x02, FLAG_USES_CUSTOM_SIZE=0x04 }; KisTool(KoCanvasBase * canvas, const QCursor & cursor); ~KisTool() override; virtual int flags() const { return 0; } void deleteSelection() override; // KoToolBase Implementation. public: /** * Called by KisToolProxy when the primary action of the tool is * going to be started now, that is when all the modifiers are * pressed and the only thing left is just to press the mouse * button. On coming of this callback the tool is supposed to * prepare the cursor and/or the outline to show the user shat is * going to happen next */ virtual void activatePrimaryAction(); /** * Called by KisToolProxy when the primary is no longer possible * to be started now, e.g. when its modifiers and released. The * tool is supposed revert all the preparetions it has doen in * activatePrimaryAction(). */ virtual void deactivatePrimaryAction(); /** * Called by KisToolProxy when a primary action for the tool is * started. The \p event stores the original event that * started the stroke. The \p event is _accepted_ by default. If * the tool decides to ignore this particular action (e.g. when * the node is not editable), it should call event->ignore(). Then * no further continuePrimaryAction() or endPrimaryAction() will * be called until the next user action. */ virtual void beginPrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is in progress * of pointer movement. If the tool has ignored the event in * beginPrimaryAction(), this method will not be called. */ virtual void continuePrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is being * finished, that is while mouseRelease or tabletRelease event. * If the tool has ignored the event in beginPrimaryAction(), this * method will not be called. */ virtual void endPrimaryAction(KoPointerEvent *event); /** * The same as beginPrimaryAction(), but called when the stroke is * started by a double-click * * \see beginPrimaryAction() */ virtual void beginPrimaryDoubleClickAction(KoPointerEvent *event); /** * Returns true if the tool can handle (and wants to handle) a * very tight flow of input events from the tablet */ virtual bool primaryActionSupportsHiResEvents() const; enum ToolAction { Primary, AlternateChangeSize, AlternatePickFgNode, AlternatePickBgNode, AlternatePickFgImage, AlternatePickBgImage, AlternateSecondary, AlternateThird, AlternateFourth, AlternateFifth, Alternate_NONE = 10000 }; // Technically users are allowed to configure this, but nobody ever would do that. // So these can basically be thought of as aliases to ctrl+click, etc. enum AlternateAction { ChangeSize = AlternateChangeSize, // Default: Shift+Left click PickFgNode = AlternatePickFgNode, // Default: Ctrl+Alt+Left click PickBgNode = AlternatePickBgNode, // Default: Ctrl+Alt+Right click PickFgImage = AlternatePickFgImage, // Default: Ctrl+Left click PickBgImage = AlternatePickBgImage, // Default: Ctrl+Right click Secondary = AlternateSecondary, Third = AlternateThird, Fourth = AlternateFourth, Fifth = AlternateFifth, NONE = 10000 }; static AlternateAction actionToAlternateAction(ToolAction action); virtual void activateAlternateAction(AlternateAction action); virtual void deactivateAlternateAction(AlternateAction action); virtual void beginAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void continueAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void endAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action); void mousePressEvent(KoPointerEvent *event) override; void mouseDoubleClickEvent(KoPointerEvent *event) override; void mouseTripleClickEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; bool isActive() const; public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; void canvasResourceChanged(int key, const QVariant & res) override; // Implement this slot in case there are any widgets or properties which need // to be updated after certain operations, to reflect the inner state correctly. // At the moment this is used for smoothing options in the freehand brush, but // this will likely be expanded. virtual void updateSettingsViews(); Q_SIGNALS: void isActiveChanged(bool isActivated); protected: // conversion methods are also needed by the paint information builder friend class KisToolPaintingInformationBuilder; /// Convert from native (postscript points) to image pixel /// coordinates. QPointF convertToPixelCoord(KoPointerEvent *e); QPointF convertToPixelCoord(const QPointF& pt); QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset = QPointF(), bool useModifiers = true); QPointF convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset = QPointF()); protected: QPointF widgetCenterInWidgetPixels(); QPointF convertDocumentToWidget(const QPointF& pt); /// Convert from native (postscript points) to integer image pixel /// coordinates. This rounds down (not truncate) the pixel coordinates and /// should be used in preference to QPointF::toPoint(), which rounds, /// to ensure the cursor acts on the pixel it is visually over. QPoint convertToImagePixelCoordFloored(KoPointerEvent *e); QRectF convertToPt(const QRectF &rect); qreal convertToPt(qreal value); QPointF viewToPixel(const QPointF &viewCoord) const; /// Convert an integer pixel coordinate into a view coordinate. /// The view coordinate is at the centre of the pixel. QPointF pixelToView(const QPoint &pixelCoord) const; /// Convert a floating point pixel coordinate into a view coordinate. QPointF pixelToView(const QPointF &pixelCoord) const; /// Convert a pixel rectangle into a view rectangle. QRectF pixelToView(const QRectF &pixelRect) const; /// Convert a pixel path into a view path QPainterPath pixelToView(const QPainterPath &pixelPath) const; /// Convert a pixel polygon into a view path QPolygonF pixelToView(const QPolygonF &pixelPolygon) const; /// Update the canvas for the given rectangle in image pixel coordinates. void updateCanvasPixelRect(const QRectF &pixelRect); /// Update the canvas for the given rectangle in view coordinates. void updateCanvasViewRect(const QRectF &viewRect); QWidget* createOptionWidget() override; /** * To determine whether this tool will change its behavior when * modifier keys are pressed */ virtual bool listeningToModifiers(); /** * Request that this tool no longer listen to modifier keys * (Responding to the request is optional) */ virtual void listenToModifiers(bool listen); protected: KisImageWSP image() const; QCursor cursor() const; /// Call this to set the document modified void notifyModified() const; KisImageWSP currentImage(); KoPattern* currentPattern(); KoAbstractGradient *currentGradient(); KisNodeSP currentNode() const; KisNodeList selectedNodes() const; KoColor currentFgColor(); KoColor currentBgColor(); KisPaintOpPresetSP currentPaintOpPreset(); KisFilterConfigurationSP currentGenerator(); /// paint the path which is in view coordinates, default paint mode is XOR_MODE, BW_MODE is also possible /// never apply transformations to the painter, they would be useless, if drawing in OpenGL mode. The coordinates in the path should be in view coordinates. void paintToolOutline(QPainter * painter, const QPainterPath &path); /// Checks checks if the current node is editable bool nodeEditable(); /// Checks checks if the selection is editable, only applies to local selection as global selection is always editable bool selectionEditable(); /// Override the cursor appropriately if current node is not editable bool overrideCursorIfNotEditable(); bool blockUntilOperationsFinished(); void blockUntilOperationsFinishedForced(); protected: - enum ToolMode { + enum ToolMode: int { HOVER_MODE, PAINT_MODE, SECONDARY_PAINT_MODE, MIRROR_AXIS_SETUP_MODE, GESTURE_MODE, PAN_MODE, - OTHER // not used now + OTHER, // tool-specific modes, like multibrush's symmetry axis setup + OTHER_1 }; virtual void setMode(ToolMode mode); virtual ToolMode mode() const; void setCursor(const QCursor &cursor); protected Q_SLOTS: /** * Called whenever the configuration settings change. */ virtual void resetCursorStyle(); private: struct Private; Private* const d; }; #endif // KIS_TOOL_H_ diff --git a/plugins/tools/basictools/kis_tool_multihand.cpp b/plugins/tools/basictools/kis_tool_multihand.cpp index 79e4928469..c94bf9c84d 100644 --- a/plugins/tools/basictools/kis_tool_multihand.cpp +++ b/plugins/tools/basictools/kis_tool_multihand.cpp @@ -1,421 +1,525 @@ /* * Copyright (c) 2011 Lukáš Tvrdý * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - #include "kis_tool_multihand.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_tool_multihand_helper.h" static const int MAXIMUM_BRUSHES = 50; #include #ifdef Q_OS_WIN // quoting DRAND48(3) man-page: // These functions are declared obsolete by SVID 3, // which states that rand(3) should be used instead. #define drand48() (static_cast(qrand()) / static_cast(RAND_MAX)) #endif KisToolMultihand::KisToolMultihand(KoCanvasBase *canvas) : KisToolBrush(canvas), m_transformMode(SYMMETRY), m_angle(0), m_handsCount(6), m_mirrorVertically(false), m_mirrorHorizontally(false), m_showAxes(false), m_translateRadius(100), - m_setupAxesFlag(false) + m_setupAxesFlag(false), + m_addSubbrushesMode(false) , customUI(0) { m_helper = new KisToolMultihandHelper(paintingInformationBuilder(), kundo2_i18n("Multibrush Stroke")); resetHelper(m_helper); if (image()) { m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); } } KisToolMultihand::~KisToolMultihand() { } - - void KisToolMultihand::beginPrimaryAction(KoPointerEvent *event) { if(m_setupAxesFlag) { setMode(KisTool::OTHER); m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } + else if (m_addSubbrushesMode){ + QPointF newPoint = convertToPixelCoord(event->point); + m_subbrOriginalLocations << newPoint; + requestUpdateOutline(event->point, 0); + updateCanvas(); + } else { initTransformations(); KisToolFreehand::beginPrimaryAction(event); } } void KisToolMultihand::continuePrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else { + requestUpdateOutline(event->point, 0); KisToolFreehand::continuePrimaryAction(event); } } void KisToolMultihand::endPrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { setMode(KisTool::HOVER_MODE); requestUpdateOutline(event->point, 0); finishAxesSetup(); } else { KisToolFreehand::endPrimaryAction(event); } } +void KisToolMultihand::beginAlternateAction(KoPointerEvent* event, AlternateAction action) +{ + if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { + KisToolBrush::beginAlternateAction(event, action); + return; + } + setMode(KisTool::OTHER_1); + m_axesPoint = convertToPixelCoord(event->point); + requestUpdateOutline(event->point, 0); + updateCanvas(); +} + +void KisToolMultihand::continueAlternateAction(KoPointerEvent* event, AlternateAction action) +{ + if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { + KisToolBrush::continueAlternateAction(event, action); + return; + } + if (mode() == KisTool::OTHER_1) { + m_axesPoint = convertToPixelCoord(event->point); + requestUpdateOutline(event->point, 0); + updateCanvas(); + } +} + +void KisToolMultihand::endAlternateAction(KoPointerEvent* event, AlternateAction action) +{ + if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { + KisToolBrush::continueAlternateAction(event, action); + return; + } + if (mode() == KisTool::OTHER_1) { + setMode(KisTool::HOVER_MODE); + } +} + +void KisToolMultihand::mouseMoveEvent(KoPointerEvent* event) +{ + if (mode() == HOVER_MODE) { + m_lastToolPos=convertToPixelCoord(event->point); + } + KisToolBrush::mouseMoveEvent(event); +} + void KisToolMultihand::paint(QPainter& gc, const KoViewConverter &converter) { + QPainterPath path; + if (m_transformMode == COPYTRANSLATE) { + for (QPointF dPos : m_subbrOriginalLocations) { + if (m_addSubbrushesMode) { + path.addEllipse(dPos, 10, 10); // Show subbrush reference locations + path.moveTo(dPos.x() - 15, dPos.y()); + path.lineTo(dPos.x() + 15, dPos.y()); + path.moveTo(dPos.x(), dPos.y() - 15); + path.lineTo(dPos.x(), dPos.y() + 15); + } + else { + // Show where subbrush strokes are predicted to land + path += m_currentOutline.translated(dPos - m_axesPoint); + } + } + if (m_addSubbrushesMode) { + // add a cross shape to the main tool outline as well + + path.moveTo(m_lastToolPos.x() - 15, m_lastToolPos.y()); + path.lineTo(m_lastToolPos.x() + 15, m_lastToolPos.y()); + path.moveTo(m_lastToolPos.x(), m_lastToolPos.y() - 15); + path.lineTo(m_lastToolPos.x(), m_lastToolPos.y() + 15); + } + } if(m_setupAxesFlag) { int diagonal = (currentImage()->height() + currentImage()->width()); - QPainterPath path; path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle), m_axesPoint.y()-diagonal*sin(m_angle)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle), m_axesPoint.y()+diagonal*sin(m_angle)); path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()-diagonal*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()+diagonal*sin(m_angle+M_PI_2)); - paintToolOutline(&gc, pixelToView(path)); } else { KisToolFreehand::paint(gc, converter); - if(m_showAxes){ + // Force paint axeslines of "origin" point when in COPYTRANSLATE addSubbrushes mode. + if(m_showAxes || (m_transformMode == COPYTRANSLATE && m_addSubbrushesMode)){ int diagonal = (currentImage()->height() + currentImage()->width()); - QPainterPath path; path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle), m_axesPoint.y()-diagonal*sin(m_angle)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle), m_axesPoint.y()+diagonal*sin(m_angle)); path.moveTo(m_axesPoint.x()-diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()-diagonal*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+diagonal*cos(m_angle+M_PI_2), m_axesPoint.y()+diagonal*sin(m_angle+M_PI_2)); - paintToolOutline(&gc, pixelToView(path)); } } + paintToolOutline(&gc, pixelToView(path)); } void KisToolMultihand::initTransformations() { QVector transformations; QTransform m; if(m_transformMode == SYMMETRY) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount; for(int i = 0; i < m_handsCount; i++) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep; } } else if(m_transformMode == MIRROR) { transformations << m; if (m_mirrorHorizontally) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically && m_mirrorHorizontally){ m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } else if(m_transformMode == SNOWFLAKE) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount/4; for(int i = 0; i < m_handsCount*4; i++) { if ((i%2)==1) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.scale(-1,1); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } else { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } } } - else /* if(m_transformationNode == TRANSLATE) */ { + else if(m_transformMode == TRANSLATE) { /** * TODO: currently, the seed is the same for all the * strokes */ for (int i = 0; i < m_handsCount; i++){ qreal angle = drand48() * M_PI * 2; qreal length = drand48(); // convert the Polar coordinates to Cartesian coordinates qreal nx = (m_translateRadius * cos(angle) * length); qreal ny = (m_translateRadius * sin(angle) * length); m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.translate(nx,ny); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } + } else if (m_transformMode == COPYTRANSLATE) { + transformations << m; + for (QPointF dPos : m_subbrOriginalLocations) { + QPointF resPos = dPos-m_axesPoint; // Calculate the difference between subbrush reference position and "origin" reference + m.translate(resPos.x(), resPos.y()); + transformations << m; + m.reset(); + } } m_helper->setupTransformations(transformations); } QWidget* KisToolMultihand::createOptionWidget() { QWidget *widget = KisToolBrush::createOptionWidget(); customUI = new KisToolMultiHandConfigWidget(); // brush smoothing option. - customUI->layout()->addWidget(widget); + //customUI->layout()->addWidget(widget); customUI->smoothingOptionsLayout->addWidget(widget); // setup common parameters that all of the modes will see connect(customUI->showAxesCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetAxesVisible(bool))); customUI->showAxesCheckbox->setChecked((bool)m_configGroup.readEntry("showAxes", false)); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(resetAxes())); customUI->moveOriginButton->setCheckable(true); connect(customUI->moveOriginButton, SIGNAL(clicked(bool)),this, SLOT(activateAxesPointModeSetup())); connect(customUI->resetOriginButton, SIGNAL(released()), this, SLOT(resetAxes())); customUI->multihandTypeCombobox->addItem(i18n("Symmetry"),int(SYMMETRY)); // axis mode customUI->multihandTypeCombobox->addItem(i18n("Mirror"),int(MIRROR)); customUI->multihandTypeCombobox->addItem(i18n("Translate"),int(TRANSLATE)); customUI->multihandTypeCombobox->addItem(i18n("Snowflake"),int(SNOWFLAKE)); + customUI->multihandTypeCombobox->addItem(i18n("Copy Translate"),int(COPYTRANSLATE)); connect(customUI->multihandTypeCombobox,SIGNAL(currentIndexChanged(int)),this, SLOT(slotSetTransformMode(int))); customUI->multihandTypeCombobox->setCurrentIndex(m_configGroup.readEntry("transformMode", 0)); slotSetTransformMode(customUI->multihandTypeCombobox->currentIndex()); customUI->axisRotationSpinbox->setSuffix(QChar(Qt::Key_degree)); // origin rotation customUI->axisRotationSpinbox->setSingleStep(0.5); customUI->axisRotationSpinbox->setRange(0.0, 90.0, 1); customUI->axisRotationSpinbox->setValue(m_configGroup.readEntry("axesAngle", 0.0)); connect( customUI->axisRotationSpinbox, SIGNAL(valueChanged(qreal)),this, SLOT(slotSetAxesAngle(qreal))); - - // symmetry mode options customUI->brushCountSpinBox->setRange(1, MAXIMUM_BRUSHES); connect(customUI->brushCountSpinBox, SIGNAL(valueChanged(int)),this, SLOT(slotSetHandsCount(int))); customUI->brushCountSpinBox->setValue(m_configGroup.readEntry("handsCount", 4)); // mirror mode specific options connect(customUI->horizontalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorHorizontally(bool))); customUI->horizontalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorHorizontally", false)); connect(customUI->verticalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorVertically(bool))); customUI->verticalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorVertically", false)); // translate mode options customUI->translationRadiusSpinbox->setRange(0, 200); customUI->translationRadiusSpinbox->setSuffix(i18n(" px")); customUI->translationRadiusSpinbox->setValue(m_configGroup.readEntry("translateRadius", 0)); connect(customUI->translationRadiusSpinbox,SIGNAL(valueChanged(int)),this,SLOT(slotSetTranslateRadius(int))); + // Copy translate mode options and actions + connect(customUI->addSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotAddSubbrushesMode); + connect(customUI->removeSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotRemoveAllSubbrushes); // snowflake re-uses the existing options, so there is no special parameters for that... return static_cast(customUI); // keeping it in the native class until the end allows us to access the UI components } void KisToolMultihand::activateAxesPointModeSetup() { if (customUI->moveOriginButton->isChecked()){ m_setupAxesFlag = true; useCursor(KisCursor::crossCursor()); updateCanvas(); } else { finishAxesSetup(); } } void KisToolMultihand::resetAxes() { m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); finishAxesSetup(); } void KisToolMultihand::finishAxesSetup() { m_setupAxesFlag = false; customUI->moveOriginButton->setChecked(false); resetCursorStyle(); updateCanvas(); } void KisToolMultihand::updateCanvas() { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); Q_ASSERT(kisCanvas); kisCanvas->updateCanvas(); } void KisToolMultihand::slotSetHandsCount(int count) { m_handsCount = count; m_configGroup.writeEntry("handsCount", count); } void KisToolMultihand::slotSetAxesAngle(qreal angle) { //negative so axes rotates counter clockwise m_angle = -angle*M_PI/180; updateCanvas(); m_configGroup.writeEntry("axesAngle", angle); } void KisToolMultihand::slotSetTransformMode(int index) { m_transformMode = enumTransforModes(customUI->multihandTypeCombobox->itemData(index).toInt()); m_configGroup.writeEntry("transformMode", index); // hide all of the UI elements by default customUI->horizontalCheckbox->setVisible(false); customUI->verticalCheckbox->setVisible(false); customUI->translationRadiusSpinbox->setVisible(false); customUI->radiusLabel->setVisible(false); customUI->brushCountSpinBox->setVisible(false); customUI->brushesLabel->setVisible(false); + customUI->subbrushLabel->setVisible(false); + customUI->addSubbrushButton->setVisible(false); + customUI->removeSubbrushButton->setVisible(false); // turn on what we need - if (index == int(MIRROR)) { + if (index == MIRROR) { customUI->horizontalCheckbox->setVisible(true); customUI->verticalCheckbox->setVisible(true); } - if (index == int(TRANSLATE)) { - customUI->translationRadiusSpinbox->setVisible(true); - customUI->radiusLabel->setVisible(true); + else if (index == TRANSLATE) { + customUI->translationRadiusSpinbox->setVisible(true); + customUI->radiusLabel->setVisible(true); + customUI->brushCountSpinBox->setVisible(true); + customUI->brushesLabel->setVisible(true); } - if (index == int(SYMMETRY) || index == int(SNOWFLAKE) || index == int(TRANSLATE) ) { + else if (index == SYMMETRY || index == SNOWFLAKE || index == TRANSLATE ) { customUI->brushCountSpinBox->setVisible(true); customUI->brushesLabel->setVisible(true); } + + else if (index == COPYTRANSLATE) { + customUI->subbrushLabel->setVisible(true); + customUI->addSubbrushButton->setVisible(true); + customUI->removeSubbrushButton->setVisible(true); + } } void KisToolMultihand::slotSetAxesVisible(bool vis) { m_showAxes = vis; updateCanvas(); m_configGroup.writeEntry("showAxes", vis); } void KisToolMultihand::slotSetMirrorVertically(bool mirror) { m_mirrorVertically = mirror; m_configGroup.writeEntry("mirrorVertically", mirror); } void KisToolMultihand::slotSetMirrorHorizontally(bool mirror) { m_mirrorHorizontally = mirror; m_configGroup.writeEntry("mirrorHorizontally", mirror); } void KisToolMultihand::slotSetTranslateRadius(int radius) { m_translateRadius = radius; m_configGroup.writeEntry("translateRadius", radius); } +void KisToolMultihand::slotAddSubbrushesMode(bool checked) +{ + m_addSubbrushesMode = checked; + updateCanvas(); +} + +void KisToolMultihand::slotRemoveAllSubbrushes() +{ + m_subbrOriginalLocations.clear(); + updateCanvas(); +} + diff --git a/plugins/tools/basictools/kis_tool_multihand.h b/plugins/tools/basictools/kis_tool_multihand.h index 2f1f640f6e..a9d1cf7ca1 100644 --- a/plugins/tools/basictools/kis_tool_multihand.h +++ b/plugins/tools/basictools/kis_tool_multihand.h @@ -1,120 +1,121 @@ /* * Copyright (c) 2011 Lukáš Tvrdý * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_MULTIHAND_H #define __KIS_TOOL_MULTIHAND_H #include "kis_tool_brush.h" #include #include "kis_tool_multihand_config.h" class QPushButton; class QCheckBox; class QComboBox; class QStackedWidget; class KisSliderSpinBox; class KisToolMultihandHelper; class KisToolMultihand : public KisToolBrush { Q_OBJECT public: KisToolMultihand(KoCanvasBase *canvas); ~KisToolMultihand() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; + void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override; + void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override; + void endAlternateAction(KoPointerEvent *event, AlternateAction action) override; + + void mouseMoveEvent(KoPointerEvent* event) override; + protected: void paint(QPainter& gc, const KoViewConverter &converter) override; - + QWidget* createOptionWidget() override; private: void initTransformations(); void finishAxesSetup(); void updateCanvas(); private Q_SLOTS: void activateAxesPointModeSetup(); void resetAxes(); void slotSetHandsCount(int count); void slotSetAxesAngle(qreal angle); void slotSetTransformMode(int qcomboboxIndex); void slotSetAxesVisible(bool vis); void slotSetMirrorVertically(bool mirror); void slotSetMirrorHorizontally(bool mirror); void slotSetTranslateRadius(int radius); + void slotAddSubbrushesMode(bool checked); + void slotRemoveAllSubbrushes(); private: KisToolMultihandHelper *m_helper; - enum enumTransforModes { SYMMETRY, MIRROR, TRANSLATE, SNOWFLAKE }; + enum enumTransforModes:int { SYMMETRY=0, MIRROR, TRANSLATE, SNOWFLAKE, COPYTRANSLATE }; enumTransforModes m_transformMode; QPointF m_axesPoint; qreal m_angle; int m_handsCount; bool m_mirrorVertically; bool m_mirrorHorizontally; bool m_showAxes; int m_translateRadius; bool m_setupAxesFlag; - QComboBox * m_transformModesComboBox; - KisSliderSpinBox *m_handsCountSlider; - KisDoubleSliderSpinBox *m_axesAngleSlider; - QCheckBox *m_axesChCkBox; - QStackedWidget *m_modeCustomOption; - QCheckBox *m_mirrorVerticallyChCkBox; - QCheckBox *m_mirrorHorizontallyChCkBox; - KisSliderSpinBox *m_translateRadiusSlider; - QPushButton *m_axesPointBtn; - + bool m_addSubbrushesMode; + QPointF m_lastToolPos; + QVector m_subbrOriginalLocations; KisToolMultiHandConfigWidget* customUI; }; class KisToolMultiBrushFactory : public KisToolPaintFactoryBase { public: KisToolMultiBrushFactory() : KisToolPaintFactoryBase("KritaShape/KisToolMultiBrush") { setToolTip(i18n("Multibrush Tool")); // Temporarily setSection(TOOL_TYPE_SHAPE); setIconName(koIconNameCStr("krita_tool_multihand")); setShortcut(QKeySequence(Qt::Key_Q)); setPriority(11); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolMultiBrushFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolMultihand(canvas); } }; #endif /* __KIS_TOOL_MULTIHAND_H */ diff --git a/plugins/tools/basictools/kis_tool_multihand_config.h b/plugins/tools/basictools/kis_tool_multihand_config.h index e67e4cc293..978bc0d657 100644 --- a/plugins/tools/basictools/kis_tool_multihand_config.h +++ b/plugins/tools/basictools/kis_tool_multihand_config.h @@ -1,44 +1,43 @@ /* Copyright (C) 2016 Scott Petrovic This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #ifndef KISTOOLMULTIHANDCONFIG_H #define KISTOOLMULTIHANDCONFIG_H #include "ui_wdgmultihandtool.h" class KisToolMultiHandConfigWidget : public QWidget, public Ui::WdgMultiHandTool { Q_OBJECT public: KisToolMultiHandConfigWidget(QWidget *parent=0); ~KisToolMultiHandConfigWidget() override; //Q_SIGNALS: //public Q_SLOTS: //void cropTypeSelectableChanged(); //private: //KisToolCrop* m_cropTool; }; #endif // KISTOOLMULTIHANDCONFIG_H diff --git a/plugins/tools/basictools/wdgmultihandtool.ui b/plugins/tools/basictools/wdgmultihandtool.ui index 8e4e5622a4..3e3e11704e 100644 --- a/plugins/tools/basictools/wdgmultihandtool.ui +++ b/plugins/tools/basictools/wdgmultihandtool.ui @@ -1,264 +1,295 @@ WdgMultiHandTool 0 0 200 - 282 + 290 200 0 0 10 QFrame::Plain Qt::Horizontal 4 7 - - + + 0 0 - Type: + Brushes: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - true - - - - 0 - 0 - - - - Move - - - false - - - - - - - true - - - - 0 - 0 - - - - Reset - - - false - - - - + + - - + + + + Subbrushes: + + - - + + 0 0 - Rotation: + Radius: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + 0 0 - - - 0 - 15 - + + Horizontal - - - - + 0 0 Show Origin - - + + 0 0 - Brushes: + Rotation: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + 0 0 + + + 0 + 15 + + + + + + - Horizontal + Vertical - - + + 0 0 - - Qt::LeftToRight - - Origin: + Type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - Vertical - - + + + + + + true + + + + 0 + 0 + + + + Move + + + false + + + + + + + true + + + + 0 + 0 + + + + Reset + + + false + + + + - - + + + + + 0 0 + + Qt::LeftToRight + - Radius: + Origin: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + + + Add + + + true + + + false + + + + + + + Remove All + + + + + + + Qt::Vertical 20 40 KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
- + KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1