...
 
Commits (42)
......@@ -2,8 +2,8 @@
| Jenkins CI Name | Master | Stable |
| --------------- | ------ | ------ |
| OpenSuse Qt 5.10 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.10/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.10/) |[![Build Status](https://build.kde.org/buildStatus/icon?job=Extragear%2Fkrita%2Fstable-kf5-qt5+SUSEQt5.10)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20SUSEQt5.10/)|
| FreeBSD Qt 5.12 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.12/) |[![Build Status](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.12/)|
| OpenSuse Qt 5.12 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/) |[![Build Status](https://build.kde.org/buildStatus/icon?job=Extragear%2Fkrita%2Fstable-kf5-qt5+SUSEQt5.12)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20SUSEQt5.12/)|
| FreeBSD Qt 5.13 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/) |[![Build Status](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/)|
Krita is a free and open source digital painting application. It is for artists who want to create professional work from start to end. Krita is used by comic book artists, illustrators, concept artists, matte and texture painters and in the digital VFX industry.
......
This diff is collapsed.
This diff is collapsed.
......@@ -16,6 +16,7 @@ add_subdirectory( image )
add_subdirectory( ui )
add_subdirectory( impex )
add_subdirectory( libkis )
add_subdirectory( stroke )
if (NOT APPLE AND HAVE_QT_QUICK)
add_subdirectory( libqml )
endif()
......
......@@ -501,7 +501,7 @@ bool KoCreatePathTool::addPathShapeImpl(KoPathShape *pathShape, bool tryMergeOnl
return false;
}
KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape, 0);
KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape, 0, 0);
KIS_SAFE_ASSERT_RECOVER(cmd) {
canvas()->updateCanvas(pathShape->boundingRect());
delete pathShape;
......
......@@ -402,7 +402,7 @@ void KoPencilTool::addPathShape(KoPathShape* path, bool closePath)
}
}
KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0);
KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0, 0);
if (cmd) {
KoSelection *selection = canvas()->shapeManager()->selection();
selection->deselectAll();
......
......@@ -93,7 +93,7 @@ bool KisPngBrush::loadFromDevice(QIODevice *dev)
}
else {
QFileInfo info(filename());
setName(info.baseName());
setName(info.completeBaseName());
}
QImage image = reader.read();
......
......@@ -97,7 +97,7 @@ bool KisSvgBrush::loadFromDevice(QIODevice *dev)
setHeight(brushTipImage().height());
QFileInfo fi(filename());
setName(fi.baseName());
setName(fi.completeBaseName());
return !brushTipImage().isNull() && valid();
}
......
......@@ -10,6 +10,7 @@ include_directories(
add_subdirectory(styles)
add_subdirectory(tests)
add_subdirectory(resources/tests)
set(kritaflake_SRCS
KoGradientHelper.cpp
......@@ -241,7 +242,7 @@ target_include_directories(kritaflake
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/text>
)
target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg)
target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand kritastroke KF5::WidgetsAddons Qt5::Svg)
set_target_properties(kritaflake PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
......
......@@ -22,6 +22,7 @@
#include <QRectF>
#include <QSizeF>
#include <kis_assert.h>
#include <FlakeDebug.h>
//#define DEBUG_GAMUT_MASK_CONVERTER
......@@ -155,6 +156,9 @@ void KisGamutMaskViewConverter::setViewSize(QSize viewSize)
void KisGamutMaskViewConverter::setMaskSize(QSizeF maskSize)
{
KIS_ASSERT(!qIsNull(maskSize.width()));
KIS_ASSERT(!qIsNull(maskSize.height()));
m_maskSize = maskSize;
m_maskResolution = maskSize.width();
......
......@@ -220,7 +220,7 @@ void Viewport::handleDropEvent(QDropEvent *event)
m_draggedShape->setAbsolutePosition(newPos);
KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0);
KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0, 0);
if (cmd) {
m_parent->canvas()->addCommand(cmd);
......
......@@ -468,10 +468,10 @@ QRectF KoPathShape::outlineRect() const
QPainterPath KoPathShape::outline() const
{
QPainterPath path;
Q_FOREACH (KoSubpath * subpath, d->subpaths) {
KoPathPoint * lastPoint = subpath->first();
Q_FOREACH (const KoSubpath * subpath, d->subpaths) {
const KoPathPoint * lastPoint = subpath->first();
bool activeCP = false;
Q_FOREACH (KoPathPoint * currPoint, *subpath) {
Q_FOREACH (const KoPathPoint * currPoint, *subpath) {
KoPathPoint::PointProperties currProperties = currPoint->properties();
if (currPoint == subpath->first()) {
if (currProperties & KoPathPoint::StartSubpath) {
......@@ -498,7 +498,7 @@ QPainterPath KoPathShape::outline() const
}
if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) {
// add curve when there is a curve on the way to the first point
KoPathPoint * firstPoint = subpath->first();
const KoPathPoint * firstPoint = subpath->first();
Q_ASSERT(!qIsNaNPoint(firstPoint->point()));
if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) {
path.cubicTo(
......
......@@ -3,6 +3,7 @@
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2011 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
......@@ -30,6 +31,7 @@
#include "commands/KoShapeCreateCommand.h"
#include "commands/KoShapeDeleteCommand.h"
#include "commands/KoShapeConnectionChangeCommand.h"
#include "commands/KoShapeReorderCommand.h"
#include "KoCanvasBase.h"
#include "KoShapeConfigWidgetBase.h"
#include "KoShapeFactoryBase.h"
......@@ -102,11 +104,85 @@ public:
return addShapesDirect({shape}, parentShape, parent);
}
void addShape(KoShape *shape, bool showDialog, KoShapeContainer *parentShape) {
if (canvas) {
if (showDialog && !shape->shapeId().isEmpty()) {
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId());
Q_ASSERT(factory);
qint16 z = 0;
Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) {
z = qMax(z, sh->zIndex());
}
shape->setZIndex(z + 1);
// show config dialog.
KPageDialog *dialog = new KPageDialog(canvas->canvasWidget());
dialog->setWindowTitle(i18n("%1 Options", factory->name()));
int pageCount = 0;
QList<KoShapeConfigWidgetBase*> widgets;
Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) {
if (! panel->showOnShapeCreate())
continue;
panel->open(shape);
panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept()));
widgets.append(panel);
panel->setResourceManager(canvas->resourceManager());
panel->setUnit(canvas->unit());
QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle();
dialog->addPage(panel, title);
pageCount ++;
}
if (pageCount > 0) {
if (pageCount > 1)
dialog->setFaceType(KPageDialog::Tabbed);
if (dialog->exec() != KPageDialog::Accepted) {
delete dialog;
return; /// FIXME how to indicate failure?
}
Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets)
widget->save();
}
delete dialog;
}
}
addShapesDirect({shape}, parentShape);
}
KUndo2Command* addShapesDirect(const QList<KoShape*> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return new KoShapeCreateCommand(shapeController, shapes, parentShape, parent);
}
void addShapesDirect(const QList<KoShape*> shapes, KoShapeContainer *parentShape)
{
// from KoShapeCreateCommand::redo()
Q_FOREACH (KoShape *shape, shapes) {
if (parentShape) {
shape->setParent(parentShape);
}
shapeController->addShape(shape);
KoShapeContainer *shapeParent = shape->parent();
KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() ||
dynamic_cast<KoShapeLayer*>(shape));
if (shapeParent) {
// TODO get rid of undo commands here
QScopedPointer<KUndo2Command> cmd(KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape));
if (cmd) {
cmd->redo();
}
}
}
}
void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) {
foreach (KoShape *dependee, shape->dependees()) {
KoConnectionShape *connection = dynamic_cast<KoConnectionShape*>(dependee);
......@@ -121,6 +197,19 @@ public:
}
}
}
void handleAttachedConnections(KoShape *shape) {
foreach (KoShape *dependee, shape->dependees()) {
KoConnectionShape *connection = dynamic_cast<KoConnectionShape*>(dependee);
if (connection) {
if (shape == connection->firstShape()) {
connection->connectFirst(/* shape = */ 0, /* connectionPointId = */ -1);
} else if (shape == connection->secondShape()) {
connection->connectSecond(/* shape = */ 0, /* connectionPointId = */ -1);
}
}
}
}
};
KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeControllerBase *shapeController)
......@@ -146,25 +235,52 @@ KUndo2Command* KoShapeController::addShape(KoShape *shape, KoShapeContainer *par
return d->addShape(shape, true, parentShape, parent);
}
void KoShapeController::addShape(KoShape *shape, KoShapeContainer *parentShape)
{
d->addShape(shape, true, parentShape);
}
KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return d->addShapesDirect({shape}, parentShape, parent);
}
void KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape)
{
d->addShapesDirect({shape}, parentShape);
}
KUndo2Command *KoShapeController::addShapesDirect(const QList<KoShape *> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return d->addShapesDirect(shapes, parentShape, parent);
}
KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent)
void KoShapeController::addShapesDirect(const QList<KoShape *> shapes, KoShapeContainer *parentShape)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shape, parent);
QList<KoShape*> shapes;
shapes.append(shape);
d->shapeController->shapesRemoved(shapes, cmd);
// detach shape from any attached connection shapes
d->handleAttachedConnections(shape, cmd);
return cmd;
d->addShapesDirect(shapes, parentShape);
}
void KoShapeController::removeShape(KoShape *shape)
{
removeShapes({shape});
}
void KoShapeController::removeShapes(const QList<KoShape*> &shapes)
{
d->shapeController->shapesRemoved(shapes, 0);
Q_FOREACH (KoShape *shape, shapes) {
KoShapeContainer *parent = shape->parent();
d->shapeController->removeShape(shape);
if (parent) {
parent->removeShape(shape);
}
d->handleAttachedConnections(shape);
}
}
KUndo2Command *KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent)
{
return removeShapes({shape}, parent);
}
KUndo2Command* KoShapeController::removeShapes(const QList<KoShape*> &shapes, KUndo2Command *parent)
......@@ -177,6 +293,7 @@ KUndo2Command* KoShapeController::removeShapes(const QList<KoShape*> &shapes, KU
return cmd;
}
void KoShapeController::setShapeControllerBase(KoShapeControllerBase *shapeController)
{
d->shapeController = shapeController;
......
......@@ -75,7 +75,8 @@ public:
* @return command which will insert the shape into the document or 0 if the
* insertion was cancelled. The command is not yet executed.
*/
KUndo2Command* addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0);
KUndo2Command* addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent);
void addShape(KoShape *shape, KoShapeContainer *parentShape);
/**
* @brief Add a shape to the document, skipping any dialogs or other user interaction.
......@@ -86,7 +87,8 @@ public:
*
* @return command which will insert the shape into the document. The command is not yet executed.
*/
KUndo2Command* addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0);
KUndo2Command* addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent);
void addShapeDirect(KoShape *shape, KoShapeContainer *parentShape);
/**
* @brief Add shapes to the document, skipping any dialogs or other user interaction.
......@@ -97,7 +99,8 @@ public:
*
* @return command which will insert the shapes into the document. The command is not yet executed.
*/
KUndo2Command* addShapesDirect(const QList<KoShape*> shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0);
KUndo2Command* addShapesDirect(const QList<KoShape*> shape, KoShapeContainer *parentShape, KUndo2Command *parent);
void addShapesDirect(const QList<KoShape *> shape, KoShapeContainer *parentShape);
/**
* @brief Remove a shape from the document.
......@@ -108,7 +111,8 @@ public:
* @return command which will remove the shape from the document.
* The command is not yet executed.
*/
KUndo2Command* removeShape(KoShape *shape, KUndo2Command *parent = 0);
KUndo2Command* removeShape(KoShape *shape, KUndo2Command *parent);
void removeShape(KoShape *shape);
/**
* Remove a shape from the document.
......@@ -119,7 +123,8 @@ public:
* @return command which will remove the shape from the document.
* The command is not yet executed.
*/
KUndo2Command* removeShapes(const QList<KoShape*> &shapes, KUndo2Command *parent = 0);
KUndo2Command* removeShapes(const QList<KoShape*> &shapes, KUndo2Command *parent);
void removeShapes(const QList<KoShape *> &shapes);
/**
* @brief Set the KoShapeControllerBase used to add/remove shapes.
......
......@@ -65,6 +65,7 @@ bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape)
void KoShapeManager::Private::updateTree()
{
qDebug() << "start updateTree()";
// for detecting collisions between shapes.
DetectCollision detector;
bool selectionModified = false;
......@@ -98,6 +99,7 @@ void KoShapeManager::Private::updateTree()
if (anyModified) {
emit q->contentChanged();
}
qDebug() << "end updateTree()";
}
void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
......
......@@ -41,7 +41,6 @@
#include <KisGamutMaskViewConverter.h>
#include <kis_assert.h>
KoGamutMaskShape::KoGamutMaskShape(KoShape* shape)
: m_maskShape(shape)
, m_shapePaintingContext(KoShapePaintingContext())
......
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include(ECMAddTests)
include_directories(${CMAKE_SOURCE_DIR}/sdk/tests)
macro_add_unittest_definitions()
# needs kritaimage for TestUtil
ecm_add_tests(
KoGamutMaskTest.cpp
# KoGamutMaskShapeTest.cpp
LINK_LIBRARIES kritaflake kritaimage Qt5::Test
NAME_PREFIX "libs-flake-")
/*
* Copyright (c) 2019 Anna Medonosova <anna.medonosova@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 <QTest>
#include <resources/KoGamutMask.h>
#include <KisGamutMaskViewConverter.h>
#include <testutil.h>
#include "KoGamutMaskTest.h"
KoGamutMaskTest::KoGamutMaskTest(QObject *parent) : QObject(parent)
{
}
void KoGamutMaskTest::testCoordIsClear()
{
QFETCH(QString, maskFile);
QFETCH(QPointF, coord);
QFETCH(int, maskRotation);
QFETCH(bool, expectedOutput);
QScopedPointer<KoGamutMask> mask(new KoGamutMask(TestUtil::fetchDataFileLazy(maskFile)));
mask->load();
Q_ASSERT(mask->valid());
mask->setRotation(maskRotation);
KisGamutMaskViewConverter* converter = new KisGamutMaskViewConverter();
converter->setMaskSize(mask->maskSize());
converter->setViewSize(QSize(100.0, 100.0));
bool maskOutput = mask->coordIsClear(coord, *converter, false);
QCOMPARE(maskOutput, expectedOutput);
}
void KoGamutMaskTest::testCoordIsClear_data()
{
QTest::addColumn<QString>("maskFile");
QTest::addColumn<QPointF>("coord");
QTest::addColumn<int>("maskRotation");
QTest::addColumn<bool>("expectedOutput");
// single shape mask
QTest::addRow("Atmospheric_Triad.kgm: disallowed coordinate, no rotation") << "Atmospheric_Triad.kgm"
<< QPointF(0.0, 0.0) << 0
<< false;
QTest::addRow("Atmospheric_Triad.kgm: allowed coordinate, no rotation") << "Atmospheric_Triad.kgm"
<< QPointF(33.0, 71.0) << 0
<< true;
QTest::addRow("Atmospheric_Triad.kgm: disallowed coordinate, with rotation") << "Atmospheric_Triad.kgm"
<< QPointF(33.0, 71.0) << 180
<< false;
QTest::addRow("Atmospheric_Triad.kgm: allowed coordinate, with rotation") << "Atmospheric_Triad.kgm"
<< QPointF(76.4,60.9) << 180
<< true;
// multiple shapes mask
QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 1, no rotation")
<< "Dominant_Hue_With_Accent.kgm"
<< QPointF(71.0, 49.0) << 0
<< true;
QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 2, no rotation")
<< "Dominant_Hue_With_Accent.kgm"
<< QPointF(11.0, 51.0) << 0
<< true;
QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 1, with rotation")
<< "Dominant_Hue_With_Accent.kgm"
<< QPointF(40.0, 21.0) << 256
<< true;
QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 2, with rotation")
<< "Dominant_Hue_With_Accent.kgm"
<< QPointF(57.0, 82.0) << 256
<< true;
}
void KoGamutMaskTest::testLoad()
{
QFETCH(QString, maskFile);
QFETCH(QString, expectedTitle);
QFETCH(QString, expectedDescription);
QFETCH(int, expectedShapeCount);
QScopedPointer<KoGamutMask> mask(new KoGamutMask(TestUtil::fetchDataFileLazy(maskFile)));
mask->load();
Q_ASSERT(mask->valid());
QCOMPARE(mask->title(), expectedTitle);
QCOMPARE(mask->description(), expectedDescription);
QCOMPARE(mask->koShapes().size(), expectedShapeCount);
}
void KoGamutMaskTest::testLoad_data()
{
QTest::addColumn<QString>("maskFile");
QTest::addColumn<QString>("expectedTitle");
QTest::addColumn<QString>("expectedDescription");
QTest::addColumn<int>("expectedShapeCount");
QTest::addRow("single shape mask")
<< "Atmospheric_Triad.kgm"
<< "Atmospheric Triad" << "test gamut mask description"
<< 1;
QTest::addRow("multiple shape mask")
<< "Dominant_Hue_With_Accent.kgm"
<< "Dominant Hue With Accent" << ""
<< 2;
}
QTEST_MAIN(KoGamutMaskTest);
/*
* Copyright (c) 2019 Anna Medonosova <anna.medonosova@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 KOGAMUTMASKTEST_H
#define KOGAMUTMASKTEST_H
#include <QObject>
class KoGamutMaskTest : public QObject
{
Q_OBJECT
public:
explicit KoGamutMaskTest(QObject *parent = nullptr);
private Q_SLOTS:
void testCoordIsClear();
void testCoordIsClear_data();
void testLoad();
void testLoad_data();
// TODO: add preview vs. non-preview testing
};
#endif // KOGAMUTMASKTEST_H
......@@ -172,7 +172,7 @@ QString SvgSavingContext::createFileName(const QString &extension)
QFileInfo fi(file->fileName());
QString path = fi.absolutePath();
QString dstBaseFilename = fi.baseName();
QString dstBaseFilename = fi.completeBaseName();
// create a filename for the image file at the destination directory
QString fname = dstBaseFilename + '_' + createUID("file");
......
......@@ -33,6 +33,7 @@ ecm_add_tests(
TestSegmentTypeCommand.cpp
TestKoDrag.cpp
TestKoMarkerCollection.cpp
KisGamutMaskViewConverterTest.cpp
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
......
/*
* Copyright (c) 2019 Anna Medonosova <anna.medonosova@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 <QScopedPointer>
#include <KisGamutMaskViewConverter.h>
#include <kis_assert_exception.h>
#include "KisGamutMaskViewConverterTest.h"
KisGamutMaskViewConverterTest::KisGamutMaskViewConverterTest(QObject *parent) : QObject(parent)
{
}
void KisGamutMaskViewConverterTest::testDocumentToViewX()
{
QFETCH(qreal, input);
QFETCH(qreal, expectedOutput);
QFETCH(QSizeF, maskSize);
QFETCH(QSize, viewSize);
QScopedPointer<KisGamutMaskViewConverter> converter(new KisGamutMaskViewConverter());
converter->setMaskSize(maskSize);
converter->setViewSize(viewSize);
qreal converterOutput = converter->documentToViewX(input);
QCOMPARE(converterOutput, expectedOutput);
}
void KisGamutMaskViewConverterTest::testDocumentToViewX_data()
{
QTest::addColumn<qreal>("input");
QTest::addColumn<qreal>("expectedOutput");
QTest::addColumn<QSizeF>("maskSize");
QTest::addColumn<QSize>("viewSize");
QTest::newRow("mask < view") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 400);
QTest::newRow("view < mask") << 2.0 << 1.0 << QSizeF(400.0, 400.0) << QSize(200, 200);
QTest::newRow("view not square") << 2.0 << 4.0 << QSizeF(200.0, 300.0) << QSize(400, 400);
QTest::newRow("mask not square") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 600);
}
void KisGamutMaskViewConverterTest::testDocumentToViewY()
{
QFETCH(qreal, input);
QFETCH(qreal, expectedOutput);
QFETCH(QSizeF, maskSize);
QFETCH(QSize, viewSize);
QScopedPointer<KisGamutMaskViewConverter> converter(new KisGamutMaskViewConverter());
converter->setMaskSize(maskSize);
converter->setViewSize(viewSize);
qreal converterOutput = converter->documentToViewY(input);
QCOMPARE(converterOutput, expectedOutput);
}
void KisGamutMaskViewConverterTest::testDocumentToViewY_data()
{
QTest::addColumn<qreal>("input");
QTest::addColumn<qreal>("expectedOutput");
QTest::addColumn<QSizeF>("maskSize");
QTest::addColumn<QSize>("viewSize");
QTest::newRow("mask < view") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 400);
QTest::newRow("view < mask") << 2.0 << 1.0 << QSizeF(400.0, 400.0) << QSize(200, 200);
QTest::newRow("view not square") << 2.0 << 4.0 << QSizeF(200.0, 300.0) << QSize(400, 400);
QTest::newRow("mask not square") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 600);
}
void KisGamutMaskViewConverterTest::testViewToDocumentX()
{
QFETCH(qreal, input);
QFETCH(qreal, expectedOutput);
QFETCH(QSizeF, maskSize);
QFETCH(QSize, viewSize);
QScopedPointer<KisGamutMaskViewConverter> converter(new KisGamutMaskViewConverter());
converter->setMaskSize(maskSize);
converter->setViewSize(viewSize);
qreal converterOutput = converter->viewToDocumentX(input);
QCOMPARE(converterOutput, expectedOutput);
}
void KisGamutMaskViewConverterTest::testViewToDocumentX_data()
{
QTest::addColumn<qreal>("input");
QTest::addColumn<qreal>("expectedOutput");
QTest::addColumn<QSizeF>("maskSize");
QTest::addColumn<QSize>("viewSize");
QTest::newRow("mask < view") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 400);
QTest::newRow("view < mask") << 1.0 << 2.0 << QSizeF(400.0, 400.0) << QSize(200, 200);
QTest::newRow("view not square") << 4.0 << 2.0 << QSizeF(200.0, 300.0) << QSize(400, 400);
QTest::newRow("mask not square") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 600);
}
void KisGamutMaskViewConverterTest::testViewToDocumentY()
{
QFETCH(qreal, input);
QFETCH(qreal, expectedOutput);
QFETCH(QSizeF, maskSize);
QFETCH(QSize, viewSize);
QScopedPointer<KisGamutMaskViewConverter> converter(new KisGamutMaskViewConverter());
converter->setMaskSize(maskSize);
converter->setViewSize(viewSize);
qreal converterOutput = converter->viewToDocumentY(input);
QCOMPARE(converterOutput, expectedOutput);
}
void KisGamutMaskViewConverterTest::testViewToDocumentY_data()
{
QTest::addColumn<qreal>("input");
QTest::addColumn<qreal>("expectedOutput");
QTest::addColumn<QSizeF>("maskSize");
QTest::addColumn<QSize>("viewSize");
QTest::newRow("mask < view") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 400);
QTest::newRow("view < mask") << 1.0 << 2.0 << QSizeF(400.0, 400.0) << QSize(200, 200);
QTest::newRow("view not square") << 4.0 << 2.0 << QSizeF(200.0, 300.0) << QSize(400, 400);
QTest::newRow("mask not square") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 600);
}
QTEST_MAIN(KisGamutMaskViewConverterTest);
/*
* Copyright (c) 2019 Anna Medonosova <anna.medonosova@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 KISGAMUTMASKVIEWCONVERTERTEST_H
#define KISGAMUTMASKVIEWCONVERTERTEST_H
#include <QObject>
#include <QTest>
class KisGamutMaskViewConverterTest : public QObject
{
Q_OBJECT
public:
explicit KisGamutMaskViewConverterTest(QObject *parent = nullptr);
private Q_SLOTS:
void testDocumentToViewX();
void testDocumentToViewX_data();
void testDocumentToViewY();
void testDocumentToViewY_data();
void testViewToDocumentX();
void testViewToDocumentX_data();
void testViewToDocumentY();
void testViewToDocumentY_data();
};
#endif // KISGAMUTMASKVIEWCONVERTERTEST_H
......@@ -80,7 +80,7 @@ KUndo2Command* KoCreateShapeStrategy::createCommand()
if (newSize.width() > 1.0 && newSize.height() > 1.0)
shape->setSize(newSize);
KUndo2Command * cmd = parent->canvas()->shapeController()->addShape(shape, 0);
KUndo2Command * cmd = parent->canvas()->shapeController()->addShape(shape, 0, 0);
if (cmd) {
KoSelection *selection = parent->canvas()->shapeManager()->selection();
selection->deselectAll();
......
......@@ -27,7 +27,6 @@
#include "kis_global.h"
#include "kis_assert.h"
KoInteractionTool::KoInteractionTool(KoCanvasBase *canvas)
: KoToolBase(*(new KoInteractionToolPrivate(this, canvas)))
{
......
......@@ -18,7 +18,6 @@ set(kritaglobal_LIB_SRCS
kis_painting_tweaks.cpp
KisHandlePainterHelper.cpp
KisHandleStyle.cpp
kis_relaxed_timer.cpp
kis_signal_compressor.cpp
kis_signal_compressor_with_param.cpp
kis_thread_safe_signal_compressor.cpp
......
......@@ -261,5 +261,6 @@ private:
T *m_lock;
};
#endif // KISGLOBAL_H_
/*
* Copyright (c) 2017 Bernhard Liebl <poke1024@gmx.de>
*
* 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_relaxed_timer.h"
#include "kis_assert.h"
KisRelaxedTimer::KisRelaxedTimer(QObject *parent)
: QObject(parent)
, m_interval(0)
, m_singleShot(false)
, m_nextTimerTickSeqNo(1)
, m_emitOnTimeTick(0)
, m_isEmitting(false)
{
}
void KisRelaxedTimer::setInterval(int interval)
{
KIS_SAFE_ASSERT_RECOVER(!isActive()) {
this->stop();
}
m_interval = interval;
}
void KisRelaxedTimer::setSingleShot(bool singleShot)
{
m_singleShot = singleShot;
}
int KisRelaxedTimer::remainingTime() const
{
// in contrast to normal QTimers, the remaining time is calculated in
// terms of 2 * m_interval as this is the worst case interval.
if (!isActive()) {
return -1;
} else {
return qMax(qint64(0), 2 * m_interval - qint64(m_elapsed.elapsed()));
}
}
void KisRelaxedTimer::start()
{
m_elapsed.start();
// cancels any previously scheduled timer and schedules a new timer to be
// triggered as soon as possible, but never sooner than \p m_interval ms.
if (!m_timer.isActive()) {
// no internal timer is running. start one, and configure it to send
// us a timeout event on the next possible tick which will be exactly
// \p m_interval ms in the future.
m_emitOnTimeTick = m_nextTimerTickSeqNo;
m_timer.start(m_interval, this);
} else if (m_isEmitting) {
// an internal timer is running and we are actually called from a
// timeout event. so we know the next tick will happen in exactly
// \p m_interval ms.
m_emitOnTimeTick = m_nextTimerTickSeqNo;
} else {
// an internal timer is already running, but we do not know when
// the next tick will happen. we need to skip next tick as it
// will be sooner than m_delay. the one after that will be good as
// it will be m_interval * (1 + err) in the future.
m_emitOnTimeTick = m_nextTimerTickSeqNo + 1;
}
}
void KisRelaxedTimer::timerEvent(QTimerEvent *event)
{
Q_UNUSED(event);
const int ticksStopThreshold = 5;
const qint64 timerTickSeqNo = m_nextTimerTickSeqNo;
// from this point on, if this is an emit tick, we are no longer active.
m_nextTimerTickSeqNo++;
if (timerTickSeqNo == m_emitOnTimeTick) {
if (m_singleShot) {
stop();
}
const IsEmitting emitting(*this);
emit timeout();
} else if (timerTickSeqNo - m_emitOnTimeTick > ticksStopThreshold) {
m_timer.stop();
}
}
......@@ -28,38 +28,38 @@
* err == 0 if this is the first signal after a while):
*
* POSTPONE:
* - timeout after <= (1 + err) * \p delay ms.
* - timeout after = [0.5 ... 1.0] * \p delay ms.
* FIRST_ACTIVE_POSTPONE_NEXT:
* - first timeout immediately
* - postponed timeout after (1 + err) * \p delay ms
* - postponed timeout after [0.5 ... 1.0] * \p delay ms
* FIRST_ACTIVE:
* - first timeout immediately
* - second timeout after (1 + err) * \p delay ms
* - after that: \p delay ms
* FIRST_INACTIVE:
* - timeout after (1 + err) * \p delay ms
* - after that [0.5 ... 1.5] * \p delay ms
* FIRST_INACTIVE:
* - timeout after [0.5 ... 1.5] * \p delay ms
*/
#include "kis_signal_compressor.h"
#include "kis_relaxed_timer.h"
#include <QTimer>
#include "kis_assert.h"
#include "kis_debug.h"
KisSignalCompressor::KisSignalCompressor()
: QObject(0)
, m_timer(new KisRelaxedTimer(this))
, m_mode(UNDEFINED)
, m_gotSignals(false)
, m_timer(new QTimer(this))
{
m_timer->setSingleShot(true);
m_timer->setSingleShot(false);
connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired()));
}
KisSignalCompressor::KisSignalCompressor(int delay, Mode mode, QObject *parent)
: QObject(parent),
m_timer(new KisRelaxedTimer(this)),
m_mode(mode),
m_gotSignals(false)
m_timer(new QTimer(this)),
m_mode(mode)
{
m_timer->setSingleShot(true);
m_timer->setSingleShot(false);
m_timer->setInterval(delay);
connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired()));
}
......@@ -81,59 +81,125 @@ void KisSignalCompressor::setDelay(int delay)
void KisSignalCompressor::start()
{
Q_ASSERT(m_mode != UNDEFINED);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_mode != UNDEFINED);
const bool isFirstStart = !m_timer->isActive();
KIS_SAFE_ASSERT_RECOVER_NOOP(!isFirstStart || !m_signalsPending);
switch (m_mode) {
case POSTPONE:
m_timer->start();
if (isFirstStart) {
m_timer->start();
}
m_lastEmittedTimer.restart();
m_signalsPending = true;
break;
case FIRST_ACTIVE_POSTPONE_NEXT:
case FIRST_ACTIVE:
if (!m_timer->isActive()) {
m_gotSignals = false;
if (isFirstStart) {
m_timer->start();
emit timeout();
m_lastEmittedTimer.restart();
m_signalsPending = false;
if (!tryEmitSignalSafely()) {
m_signalsPending = true;
}
} else {
m_gotSignals = true;
if (m_mode == FIRST_ACTIVE_POSTPONE_NEXT) {
m_timer->start();
} else if (m_mode == FIRST_ACTIVE && m_timer->remainingTime() == 0) {
// overdue, swamped by other events
m_timer->stop();
slotTimerExpired();
if (m_mode == FIRST_ACTIVE) {
m_signalsPending = true;
tryEmitOnTick(false);
} else {
m_lastEmittedTimer.restart();
m_signalsPending = true;
}
}
break;
case FIRST_INACTIVE:
if (!m_timer->isActive()) {
if (isFirstStart) {
m_timer->start();
m_lastEmittedTimer.restart();
m_signalsPending = true;
} else {
m_signalsPending = true;
tryEmitOnTick(false);
}
case UNDEFINED:
; // Should never happen, but do nothing
};
if (m_mode == POSTPONE || !m_timer->isActive()) {
KIS_SAFE_ASSERT_RECOVER(m_timer->isActive()) {
m_timer->start();
}
}
void KisSignalCompressor::slotTimerExpired()
bool KisSignalCompressor::tryEmitOnTick(bool isFromTimer)
{
bool wasEmitted = false;
// we have different requirements for hi-frequency events (the mean
// of the events rate must be min(compressorRate, eventsRate)
const int realInterval = m_timer->interval();
const int minInterval = realInterval < 100 ? 0.5 * realInterval : realInterval;
// Enable for debugging:
// ENTER_FUNCTION() << ppVar(isFromTimer) << ppVar(m_signalsPending) << m_lastEmittedTimer.elapsed();
if (m_signalsPending && m_lastEmittedTimer.elapsed() >= minInterval) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!isFromTimer || !m_isEmitting);
m_lastEmittedTimer.start();
m_signalsPending = false;
if (!tryEmitSignalSafely()) {
m_signalsPending = true;
}
wasEmitted = true;
} else if (!isFromTimer) {
m_signalsPending = true;
}
return wasEmitted;
}
bool KisSignalCompressor::tryEmitSignalSafely()
{
Q_ASSERT(m_mode != UNDEFINED);
if ((m_mode != FIRST_ACTIVE && m_mode != FIRST_ACTIVE_POSTPONE_NEXT) || m_gotSignals) {
m_gotSignals = false;
bool wasEmitted = false;
m_isEmitting++;
if (m_isEmitting == 1) {
emit timeout();
wasEmitted = true;
}
m_isEmitting--;
return wasEmitted;
}
void KisSignalCompressor::slotTimerExpired()
{
KIS_ASSERT_RECOVER_NOOP(m_mode != UNDEFINED);
if (!tryEmitOnTick(true)) {
const int calmDownInterval = 5 * m_timer->interval();
if (!m_lastEmittedTimer.isValid() ||
m_lastEmittedTimer.elapsed() > calmDownInterval) {
m_timer->stop();
}
}
}
void KisSignalCompressor::stop()
{
m_timer->stop();
m_signalsPending = false;
m_lastEmittedTimer.invalidate();
}
bool KisSignalCompressor::isActive() const
{
return m_timer->isActive() && (m_mode != FIRST_ACTIVE || m_gotSignals);
return m_signalsPending && m_timer->isActive();
}
void KisSignalCompressor::setMode(KisSignalCompressor::Mode mode)
......
......@@ -22,7 +22,9 @@
#include <QObject>
#include "kritaglobal_export.h"
class KisRelaxedTimer;
#include <QElapsedTimer>
class QTimer;
/**
* Sets a timer to delay or throttle activation of a Qt slot. One example of
......@@ -86,9 +88,15 @@ Q_SIGNALS:
void timeout();
private:
KisRelaxedTimer *m_timer;
Mode m_mode;
bool m_gotSignals;
bool tryEmitOnTick(bool isFromTimer);
bool tryEmitSignalSafely();
private:
QTimer *m_timer = 0;
Mode m_mode = UNDEFINED;
bool m_signalsPending = false;
QElapsedTimer m_lastEmittedTimer;
int m_isEmitting = 0;
};
#endif /* __KIS_SIGNAL_COMPRESSOR_H */
......@@ -5,5 +5,6 @@ macro_add_unittest_definitions()
ecm_add_tests(KisSharedThreadPoolAdapterTest.cpp
KisSignalAutoConnectionTest.cpp
KisSignalCompressorTest.cpp
NAME_PREFIX libs-global-
LINK_LIBRARIES kritaglobal Qt5::Test)