Commit dea19f7c authored by Dmitry Kazakov's avatar Dmitry Kazakov

Finished basic implementation of the Geometry panel

It basically works, uses a shared anchor point selection widget,
handles aspect ratio locking and so on.

Still TODO:
1) Global coordinates
2) Anchor lock
parent af5d139b
......@@ -126,6 +126,7 @@ set(kritaflake_SRCS
commands/KoShapeDistributeCommand.cpp
commands/KoShapeLockCommand.cpp
commands/KoShapeMoveCommand.cpp
commands/KoShapeResizeCommand.cpp
commands/KoShapeShearCommand.cpp
commands/KoShapeSizeCommand.cpp
commands/KoShapeStrokeCommand.cpp
......
......@@ -115,3 +115,33 @@ void KoFlake::resizeShape(KoShape *shape, qreal scaleX, qreal scaleY,
QPointF diff = parentalStillPointBefore - parentalStillPointAfter;
shape->setTransformation(shape->transformation() * QTransform::fromTranslate(diff.x(), diff.y()));
}
QPointF KoFlake::anchorToPoint(AnchorPosition anchor, const QRectF rect, bool *valid)
{
static QVector<QPointF> anchorTable;
if (anchorTable.isEmpty()) {
anchorTable << QPointF(0.0,0.0);
anchorTable << QPointF(0.5,0.0);
anchorTable << QPointF(1.0,0.0);
anchorTable << QPointF(0.0,0.5);
anchorTable << QPointF(0.5,0.5);
anchorTable << QPointF(1.0,0.5);
anchorTable << QPointF(0.0,1.0);
anchorTable << QPointF(0.5,1.0);
anchorTable << QPointF(1.0,1.0);
}
if (anchor == NoAnchor) {
if (valid) {
*valid = false;
}
return rect.topLeft();
} else if (valid) {
*valid = true;
}
return KisAlgebra2D::relativeToAbsolute(anchorTable[int(anchor)], rect);
}
......@@ -24,6 +24,7 @@
#include "kritaflake_export.h"
class QGradient;
class QRectF;
class QPointF;
class QSizeF;
......@@ -84,6 +85,26 @@ namespace KoFlake
Foreground ///< the foreground / stroke style is active
};
enum AnchorPosition {
TopLeft,
Top,
TopRight,
Left,
Center,
Right,
BottomLeft,
Bottom,
BottomRight,
NoAnchor,
NumAnchorPositions
};
KRITAFLAKE_EXPORT QPointF anchorToPoint(AnchorPosition anchor, const QRectF rect, bool *valid = 0);
enum CanvasResource {
HotPosition = 1410100299
};
/// clones the given gradient
KRITAFLAKE_EXPORT QGradient *cloneGradient(const QGradient *gradient);
......
......@@ -700,6 +700,21 @@ QPointF KoShape::absolutePosition(KoFlake::Position anchor) const
return absoluteTransformation(0).map(point);
}
QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const
{
const QRectF rc = outlineRect();
QPointF point = rc.topLeft();
bool valid = false;
QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid);
if (valid) {
point = anchoredPoint;
}
return absoluteTransformation(0).map(point);
}
void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::Position anchor)
{
Q_D(KoShape);
......@@ -712,6 +727,18 @@ void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::Position
d->shapeChanged(PositionChanged);
}
void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor)
{
Q_D(KoShape);
QPointF currentAbsPosition = absolutePosition(anchor);
QPointF translate = newPosition - currentAbsPosition;
QTransform translateMatrix;
translateMatrix.translate(translate.x(), translate.y());
applyAbsoluteTransformation(translateMatrix);
notifyChanged();
d->shapeChanged(PositionChanged);
}
void KoShape::copySettings(const KoShape *shape)
{
Q_D(KoShape);
......@@ -1139,6 +1166,9 @@ void KoShape::setKeepAspectRatio(bool keepAspect)
{
Q_D(KoShape);
d->keepAspect = keepAspect;
d->shapeChanged(KeepAspectRatioChange);
notifyChanged();
}
bool KoShape::keepAspectRatio() const
......
......@@ -116,6 +116,7 @@ public:
ShearChanged, ///< used after a shear()
SizeChanged, ///< used after a setSize()
GenericMatrixChange, ///< used after the matrix was changed without knowing which property explicitly changed
KeepAspectRatioChange, ///< used after setKeepAspectRatio()
ParentChanged, ///< used after a setParent()
CollisionDetected, ///< used when another shape moved in our boundingrect
Deleted, ///< the shape was deleted
......@@ -802,6 +803,8 @@ public:
*/
QPointF absolutePosition(KoFlake::Position anchor = KoFlake::CenteredPosition) const;
QPointF absolutePosition(KoFlake::AnchorPosition anchor) const;
/**
* Move this shape to an absolute position where the end location will be the same
* regardless of the shape's rotation/skew/scaling and regardless of this shape having
......@@ -818,6 +821,8 @@ public:
*/
void setAbsolutePosition(const QPointF &newPosition, KoFlake::Position anchor = KoFlake::CenteredPosition);
void setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor);
/**
* Set a data object on the shape to be used by an application.
* This is specifically useful when a shape is created in a plugin and that data from that
......
......@@ -20,6 +20,7 @@
#ifndef KOSHAPEKEEPASPECTRATIOCOMMAND_H
#define KOSHAPEKEEPASPECTRATIOCOMMAND_H
#include "kritaflake_export.h"
#include <kundo2command.h>
#include <QList>
......@@ -28,7 +29,7 @@ class KoShape;
/**
* Command that changes the keepAspectRatio property of KoShape
*/
class KoShapeKeepAspectRatioCommand : public KUndo2Command
class KRITAFLAKE_EXPORT KoShapeKeepAspectRatioCommand : public KUndo2Command
{
public:
/**
......
......@@ -28,15 +28,17 @@ class Q_DECL_HIDDEN KoShapeMoveCommand::Private
public:
QList<KoShape*> shapes;
QList<QPointF> previousPositions, newPositions;
KoFlake::AnchorPosition anchor;
};
KoShapeMoveCommand::KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPointF> &previousPositions, QList<QPointF> &newPositions, KUndo2Command *parent)
KoShapeMoveCommand::KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPointF> &previousPositions, QList<QPointF> &newPositions, KoFlake::AnchorPosition anchor, KUndo2Command *parent)
: KUndo2Command(parent),
d(new Private())
{
d->shapes = shapes;
d->previousPositions = previousPositions;
d->newPositions = newPositions;
d->anchor = anchor;
Q_ASSERT(d->shapes.count() == d->previousPositions.count());
Q_ASSERT(d->shapes.count() == d->newPositions.count());
......@@ -53,7 +55,7 @@ void KoShapeMoveCommand::redo()
KUndo2Command::redo();
for (int i = 0; i < d->shapes.count(); i++) {
d->shapes.at(i)->update();
d->shapes.at(i)->setPosition(d->newPositions.at(i));
d->shapes.at(i)->setAbsolutePosition(d->newPositions.at(i), d->anchor);
d->shapes.at(i)->update();
}
}
......@@ -63,13 +65,32 @@ void KoShapeMoveCommand::undo()
KUndo2Command::undo();
for (int i = 0; i < d->shapes.count(); i++) {
d->shapes.at(i)->update();
d->shapes.at(i)->setPosition(d->previousPositions.at(i));
d->shapes.at(i)->setAbsolutePosition(d->previousPositions.at(i), d->anchor);
d->shapes.at(i)->update();
}
}
int KoShapeMoveCommand::id() const
{
return 954687634; // TODO: use better system for making ids unique
}
/// update newPositions list with new postions.
void KoShapeMoveCommand::setNewPositions(QList<QPointF> newPositions)
{
d->newPositions = newPositions;
}
bool KoShapeMoveCommand::mergeWith(const KUndo2Command *command)
{
const KoShapeMoveCommand *other = dynamic_cast<const KoShapeMoveCommand*>(command);
if (other->d->shapes != d->shapes ||
other->d->anchor != d->anchor) {
return false;
}
d->newPositions = other->d->newPositions;
return true;
}
......@@ -26,6 +26,7 @@
#include <kundo2command.h>
#include <QList>
#include <QPointF>
#include <KoFlake.h>
class KoShape;
......@@ -43,12 +44,15 @@ public:
* @param parent the parent command used for macro commands
*/
KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPointF> &previousPositions, QList<QPointF> &newPositions,
KUndo2Command *parent = 0);
KoFlake::AnchorPosition anchor = KoFlake::Center, KUndo2Command *parent = 0);
~KoShapeMoveCommand();
/// redo the command
void redo();
void redo() override;
/// revert the actions done in redo
void undo();
void undo() override;
int id() const override;
bool mergeWith(const KUndo2Command *command) override;
/// update newPositions list with new postions.
void setNewPositions(QList<QPointF> newPositions);
......
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KoShapeResizeCommand.h"
#include <KoShape.h>
struct Q_DECL_HIDDEN KoShapeResizeCommand::Private
{
QList<KoShape *> shapes;
qreal scaleX;
qreal scaleY;
QPointF absoluteStillPoint;
bool usePostScaling;
QTransform postScalingCoveringTransform;
QList<QSizeF> oldSizes;
QList<QTransform> oldTransforms;
};
KoShapeResizeCommand::KoShapeResizeCommand(const QList<KoShape*> &shapes,
qreal scaleX, qreal scaleY,
const QPointF &absoluteStillPoint,
bool usePostScaling,
const QTransform &postScalingCoveringTransform,
KUndo2Command *parent)
: KUndo2Command(kundo2_i18n("Resize"), parent),
m_d(new Private)
{
m_d->shapes = shapes;
m_d->scaleX = scaleX;
m_d->scaleY = scaleY;
m_d->absoluteStillPoint = absoluteStillPoint;
m_d->usePostScaling = usePostScaling;
m_d->postScalingCoveringTransform = postScalingCoveringTransform;
Q_FOREACH (KoShape *shape, m_d->shapes) {
m_d->oldSizes << shape->size();
m_d->oldTransforms << shape->transformation();
}
}
KoShapeResizeCommand::~KoShapeResizeCommand()
{
}
void KoShapeResizeCommand::redo()
{
Q_FOREACH (KoShape *shape, m_d->shapes) {
shape->update();
KoFlake::resizeShape(shape,
m_d->scaleX, m_d->scaleY,
m_d->absoluteStillPoint,
m_d->usePostScaling,
m_d->postScalingCoveringTransform);
shape->update();
}
}
void KoShapeResizeCommand::undo()
{
for (int i = 0; i < m_d->shapes.size(); i++) {
KoShape *shape = m_d->shapes[i];
shape->update();
shape->setSize(m_d->oldSizes[i]);
shape->setTransformation(m_d->oldTransforms[i]);
shape->update();
}
}
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 KOSHAPERESIZECOMMAND_H
#define KOSHAPERESIZECOMMAND_H
#include "kritaflake_export.h"
#include "kundo2command.h"
#include <QList>
#include <QPointF>
#include <KoFlake.h>
#include <QScopedPointer>
class KoShape;
class KRITAFLAKE_EXPORT KoShapeResizeCommand : public KUndo2Command
{
public:
KoShapeResizeCommand(const QList<KoShape*> &shapes,
qreal scaleX, qreal scaleY,
const QPointF &absoluteStillPoint,
bool usePostScaling, const QTransform &postScalingCoveringTransform,
KUndo2Command *parent = 0);
~KoShapeResizeCommand();
void redo() override;
void undo() override;
//int id() const override;
//bool mergeWith(const KUndo2Command *command) override;
private:
class Private;
QScopedPointer<Private> const m_d;
};
#endif // KOSHAPERESIZECOMMAND_H
......@@ -200,3 +200,9 @@ void KisAspectRatioLocker::setBlockUpdateSignalOnDrag(bool value)
{
m_d->blockUpdatesOnDrag = value;
}
void KisAspectRatioLocker::updateAspect()
{
KisSignalsBlocker b(this);
slotAspectButtonChanged();
}
......@@ -40,6 +40,7 @@ public:
void connectSpinBoxes(SpinBoxType *spinOne, SpinBoxType *spinTwo, KoAspectButton *aspectButton);
void setBlockUpdateSignalOnDrag(bool block);
void updateAspect();
private Q_SLOTS:
void slotSpinOneChanged();
......
......@@ -85,6 +85,7 @@ set(kritawidgets_LIB_SRCS
kis_int_parse_spin_box.cpp
KisColorSelectorInterface.cpp
KoAnchorSelectionWidget.cpp
)
ki18n_wrap_ui( kritawidgets_LIB_SRCS
......
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KoAnchorSelectionWidget.h"
#include <array>
#include <QToolButton>
#include <QButtonGroup>
#include <QGridLayout>
#include <QFontMetrics>
#include "kis_icon_utils.h"
#include "kis_debug.h"
#include "kis_signals_blocker.h"
#include "kis_algebra_2d.h"
struct Q_DECL_HIDDEN KoAnchorSelectionWidget::Private {
std::array<QToolButton*, KoFlake::NumAnchorPositions> buttons;
QButtonGroup *buttonGroup;
};
KoAnchorSelectionWidget::KoAnchorSelectionWidget(QWidget *parent)
: QWidget(parent),
m_d(new Private)
{
QVector<QIcon> icons;
icons << KisIconUtils::loadIcon("arrow-topleft");
icons << KisIconUtils::loadIcon("arrow-up");
icons << KisIconUtils::loadIcon("arrow-topright");
icons << KisIconUtils::loadIcon("arrow-left");
icons << QIcon(); // center
icons << KisIconUtils::loadIcon("arrow-right");
icons << KisIconUtils::loadIcon("arrow-downleft");
icons << KisIconUtils::loadIcon("arrow-down");
icons << KisIconUtils::loadIcon("arrow-downright");
icons << QIcon(); // no anchor
QGridLayout *gridLayout = new QGridLayout(this);
gridLayout->setSpacing(0);
gridLayout->setContentsMargins(0,0,0,0);
m_d->buttonGroup = new QButtonGroup(this);
for (int i = 0; i < KoFlake::NumAnchorPositions; i++) {
QToolButton *button = new QToolButton(this);
button->setCheckable(true);
//button->setAutoRaise(true);
button->setAutoExclusive(true);
button->setIcon(icons[i]);
button->setFocusPolicy(Qt::NoFocus);
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
if (i != KoFlake::NoAnchor) {
gridLayout->addWidget(button, i / 3, i % 3, Qt::AlignCenter);
} else {
button->setVisible(false);
}
m_d->buttonGroup->addButton(button, i);
m_d->buttons[i] = button;
}
connect(m_d->buttonGroup, SIGNAL(buttonClicked(int)), SLOT(slotGroupClicked(int)));
setLayout(gridLayout);
}
KoAnchorSelectionWidget::~KoAnchorSelectionWidget()
{
}
KoFlake::AnchorPosition KoAnchorSelectionWidget::value() const
{
return KoFlake::AnchorPosition(m_d->buttonGroup->checkedId());
}
QPointF KoAnchorSelectionWidget::value(const QRectF rect, bool *valid) const
{
KoFlake::AnchorPosition anchor = this->value();
return anchorToPoint(anchor, rect, valid);
}
void KoAnchorSelectionWidget::setValue(KoFlake::AnchorPosition value)
{
if (value == this->value()) return;
KisSignalsBlocker b(m_d->buttonGroup);
m_d->buttonGroup->button(int(value))->setChecked(true);
emit valueChanged(value);
}
QSize KoAnchorSelectionWidget::sizeHint() const
{
const QSize minSize = minimumSizeHint();
const int preferredHint = qMax(minSize.height(), height());
return QSize(preferredHint, preferredHint);
}
QSize KoAnchorSelectionWidget::minimumSizeHint() const
{
QFontMetrics metrics(this->font());
const int minHeight = 3 * (metrics.height() + 5);
return QSize(minHeight, minHeight);
}
void KoAnchorSelectionWidget::slotGroupClicked(int id)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(id >= 0 && id < KoFlake::NumAnchorPositions);
emit valueChanged(KoFlake::AnchorPosition(id));
}
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 KOANCHORSELECTIONWIDGET_H
#define KOANCHORSELECTIONWIDGET_H
#include <QWidget>
#include <KoFlake.h>
#include "kritawidgets_export.h"
class KRITAWIDGETS_EXPORT KoAnchorSelectionWidget : public QWidget
{
Q_OBJECT
public:
explicit KoAnchorSelectionWidget(QWidget *parent = 0);
~KoAnchorSelectionWidget();
KoFlake::AnchorPosition value() const;
QPointF value(const QRectF rect, bool *valid) const;
void setValue(KoFlake::AnchorPosition value);
QSize sizeHint() const;
QSize minimumSizeHint() const;
Q_SIGNALS:
void valueChanged(KoFlake::AnchorPosition id);
public Q_SLOTS:
void slotGroupClicked(int id);
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif // KOANCHORSELECTIONWIDGET_H
......@@ -23,3 +23,8 @@ ecm_add_tests(
kis_parse_spin_boxes_test.cpp
NAME_PREFIX "krita-ui-"
LINK_LIBRARIES kritaui Qt5::Test)
ecm_add_tests(
KoAnchorSelectionWidgetTest.cpp
NAME_PREFIX "libs-widgets-"
LINK_LIBRARIES kritaui Qt5::Test)
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KoAnchorSelectionWidgetTest.h"
#include <QTest>
#include <QDialog>
#include <QVBoxLayout>
#include "kis_debug.h"
void KoAnchorSelectionWidgetTest::test()
{
QDialog dlg;
KoAnchorSelectionWidget *widget = new KoAnchorSelectionWidget(&dlg);
connect(widget,
SIGNAL(valueChanged(KoFlake::AnchorPosition)),
SLOT(slotValueChanged(KoFlake::AnchorPosition)));
QVBoxLayout *layout = new QVBoxLayout(&dlg);
layout->addWidget(widget);
dlg.setLayout(layout);
dlg.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);