diff --git a/libs/flake/KoSelectedShapesProxy.h b/libs/flake/KoSelectedShapesProxy.h --- a/libs/flake/KoSelectedShapesProxy.h +++ b/libs/flake/KoSelectedShapesProxy.h @@ -44,6 +44,16 @@ */ virtual KoSelection *selection() = 0; + + /** + * @brief The shape wants to edited. This is used when a shape is passed + * between two different tools. This notifies the new tool that it needs + * to enter some extra edit mode. + * + */ + bool isRequestingToBeEdited(); + void setRequestingToBeEdited(bool value); + Q_SIGNALS: // forwards a corresponding signal of KoShapeManager @@ -54,6 +64,9 @@ // forwards a corresponding signal of KoSelection void currentLayerChanged(const KoShapeLayer *layer); + +private: + bool m_isRequestingEditing = false; }; #endif // KOSELECTEDSHAPESPROXY_H diff --git a/libs/flake/KoSelectedShapesProxy.cpp b/libs/flake/KoSelectedShapesProxy.cpp --- a/libs/flake/KoSelectedShapesProxy.cpp +++ b/libs/flake/KoSelectedShapesProxy.cpp @@ -23,3 +23,12 @@ { } +bool KoSelectedShapesProxy::isRequestingToBeEdited() +{ + return m_isRequestingEditing; +} + +void KoSelectedShapesProxy::setRequestingToBeEdited(bool value) +{ + m_isRequestingEditing = value; +} diff --git a/libs/global/KisHandlePainterHelper.h b/libs/global/KisHandlePainterHelper.h --- a/libs/global/KisHandlePainterHelper.h +++ b/libs/global/KisHandlePainterHelper.h @@ -81,6 +81,8 @@ * Draws a handle rect with a custom \p radius at position \p center */ void drawHandleRect(const QPointF ¢er, qreal radius); + void drawHandleRect(const QPointF ¢er, qreal radius, QPoint offset); + void fillHandleRect(const QPointF ¢er, qreal radius, QColor fillColor, QPoint offset); /** * Draws a handle circle with a custom \p radius at position \p center @@ -145,6 +147,11 @@ */ void drawPath(const QPainterPath &path); + /** + * Draw an a given pixmap on the UI + */ + void drawPixmap(const QPixmap &pixmap, QPointF position, int size, QRectF sourceRect); + private: /** diff --git a/libs/global/KisHandlePainterHelper.cpp b/libs/global/KisHandlePainterHelper.cpp --- a/libs/global/KisHandlePainterHelper.cpp +++ b/libs/global/KisHandlePainterHelper.cpp @@ -82,17 +82,31 @@ m_handleStyle = style; } -void KisHandlePainterHelper::drawHandleRect(const QPointF ¢er, qreal radius) { +void KisHandlePainterHelper::drawHandleRect(const QPointF ¢er, qreal radius, QPoint offset = QPoint(0,0)) +{ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius); QPolygonF handlePolygon = m_handleTransform.map(QPolygonF(handleRect)); handlePolygon.translate(m_painterTransform.map(center)); + handlePolygon.translate(offset); + + const QPen originalPen = m_painter->pen(); + + // temporarily set the pen width to 2 to avoid pixel shifting dropping pixels the border + QPen *tempPen = new QPen(m_painter->pen()); + tempPen->setWidth(4); + const QPen customPen = *tempPen; + m_painter->setPen(customPen); + + Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) { PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop); m_painter->drawPolygon(handlePolygon); } + + m_painter->setPen(originalPen); } void KisHandlePainterHelper::drawHandleCircle(const QPointF ¢er, qreal radius) { @@ -278,3 +292,38 @@ m_painter->drawPath(realPath); } } + +void KisHandlePainterHelper::drawPixmap(const QPixmap &pixmap, QPointF position, int size, QRectF sourceRect) +{ + QPointF handlePolygon = m_painterTransform.map(position); + + QPoint offsetPosition(0, 40); + handlePolygon += offsetPosition; + + handlePolygon -= QPointF(size*0.5,size*0.5); + + m_painter->drawPixmap(QRect(handlePolygon.x(), handlePolygon.y(), + size, size), + pixmap, + sourceRect); +} + +void KisHandlePainterHelper::fillHandleRect(const QPointF ¢er, qreal radius, QColor fillColor, QPoint offset = QPoint(0,0)) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter); + + QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius); + QPolygonF handlePolygon = m_handleTransform.map(QPolygonF(handleRect)); + handlePolygon.translate(m_painterTransform.map(center)); + + QPainterPath painterPath; + painterPath.addPolygon(handlePolygon); + + // offset that happens after zoom transform. This means the offset will be the same, no matter the zoom level + // this is good for UI elements that need to be below the bounding box + painterPath.translate(offset); + + const QPainterPath pathToSend = painterPath; + const QBrush brushStyle(fillColor); + m_painter->fillPath(pathToSend, brushStyle); +} diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.h b/plugins/tools/defaulttool/defaulttool/DefaultTool.h --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.h +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.h @@ -26,6 +26,7 @@ #include #include #include +#include "SelectionDecorator.h" #include #include @@ -91,6 +92,9 @@ */ KoFlake::SelectionHandle handleAt(const QPointF &point, bool *innerHandleMeaning = 0); + /// similar to normal bounds handles, but checks to see if user wants to edit text + bool isSelectingTextEditorButton(const QPointF &point); + public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; @@ -175,6 +179,8 @@ QPolygonF m_selectionOutline; QPointF m_lastPoint; + SelectionDecorator *decorator; + // TODO alter these 3 arrays to be static const instead QCursor m_sizeCursors[8]; QCursor m_rotateCursors[8]; diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -144,7 +144,7 @@ KoShapeRubberSelectStrategy::paint(painter, converter); } - void finishInteraction(Qt::KeyboardModifiers modifiers) override + void finishInteraction(Qt::KeyboardModifiers modifiers = 0) override { Q_UNUSED(modifiers); DefaultTool *defaultTool = dynamic_cast(tool()); @@ -641,6 +641,13 @@ if (shearHandle) { statusText = i18n("Click and drag to shear selection."); } + + if (decorator->isOverTextEditorButton()) { + cursor = Qt::PointingHandCursor; + } else { + cursor = Qt::ArrowCursor; + } + } else { statusText = i18n("Click and drag to resize selection."); m_angle = rotationOfHandle(m_lastHandle, false); @@ -700,7 +707,7 @@ { KoSelection *selection = koSelection(); if (selection) { - SelectionDecorator decorator(canvas()->resourceManager()); + this->decorator = new SelectionDecorator(canvas()->resourceManager()); { /** @@ -711,14 +718,14 @@ KisCanvas2 *kisCanvas = static_cast(canvas()); KisNodeSP node = kisCanvas->viewManager()->nodeManager()->activeNode(); const bool isSelectionMask = node && node->inherits("KisSelectionMask"); - decorator.setForceShapeOutlines(isSelectionMask); + decorator->setForceShapeOutlines(isSelectionMask); } - decorator.setSelection(selection); - decorator.setHandleRadius(handleRadius()); - decorator.setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId)); - decorator.setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId)); - decorator.paint(painter, converter); + decorator->setSelection(selection); + decorator->setHandleRadius(handleRadius()); + decorator->setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId)); + decorator->setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId)); + decorator->paint(painter, converter); } KoInteractionTool::paint(painter, converter); @@ -784,6 +791,8 @@ // there used to be guides... :'''( } + isSelectingTextEditorButton(event->point); + updateCursor(); } @@ -810,6 +819,20 @@ { KoInteractionTool::mouseReleaseEvent(event); updateCursor(); + + // test to see if we are selecting button before we decide to check for a selection/de-selection + const bool selectingTextEditorButton = isSelectingTextEditorButton(event->point); + + // this helps tell the next tool that we ned to enter edit mode when it gets activated + canvas()->selectedShapesProxy()->setRequestingToBeEdited(selectingTextEditorButton); + + + if (selectingTextEditorButton) { // activate text tool + KoToolManager::instance()->switchToolRequested(KoToolManager::instance()->preferredToolForSelection(koSelection()->selectedShapes())); + } + + // This makes sure the decorations that are shown are refreshed. especally the "T" icon + canvas()->updateCanvas(QRectF(0,0,canvas()->canvasWidget()->width(), canvas()->canvasWidget()->height())); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) @@ -1510,6 +1533,15 @@ } if (!selectMultiple && !selectNextInStack) { + + // move the selection if we hold and drag with the text editor button + // this also helps with how the click events flow to resolve this createStrategy + const bool selectingTextEditorButton = isSelectingTextEditorButton(event->point); + + if (selectingTextEditorButton) { // ignore the event if we are selecting the text editor button + return new SelectionInteractionStrategy(this, event->point, false); + } + if (insideSelection) { return new ShapeMoveStrategy(this, selection, event->point); } @@ -1704,3 +1736,43 @@ QList shapes = koSelection()->selectedEditableShapesAndDelegates(); emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes)); } + +bool DefaultTool::isSelectingTextEditorButton(const QPointF &mousePosition) +{ + + if (!canvas() || !decorator) { + return false; + } + + // calculate position for textEditorBoxButton + KoSelection *selection = koSelection(); + const KoViewConverter *converter = canvas()->viewConverter(); + + if (!selection || !selection->count() || !converter) { + return false; + } + + QRectF outline = selection->boundingRect(); + + QPointF absoluteTransormPosition( + outline.x() + outline.width()*0.5, + outline.y() + outline.height()); + + + QPointF textEditorAbsPosition = converter->documentToView(absoluteTransormPosition); + textEditorAbsPosition += decoratorIconPositions.uiOffset; + + // check to see if the text decorator is checked (only for text objects) + const QPointF viewPoint = converter->documentToView(mousePosition); + const QPointF handlePoint = textEditorAbsPosition; + const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); + + if (distanceSq < 18 * 18) { // 18 is "handle" area + decorator->setIsOverTextEditorButton(true); + return true; + } + else { + decorator->setIsOverTextEditorButton(false); + return false; + } +} diff --git a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h --- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h +++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h @@ -31,6 +31,10 @@ class KoSelection; class KoCanvasResourceManager; +static const struct DecoratorIconPositions { + QPoint uiOffset = QPoint(0, 40); +} decoratorIconPositions; + /** * The SelectionDecorator is used to paint extra user-interface items on top of a selection. */ @@ -77,6 +81,14 @@ */ void setShowStrokeFillGradientHandles(bool value); + /** + * Text vector objects GUI button for editing text + */ + QPointF textEditorButtonPos(); + void setIsOverTextEditorButton(bool value); + bool isOverTextEditorButton(); + + void setForceShapeOutlines(bool value); private: @@ -90,6 +102,9 @@ bool m_showFillGradientHandles; bool m_showStrokeFillGradientHandles; bool m_forceShapeOutlines; + + QPointF m_textEditorButtonPosition; + bool m_isHoveringOverTextButton; }; #endif diff --git a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp --- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp +++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp @@ -31,8 +31,14 @@ #include #include #include "KoShapeGradientHandles.h" +#include +#include #include "kis_painting_tweaks.h" +#include "kis_coordinates_converter.h" +#include "kis_icon_utils.h" + + #define HANDLE_DISTANCE 10 @@ -152,6 +158,27 @@ if (haveOnlyOneEditableShape) { KoShape *shape = selectedShapes.first(); + // draw a button with "edit text" on it that activates the text editor + KoSvgTextShape *textShape = dynamic_cast(shape); + if (textShape) { + KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, m_selection, converter, m_handleRadius); + + QPolygonF outline = handleArea; + m_textEditorButtonPosition = QPointF(0.5 * (outline.value(2) + outline.value(3))); + + const QPointF finalHandleRect = m_textEditorButtonPosition; + helper.drawHandleRect(finalHandleRect, 15, decoratorIconPositions.uiOffset ); + helper.fillHandleRect(finalHandleRect, 15, Qt::white, decoratorIconPositions.uiOffset); + + // T icon inside box + QSize buttonSize(20,20); + const QPixmap textEditorIcon = KisIconUtils::loadIcon("draw-text").pixmap(buttonSize); + const QRectF iconSourceRect(QPointF(0, 0), textEditorIcon.size()); + helper.drawPixmap(textEditorIcon, m_textEditorButtonPosition, 20, iconSourceRect); // icon, position, size, sourceRect + + } + + if (m_showFillGradientHandles) { paintGradientHandles(shape, KoFlake::Fill, painter, converter); } else if (m_showStrokeFillGradientHandles) { @@ -194,3 +221,24 @@ { m_forceShapeOutlines = value; } + +QPointF SelectionDecorator::textEditorButtonPos() +{ + return m_textEditorButtonPosition; +} + +void SelectionDecorator::setIsOverTextEditorButton(bool value) +{ + if (value) { // null check + m_isHoveringOverTextButton = value; + } else { + m_isHoveringOverTextButton = false; + } + + +} + +bool SelectionDecorator::isOverTextEditorButton() +{ + return m_isHoveringOverTextButton; +} diff --git a/plugins/tools/svgtexttool/SvgTextEditor.h b/plugins/tools/svgtexttool/SvgTextEditor.h --- a/plugins/tools/svgtexttool/SvgTextEditor.h +++ b/plugins/tools/svgtexttool/SvgTextEditor.h @@ -64,6 +64,9 @@ * switch the text editor tab. */ void switchTextEditorTab(bool convertData = true); + + void slotCloseEditor(); + /** * in rich text, check the current format, and toggle the given buttons. */ @@ -127,6 +130,7 @@ Q_SIGNALS: void textUpdated(KoSvgTextShape *shape, const QString &svg, const QString &defs, bool richTextPreferred); + void textEditorClosed(); protected: diff --git a/plugins/tools/svgtexttool/SvgTextEditor.cpp b/plugins/tools/svgtexttool/SvgTextEditor.cpp --- a/plugins/tools/svgtexttool/SvgTextEditor.cpp +++ b/plugins/tools/svgtexttool/SvgTextEditor.cpp @@ -91,7 +91,7 @@ m_charSelectDialog->setButtons(KoDialog::Close); #endif connect(m_textEditorWidget.buttons, SIGNAL(accepted()), this, SLOT(save())); - connect(m_textEditorWidget.buttons, SIGNAL(rejected()), this, SLOT(close())); + connect(m_textEditorWidget.buttons, SIGNAL(rejected()), this, SLOT(slotCloseEditor())); connect(m_textEditorWidget.buttons, SIGNAL(clicked(QAbstractButton*)), this, SLOT(dialogButtonClicked(QAbstractButton*))); KConfigGroup cg(KSharedConfig::openConfig(), "SvgTextTool"); @@ -962,7 +962,7 @@ // File: new, open, save, save as, close KStandardAction::save(this, SLOT(save()), actionCollection()); - KStandardAction::close(this, SLOT(close()), actionCollection()); + KStandardAction::close(this, SLOT(slotCloseEditor()), actionCollection()); // Edit KStandardAction::undo(this, SLOT(undo()), actionCollection()); @@ -1099,3 +1099,9 @@ action->setEnabled(enable); } } + +void SvgTextEditor::slotCloseEditor() +{ + close(); + emit textEditorClosed(); +} diff --git a/plugins/tools/svgtexttool/SvgTextTool.h b/plugins/tools/svgtexttool/SvgTextTool.h --- a/plugins/tools/svgtexttool/SvgTextTool.h +++ b/plugins/tools/svgtexttool/SvgTextTool.h @@ -65,6 +65,7 @@ private Q_SLOTS: void showEditor(); + void slotTextEditorClosed(); void textUpdated(KoSvgTextShape *shape, const QString &svg, const QString &defs, bool richTextUpdated); /** diff --git a/plugins/tools/svgtexttool/SvgTextTool.cpp b/plugins/tools/svgtexttool/SvgTextTool.cpp --- a/plugins/tools/svgtexttool/SvgTextTool.cpp +++ b/plugins/tools/svgtexttool/SvgTextTool.cpp @@ -51,6 +51,7 @@ #include #include #include +#include "KoToolManager.h" #include "SvgTextEditor.h" #include "KisHandlePainterHelper.h" @@ -79,6 +80,11 @@ KoSvgTextShape *textShape = dynamic_cast(*shapes.constBegin()); if (!textShape) { koSelection()->deselectAll(); + } else { + // if we are a text shape...and the proxy tells us we want to edit the shape. open the text editor + if (canvas()->selectedShapesProxy()->isRequestingToBeEdited()) { + showEditor(); + } } } else if (shapes.size() > 1) { KoSvgTextShape *foundTextShape = 0; @@ -217,6 +223,7 @@ m_editor = new SvgTextEditor(); m_editor->setWindowModality(Qt::ApplicationModal); connect(m_editor, SIGNAL(textUpdated(KoSvgTextShape*,QString,QString,bool)), SLOT(textUpdated(KoSvgTextShape*,QString,QString,bool))); + connect(m_editor, SIGNAL(textEditorClosed()), SLOT(slotTextEditorClosed())); } m_editor->setShape(shape); @@ -230,6 +237,13 @@ canvas()->addCommand(cmd); } +void SvgTextTool::slotTextEditorClosed() +{ + // change tools to the shape selection tool when we close the text editor to allow moving and further editing of the object. + // most of the time when we edit text, the shape selection tool is where we left off anyway + KoToolManager::instance()->switchToolRequested("InteractionTool"); +} + QString SvgTextTool::generateDefs() { QString font = m_defFont->currentFont().family();