diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp
index 8610e2503e..9541dcd4f6 100644
--- a/libs/flake/KoShapeManager.cpp
+++ b/libs/flake/KoShapeManager.cpp
@@ -1,652 +1,685 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann
Copyright (C) 2006-2010 Thomas Zander
Copyright (C) 2009-2010 Jan Hambrecht
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoShapeManager.h"
#include "KoShapeManager_p.h"
#include "KoSelection.h"
#include "KoToolManager.h"
#include "KoPointerEvent.h"
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoCanvasBase.h"
#include "KoShapeContainer.h"
#include "KoShapeStrokeModel.h"
#include "KoShapeGroup.h"
#include "KoToolProxy.h"
#include "KoShapeShadow.h"
#include "KoShapeLayer.h"
#include "KoFilterEffect.h"
#include "KoFilterEffectStack.h"
#include "KoFilterEffectRenderContext.h"
#include "KoShapeBackground.h"
#include
#include "KoClipPath.h"
#include "KoClipMaskPainter.h"
#include "KoShapePaintingContext.h"
#include "KoViewConverter.h"
#include "KisQPainterStateSaver.h"
#include "KoSvgTextChunkShape.h"
#include "KoSvgTextShape.h"
#include
#include
#include
#include
#include "kis_painting_tweaks.h"
bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape)
{
// FIXME: make more general!
return !dynamic_cast(shape) &&
!dynamic_cast(shape) &&
!(dynamic_cast(shape) && !dynamic_cast(shape));
}
void KoShapeManager::Private::updateTree()
{
+ QMutexLocker l(&this->treeMutex);
+
// for detecting collisions between shapes.
DetectCollision detector;
bool selectionModified = false;
bool anyModified = false;
Q_FOREACH (KoShape *shape, aggregate4update) {
if (shapeIndexesBeforeUpdate.contains(shape))
detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
selectionModified = selectionModified || selection->isSelected(shape);
anyModified = true;
}
foreach (KoShape *shape, aggregate4update) {
if (!shapeUsedInRenderingTree(shape)) continue;
tree.remove(shape);
QRectF br(shape->boundingRect());
tree.insert(br, shape);
}
// do it again to see which shapes we intersect with _after_ moving.
foreach (KoShape *shape, aggregate4update) {
detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
}
aggregate4update.clear();
shapeIndexesBeforeUpdate.clear();
detector.fireSignals();
if (selectionModified) {
emit q->selectionContentChanged();
}
if (anyModified) {
emit q->contentChanged();
}
}
void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
QList shapes = group->shapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (KoShape *child, shapes) {
// we paint recursively here, so we do not have to check recursively for visibility
if (!child->isVisible(false))
continue;
KoShapeGroup *childGroup = dynamic_cast(child);
if (childGroup) {
paintGroup(childGroup, painter, converter, paintContext);
} else {
painter.save();
KoShapeManager::renderSingleShape(child, painter, converter, paintContext);
painter.restore();
}
}
}
KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList &shapes)
: d(new Private(this, canvas))
{
Q_ASSERT(d->canvas); // not optional.
connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
setShapes(shapes);
/**
* Shape manager uses signal compressors with timers, therefore
* it might handle queued signals, therefore it should belong
* to the GUI thread.
*/
this->moveToThread(qApp->thread());
}
KoShapeManager::KoShapeManager(KoCanvasBase *canvas)
: d(new Private(this, canvas))
{
Q_ASSERT(d->canvas); // not optional.
connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
// see a comment in another constructor
this->moveToThread(qApp->thread());
}
void KoShapeManager::Private::unlinkFromShapesRecursively(const QList &shapes)
{
Q_FOREACH (KoShape *shape, shapes) {
shape->removeShapeManager(q);
KoShapeContainer *container = dynamic_cast(shape);
if (container) {
unlinkFromShapesRecursively(container->shapes());
}
}
}
KoShapeManager::~KoShapeManager()
{
d->unlinkFromShapesRecursively(d->shapes);
d->shapes.clear();
delete d;
}
void KoShapeManager::setShapes(const QList &shapes, Repaint repaint)
{
- QMutexLocker l(&d->mutex);
-
- //clear selection
- d->selection->deselectAll();
- d->unlinkFromShapesRecursively(d->shapes);
- d->aggregate4update.clear();
- d->tree.clear();
- d->shapes.clear();
+ {
+ QMutexLocker l1(&d->shapesMutex);
+ QMutexLocker l2(&d->treeMutex);
+
+ //clear selection
+ d->selection->deselectAll();
+ d->unlinkFromShapesRecursively(d->shapes);
+ d->aggregate4update.clear();
+ d->shapeIndexesBeforeUpdate.clear();
+ d->tree.clear();
+ d->shapes.clear();
+ }
Q_FOREACH (KoShape *shape, shapes) {
addShape(shape, repaint);
}
}
void KoShapeManager::addShape(KoShape *shape, Repaint repaint)
{
- QMutexLocker l(&d->mutex);
+ {
+ QMutexLocker l1(&d->shapesMutex);
- if (d->shapes.contains(shape))
- return;
- shape->addShapeManager(this);
- d->shapes.append(shape);
+ if (d->shapes.contains(shape))
+ return;
+ shape->addShapeManager(this);
+ d->shapes.append(shape);
- if (d->shapeUsedInRenderingTree(shape)) {
- QRectF br(shape->boundingRect());
- d->tree.insert(br, shape);
+ if (d->shapeUsedInRenderingTree(shape)) {
+ QMutexLocker l2(&d->treeMutex);
+
+ QRectF br(shape->boundingRect());
+ d->tree.insert(br, shape);
+ }
}
if (repaint == PaintShapeOnAdd) {
shape->update();
}
// add the children of a KoShapeContainer
KoShapeContainer *container = dynamic_cast(shape);
if (container) {
foreach (KoShape *containerShape, container->shapes()) {
addShape(containerShape, repaint);
}
}
- Private::DetectCollision detector;
- detector.detect(d->tree, shape, shape->zIndex());
- detector.fireSignals();
+ {
+ QMutexLocker l(&d->treeMutex);
+
+ Private::DetectCollision detector;
+ detector.detect(d->tree, shape, shape->zIndex());
+ detector.fireSignals();
+ }
}
void KoShapeManager::remove(KoShape *shape)
{
- QMutexLocker l(&d->mutex);
+ {
+ QMutexLocker l1(&d->shapesMutex);
+ QMutexLocker l2(&d->treeMutex);
- Private::DetectCollision detector;
- detector.detect(d->tree, shape, shape->zIndex());
- detector.fireSignals();
+ Private::DetectCollision detector;
+ detector.detect(d->tree, shape, shape->zIndex());
+ detector.fireSignals();
- shape->update();
- shape->removeShapeManager(this);
- d->selection->deselect(shape);
- d->aggregate4update.remove(shape);
+ shape->update();
+ shape->removeShapeManager(this);
+ d->selection->deselect(shape);
+ d->aggregate4update.remove(shape);
- if (d->shapeUsedInRenderingTree(shape)) {
- d->tree.remove(shape);
+ if (d->shapeUsedInRenderingTree(shape)) {
+ d->tree.remove(shape);
+ }
+ d->shapes.removeAll(shape);
}
- d->shapes.removeAll(shape);
// remove the children of a KoShapeContainer
KoShapeContainer *container = dynamic_cast(shape);
if (container) {
foreach (KoShape *containerShape, container->shapes()) {
remove(containerShape);
}
}
}
KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q)
: q(_q)
{
}
void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape)
{
- QMutexLocker l(&q->d->mutex);
+ QMutexLocker l1(&q->d->shapesMutex);
+ QMutexLocker l2(&q->d->treeMutex);
q->d->selection->deselect(shape);
q->d->aggregate4update.remove(shape);
// we cannot access RTTI of the semi-destructed shape, so just
// unlink it lazily
if (q->d->tree.contains(shape)) {
q->d->tree.remove(shape);
}
q->d->shapes.removeAll(shape);
}
KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface()
{
return &d->shapeInterface;
}
void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint)
{
- QMutexLocker l(&d->mutex);
+ QMutexLocker l1(&d->shapesMutex);
d->updateTree();
painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off.
painter.setBrush(Qt::NoBrush);
QList unsortedShapes;
if (painter.hasClipping()) {
+ QMutexLocker l(&d->treeMutex);
+
QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter));
unsortedShapes = d->tree.intersects(rect);
} else {
unsortedShapes = shapes();
warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!";
}
// filter all hidden shapes from the list
// also filter shapes with a parent which has filter effects applied
QList sortedShapes;
foreach (KoShape *shape, unsortedShapes) {
if (!shape->isVisible())
continue;
bool addShapeToList = true;
// check if one of the shapes ancestors have filter effects
KoShapeContainer *parent = shape->parent();
while (parent) {
// parent must be part of the shape manager to be taken into account
if (!d->shapes.contains(parent))
break;
if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) {
addShapeToList = false;
break;
}
parent = parent->parent();
}
if (addShapeToList) {
sortedShapes.append(shape);
} else if (parent) {
sortedShapes.append(parent);
}
}
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
foreach (KoShape *shape, sortedShapes) {
renderSingleShape(shape, painter, converter, paintContext);
}
#ifdef CALLIGRA_RTREE_DEBUG
// paint tree
qreal zx = 0;
qreal zy = 0;
converter.zoom(&zx, &zy);
painter.save();
painter.scale(zx, zy);
d->tree.paint(painter);
painter.restore();
#endif
if (! forPrint) {
KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
d->selection->paint(painter, converter, paintContext);
}
}
void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
KisQPainterStateSaver saver(&painter);
// apply shape clipping
KoClipPath::applyClipping(shape, painter, converter);
// apply transformation
painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform());
// paint the shape
paintShape(shape, painter, converter, paintContext);
}
void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
qreal transparency = shape->transparency(true);
if (transparency > 0.0) {
painter.setOpacity(1.0-transparency);
}
if (shape->shadow()) {
painter.save();
shape->shadow()->paint(shape, painter, converter);
painter.restore();
}
if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) {
QScopedPointer clipMaskPainter;
QPainter *shapePainter = &painter;
KoClipMask *clipMask = shape->clipMask();
if (clipMask) {
clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect()));
shapePainter = clipMaskPainter->shapePainter();
}
/**
* We expect the shape to save/restore the painter's state itself. Such design was not
* not always here, so we need a period of sanity checks to ensure all the shapes are
* ported correctly.
*/
const QTransform sanityCheckTransformSaved = shapePainter->transform();
shape->paint(*shapePainter, converter, paintContext);
shape->paintStroke(*shapePainter, converter, paintContext);
KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) {
shapePainter->setTransform(sanityCheckTransformSaved);
}
if (clipMask) {
shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape);
clipMaskPainter->renderOnGlobalPainter();
}
} else {
// TODO: clipping mask is not implemented for this case!
// There are filter effects, then we need to prerender the shape on an image, to filter it
QRectF shapeBound(QPointF(), shape->size());
// First step, compute the rectangle used for the image
QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound);
// convert clip region to view coordinates
QRectF zoomedClipRegion = converter.documentToView(clipRegion);
// determine the offset of the clipping rect from the shapes origin
QPointF clippingOffset = zoomedClipRegion.topLeft();
// Initialize the buffer image
QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied);
sourceGraphic.fill(qRgba(0,0,0,0));
QHash imageBuffers;
QSet requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs();
if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) {
// Init the buffer painter
QPainter imagePainter(&sourceGraphic);
imagePainter.translate(-1.0f*clippingOffset);
imagePainter.setPen(Qt::NoPen);
imagePainter.setBrush(Qt::NoBrush);
imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing));
// Paint the shape on the image
KoShapeGroup *group = dynamic_cast(shape);
if (group) {
// the childrens matrix contains the groups matrix as well
// so we have to compensate for that before painting the children
imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true);
Private::paintGroup(group, imagePainter, converter, paintContext);
} else {
imagePainter.save();
shape->paint(imagePainter, converter, paintContext);
shape->paintStroke(imagePainter, converter, paintContext);
imagePainter.restore();
imagePainter.end();
}
}
if (requiredStdInputs.contains("SourceAlpha")) {
QImage sourceAlpha = sourceGraphic;
sourceAlpha.fill(qRgba(0,0,0,255));
sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel());
imageBuffers.insert("SourceAlpha", sourceAlpha);
}
if (requiredStdInputs.contains("FillPaint")) {
QImage fillPaint = sourceGraphic;
if (shape->background()) {
QPainter fillPainter(&fillPaint);
QPainterPath fillPath;
fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1));
shape->background()->paint(fillPainter, converter, paintContext, fillPath);
} else {
fillPaint.fill(qRgba(0,0,0,0));
}
imageBuffers.insert("FillPaint", fillPaint);
}
imageBuffers.insert("SourceGraphic", sourceGraphic);
imageBuffers.insert(QString(), sourceGraphic);
KoFilterEffectRenderContext renderContext(converter);
renderContext.setShapeBoundingBox(shapeBound);
QImage result;
QList filterEffects = shape->filterEffectStack()->filterEffects();
// Filter
foreach (KoFilterEffect *filterEffect, filterEffects) {
QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound);
filterRegion = converter.documentToView(filterRegion);
QRect subRegion = filterRegion.translated(-clippingOffset).toRect();
// set current filter region
renderContext.setFilterRegion(subRegion & sourceGraphic.rect());
if (filterEffect->maximalInputCount() <= 1) {
QList inputs = filterEffect->inputs();
QString input = inputs.count() ? inputs.first() : QString();
// get input image from image buffers and apply the filter effect
QImage image = imageBuffers.value(input);
if (!image.isNull()) {
result = filterEffect->processImage(imageBuffers.value(input), renderContext);
}
} else {
QList inputImages;
Q_FOREACH (const QString &input, filterEffect->inputs()) {
QImage image = imageBuffers.value(input);
if (!image.isNull())
inputImages.append(imageBuffers.value(input));
}
// apply the filter effect
if (filterEffect->inputs().count() == inputImages.count())
result = filterEffect->processImages(inputImages, renderContext);
}
// store result of effect
imageBuffers.insert(filterEffect->output(), result);
}
KoFilterEffect *lastEffect = filterEffects.last();
// Paint the result
painter.save();
painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output()));
painter.restore();
}
}
KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes)
{
- QMutexLocker l(&d->mutex);
+ QMutexLocker l(&d->shapesMutex);
d->updateTree();
- QList sortedShapes(d->tree.contains(position));
+ QList sortedShapes;
+
+ {
+ QMutexLocker l(&d->treeMutex);
+ sortedShapes = d->tree.contains(position);
+ }
+
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
KoShape *firstUnselectedShape = 0;
for (int count = sortedShapes.count() - 1; count >= 0; count--) {
KoShape *shape = sortedShapes.at(count);
if (omitHiddenShapes && ! shape->isVisible())
continue;
if (! shape->hitTest(position))
continue;
switch (selection) {
case KoFlake::ShapeOnTop:
if (shape->isSelectable())
return shape;
break;
case KoFlake::Selected:
if (d->selection->isSelected(shape))
return shape;
break;
case KoFlake::Unselected:
if (! d->selection->isSelected(shape))
return shape;
break;
case KoFlake::NextUnselected:
// we want an unselected shape
if (d->selection->isSelected(shape))
continue;
// memorize the first unselected shape
if (! firstUnselectedShape)
firstUnselectedShape = shape;
// check if the shape above is selected
if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1)))
return shape;
break;
}
}
// if we want the next unselected below a selected but there was none selected,
// return the first found unselected shape
if (selection == KoFlake::NextUnselected && firstUnselectedShape)
return firstUnselectedShape;
if (d->selection->hitTest(position))
return d->selection;
return 0; // missed everything
}
QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode)
{
- QMutexLocker l(&d->mutex);
+ QMutexLocker l(&d->shapesMutex);
d->updateTree();
- QList shapes(containedMode ? d->tree.contained(rect) : d->tree.intersects(rect));
+ QList shapes;
+
+ {
+ QMutexLocker l(&d->treeMutex);
+ shapes = containedMode ? d->tree.contained(rect) : d->tree.intersects(rect);
+ }
for (int count = shapes.count() - 1; count >= 0; count--) {
KoShape *shape = shapes.at(count);
if (omitHiddenShapes && !shape->isVisible()) {
shapes.removeAt(count);
} else {
const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline());
if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) {
shapes.removeAt(count);
} else if (containedMode) {
QPainterPath containingPath;
containingPath.addRect(rect);
if (!containingPath.contains(outline)) {
shapes.removeAt(count);
}
}
}
}
return shapes;
}
void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles)
{
// TODO: do we need locking here?
d->canvas->updateCanvas(rect);
if (selectionHandles && d->selection->isSelected(shape)) {
if (d->canvas->toolProxy())
d->canvas->toolProxy()->repaintDecorations();
}
}
void KoShapeManager::notifyShapeChanged(KoShape *shape)
{
- QMutexLocker l(&d->mutex);
+ {
+ QMutexLocker l(&d->treeMutex);
- Q_ASSERT(shape);
- if (d->aggregate4update.contains(shape)) {
- return;
- }
+ Q_ASSERT(shape);
+ if (d->aggregate4update.contains(shape)) {
+ return;
+ }
- d->aggregate4update.insert(shape);
- d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex());
+ d->aggregate4update.insert(shape);
+ d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex());
+ }
KoShapeContainer *container = dynamic_cast(shape);
if (container) {
Q_FOREACH (KoShape *child, container->shapes())
notifyShapeChanged(child);
}
}
QList KoShapeManager::shapes() const
{
- QMutexLocker l(&d->mutex);
+ QMutexLocker l(&d->shapesMutex);
return d->shapes;
}
QList KoShapeManager::topLevelShapes() const
{
- QMutexLocker l(&d->mutex);
+ QMutexLocker l(&d->shapesMutex);
QList shapes;
// get all toplevel shapes
Q_FOREACH (KoShape *shape, d->shapes) {
if (!shape->parent() || dynamic_cast(shape->parent())) {
shapes.append(shape);
}
}
return shapes;
}
KoSelection *KoShapeManager::selection() const
{
return d->selection;
}
KoCanvasBase *KoShapeManager::canvas()
{
return d->canvas;
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoShapeManager.cpp"
diff --git a/libs/flake/KoShapeManager_p.h b/libs/flake/KoShapeManager_p.h
index 067da39630..c6cbcdf60b 100644
--- a/libs/flake/KoShapeManager_p.h
+++ b/libs/flake/KoShapeManager_p.h
@@ -1,122 +1,123 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann
Copyright (C) 2006-2010 Thomas Zander
Copyright (C) 2009-2010 Jan Hambrecht
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KoShapeManager_p_h
#define KoShapeManager_p_h
#include "KoSelection.h"
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeContainer.h"
#include "KoShapeManager.h"
#include
#include
class KoCanvasBase;
class KoShapeGroup;
class KoShapePaintingContext;
class QPainter;
class Q_DECL_HIDDEN KoShapeManager::Private
{
public:
Private(KoShapeManager *shapeManager, KoCanvasBase *c)
: selection(new KoSelection(shapeManager)),
canvas(c),
tree(4, 2),
q(shapeManager),
shapeInterface(shapeManager)
{
}
~Private() {
delete selection;
}
/**
* Update the tree when there are shapes in m_aggregate4update. This is done so not all
* updates to the tree are done when they are asked for but when they are needed.
*/
void updateTree();
/**
* Returns whether the shape should be added to the RTree for collision and ROI
* detection.
*/
bool shapeUsedInRenderingTree(KoShape *shape);
/**
* Recursively detach the shapes from this shape manager
*/
void unlinkFromShapesRecursively(const QList &shapes);
/**
* Recursively paints the given group shape to the specified painter
* This is needed for filter effects on group shapes where the filter effect
* applies to all the children of the group shape at once
*/
static void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
class DetectCollision
{
public:
DetectCollision() {}
void detect(KoRTree &tree, KoShape *s, int prevZIndex) {
Q_FOREACH (KoShape *shape, tree.intersects(s->boundingRect())) {
bool isChild = false;
KoShapeContainer *parent = s->parent();
while (parent && !isChild) {
if (parent == shape)
isChild = true;
parent = parent->parent();
}
if (isChild)
continue;
if (s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex())
// Moving a shape will only make it collide with shapes below it.
continue;
if (shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape))
shapesWithCollisionDetection.append(shape);
}
}
void fireSignals() {
Q_FOREACH (KoShape *shape, shapesWithCollisionDetection)
shape->shapeChangedPriv(KoShape::CollisionDetected);
}
private:
QList shapesWithCollisionDetection;
};
QList shapes;
KoSelection *selection;
KoCanvasBase *canvas;
KoRTree tree;
QSet aggregate4update;
QHash shapeIndexesBeforeUpdate;
KoShapeManager *q;
KoShapeManager::ShapeInterface shapeInterface;
- QMutex mutex;
+ QMutex shapesMutex;
+ QMutex treeMutex;
};
#endif
diff --git a/libs/image/tests/data/rotation_120_result.png b/libs/image/tests/data/rotation_120_result.png
index b3b83c8982..c003a9eacf 100644
Binary files a/libs/image/tests/data/rotation_120_result.png and b/libs/image/tests/data/rotation_120_result.png differ
diff --git a/libs/image/tests/data/rotation_210_result.png b/libs/image/tests/data/rotation_210_result.png
index 393efdd777..3fb2c285bf 100644
Binary files a/libs/image/tests/data/rotation_210_result.png and b/libs/image/tests/data/rotation_210_result.png differ
diff --git a/libs/image/tests/data/rotation_300_result.png b/libs/image/tests/data/rotation_300_result.png
index 38944a451e..928376768c 100644
Binary files a/libs/image/tests/data/rotation_300_result.png and b/libs/image/tests/data/rotation_300_result.png differ
diff --git a/libs/image/tests/data/rotation_30_result.png b/libs/image/tests/data/rotation_30_result.png
index dcc99a5e64..e9befff875 100644
Binary files a/libs/image/tests/data/rotation_30_result.png and b/libs/image/tests/data/rotation_30_result.png differ
diff --git a/libs/image/tests/data/rotation_60_result.png b/libs/image/tests/data/rotation_60_result.png
index 0f07fd48e9..8875416173 100644
Binary files a/libs/image/tests/data/rotation_60_result.png and b/libs/image/tests/data/rotation_60_result.png differ
diff --git a/libs/image/tests/data/rotation_90_result.png b/libs/image/tests/data/rotation_90_result.png
index a3f2341cbb..aececb3ee6 100644
Binary files a/libs/image/tests/data/rotation_90_result.png and b/libs/image/tests/data/rotation_90_result.png differ
diff --git a/libs/image/tests/data/scaledownx_result.png b/libs/image/tests/data/scaledownx_result.png
index 1d708e2b29..05819a2713 100644
Binary files a/libs/image/tests/data/scaledownx_result.png and b/libs/image/tests/data/scaledownx_result.png differ
diff --git a/libs/image/tests/data/scaledowny_result.png b/libs/image/tests/data/scaledowny_result.png
index 8ac041caf3..dbe8faa3ee 100644
Binary files a/libs/image/tests/data/scaledowny_result.png and b/libs/image/tests/data/scaledowny_result.png differ
diff --git a/libs/image/tests/data/test_scaledown_result.png b/libs/image/tests/data/test_scaledown_result.png
index 409fac410d..037e9b6359 100644
Binary files a/libs/image/tests/data/test_scaledown_result.png and b/libs/image/tests/data/test_scaledown_result.png differ
diff --git a/libs/image/tests/kis_transform_worker_test.cpp b/libs/image/tests/kis_transform_worker_test.cpp
index 0613bd4967..48dfea4172 100644
--- a/libs/image/tests/kis_transform_worker_test.cpp
+++ b/libs/image/tests/kis_transform_worker_test.cpp
@@ -1,997 +1,993 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* 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_transform_worker_test.h"
#include
#include
#include
#include
#include
#include
#include
#include "kis_types.h"
#include "kis_image.h"
#include "kis_filter_strategy.h"
#include "kis_paint_device.h"
#include "kis_transform_worker.h"
#include "testutil.h"
#include "kis_transaction.h"
#include "kis_random_accessor_ng.h"
void KisTransformWorkerTest::testCreation()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
KisTransformWorker tw(dev, 1.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.5,
0, 0, updater, filter);
}
void testMirror(const QRect &imageRect, const QRect &mirrorRect, Qt::Orientation orientation)
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
qreal axis = QRectF(mirrorRect).center().x();
KisRandomAccessorSP it = dev->createRandomAccessorNG(imageRect.x(), imageRect.y());
int i = 0;
for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++) {
for (int x = imageRect.x(); x < imageRect.x() + imageRect.width(); x++) {
it->moveTo(x, y);
*reinterpret_cast(it->rawData()) = 0xFFFFFFFF - i++;
}
}
QCOMPARE(dev->exactBounds(), imageRect);
QImage srcImage = dev->convertToQImage(0, mirrorRect.x(), mirrorRect.y(), mirrorRect.width(), mirrorRect.height());
QImage mirroredImage = srcImage.mirrored(orientation == Qt::Horizontal, orientation == Qt::Vertical);
QImage result;
//srcImage.save("input.png");
//mirroredImage.save("mirror_expected.png");
QBENCHMARK_ONCE {
KisTransformWorker::mirror(dev, axis, orientation);
}
result = dev->convertToQImage(0, mirrorRect.x(), mirrorRect.y(), mirrorRect.width(), mirrorRect.height());
QCOMPARE(result, mirroredImage);
//result.save("mirror1.png");
KisTransformWorker::mirror(dev, axis, orientation);
result = dev->convertToQImage(0, mirrorRect.x(), mirrorRect.y(), mirrorRect.width(), mirrorRect.height());
QCOMPARE(result, srcImage);
//result.save("mirror2.png");
KisTransformWorker::mirror(dev, axis, orientation);
result = dev->convertToQImage(0, mirrorRect.x(), mirrorRect.y(), mirrorRect.width(), mirrorRect.height());
QCOMPARE(result, mirroredImage);
//result.save("mirror3.png");
}
void KisTransformWorkerTest::testMirrorX_Even()
{
testMirror(QRect(10,10,30,30), QRect(1,1,70,70), Qt::Horizontal);
}
void KisTransformWorkerTest::testMirrorX_Odd()
{
testMirror(QRect(10,10,30,30), QRect(1,1,71,71), Qt::Horizontal);
}
void KisTransformWorkerTest::testMirrorY_Even()
{
testMirror(QRect(10,10,30,30), QRect(1,1,70,70), Qt::Vertical);
}
void KisTransformWorkerTest::testMirrorY_Odd()
{
testMirror(QRect(10,10,30,30), QRect(1,1,71,71), Qt::Vertical);
}
void KisTransformWorkerTest::benchmarkMirrorX()
{
testMirror(QRect(10,10,4000,4000), QRect(1,1,7000,7000), Qt::Horizontal);
}
void KisTransformWorkerTest::benchmarkMirrorY()
{
testMirror(QRect(10,10,4000,4000), QRect(1,1,7000,7000), Qt::Vertical);
}
void KisTransformWorkerTest::testOffset()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QString imageName("mirror_source.png");
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + imageName);
QPoint bottomRight(image.width(), image.height());
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
QVector offsetPoints;
offsetPoints.append(QPoint(image.width() / 2, image.height() / 2)); // offset to 1/2 of image
offsetPoints.append(QPoint(image.width() / 4, image.height() / 4)); // offset to 1/4 of image
offsetPoints.append(QPoint(image.width() - image.width() / 4, image.height() - image.height() / 4)); // offset to 3/4 of image
offsetPoints.append(QPoint(image.width() / 4, 0)); // offset with y == 0
offsetPoints.append(QPoint(0, image.height() / 4)); // offset with x == 0
QPoint errpoint;
QPoint backOffsetPoint;
QImage result;
int test = 0;
QPoint origin(0,0);
Q_FOREACH (QPoint offsetPoint, offsetPoints)
{
dev2->convertFromQImage(image, 0);
KisTransformWorker::offset(dev2, offsetPoint, QRect(origin, image.size()) );
backOffsetPoint = bottomRight - offsetPoint;
KisTransformWorker::offset(dev2, backOffsetPoint , QRect(origin, image.size()) );
result = dev2->convertToQImage(0, 0, 0, image.width(), image.height());
if (!TestUtil::compareQImages(errpoint, image, result))
{
// They are the same, but should be mirrored
image.save(QString("offset_test_%1_source.png").arg(test));
result.save(QString("offset_test_%1_result.png").arg(test));
QFAIL(QString("Failed to offset the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
}
void KisTransformWorkerTest::testMirrorTransactionX()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
dev2->convertFromQImage(image, 0);
KisTransaction t(kundo2_noi18n("mirror"), dev2);
KisTransformWorker::mirrorX(dev2);
t.end();
QImage result = dev2->convertToQImage(0, 0, 0, image.width(), image.height());
image = image.mirrored(true, false);
QPoint errpoint;
if (!TestUtil::compareQImages(errpoint, image, result)) {
// They are the same, but should be mirrored
image.save("mirror_test_3_source.png");
result.save("mirror_test_3_result.png");
QFAIL(QString("Failed to mirror the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testMirrorTransactionY()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
dev2->convertFromQImage(image, 0);
KisTransaction t(kundo2_noi18n("mirror"), dev2);
KisTransformWorker::mirrorY(dev2);
t.end();
QImage result = dev2->convertToQImage(0, 0, 0, image.width(), image.height());
image = image.mirrored(false, true);
QPoint errpoint;
if (!TestUtil::compareQImages(errpoint, image, result)) {
// They are the same, but should be mirrored
image.save("mirror_test_4_source.png");
result.save("mirror_test_4_result.png");
QFAIL(QString("Failed to mirror the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testScaleUp()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 2.4, 2.4,
0.0, 0.0,
0.0, 0.0,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QCOMPARE(rc.width(), qCeil(image.width() * 2.4));
QCOMPARE(rc.height(), qCeil(image.height() * 2.4));
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
image.load(QString(FILES_DATA_DIR) + QDir::separator() + "test_scaleup_result.png");
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("test_scaleup_source.png");
result.save("test_scaleup_result.png");
QFAIL(QString("Failed to scale the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testXScaleUp()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 2.0, 1.0,
0.0, 0.0,
0.0, 0.0,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QVERIFY(rc.width() == image.width() * 2);
QVERIFY(rc.height() == image.height());
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
image.load(QString(FILES_DATA_DIR) + QDir::separator() + "scaleupx_result.png");
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("test_x_scaleup_source.png");
result.save("test_x_scaleup_result.png");
QFAIL(QString("Failed to scale up the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testYScaleUp()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 1.0, 2.0,
0.0, 0.0,
0.0, 0.0,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QCOMPARE(rc.width(), image.width());
QCOMPARE(rc.height(), image.height() * 2);
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
image.load(QString(FILES_DATA_DIR) + QDir::separator() + "scaleupy_result.png");
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("test_y_scaleup_source.png");
result.save("test_y_scaleup_result.png");
QFAIL(QString("Failed to scale up the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testIdentity()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 1.0, 1.0,
0.0, 0.0,
0.0, 0.0,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QVERIFY(rc.width() ==image.width());
QVERIFY(rc.height() == image.height());
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("test_identity_source.png");
result.save("test_identity_result.png");
QFAIL(QString("Failed to apply identity transformation to image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testScaleDown()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 0.123, 0.123,
0.0, 0.0,
0.0, 0.0,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QCOMPARE(rc.width(), qCeil(image.width() * 0.123));
QCOMPARE(rc.height(), qCeil(image.height() * 0.123));
// KisTransaction t2("test", dev);
// KisRandomAccessorSP ac = dev->createRandomAccessorNG(rc.x(), rc.y());
// for(int x = rc.x(); x < rc.width(); ++x) {
// for(int y = rc.y(); y < rc.height(); ++y) {
// ac->moveTo(x, y);
// cs->setOpacity(ac->rawData(), 0.5, 1);
// }
// }
// t2.end();
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
image.load(QString(FILES_DATA_DIR) + QDir::separator() + "test_scaledown_result.png");
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("test_scaledown_source.png");
result.save("test_scaledown_result.png");
QFAIL(QString("Failed to scale down the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testXScaleDown()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 0.123, 1.0,
0.0, 0.0,
0.0, 0.0,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
-
- QCOMPARE(rc.width(), qCeil(image.width() * 0.123));
- QCOMPARE(rc.height(), image.height());
-
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
image.load(QString(FILES_DATA_DIR) + QDir::separator() + "scaledownx_result.png");
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("scaledownx_source.png");
result.save("scaledownx_result.png");
QFAIL(QString("Failed to scale down the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testYScaleDown()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 1.0, 0.123,
0.0, 0.0,
0.0, 0.0,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QCOMPARE(rc.width(), image.width());
QCOMPARE(rc.height(), qCeil(image.height() * 0.123));
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
image.load(QString(FILES_DATA_DIR) + QDir::separator() + "scaledowny_result.png");
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("scaledowny_source.png");
result.save("scaledowny_result.png");
QFAIL(QString("Failed to scale down the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testXShear()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 1.0, 1.0,
1.0, 0.0,
300., 200.,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QVERIFY(rc.width() == 959);
QVERIFY(rc.height() == image.height());
// KisTransaction t2("test", dev);
// KisRandomAccessorSP ac = dev->createRandomAccessorNG(rc.x(), rc.y());
// for(int x = rc.x(); x < rc.width(); ++x) {
// for(int y = rc.y(); y < rc.height(); ++y) {
// ac->moveTo(x, y);
// cs->setOpacity(ac->rawData(), 0.5, 1);
// }
// }
// t2.end();
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
image.load(QString(FILES_DATA_DIR) + QDir::separator() + "shearx_result.png");
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("shearx_source.png");
result.save("shearx_result.png");
QFAIL(QString("Failed to shear the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testYShear()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBoxFilterStrategy();
KisTransaction t(dev);
KisTransformWorker tw(dev, 1.0, 1.0,
0.0, 1.0,
300., 200.,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QVERIFY(rc.width() == image.width());
QVERIFY(rc.height() == 959);
// KisTransaction t2("test", dev);
// KisRandomAccessorSP ac = dev->createRandomAccessorNG(rc.x(), rc.y());
// for(int x = rc.x(); x < rc.width(); ++x) {
// for(int y = rc.y(); y < rc.height(); ++y) {
// ac->moveTo(x, y);
// cs->setOpacity(ac->rawData(), 0.5, 1);
// }
// }
// t2.end();
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
image.load(QString(FILES_DATA_DIR) + QDir::separator() + "sheary_result.png");
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("sheary_source.png");
result.save("sheary_result.png");
QFAIL(QString("Failed to shear the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
bool fuzzyCompareRects(const QRectF rc1, const QRectF rc2, qreal accuracy)
{
bool result =
qAbs(rc1.x() - rc2.x()) < accuracy &&
qAbs(rc1.y() - rc2.y()) < accuracy &&
qAbs(rc1.width() - rc2.width()) < 2 * accuracy &&
qAbs(rc1.height() - rc2.height()) < 2 * accuracy;
if(!result) {
dbgKrita << "Failed to fuzzy compare rects";
dbgKrita << "\t" << ppVar(accuracy);
dbgKrita << "\t" << "actual " << rc1;
dbgKrita << "\t" << "expected" << rc2;
dbgKrita << "+---------------------------+";
}
return result;
}
void KisTransformWorkerTest::testMatrices()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
KisFilterStrategy *filter = new KisBoxFilterStrategy();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QRect fillRect(0,0,300,200);
KoColor fillColor(Qt::white, cs);
dev->fill(fillRect, fillColor);
qreal scaleX = 1.5, scaleY = 1.5;
qreal shearX = 1, shearY = 1.33;
qreal shearOrigX = 150, shearOrigY = 100;
qreal angle = M_PI/6;
qreal transX = 77, transY = 33;
KisTransaction t(dev);
KisTransformWorker tw(dev, scaleX, scaleY,
shearX, shearY,
shearOrigX, shearOrigY,
angle,
transX, transY,
updater, filter);
tw.run();
t.end();
QPolygonF referencePolygon =
tw.transform().map(QPolygonF(QRectF(fillRect)));
QVERIFY(fuzzyCompareRects(dev->exactBounds(),
referencePolygon.boundingRect(), 3));
}
void testRotationImpl(qreal angle, QString filePrefix, bool useUniformColor = false, const QString &filterId = "NearestNeighbor")
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QImage image;
if (!useUniformColor) {
image = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "mirror_source.png");
dev->convertFromQImage(image, 0);
} else {
dev->fill(QRect(120, 130, 374, 217), KoColor(QColor(150, 180, 230), cs));
}
KisFilterStrategy * filter = KisFilterStrategyRegistry::instance()->value(filterId);
Q_ASSERT(filter);
KisTransaction t(dev);
KisTransformWorker tw(dev, 1.0, 1.0,
0.0, 0.0,
0.0, 0.0,
angle,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QPoint errpoint;
QString resFileName = QString("%1_result.png").arg(filePrefix);
QString refFileName = QString("%1_expected.png").arg(filePrefix);
image = QImage();
image.load(QString(FILES_DATA_DIR) + QDir::separator() + resFileName);
if (!TestUtil::compareQImages(errpoint, image, result)) {
dbgKrita << filePrefix;
image.save(refFileName);
result.save(resFileName);
QFAIL(QString("Failed to rotate the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::testRotation()
{
testRotationImpl(M_PI/6, "rotation_30");
testRotationImpl(M_PI/3, "rotation_60");
testRotationImpl(M_PI/2, "rotation_90");
testRotationImpl(2*M_PI/3, "rotation_120");
testRotationImpl(7*M_PI/6, "rotation_210");
testRotationImpl(5*M_PI/3, "rotation_300");
testRotationImpl(M_PI/6, "rotation_30_uniform_blin", true, "Bilinear");
testRotationImpl(M_PI/6, "rotation_30_uniform_bcub", true, "Bicubic");
testRotationImpl(M_PI/6, "rotation_30_uniform_lanc", true, "Lanczos3");
}
void KisTransformWorkerTest::testRotationSpecialCases()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
KisFilterStrategy *filter = new KisBoxFilterStrategy();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QRect fillRect(0,0,600,300);
KoColor fillColor(Qt::white, cs);
dev->fill(fillRect, fillColor);
qreal scaleX = 0.5, scaleY = 0.5;
qreal shearX = 0, shearY = 0;
qreal shearOrigX = 0, shearOrigY = 0;
qreal angle = M_PI;
qreal transX = 300, transY = 150;
KisTransaction t(dev);
KisTransformWorker tw(dev, scaleX, scaleY,
shearX, shearY,
shearOrigX, shearOrigY,
angle,
transX, transY,
updater, filter);
tw.run();
t.end();
QCOMPARE(dev->exactBounds(), QRect(0,0,300,150));
}
void KisTransformWorkerTest::testScaleUp5times()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
QImage image(QSize(2000,2000), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(Qt::green).rgba());
int checkSize = 20;
QImage tile(checkSize * 2, checkSize * 2, QImage::Format_ARGB32_Premultiplied);
QPainter pt(&tile);
pt.fillRect(tile.rect(), Qt::green);
pt.fillRect(0, 0, checkSize, checkSize, Qt::white);
pt.fillRect(checkSize, checkSize, checkSize, checkSize, Qt::white);
pt.end();
pt.begin(&image);
pt.setBrush(QBrush(tile));
pt.drawRect(image.rect());
pt.end();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisFilterStrategy * filter = new KisBicubicFilterStrategy();
KisTransaction t(dev);
qreal SCALE = 5.0;
KisTransformWorker tw(dev, SCALE, SCALE,
0.0, 0.0,
0.0, 0.0,
0.0,
0, 0, updater, filter);
tw.run();
t.end();
QRect rc = dev->exactBounds();
#if 0
// here you can check the input and result images
image.save("test_scale_2000_2000_input.bmp");
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
result.save("test_scale_2000_2000_" + QString::number(SCALE) + "_result.bmp");
#endif
QCOMPARE(rc.width(), qCeil(image.width() * SCALE));
QCOMPARE(rc.height(), qCeil(image.height() * SCALE));
}
void KisTransformWorkerTest::rotate90Left()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "transform_rotate_test.png");
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
dev2->convertFromQImage(image, 0);
QRect boundRect = dev2->exactBounds();
KisTransaction t(kundo2_noi18n("rotate left 90"), dev2);
QRect rc = KisTransformWorker::rotateLeft90(dev2, boundRect, 0, 0);
t.end();
QImage result = dev2->convertToQImage(0, rc.x(), rc.y(), image.width(), image.height());
QTransform tf;
QImage rotatedimage = image.transformed(tf.rotate(270));
QPoint errpoint;
if (!TestUtil::compareQImages(errpoint, rotatedimage, result)) {
// They are the same, but should be mirrored
image.save("rotate_90_left_test_1_source.png");
rotatedimage.save("rotate_90_left_test_1_rotated_source.png");
result.save("rotate_90_left_test_1_result.png");
QFAIL(QString("Failed to rotate 90 left the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::rotate90Right()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "transform_rotate_test.png");
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
dev2->convertFromQImage(image, 0);
QRect boundRect = dev2->exactBounds();
KisTransaction t(kundo2_noi18n("rotate right 90"), dev2);
QRect rc = KisTransformWorker::rotateRight90(dev2, boundRect, 0, 0);
t.end();
QTransform tf;
QImage rotatedimage = image.transformed(tf.rotate(90));
QImage result = dev2->convertToQImage(0, rc.x(), rc.y(), image.width(), image.height());
QPoint errpoint;
if (!TestUtil::compareQImages(errpoint, rotatedimage, result)) {
// They are the same, but should be mirrored
image.save("rotate_90_right_test_1_source.png");
rotatedimage.save("rotate_90_right_test_1_rotated_source.png");
result.save("rotate_90_right_1_result.png");
QFAIL(QString("Failed to rotate 90 right the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisTransformWorkerTest::rotate180()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "transform_rotate_test.png");
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
dev2->convertFromQImage(image, 0);
QRect boundRect = dev2->exactBounds();
KisTransaction t(kundo2_noi18n("rotate 180"), dev2);
QRect rc = KisTransformWorker::rotate180(dev2, boundRect, 0, 0);
t.end();
QImage result = dev2->convertToQImage(0, rc.x(), rc.y(), image.width(), image.height());
QTransform tf;
QImage rotatedimage = image.transformed(tf.rotate(180));
QPoint errpoint;
if (!TestUtil::compareQImages(errpoint, rotatedimage, result)) {
// They are the same, but should be mirrored
image.save("rotate_180_1_source.png");
rotatedimage.save("rotate_180_1_rotated_source.png");
result.save("rotate_180_1_result.png");
QFAIL(QString("Failed to rotate 180 the image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void generateTestImage(QString inputFileName, qreal scale, qreal rotation, qreal xshear, KisFilterStrategy *filter, bool saveImage = true)
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + inputFileName);
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
KisTransformWorker tw(dev, scale, scale,
xshear, 0.0,
0.0, 0.0,
rotation,
0, 0,
updater, filter);
tw.run();
if (saveImage) {
QStringList tmp = inputFileName.split('.');
QString filename =
QString("transform_%1_%2_%3_%4_%5_new.png")
.arg(tmp[0]).arg(scale).arg(rotation).arg(xshear).arg(filter->name());
dbgKrita << filename;
dev->convertToQImage(0).save(filename);
}
}
void KisTransformWorkerTest::benchmarkScale()
{
QBENCHMARK {
generateTestImage("hakonepa.png", 1.379,0.0,0.0,new KisBicubicFilterStrategy(), false);
}
}
void KisTransformWorkerTest::benchmarkRotate()
{
QBENCHMARK {
generateTestImage("hakonepa.png", 1.0,M_PI/6.0,0.0,new KisBicubicFilterStrategy(), false);
}
}
void KisTransformWorkerTest::benchmarkRotate1Q()
{
QBENCHMARK {
generateTestImage("hakonepa.png", 1.0,2 * M_PI/3.0,0.0,new KisBicubicFilterStrategy(), false);
}
}
void KisTransformWorkerTest::benchmarkShear()
{
QBENCHMARK {
generateTestImage("hakonepa.png", 1.0,0.0,0.479,new KisBicubicFilterStrategy(), false);
}
}
void KisTransformWorkerTest::benchmarkScaleRotateShear()
{
QBENCHMARK {
generateTestImage("hakonepa.png", 1.379,M_PI/6.0,0.479,new KisBicubicFilterStrategy(), false);
}
}
void KisTransformWorkerTest::generateTestImages()
{
QList filters;
filters << new KisBoxFilterStrategy();
filters << new KisHermiteFilterStrategy();
filters << new KisBicubicFilterStrategy();
filters << new KisBilinearFilterStrategy();
filters << new KisBellFilterStrategy();
filters << new KisBSplineFilterStrategy();
filters << new KisLanczos3FilterStrategy();
filters << new KisMitchellFilterStrategy();
QStringList names;
names << "hakonepa.png";
//names << "star-chart-bars-full-600dpi.png";
Q_FOREACH (const QString &name, names) {
Q_FOREACH (KisFilterStrategy *filter, filters) {
generateTestImage(name, 0.5,0.0,0.0,filter);
generateTestImage(name, 0.734,0.0,0.0,filter);
generateTestImage(name, 1.387,0.0,0.0,filter);
generateTestImage(name, 2.0,0.0,0.0,filter);
generateTestImage(name, 3.789,0.0,0.0,filter);
generateTestImage(name, 1.0,M_PI/6,0.0,filter);
generateTestImage(name, 1.0,0.0,0.5,filter);
}
}
}
#include "kis_perspectivetransform_worker.h"
void KisTransformWorkerTest::testPartialProcessing()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QImage image(TestUtil::fetchDataFileLazy("test_transform_quality.png"));
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
KisTransaction t(dev);
QTransform transform = QTransform::fromScale(2.0, 1.1);
transform.shear(1.1, 0);
transform.rotateRadians(M_PI / 18);
KisPerspectiveTransformWorker tw(0, transform, updater);
tw.runPartialDst(dev, dev, QRect(1200, 1200, 150, 150));
tw.runPartialDst(dev, dev, QRect(1350, 1200, 150, 150));
tw.runPartialDst(dev, dev, QRect(1200, 1350, 150, 150));
tw.runPartialDst(dev, dev, QRect(1350, 1350, 150, 150));
t.end();
QImage result = dev->convertToQImage(0);
TestUtil::checkQImage(result, "transform_test", "partial", "single");
}
QTEST_MAIN(KisTransformWorkerTest)
diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp
index eb552c1d4f..ca9849fb4b 100644
--- a/libs/ui/KisImportExportManager.cpp
+++ b/libs/ui/KisImportExportManager.cpp
@@ -1,702 +1,702 @@
/*
* Copyright (C) 2016 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisImportExportManager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_config.h"
#include "KisImportExportFilter.h"
#include "KisDocument.h"
#include
#include
#include "kis_painter.h"
#include "kis_guides_config.h"
#include "kis_grid_config.h"
#include "kis_popup_button.h"
#include
#include "kis_async_action_feedback.h"
#include "KisReferenceImagesLayer.h"
// static cache for import and export mimetypes
QStringList KisImportExportManager::m_importMimeTypes;
QStringList KisImportExportManager::m_exportMimeTypes;
class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
KoUpdaterPtr updater;
QString cachedExportFilterMimeType;
QSharedPointer cachedExportFilter;
};
struct KisImportExportManager::ConversionResult {
ConversionResult()
{
}
ConversionResult(const QFuture &futureStatus)
: m_isAsync(true),
m_futureStatus(futureStatus)
{
}
ConversionResult(KisImportExportErrorCode status)
: m_isAsync(false),
m_status(status)
{
}
bool isAsync() const {
return m_isAsync;
}
QFuture futureStatus() const {
// if the result is not async, then it means some failure happened,
// just return a cancelled future
KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk());
return m_futureStatus;
}
KisImportExportErrorCode status() const {
return m_status;
}
void setStatus(KisImportExportErrorCode value) {
m_status = value;
}
private:
bool m_isAsync = false;
QFuture m_futureStatus;
KisImportExportErrorCode m_status = ImportExportCodes::InternalError;
};
KisImportExportManager::KisImportExportManager(KisDocument* document)
: m_document(document)
, d(new Private)
{
}
KisImportExportManager::~KisImportExportManager()
{
delete d;
}
KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
{
ConversionResult result = convert(Import, location, location, mimeType, false, 0, false);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError);
return result.status();
}
KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError);
return result.status();
}
QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType,
KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() ||
!result.status().isOk(), QFuture());
status = result.status();
return result.futureStatus();
}
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KisImportExportManager::supportedMimeTypes(Direction direction)
{
// Find the right mimetype by the extension
QSet mimeTypes;
// mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
if (direction == KisImportExportManager::Import) {
if (m_importMimeTypes.isEmpty()) {
QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_importMimeTypes = mimeTypes.toList();
}
return m_importMimeTypes;
}
else if (direction == KisImportExportManager::Export) {
if (m_exportMimeTypes.isEmpty()) {
QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_exportMimeTypes = mimeTypes.toList();
}
return m_exportMimeTypes;
}
return QStringList();
}
KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
{
int weight = -1;
KisImportExportFilter *filter = 0;
QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) {
KLibFactory *factory = qobject_cast(loader->instance());
if (!factory) {
warnUI << loader->errorString();
continue;
}
QObject* obj = factory->create(0);
if (!obj || !obj->inherits("KisImportExportFilter")) {
delete obj;
continue;
}
KisImportExportFilter *f = qobject_cast(obj);
if (!f) {
delete obj;
continue;
}
int w = json.value("X-KDE-Weight").toInt();
if (w > weight) {
delete filter;
filter = f;
f->setObjectName(loader->fileName());
weight = w;
}
}
}
qDeleteAll(list);
if (filter) {
filter->setMimeType(mimetype);
}
return filter;
}
bool KisImportExportManager::batchMode(void) const
{
return m_document->fileBatchMode();
}
void KisImportExportManager::setUpdater(KoUpdaterPtr updater)
{
d->updater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
{
KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
if (!defaultDir.isEmpty()) {
dialog.setDefaultDir(defaultDir);
}
QStringList mimeTypes;
mimeTypes << "audio/mpeg";
mimeTypes << "audio/ogg";
mimeTypes << "audio/vorbis";
mimeTypes << "audio/vnd.wave";
mimeTypes << "audio/flac";
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@titile:window", "Open Audio"));
return dialog.filename();
}
KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync)
{
// export configuration is supported for export only
KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration));
QString typeName = mimeType;
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true);
}
QSharedPointer filter;
/**
* Fetching a filter from the registry is a really expensive operation,
* because it blocks all the threads. Cache the filter if possible.
*/
if (direction == KisImportExportManager::Export &&
d->cachedExportFilter &&
d->cachedExportFilterMimeType == typeName) {
filter = d->cachedExportFilter;
} else {
filter = toQShared(filterForMimeType(typeName, direction));
if (direction == Export) {
d->cachedExportFilter = filter;
d->cachedExportFilterMimeType = typeName;
}
}
if (!filter) {
return KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect);
}
filter->setFilename(location);
filter->setRealFilename(realLocation);
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (!d->updater.isNull()) {
// WARNING: The updater is not guaranteed to be persistent! If you ever want
// to add progress reporting to "Save also as .kra", make sure you create
// a separate KoProgressUpdater for that!
// WARNING2: the failsafe completion of the updater happens in the destructor
// the filter.
filter->setUpdater(d->updater);
}
QByteArray from, to;
if (direction == Export) {
from = m_document->nativeFormatMimeType();
to = typeName.toLatin1();
}
else {
from = typeName.toLatin1();
to = m_document->nativeFormatMimeType();
}
KIS_ASSERT_RECOVER_RETURN_VALUE(
direction == Import || direction == Export,
KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph"
ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK);
if (direction == Import) {
KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5")
.arg(QString::fromLatin1(from))
.arg(QString::fromLatin1(to))
.arg(location)
.arg(realLocation)
.arg(batchMode()));
// async importing is not yet supported!
KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync);
if (0 && !batchMode()) {
KisAsyncActionFeedback f(i18n("Opening document..."), 0);
result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter));
} else {
result = doImport(location, filter);
}
}
else /* if (direction == Export) */ {
if (!exportConfiguration) {
exportConfiguration = filter->lastSavedConfiguration(from, to);
}
if (exportConfiguration) {
fillStaticExportConfigurationProperties(exportConfiguration);
}
bool alsoAsKra = false;
bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration,
from, to,
batchMode(), showWarnings,
&alsoAsKra);
if (!batchMode() && !askUser) {
return KisImportExportErrorCode(ImportExportCodes::Cancelled);
}
KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6")
.arg(QString::fromLatin1(from))
.arg(QString::fromLatin1(to))
.arg(location)
.arg(realLocation)
.arg(batchMode())
.arg(exportConfiguration ? exportConfiguration->toXML() : "none"));
if (isAsync) {
result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
// we should explicitly report that the exporting has been initiated
result.setStatus(ImportExportCodes::OK);
} else if (!batchMode()) {
KisAsyncActionFeedback f(i18n("Saving document..."), 0);
result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
} else {
result = doExport(location, filter, exportConfiguration, alsoAsKra);
}
if (exportConfiguration && !batchMode() && showWarnings) {
KisConfig(false).setExportConfiguration(typeName, exportConfiguration);
}
}
return result;
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image)
{
KisPaintDeviceSP dev = image->projection();
const KoColorSpace* cs = dev->colorSpace();
const bool isThereAlpha =
KisPainter::checkDeviceHasTransparency(image->projection());
exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha);
exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id());
exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id());
const bool sRGB =
(cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) &&
!cs->profile()->name().contains(QLatin1String("g10")));
exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB);
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration)
{
return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image());
}
bool KisImportExportManager::askUserAboutExportConfiguration(
QSharedPointer filter,
KisPropertiesConfigurationSP exportConfiguration,
const QByteArray &from,
const QByteArray &to,
const bool batchMode,
const bool showWarnings,
bool *alsoAsKra)
{
// prevents the animation renderer from running this code
const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to);
QStringList warnings;
QStringList errors;
{
KisPreExportChecker checker;
checker.check(m_document->image(), filter->exportChecks());
warnings = checker.warnings();
errors = checker.errors();
}
KisConfigWidget *wdg = 0;
if (QThread::currentThread() == qApp->thread()) {
wdg = filter->createConfigurationWidget(0, from, to);
KisMainWindow *kisMain = KisPart::instance()->currentMainwindow();
if (wdg && kisMain) {
KisViewManager *manager = kisMain->viewManager();
wdg->setView(manager);
}
}
// Extra checks that cannot be done by the checker, because the checker only has access to the image.
if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved."));
}
if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains reference images. The reference images will not be saved."));
}
if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved."));
}
if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved."));
}
if (!batchMode && !errors.isEmpty()) {
QString error = ""
+ i18n("Error: cannot save this image as a %1.", mimeUserDescription)
- + " Reasons:
"
+ + " " + i18n("Reasons:") + "
"
+ "";
Q_FOREACH(const QString &w, errors) {
error += "\n- " + w + "
";
}
error += "
";
QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
return false;
}
if (!batchMode && (wdg || !warnings.isEmpty())) {
KoDialog dlg;
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setWindowTitle(mimeUserDescription);
QWidget *page = new QWidget(&dlg);
QVBoxLayout *layout = new QVBoxLayout(page);
if (showWarnings && !warnings.isEmpty()) {
QHBoxLayout *hLayout = new QHBoxLayout();
QLabel *labelWarning = new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hLayout->addWidget(labelWarning);
KisPopupButton *bn = new KisPopupButton(0);
bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription));
hLayout->addWidget(bn);
layout->addLayout(hLayout);
QTextBrowser *browser = new QTextBrowser();
browser->setMinimumWidth(bn->width());
bn->setPopupWidget(browser);
QString warning = ""
+ i18n("You will lose information when saving this image as a %1.", mimeUserDescription);
if (warnings.size() == 1) {
- warning += " Reason:
";
+ warning += " " + i18n("Reason:") + "";
}
else {
- warning += " Reasons:";
+ warning += " " + i18n("Reasons:") + "";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
}
if (wdg) {
QGroupBox *box = new QGroupBox(i18n("Options"));
QVBoxLayout *boxLayout = new QVBoxLayout(box);
wdg->setConfiguration(exportConfiguration);
boxLayout->addWidget(wdg);
layout->addWidget(box);
}
QCheckBox *chkAlsoAsKra = 0;
if (showWarnings && !warnings.isEmpty()) {
chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
chkAlsoAsKra->setChecked(KisConfig(true).readEntry("AlsoSaveAsKra", false));
layout->addWidget(chkAlsoAsKra);
}
dlg.setMainWidget(page);
dlg.resize(dlg.minimumSize());
if (showWarnings || wdg) {
if (!dlg.exec()) {
return false;
}
}
*alsoAsKra = false;
if (chkAlsoAsKra) {
KisConfig(false).writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
*alsoAsKra = chkAlsoAsKra->isChecked();
}
if (wdg) {
*exportConfiguration = *wdg->configuration();
}
}
return true;
}
KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer filter)
{
QFile file(location);
if (!file.exists()) {
return ImportExportCodes::FileNotExist;
}
if (filter->supportsIO() && !file.open(QFile::ReadOnly)) {
return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error()));
}
KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP());
if (file.isOpen()) {
file.close();
}
return status;
}
KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra)
{
KisImportExportErrorCode status =
doExportImpl(location, filter, exportConfiguration);
if (alsoAsKra && status.isOk()) {
QString kraLocation = location + ".kra";
QByteArray mime = m_document->nativeFormatMimeType();
QSharedPointer filter(
filterForMimeType(QString::fromLatin1(mime), Export));
KIS_SAFE_ASSERT_RECOVER_NOOP(filter);
if (filter) {
filter->setFilename(kraLocation);
KisPropertiesConfigurationSP kraExportConfiguration =
filter->lastSavedConfiguration(mime, mime);
status = doExportImpl(kraLocation, filter, kraExportConfiguration);
} else {
status = ImportExportCodes::FileFormatIncorrect;
}
}
return status;
}
// Temporary workaround until QTBUG-57299 is fixed.
#ifndef Q_OS_WIN
#define USE_QSAVEFILE
#endif
KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration)
{
#ifdef USE_QSAVEFILE
QSaveFile file(location);
file.setDirectWriteFallback(true);
if (filter->supportsIO() && !file.open(QFile::WriteOnly)) {
#else
QFileInfo fi(location);
QTemporaryFile file(fi.absolutePath() + ".XXXXXX.kra");
if (filter->supportsIO() && !file.open()) {
#endif
KisImportExportErrorCannotWrite result(file.error());
#ifdef USE_QSAVEFILE
file.cancelWriting();
#endif
return result;
}
KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration);
if (filter->supportsIO()) {
if (!status.isOk()) {
#ifdef USE_QSAVEFILE
file.cancelWriting();
#endif
} else {
#ifdef USE_QSAVEFILE
if (!file.commit()) {
qWarning() << "Could not commit QSaveFile";
status = KisImportExportErrorCannotWrite(file.error());
}
#else
file.flush();
file.close();
QFile target(location);
if (target.exists()) {
// There should already be a .kra~ backup
target.remove();
}
if (!file.copy(location)) {
file.setAutoRemove(false);
return KisImportExportErrorCannotWrite(file.error());
}
#endif
}
}
// Do some minimal verification
QString verificationResult = filter->verify(location);
if (!verificationResult.isEmpty()) {
status = KisImportExportErrorCode(ImportExportCodes::ErrorWhileWriting);
m_document->setErrorMessage(verificationResult);
}
return status;
}
#include
diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp
index 8510899e2d..c2a51aa8df 100644
--- a/libs/ui/input/kis_shortcut_matcher.cpp
+++ b/libs/ui/input/kis_shortcut_matcher.cpp
@@ -1,829 +1,831 @@
/*
* Copyright (c) 2012 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_shortcut_matcher.h"
#include
#include
#include
#include "kis_assert.h"
#include "kis_abstract_input_action.h"
#include "kis_stroke_shortcut.h"
#include "kis_touch_shortcut.h"
#include "kis_native_gesture_shortcut.h"
#include "kis_config.h"
//#define DEBUG_MATCHER
#ifdef DEBUG_MATCHER
#include
#define DEBUG_ACTION(text) qDebug() << __FUNCTION__ << "-" << text;
#define DEBUG_SHORTCUT(text, shortcut) qDebug() << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name();
#define DEBUG_KEY(text) qDebug() << __FUNCTION__ << "-" << text << "keys:" << m_d->keys;
#define DEBUG_BUTTON_ACTION(text, button) qDebug() << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys;
#define DEBUG_EVENT_ACTION(text, event) if (event) {qDebug() << __FUNCTION__ << "-" << text << "type:" << event->type();}
#else
#define DEBUG_ACTION(text)
#define DEBUG_KEY(text)
#define DEBUG_SHORTCUT(text, shortcut)
#define DEBUG_BUTTON_ACTION(text, button)
#define DEBUG_EVENT_ACTION(text, event)
#endif
class Q_DECL_HIDDEN KisShortcutMatcher::Private
{
public:
Private()
: runningShortcut(0)
, readyShortcut(0)
, touchShortcut(0)
, nativeGestureShortcut(0)
, actionGroupMask([] () { return AllActionGroup; })
, suppressAllActions(false)
, cursorEntered(false)
, usingTouch(false)
, usingNativeGesture(false)
{}
~Private()
{
qDeleteAll(singleActionShortcuts);
qDeleteAll(strokeShortcuts);
qDeleteAll(touchShortcuts);
}
QList singleActionShortcuts;
QList strokeShortcuts;
QList touchShortcuts;
QList nativeGestureShortcuts;
QSet keys; // Model of currently pressed keys
QSet buttons; // Model of currently pressed buttons
KisStrokeShortcut *runningShortcut;
KisStrokeShortcut *readyShortcut;
QList candidateShortcuts;
KisTouchShortcut *touchShortcut;
KisNativeGestureShortcut *nativeGestureShortcut;
std::function actionGroupMask;
bool suppressAllActions;
bool cursorEntered;
bool usingTouch;
bool usingNativeGesture;
int recursiveCounter = 0;
int brokenByRecursion = 0;
struct RecursionNotifier {
RecursionNotifier(KisShortcutMatcher *_q)
: q(_q)
{
q->m_d->recursiveCounter++;
q->m_d->brokenByRecursion++;
}
~RecursionNotifier() {
q->m_d->recursiveCounter--;
}
bool isInRecursion() const {
return q->m_d->recursiveCounter > 1;
}
KisShortcutMatcher *q;
};
struct RecursionGuard {
RecursionGuard(KisShortcutMatcher *_q)
: q(_q)
{
q->m_d->brokenByRecursion = 0;
}
~RecursionGuard() {
}
bool brokenByRecursion() const {
return q->m_d->brokenByRecursion > 0;
}
KisShortcutMatcher *q;
};
inline bool actionsSuppressed() const {
return (suppressAllActions || !cursorEntered)
&& KisConfig(true).disableTouchOnCanvas();
}
inline bool actionsSuppressedIgnoreFocus() const {
return suppressAllActions;
}
// only for touch events with touchPoints count >= 2
inline bool isUsingTouch() const {
return usingTouch || usingNativeGesture;
}
};
KisShortcutMatcher::KisShortcutMatcher()
: m_d(new Private)
{}
KisShortcutMatcher::~KisShortcutMatcher()
{
delete m_d;
}
bool KisShortcutMatcher::hasRunningShortcut() const
{
return m_d->runningShortcut;
}
void KisShortcutMatcher::addShortcut(KisSingleActionShortcut *shortcut)
{
m_d->singleActionShortcuts.append(shortcut);
}
void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut)
{
m_d->strokeShortcuts.append(shortcut);
}
void KisShortcutMatcher::addShortcut( KisTouchShortcut* shortcut )
{
m_d->touchShortcuts.append(shortcut);
}
void KisShortcutMatcher::addShortcut(KisNativeGestureShortcut *shortcut) {
m_d->nativeGestureShortcuts.append(shortcut);
}
bool KisShortcutMatcher::supportsHiResInputEvents()
{
return
m_d->runningShortcut &&
m_d->runningShortcut->action() &&
m_d->runningShortcut->action()->supportsHiResInputEvents();
}
bool KisShortcutMatcher::keyPressed(Qt::Key key)
{
Private::RecursionNotifier notifier(this);
bool retval = false;
if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); }
if (!m_d->runningShortcut && !notifier.isInRecursion()) {
retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys);
}
m_d->keys.insert(key);
DEBUG_KEY("Pressed");
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::autoRepeatedKeyPressed(Qt::Key key)
{
Private::RecursionNotifier notifier(this);
bool retval = false;
if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); }
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
// Autorepeated key should not be included in the shortcut
QSet filteredKeys = m_d->keys;
filteredKeys.remove(key);
retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys);
}
return retval;
}
bool KisShortcutMatcher::keyReleased(Qt::Key key)
{
Private::RecursionNotifier notifier(this);
if (!m_d->keys.contains(key)) reset("Peculiar, key released but can't remember it was pressed");
else m_d->keys.remove(key);
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return false;
}
bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event)
{
Private::RecursionNotifier notifier(this);
DEBUG_BUTTON_ACTION("entered", button);
bool retval = false;
if (m_d->isUsingTouch()) {
return retval;
}
if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); }
if (!m_d->runningShortcut && !notifier.isInRecursion()) {
prepareReadyShortcuts();
retval = tryRunReadyShortcut(button, event);
}
m_d->buttons.insert(button);
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event)
{
Private::RecursionNotifier notifier(this);
DEBUG_BUTTON_ACTION("entered", button);
bool retval = false;
if (m_d->isUsingTouch()) {
return retval;
}
if (m_d->runningShortcut) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!notifier.isInRecursion());
retval = tryEndRunningShortcut(button, event);
DEBUG_BUTTON_ACTION("ended", button);
}
if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed");
else m_d->buttons.remove(button);
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
{
Private::RecursionNotifier notifier(this);
if (m_d->runningShortcut || m_d->isUsingTouch() || notifier.isInRecursion()) {
DEBUG_ACTION("Wheel event canceled.");
return false;
}
return tryRunWheelShortcut(wheelAction, event);
}
bool KisShortcutMatcher::pointerMoved(QEvent *event)
{
Private::RecursionNotifier notifier(this);
if (m_d->isUsingTouch() || !m_d->runningShortcut || notifier.isInRecursion()) {
return false;
}
m_d->runningShortcut->action()->inputEvent(event);
return true;
}
void KisShortcutMatcher::enterEvent()
{
Private::RecursionNotifier notifier(this);
m_d->cursorEntered = true;
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
void KisShortcutMatcher::leaveEvent()
{
Private::RecursionNotifier notifier(this);
m_d->cursorEntered = false;
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event )
{
Q_UNUSED(event)
Private::RecursionNotifier notifier(this);
return !notifier.isInRecursion();
}
bool KisShortcutMatcher::touchUpdateEvent( QTouchEvent* event )
{
bool retval = false;
if (m_d->touchShortcut && !m_d->touchShortcut->match( event ) ) {
retval = tryEndTouchShortcut( event );
}
if (!m_d->touchShortcut ) {
retval = tryRunTouchShortcut( event );
}
else {
m_d->touchShortcut->action()->inputEvent( event );
retval = true;
}
return retval;
}
bool KisShortcutMatcher::touchEndEvent( QTouchEvent* event )
{
m_d->usingTouch = false; // we need to say we are done because qt will not send further event
// we should try and end the shortcut too (it might be that there is none? (sketch))
if (tryEndTouchShortcut(event)) {
return true;
}
return false;
}
bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event)
{
Q_UNUSED(event)
Private::RecursionNotifier notifier(this);
return !notifier.isInRecursion();
}
bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event)
{
bool retval = false;
if ( !m_d->nativeGestureShortcut ) {
retval = tryRunNativeGestureShortcut( event );
}
else {
m_d->nativeGestureShortcut->action()->inputEvent( event );
retval = true;
}
return retval;
}
bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event)
{
if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) {
tryEndNativeGestureShortcut( event );
}
m_d->usingNativeGesture = false;
return true;
}
Qt::MouseButtons listToFlags(const QList &list) {
Qt::MouseButtons flags;
Q_FOREACH (Qt::MouseButton b, list) {
flags |= b;
}
return flags;
}
void KisShortcutMatcher::reinitialize()
{
Private::RecursionNotifier notifier(this);
reset("reinitialize");
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
void KisShortcutMatcher::recoveryModifiersWithoutFocus(const QVector &keys)
{
Private::RecursionNotifier notifier(this);
Q_FOREACH (Qt::Key key, m_d->keys) {
if (!keys.contains(key)) {
keyReleased(key);
}
}
Q_FOREACH (Qt::Key key, keys) {
if (!m_d->keys.contains(key)) {
keyPressed(key);
}
}
if (notifier.isInRecursion()) {
forceDeactivateAllActions();
} else if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
DEBUG_ACTION("recoverySyncModifiers");
}
void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos)
{
+ Private::RecursionNotifier notifier(this);
+
if (m_d->runningShortcut) {
forceEndRunningShortcut(localPos);
}
}
void KisShortcutMatcher::reset()
{
m_d->keys.clear();
m_d->buttons.clear();
DEBUG_ACTION("reset!");
}
void KisShortcutMatcher::reset(QString msg)
{
m_d->keys.clear();
m_d->buttons.clear();
Q_UNUSED(msg);
DEBUG_ACTION(msg);
}
void KisShortcutMatcher::suppressAllActions(bool value)
{
m_d->suppressAllActions = value;
}
void KisShortcutMatcher::clearShortcuts()
{
reset("Clearing shortcuts");
qDeleteAll(m_d->singleActionShortcuts);
m_d->singleActionShortcuts.clear();
qDeleteAll(m_d->strokeShortcuts);
qDeleteAll(m_d->touchShortcuts);
m_d->strokeShortcuts.clear();
m_d->candidateShortcuts.clear();
m_d->touchShortcuts.clear();
m_d->runningShortcut = 0;
m_d->readyShortcut = 0;
}
void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function func)
{
m_d->actionGroupMask = func;
}
bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
{
return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys);
}
// Note: sometimes event can be zero!!
template
bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState)
{
if (m_d->actionsSuppressedIgnoreFocus()) {
DEBUG_EVENT_ACTION("Event suppressed", event)
return false;
}
KisSingleActionShortcut *goodCandidate = 0;
Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
if(s->isAvailable(m_d->actionGroupMask()) &&
s->match(keysState, param) &&
(!goodCandidate || s->priority() > goodCandidate->priority())) {
goodCandidate = s;
}
}
if (goodCandidate) {
DEBUG_EVENT_ACTION("Beginning action for event", event);
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
goodCandidate->action()->end(0);
} else {
DEBUG_EVENT_ACTION("Could not match a candidate for event", event)
}
return goodCandidate;
}
void KisShortcutMatcher::prepareReadyShortcuts()
{
m_d->candidateShortcuts.clear();
if (m_d->actionsSuppressed()) return;
Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) {
if (s->matchReady(m_d->keys, m_d->buttons)) {
m_d->candidateShortcuts.append(s);
}
}
}
bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event )
{
KisStrokeShortcut *goodCandidate = 0;
Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
if (s->isAvailable(m_d->actionGroupMask()) &&
s->matchBegin(button) &&
(!goodCandidate || s->priority() > goodCandidate->priority())) {
goodCandidate = s;
}
}
if (goodCandidate) {
if (m_d->readyShortcut) {
if (m_d->readyShortcut != goodCandidate) {
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
}
m_d->readyShortcut = 0;
} else {
DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event);
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
}
DEBUG_SHORTCUT("Starting new action", goodCandidate);
{
m_d->runningShortcut = goodCandidate;
Private::RecursionGuard guard(this);
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
// the tool migh have opened some dialog, which could break our event loop
if (guard.brokenByRecursion()) {
goodCandidate->action()->end(event);
m_d->runningShortcut = 0;
forceDeactivateAllActions();
}
}
}
return m_d->runningShortcut;
}
void KisShortcutMatcher::tryActivateReadyShortcut()
{
KisStrokeShortcut *goodCandidate = 0;
Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
if (!goodCandidate || s->priority() > goodCandidate->priority()) {
goodCandidate = s;
}
}
if (goodCandidate) {
if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) {
DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut);
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
m_d->readyShortcut = 0;
}
if (!m_d->readyShortcut) {
DEBUG_SHORTCUT("Preparing new ready action", goodCandidate);
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
m_d->readyShortcut = goodCandidate;
}
} else if (m_d->readyShortcut) {
DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut);
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
m_d->readyShortcut = 0;
}
}
bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event )
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->runningShortcut, true);
KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
// it shouldn't have happened, running and ready shortcuts
// at the same time should not be possible
forceDeactivateAllActions();
}
if (m_d->runningShortcut->matchBegin(button)) {
// first reset running shortcut to avoid infinite recursion via end()
KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
m_d->runningShortcut = 0;
if (runningShortcut->action()) {
DEBUG_EVENT_ACTION("Ending running shortcut at event", event);
KisAbstractInputAction* action = runningShortcut->action();
int shortcutIndex = runningShortcut->shortcutIndex();
action->end(event);
action->deactivate(shortcutIndex);
}
}
return !m_d->runningShortcut;
}
void KisShortcutMatcher::forceEndRunningShortcut(const QPointF &localPos)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut);
KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
// it shouldn't have happened, running and ready shortcuts
// at the same time should not be possible
forceDeactivateAllActions();
}
// first reset running shortcut to avoid infinite recursion via end()
KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
m_d->runningShortcut = 0;
if (runningShortcut->action()) {
DEBUG_ACTION("Forced ending running shortcut at event");
KisAbstractInputAction* action = runningShortcut->action();
int shortcutIndex = runningShortcut->shortcutIndex();
QMouseEvent event = runningShortcut->fakeEndEvent(localPos);
action->end(&event);
action->deactivate(shortcutIndex);
}
}
void KisShortcutMatcher::forceDeactivateAllActions()
{
if (m_d->readyShortcut) {
DEBUG_SHORTCUT("Forcefully deactivating action", m_d->readyShortcut);
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
m_d->readyShortcut = 0;
}
}
bool KisShortcutMatcher::tryRunTouchShortcut( QTouchEvent* event )
{
KisTouchShortcut *goodCandidate = 0;
if (m_d->actionsSuppressed())
return false;
Q_FOREACH (KisTouchShortcut* shortcut, m_d->touchShortcuts) {
if (shortcut->isAvailable(m_d->actionGroupMask()) &&
shortcut->match( event ) &&
(!goodCandidate || shortcut->priority() > goodCandidate->priority()) ) {
goodCandidate = shortcut;
}
}
if( goodCandidate ) {
if( m_d->runningShortcut ) {
QTouchEvent touchEvent(QEvent::TouchEnd,
event->device(),
event->modifiers(),
Qt::TouchPointReleased,
event->touchPoints());
tryEndRunningShortcut(Qt::LeftButton, &touchEvent);
}
m_d->touchShortcut = goodCandidate;
m_d->usingTouch = true;
Private::RecursionGuard guard(this);
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
// the tool migh have opened some dialog, which could break our event loop
if (guard.brokenByRecursion()) {
goodCandidate->action()->end(event);
m_d->touchShortcut = 0;
forceDeactivateAllActions();
}
}
return m_d->touchShortcut;
}
bool KisShortcutMatcher::tryEndTouchShortcut( QTouchEvent* event )
{
if(m_d->touchShortcut) {
// first reset running shortcut to avoid infinite recursion via end()
KisTouchShortcut *touchShortcut = m_d->touchShortcut;
touchShortcut->action()->end(event);
touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex());
m_d->touchShortcut = 0; // empty it out now that we are done with it
return true;
}
return false;
}
bool KisShortcutMatcher::tryRunNativeGestureShortcut(QNativeGestureEvent* event)
{
KisNativeGestureShortcut *goodCandidate = 0;
if (m_d->actionsSuppressed())
return false;
Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) {
if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
goodCandidate = shortcut;
}
}
if (goodCandidate) {
m_d->nativeGestureShortcut = goodCandidate;
m_d->usingNativeGesture = true;
Private::RecursionGuard guard(this);
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
// the tool migh have opened some dialog, which could break our event loop
if (guard.brokenByRecursion()) {
goodCandidate->action()->end(event);
m_d->nativeGestureShortcut = 0;
forceDeactivateAllActions();
}
}
return m_d->nativeGestureShortcut;
}
bool KisShortcutMatcher::tryEndNativeGestureShortcut(QNativeGestureEvent* event)
{
if (m_d->nativeGestureShortcut) {
// first reset running shortcut to avoid infinite recursion via end()
KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut;
nativeGestureShortcut->action()->end(event);
nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex());
m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it
return true;
}
return false;
}
diff --git a/libs/ui/tool/kis_tool_freehand.cc b/libs/ui/tool/kis_tool_freehand.cc
index 284869d934..43059f8ab1 100644
--- a/libs/ui/tool/kis_tool_freehand.cc
+++ b/libs/ui/tool/kis_tool_freehand.cc
@@ -1,455 +1,457 @@
/*
* kis_tool_freehand.cc - part of Krita
*
* Copyright (c) 2003-2007 Boudewijn Rempt
* Copyright (c) 2004 Bart Coppens
* Copyright (c) 2007,2008,2010 Cyrille Berger
* Copyright (c) 2009 Lukáš Tvrdý
*
* 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_freehand.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//pop up palette
#include
// Krita/image
#include
#include
#include
#include
#include
#include
// Krita/ui
#include "kis_abstract_perspective_grid.h"
#include "kis_config.h"
#include "canvas/kis_canvas2.h"
#include "kis_cursor.h"
#include
#include
#include "kis_painting_information_builder.h"
#include "kis_tool_freehand_helper.h"
#include "strokes/freehand_stroke.h"
using namespace std::placeholders; // For _1 placeholder
KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText)
: KisToolPaint(canvas, cursor),
m_paintopBasedPickingInAction(false),
m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1))
{
m_assistant = false;
m_magnetism = 1.0;
m_only_one_assistant = true;
setSupportOutline(true);
setMaskSyntheticEvents(KisConfig(true).disableTouchOnCanvas()); // Disallow mouse events from finger presses unless enabled
m_infoBuilder = new KisToolFreehandPaintingInformationBuilder(this);
m_helper = new KisToolFreehandHelper(m_infoBuilder, transactionText);
connect(m_helper, SIGNAL(requestExplicitUpdateOutline()), SLOT(explicitUpdateOutline()));
}
KisToolFreehand::~KisToolFreehand()
{
delete m_helper;
delete m_infoBuilder;
}
void KisToolFreehand::mouseMoveEvent(KoPointerEvent *event)
{
KisToolPaint::mouseMoveEvent(event);
m_helper->cursorMoved(convertToPixelCoord(event));
}
KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const
{
return m_helper->smoothingOptions();
}
void KisToolFreehand::resetCursorStyle()
{
KisConfig cfg(true);
switch (cfg.newCursorStyle()) {
case CURSOR_STYLE_NO_CURSOR:
useCursor(KisCursor::blankCursor());
break;
case CURSOR_STYLE_POINTER:
useCursor(KisCursor::arrowCursor());
break;
case CURSOR_STYLE_SMALL_ROUND:
useCursor(KisCursor::roundCursor());
break;
case CURSOR_STYLE_CROSSHAIR:
useCursor(KisCursor::crossCursor());
break;
case CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
useCursor(KisCursor::triangleRightHandedCursor());
break;
case CURSOR_STYLE_TRIANGLE_LEFTHANDED:
useCursor(KisCursor::triangleLeftHandedCursor());
break;
case CURSOR_STYLE_BLACK_PIXEL:
useCursor(KisCursor::pixelBlackCursor());
break;
case CURSOR_STYLE_WHITE_PIXEL:
useCursor(KisCursor::pixelWhiteCursor());
break;
case CURSOR_STYLE_TOOLICON:
default:
KisToolPaint::resetCursorStyle();
break;
}
}
KisPaintingInformationBuilder* KisToolFreehand::paintingInformationBuilder() const
{
return m_infoBuilder;
}
void KisToolFreehand::resetHelper(KisToolFreehandHelper *helper)
{
delete m_helper;
m_helper = helper;
}
int KisToolFreehand::flags() const
{
return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET
|KisTool::FLAG_USES_CUSTOM_SIZE;
}
void KisToolFreehand::activate(ToolActivation activation, const QSet &shapes)
{
KisToolPaint::activate(activation, shapes);
}
void KisToolFreehand::deactivate()
{
if (mode() == PAINT_MODE) {
endStroke();
setMode(KisTool::HOVER_MODE);
}
KisToolPaint::deactivate();
}
void KisToolFreehand::initStroke(KoPointerEvent *event)
{
m_helper->initPaint(event,
convertToPixelCoord(event),
canvas()->resourceManager(),
image(),
currentNode(),
image().data());
}
void KisToolFreehand::doStroke(KoPointerEvent *event)
{
m_helper->paintEvent(event);
}
void KisToolFreehand::endStroke()
{
m_helper->endPaint();
bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->mouseReleaseEvent();
Q_UNUSED(paintOpIgnoredEvent);
}
bool KisToolFreehand::primaryActionSupportsHiResEvents() const
{
return true;
}
void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event)
{
// FIXME: workaround for the Duplicate Op
tryPickByPaintOp(event, PickFgImage);
requestUpdateOutline(event->point, event);
NodePaintAbility paintability = nodePaintAbility();
// XXX: move this to KisTool and make it work properly for clone layers: for clone layers, the shape paint tools don't work either
if (!nodeEditable() || paintability != PAINT) {
if (paintability == KisToolPaint::VECTOR || paintability == KisToolPaint::CLONE){
KisCanvas2 * kiscanvas = static_cast(canvas());
QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask.");
kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
}
event->ignore();
return;
}
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!m_helper->isRunning());
+
setMode(KisTool::PAINT_MODE);
KisCanvas2 *canvas2 = dynamic_cast(canvas());
if (canvas2) {
canvas2->viewManager()->disableControls();
}
initStroke(event);
}
void KisToolFreehand::continuePrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
requestUpdateOutline(event->point, event);
/**
* Actual painting
*/
doStroke(event);
}
void KisToolFreehand::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
endStroke();
if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) {
static_cast(canvas())->paintingAssistantsDecoration()->endStroke();
}
KisCanvas2 *canvas2 = dynamic_cast(canvas());
if (canvas2) {
canvas2->viewManager()->enableControls();
}
setMode(KisTool::HOVER_MODE);
}
bool KisToolFreehand::tryPickByPaintOp(KoPointerEvent *event, AlternateAction action)
{
if (action != PickFgNode && action != PickFgImage) return false;
/**
* FIXME: we need some better way to implement modifiers
* for a paintop level. This method is used in DuplicateOp only!
*/
QPointF pos = adjustPosition(event->point, event->point);
qreal perspective = 1.0;
Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->canvasResourceProvider()->perspectiveGrids()) {
if (grid && grid->contains(pos)) {
perspective = grid->distance(pos);
break;
}
}
if (!currentPaintOpPreset()) {
return false;
}
bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->
mousePressEvent(KisPaintInformation(convertToPixelCoord(event->point),
m_infoBuilder->pressureToCurve(event->pressure()),
event->xTilt(), event->yTilt(),
event->rotation(),
event->tangentialPressure(),
perspective, 0, 0),
event->modifiers(),
currentNode());
// DuplicateOP during the picking of new source point (origin)
// is the only paintop that returns "false" here
return !paintOpIgnoredEvent;
}
void KisToolFreehand::activateAlternateAction(AlternateAction action)
{
if (action != ChangeSize) {
KisToolPaint::activateAlternateAction(action);
return;
}
useCursor(KisCursor::blankCursor());
setOutlineEnabled(true);
}
void KisToolFreehand::deactivateAlternateAction(AlternateAction action)
{
if (action != ChangeSize) {
KisToolPaint::deactivateAlternateAction(action);
return;
}
resetCursorStyle();
setOutlineEnabled(false);
}
void KisToolFreehand::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (tryPickByPaintOp(event, action)) {
m_paintopBasedPickingInAction = true;
return;
}
if (action != ChangeSize) {
KisToolPaint::beginAlternateAction(event, action);
return;
}
setMode(GESTURE_MODE);
m_initialGestureDocPoint = event->point;
m_initialGestureGlobalPoint = QCursor::pos();
m_lastDocumentPoint = event->point;
m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize();
}
void KisToolFreehand::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) return;
if (action != ChangeSize) {
KisToolPaint::continueAlternateAction(event, action);
return;
}
QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint);
QPointF actualWidgetPosition = convertDocumentToWidget(event->point);
QPointF offset = actualWidgetPosition - lastWidgetPosition;
KisCanvas2 *canvas2 = dynamic_cast(canvas());
QRect screenRect = QApplication::desktop()->screenGeometry();
qreal scaleX = 0;
qreal scaleY = 0;
canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY);
const qreal maxBrushSize = KisConfig(true).readEntry("maximumBrushSize", 1000);
const qreal effectiveMaxDragSize = 0.5 * screenRect.width();
const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX);
const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize;
const qreal sizeDiff = scaleCoeff * offset.x() ;
if (qAbs(sizeDiff) > 0.01) {
KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
const qreal newSize = qBound(0.01, m_lastPaintOpSize + sizeDiff, maxBrushSize);
settings->setPaintOpSize(newSize);
requestUpdateOutline(m_initialGestureDocPoint, 0);
//m_brushResizeCompressor.start(newSize);
m_lastDocumentPoint = event->point;
m_lastPaintOpSize = newSize;
}
}
void KisToolFreehand::endAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) {
m_paintopBasedPickingInAction = false;
return;
}
if (action != ChangeSize) {
KisToolPaint::endAlternateAction(event, action);
return;
}
QCursor::setPos(m_initialGestureGlobalPoint);
requestUpdateOutline(m_initialGestureDocPoint, 0);
setMode(HOVER_MODE);
}
bool KisToolFreehand::wantsAutoScroll() const
{
return false;
}
void KisToolFreehand::setAssistant(bool assistant)
{
m_assistant = assistant;
}
void KisToolFreehand::setOnlyOneAssistantSnap(bool assistant)
{
m_only_one_assistant = assistant;
}
void KisToolFreehand::slotDoResizeBrush(qreal newSize)
{
KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
settings->setPaintOpSize(newSize);
requestUpdateOutline(m_initialGestureDocPoint, 0);
}
QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin)
{
if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) {
static_cast(canvas())->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(m_only_one_assistant);
QPointF ap = static_cast(canvas())->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin);
return (1.0 - m_magnetism) * point + m_magnetism * ap;
}
return point;
}
qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint)
{
qreal perspective = 1.0;
Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->canvasResourceProvider()->perspectiveGrids()) {
if (grid && grid->contains(documentPoint)) {
perspective = grid->distance(documentPoint);
break;
}
}
return perspective;
}
void KisToolFreehand::explicitUpdateOutline()
{
requestUpdateOutline(m_outlineDocPoint, 0);
}
QPainterPath KisToolFreehand::getOutlinePath(const QPointF &documentPos,
const KoPointerEvent *event,
KisPaintOpSettings::OutlineMode outlineMode)
{
QPointF imagePos = convertToPixelCoord(documentPos);
if (currentPaintOpPreset())
return m_helper->paintOpOutline(imagePos,
event,
currentPaintOpPreset()->settings(),
outlineMode);
else
return QPainterPath();
}
diff --git a/libs/ui/widgets/kis_curve_widget.cpp b/libs/ui/widgets/kis_curve_widget.cpp
index ae6592771c..623d11a572 100644
--- a/libs/ui/widgets/kis_curve_widget.cpp
+++ b/libs/ui/widgets/kis_curve_widget.cpp
@@ -1,535 +1,535 @@
/*
* Copyright (c) 2005 C. Boemann
* Copyright (c) 2009 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.
*/
// C++ includes.
#include
#include
// Qt includes.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// KDE includes.
#include
#include
#include
// Local includes.
#include "widgets/kis_curve_widget.h"
#define bounds(x,a,b) (xb ? b :x))
#define MOUSE_AWAY_THRES 15
#define POINT_AREA 1E-4
#define CURVE_AREA 1E-4
#include "kis_curve_widget_p.h"
KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, f), d(new KisCurveWidget::Private(this))
{
setObjectName("KisCurveWidget");
d->m_grab_point_index = -1;
d->m_readOnlyMode = false;
d->m_guideVisible = false;
d->m_pixmapDirty = true;
d->m_pixmapCache = 0;
d->setState(ST_NORMAL);
d->m_intIn = 0;
d->m_intOut = 0;
setMouseTracking(true);
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent);
setMinimumSize(150, 50);
setMaximumSize(250, 250);
d->setCurveModified();
setFocusPolicy(Qt::StrongFocus);
}
KisCurveWidget::~KisCurveWidget()
{
delete d->m_pixmapCache;
delete d;
}
void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax)
{
d->m_intIn = in;
d->m_intOut = out;
if (!d->m_intIn || !d->m_intOut)
return;
d->m_inMin = inMin;
d->m_inMax = inMax;
d->m_outMin = outMin;
d->m_outMax = outMax;
int realInMin = qMin(inMin, inMax); // tilt elevation has range (90, 0), which QSpinBox can't handle
int realInMax = qMax(inMin, inMax);
d->m_intIn->setRange(realInMin, realInMax);
d->m_intOut->setRange(d->m_outMin, d->m_outMax);
connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection);
connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection);
d->syncIOControls();
}
void KisCurveWidget::dropInOutControls()
{
if (!d->m_intIn || !d->m_intOut)
return;
disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
d->m_intIn = d->m_intOut = 0;
}
void KisCurveWidget::inOutChanged(int)
{
QPointF pt;
Q_ASSERT(d->m_grab_point_index >= 0);
pt.setX(d->io2sp(d->m_intIn->value(), d->m_inMin, d->m_inMax));
pt.setY(d->io2sp(d->m_intOut->value(), d->m_outMin, d->m_outMax));
bool newPoint = false;
if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) {
newPoint = true;
d->m_curve.setPoint(d->m_grab_point_index, pt);
d->m_grab_point_index = d->m_curve.points().indexOf(pt);
emit pointSelectedChanged();
} else {
pt = d->m_curve.points()[d->m_grab_point_index];
}
if (!newPoint) {
// if there is a new Point, no point in rewriting values in spinboxes
d->m_intIn->blockSignals(true);
d->m_intOut->blockSignals(true);
d->m_intIn->setValue(d->sp2io(pt.x(), d->m_inMin, d->m_inMax));
d->m_intOut->setValue(d->sp2io(pt.y(), d->m_outMin, d->m_outMax));
d->m_intIn->blockSignals(false);
d->m_intOut->blockSignals(false);
}
d->setCurveModified(false);
}
void KisCurveWidget::reset(void)
{
d->m_grab_point_index = -1;
emit pointSelectedChanged();
d->m_guideVisible = false;
//remove total - 2 points.
while (d->m_curve.points().count() - 2 ) {
d->m_curve.removePoint(d->m_curve.points().count() - 2);
}
d->setCurveModified();
}
void KisCurveWidget::setCurveGuide(const QColor & color)
{
d->m_guideVisible = true;
d->m_colorGuide = color;
}
void KisCurveWidget::setPixmap(const QPixmap & pix)
{
d->m_pix = pix;
d->m_pixmapDirty = true;
d->setCurveRepaint();
}
QPixmap KisCurveWidget::getPixmap()
{
return d->m_pix;
}
void KisCurveWidget::setBasePixmap(const QPixmap &pix)
{
d->m_pixmapBase = pix;
}
QPixmap KisCurveWidget::getBasePixmap()
{
return d->m_pixmapBase;
}
bool KisCurveWidget::pointSelected() const
{
return d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1;
}
void KisCurveWidget::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) {
//x() find closest point to get focus afterwards
double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x();
int left_of_grab_point_index = d->m_grab_point_index - 1;
int right_of_grab_point_index = d->m_grab_point_index + 1;
int new_grab_point_index;
if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) <
fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) {
new_grab_point_index = left_of_grab_point_index;
} else {
new_grab_point_index = d->m_grab_point_index;
}
d->m_curve.removePoint(d->m_grab_point_index);
d->m_grab_point_index = new_grab_point_index;
emit pointSelectedChanged();
setCursor(Qt::ArrowCursor);
d->setState(ST_NORMAL);
}
e->accept();
d->setCurveModified();
} else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) {
d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY) );
setCursor(Qt::ArrowCursor);
d->setState(ST_NORMAL);
e->accept();
d->setCurveModified();
} else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) {
/* FIXME: Lets user choose the hotkeys */
addPointInTheMiddle();
e->accept();
} else
QWidget::keyPressEvent(e);
}
void KisCurveWidget::addPointInTheMiddle()
{
QPointF pt(0.5, d->m_curve.value(0.5));
if (!d->jumpOverExistingPoints(pt, -1))
return;
d->m_grab_point_index = d->m_curve.addPoint(pt);
emit pointSelectedChanged();
if (d->m_intIn)
d->m_intIn->setFocus(Qt::TabFocusReason);
d->setCurveModified();
}
void KisCurveWidget::resizeEvent(QResizeEvent *e)
{
d->m_pixmapDirty = true;
QWidget::resizeEvent(e);
}
void KisCurveWidget::paintEvent(QPaintEvent *)
{
int wWidth = width() - 1;
int wHeight = height() - 1;
QPainter p(this);
// Antialiasing is not a good idea here, because
// the grid will drift one pixel to any side due to rounding of int
// FIXME: let's user tell the last word (in config)
//p.setRenderHint(QPainter::Antialiasing);
QPalette appPalette = QApplication::palette();
p.fillRect(rect(), appPalette.color(QPalette::Base)); // clear out previous paint call results
// make the entire widget grayed out if it is disabled
if (!this->isEnabled()) {
p.setOpacity(0.2);
}
// draw background
if (!d->m_pix.isNull()) {
if (d->m_pixmapDirty || !d->m_pixmapCache) {
delete d->m_pixmapCache;
d->m_pixmapCache = new QPixmap(width(), height());
QPainter cachePainter(d->m_pixmapCache);
cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height());
cachePainter.drawPixmap(0, 0, d->m_pix);
d->m_pixmapDirty = false;
}
p.drawPixmap(0, 0, *d->m_pixmapCache);
}
d->drawGrid(p, wWidth, wHeight);
KisConfig cfg(true);
if (cfg.antialiasCurves()) {
p.setRenderHint(QPainter::Antialiasing);
}
// Draw curve.
double curY;
double normalizedX;
int x;
QPolygonF poly;
p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine));
for (x = 0 ; x < wWidth ; x++) {
normalizedX = double(x) / wWidth;
curY = wHeight - d->m_curve.value(normalizedX) * wHeight;
/**
* Keep in mind that QLineF rounds doubles
* to ints mathematically, not just rounds down
* like in C
*/
poly.append(QPointF(x, curY));
}
poly.append(QPointF(x, wHeight - d->m_curve.value(1.0) * wHeight));
p.drawPolyline(poly);
QPainterPath fillCurvePath;
QPolygonF fillPoly = poly;
fillPoly.append(QPoint(rect().width(), rect().height()));
fillPoly.append(QPointF(0,rect().height()));
// add a couple points to the edges so it fills in below always
QColor fillColor = appPalette.color(QPalette::Text);
fillColor.setAlphaF(0.2);
fillCurvePath.addPolygon(fillPoly);
p.fillPath(fillCurvePath, fillColor);
// Drawing curve handles.
double curveX;
double curveY;
if (!d->m_readOnlyMode) {
for (int i = 0; i < d->m_curve.points().count(); ++i) {
curveX = d->m_curve.points().at(i).x();
curveY = d->m_curve.points().at(i).y();
int handleSize = 12; // how big should control points be (diamater size)
if (i == d->m_grab_point_index) {
// active point is slightly more "bold"
p.setPen(QPen(appPalette.color(QPalette::Text), 4, Qt::SolidLine));
p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5),
wHeight - (handleSize*0.5) - curveY * wHeight,
handleSize,
handleSize));
} else {
p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine));
p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5),
wHeight - (handleSize*0.5) - curveY * wHeight,
handleSize,
handleSize));
}
}
}
// add border around widget to help contain everything
QPainterPath widgetBoundsPath;
widgetBoundsPath.addRect(rect());
p.strokePath(widgetBoundsPath, appPalette.color(QPalette::Text));
p.setOpacity(1.0); // reset to 1.0 in case we were drawing a disabled widget before
}
void KisCurveWidget::mousePressEvent(QMouseEvent * e)
{
if (d->m_readOnlyMode) return;
if (e->button() != Qt::LeftButton)
return;
double x = e->pos().x() / (double)(width() - 1);
double y = 1.0 - e->pos().y() / (double)(height() - 1);
int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height());
if (closest_point_index < 0) {
QPointF newPoint(x, y);
if (!d->jumpOverExistingPoints(newPoint, -1))
return;
d->m_grab_point_index = d->m_curve.addPoint(newPoint);
emit pointSelectedChanged();
} else {
d->m_grab_point_index = closest_point_index;
emit pointSelectedChanged();
}
d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x();
d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y();
d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x;
d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y;
d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY));
d->m_draggedAwayPointIndex = -1;
d->setState(ST_DRAG);
d->setCurveModified();
}
void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
{
if (d->m_readOnlyMode) return;
if (e->button() != Qt::LeftButton)
return;
setCursor(Qt::ArrowCursor);
d->setState(ST_NORMAL);
d->setCurveModified();
}
void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
{
if (d->m_readOnlyMode) return;
double x = e->pos().x() / (double)(width() - 1);
double y = 1.0 - e->pos().y() / (double)(height() - 1);
if (d->state() == ST_NORMAL) { // If no point is selected set the cursor shape if on top
int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height());
if (nearestPointIndex < 0)
setCursor(Qt::ArrowCursor);
else
setCursor(Qt::CrossCursor);
} else { // Else, drag the selected point
bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES ||
e->pos().x() < -MOUSE_AWAY_THRES;
bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES ||
e->pos().y() < -MOUSE_AWAY_THRES;
bool removePoint = (crossedHoriz || crossedVert);
if (!removePoint && d->m_draggedAwayPointIndex >= 0) {
// point is no longer dragged away so reinsert it
QPointF newPoint(d->m_draggedAwayPoint);
d->m_grab_point_index = d->m_curve.addPoint(newPoint);
d->m_draggedAwayPointIndex = -1;
}
if (removePoint &&
(d->m_draggedAwayPointIndex >= 0))
return;
setCursor(Qt::CrossCursor);
x += d->m_grabOffsetX;
y += d->m_grabOffsetY;
double leftX;
double rightX;
if (d->m_grab_point_index == 0) {
leftX = 0.0;
if (d->m_curve.points().count() > 1)
rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
else
rightX = 1.0;
} else if (d->m_grab_point_index == d->m_curve.points().count() - 1) {
leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
rightX = 1.0;
} else {
Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1);
// the 1E-4 addition so we can grab the dot later.
leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
}
x = bounds(x, leftX, rightX);
y = bounds(y, 0., 1.);
d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y));
if (removePoint && d->m_curve.points().count() > 2) {
d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index];
d->m_draggedAwayPointIndex = d->m_grab_point_index;
d->m_curve.removePoint(d->m_grab_point_index);
d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1);
emit pointSelectedChanged();
}
d->setCurveModified();
}
}
KisCubicCurve KisCurveWidget::curve()
{
return d->m_curve;
}
void KisCurveWidget::setCurve(KisCubicCurve inlist)
{
d->m_curve = inlist;
d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1);
- emit pointSelectedChanged();
d->setCurveModified();
+ emit pointSelectedChanged();
}
void KisCurveWidget::leaveEvent(QEvent *)
{
}
diff --git a/plugins/paintops/libpaintop/kis_curve_option.h b/plugins/paintops/libpaintop/kis_curve_option.h
index 2397ae6ca6..881d3dc3a4 100644
--- a/plugins/paintops/libpaintop/kis_curve_option.h
+++ b/plugins/paintops/libpaintop/kis_curve_option.h
@@ -1,213 +1,213 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Boudewijn Rempt
* Copyright (C) 2011 Silvio Heinrich
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CURVE_OPTION_H
#define KIS_CURVE_OPTION_H
#include
#include
#include "kis_paintop_option.h"
#include "kis_global.h"
#include
#include "kritapaintop_export.h"
#include "kis_dynamic_sensor.h"
class KisDynamicSensor;
/**
* KisCurveOption is the base class for paintop options that are
* defined through one or more curves.
*
* Note: it is NOT a KisPaintOpOption, even though the API is pretty similar!
*
* KisCurveOption classes have a generic GUI widget, KisCurveOptionWidget. So,
* in contrast to KisPaintOpOption classes, KisCurveOption instances can and
* will be created in the constructor of KisPaintOp paintops. This class can
* manage to read and write its settings directly.
*
*/
class PAINTOP_EXPORT KisCurveOption
{
public:
KisCurveOption(const QString& name,
KisPaintOpOption::PaintopCategory category,
bool checked,
qreal value = 1.0,
qreal min = 0.0,
qreal max = 1.0);
virtual ~KisCurveOption();
virtual void writeOptionSetting(KisPropertiesConfigurationSP setting) const;
virtual void readOptionSetting(KisPropertiesConfigurationSP setting);
virtual void lodLimitations(KisPaintopLodLimitations *l) const;
//Please override for other values than 0-100 and %
virtual int intMinValue()const;
virtual int intMaxValue()const;
virtual QString valueSuffix()const;
const QString& name() const;
KisPaintOpOption::PaintopCategory category() const;
qreal minValue() const;
qreal maxValue() const;
qreal value() const;
void resetAllSensors();
KisDynamicSensorSP sensor(DynamicSensorType sensorType, bool active) const;
void replaceSensor(KisDynamicSensorSP sensor);
QList sensors();
QList activeSensors() const;
bool isCheckable();
bool isChecked() const;
bool isCurveUsed() const;
bool isSameCurveUsed() const;
bool isRandom() const;
int getCurveMode() const;
void setSeparateCurveValue(bool separateCurveValue);
void setChecked(bool checked);
void setCurveUsed(bool useCurve);
void setCurve(DynamicSensorType sensorType, bool useSameCurve, const KisCubicCurve &curve);
void setValue(qreal value);
void setCurveMode(int mode);
struct ValueComponents {
ValueComponents()
: constant(1.0),
scaling(1.0),
additive(0.0),
absoluteOffset(0.0),
hasAbsoluteOffset(false),
hasScaling(false),
hasAdditive(false)
{
}
qreal constant;
qreal scaling;
qreal additive;
qreal absoluteOffset;
bool hasAbsoluteOffset;
bool hasScaling;
bool hasAdditive;
qreal minSizeLikeValue;
qreal maxSizeLikeValue;
/**
* @param normalizedBaseAngle canvas rotation angle normalized to range [0; 1]
* @param absoluteAxesFlipped true if underlying image coordinate system is flipped (horiz. mirror != vert. mirror)
*/
qreal rotationLikeValue(qreal normalizedBaseAngle, bool absoluteAxesFlipped) const {
const qreal offset =
!hasAbsoluteOffset ? normalizedBaseAngle :
absoluteAxesFlipped ? 1.0 - absoluteOffset :
absoluteOffset;
const qreal realScalingPart = hasScaling ? KisDynamicSensor::scalingToAdditive(scaling) : 0.0;
const qreal realAdditivePart = hasAdditive ? additive : 0;
- qreal value = wrapInRange(2 * offset + constant * realScalingPart + realAdditivePart, -1.0, 1.0);
+ qreal value = wrapInRange(2 * offset + constant * (realScalingPart + realAdditivePart), -1.0, 1.0);
if (qIsNaN(value)) {
qWarning() << "rotationLikeValue returns NaN!" << normalizedBaseAngle << absoluteAxesFlipped;
value = 0;
}
return value;
}
qreal sizeLikeValue() const {
const qreal offset =
hasAbsoluteOffset ? absoluteOffset : 1.0;
const qreal realScalingPart = hasScaling ? scaling : 1.0;
const qreal realAdditivePart = hasAdditive ? KisDynamicSensor::additiveToScaling(additive) : 1.0;
return qBound(minSizeLikeValue,
constant * offset * realScalingPart * realAdditivePart,
maxSizeLikeValue);
}
private:
static inline qreal wrapInRange(qreal x, qreal min, qreal max) {
const qreal range = max - min;
x -= min;
if (x < 0.0) {
x = range + fmod(x, range);
}
if (x > range) {
x = fmod(x, range);
}
return x + min;
}
};
/**
* Uses the curves set on the sensors to compute a single
* double value that can control the parameters of a brush.
*
* This value is derives from the values stored in
* ValuesComponents object.
*/
ValueComponents computeValueComponents(const KisPaintInformation& info) const;
qreal computeSizeLikeValue(const KisPaintInformation &info) const;
qreal computeRotationLikeValue(const KisPaintInformation& info, qreal baseValue, bool absoluteAxesFlipped) const;
protected:
void setValueRange(qreal min, qreal max);
/**
* Read the option using the prefix in argument
*/
void readNamedOptionSetting(const QString& prefix, const KisPropertiesConfigurationSP setting);
QString m_name;
KisPaintOpOption::PaintopCategory m_category;
bool m_checkable;
bool m_checked;
bool m_useCurve;
bool m_useSameCurve;
bool m_separateCurveValue;
int m_curveMode;
QMap m_sensorMap;
QMap m_curveCache;
private:
qreal m_value;
qreal m_minValue;
qreal m_maxValue;
};
#endif
diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp
index d474fe0376..66d41decdb 100644
--- a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp
+++ b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp
@@ -1,324 +1,340 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Boudewijn Rempt
* Copyright (C) 2009 Sven Langkamp
* Copyright (C) 2011 Silvio Heinrich
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_curve_option_widget.h"
#include "ui_wdgcurveoption.h"
#include "widgets/kis_curve_widget.h"
#include "kis_dynamic_sensor.h"
#include "kis_global.h"
#include "kis_curve_option.h"
#include "kis_signals_blocker.h"
#include "kis_icon_utils.h"
inline void setLabel(QLabel* label, const KisCurveLabel& curve_label)
{
if (curve_label.icon().isNull()) {
label->setText(curve_label.name());
}
else {
label->setPixmap(QPixmap::fromImage(curve_label.icon()));
}
}
KisCurveOptionWidget::KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider)
: KisPaintOpOption(curveOption->category(), curveOption->isChecked())
, m_widget(new QWidget)
, m_curveOptionWidget(new Ui_WdgCurveOption())
, m_curveOption(curveOption)
{
setObjectName("KisCurveOptionWidget");
m_curveOptionWidget->setupUi(m_widget);
setConfigurationPage(m_widget);
m_curveOptionWidget->sensorSelector->setCurveOption(curveOption);
updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted());
updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted());
- connect(m_curveOptionWidget->curveWidget, SIGNAL(modified()), this, SLOT(transferCurve()));
+ connect(m_curveOptionWidget->curveWidget, SIGNAL(modified()), this, SLOT(slotModified()));
connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(emitSettingChanged()));
connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(updateLabelsOfCurrentSensor()));
connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP)), SLOT(updateSensorCurveLabels(KisDynamicSensorSP)));
connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP)), SLOT(updateCurve(KisDynamicSensorSP)));
- connect(m_curveOptionWidget->checkBoxUseSameCurve, SIGNAL(stateChanged(int)), SLOT(transferCurve()));
+ connect(m_curveOptionWidget->checkBoxUseSameCurve, SIGNAL(stateChanged(int)), SLOT(slotStateChanged()));
// set all the icons for the curve preset shapes
updateThemedIcons();
// various curve preset buttons with predefined curves
connect(m_curveOptionWidget->linearCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLinear()));
connect(m_curveOptionWidget->revLinearButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseLinear()));
connect(m_curveOptionWidget->jCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveJShape()));
connect(m_curveOptionWidget->lCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLShape()));
connect(m_curveOptionWidget->sCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveSShape()));
connect(m_curveOptionWidget->reverseSCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseSShape()));
connect(m_curveOptionWidget->uCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveUShape()));
connect(m_curveOptionWidget->revUCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveArchShape()));
m_curveOptionWidget->label_ymin->setText(minLabel);
m_curveOptionWidget->label_ymax->setText(maxLabel);
// strength settings is shown as 0-100%
m_curveOptionWidget->strengthSlider->setRange(curveOption->minValue()*100, curveOption->maxValue()*100, 0);
m_curveOptionWidget->strengthSlider->setValue(curveOption->value()*100);
m_curveOptionWidget->strengthSlider->setPrefix(i18n("Strength: "));
m_curveOptionWidget->strengthSlider->setSuffix(i18n("%"));
if (hideSlider) {
m_curveOptionWidget->strengthSlider->hide();
}
connect(m_curveOptionWidget->checkBoxUseCurve, SIGNAL(stateChanged(int)) , SLOT(updateValues()));
connect(m_curveOptionWidget->curveMode, SIGNAL(currentIndexChanged(int)), SLOT(updateMode()));
connect(m_curveOptionWidget->strengthSlider, SIGNAL(valueChanged(qreal)), SLOT(updateValues()));
}
KisCurveOptionWidget::~KisCurveOptionWidget()
{
delete m_curveOption;
delete m_curveOptionWidget;
}
void KisCurveOptionWidget::writeOptionSetting(KisPropertiesConfigurationSP setting) const
{
m_curveOption->writeOptionSetting(setting);
}
void KisCurveOptionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
//setting->dump();
m_curveOption->readOptionSetting(setting);
m_curveOptionWidget->checkBoxUseCurve->setChecked(m_curveOption->isCurveUsed());
m_curveOptionWidget->strengthSlider->setValue(m_curveOption->value()*100);
m_curveOptionWidget->checkBoxUseSameCurve->setChecked(m_curveOption->isSameCurveUsed());
m_curveOptionWidget->curveMode->setCurrentIndex(m_curveOption->getCurveMode());
disableWidgets(!m_curveOption->isCurveUsed());
m_curveOptionWidget->sensorSelector->reload();
m_curveOptionWidget->sensorSelector->setCurrent(m_curveOption->activeSensors().first());
updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted());
updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted());
+
+ if (m_curveOption->isSameCurveUsed()) {
+ // make sure the curve is transfered to all sensors to avoid updating from a wrong curve later
+ transferCurve();
+ }
}
void KisCurveOptionWidget::lodLimitations(KisPaintopLodLimitations *l) const
{
m_curveOption->lodLimitations(l);
}
bool KisCurveOptionWidget::isCheckable() const
{
return m_curveOption->isCheckable();
}
bool KisCurveOptionWidget::isChecked() const
{
return m_curveOption->isChecked();
}
void KisCurveOptionWidget::setChecked(bool checked)
{
m_curveOption->setChecked(checked);
}
KisCurveOption* KisCurveOptionWidget::curveOption()
{
return m_curveOption;
}
QWidget* KisCurveOptionWidget::curveWidget()
{
return m_widget;
}
+void KisCurveOptionWidget::slotModified()
+{
+ transferCurve();
+}
+
+void KisCurveOptionWidget::slotStateChanged()
+{
+ transferCurve();
+}
+
+
void KisCurveOptionWidget::transferCurve()
{
m_curveOptionWidget->sensorSelector->setCurrentCurve(m_curveOptionWidget->curveWidget->curve(), m_curveOptionWidget->checkBoxUseSameCurve->isChecked());
emitSettingChanged();
}
void KisCurveOptionWidget::updateSensorCurveLabels(KisDynamicSensorSP sensor)
{
if (sensor) {
m_curveOptionWidget->label_xmin->setText(KisDynamicSensor::minimumLabel(sensor->sensorType()));
m_curveOptionWidget->label_xmax->setText(KisDynamicSensor::maximumLabel(sensor->sensorType(), sensor->length()));
int inMinValue = KisDynamicSensor::minimumValue(sensor->sensorType());
int inMaxValue = KisDynamicSensor::maximumValue(sensor->sensorType(), sensor->length());
QString inSuffix = KisDynamicSensor::valueSuffix(sensor->sensorType());
int outMinValue = m_curveOption->intMinValue();
int outMaxValue = m_curveOption->intMaxValue();
QString outSuffix = m_curveOption->valueSuffix();
m_curveOptionWidget->intIn->setSuffix(inSuffix);
m_curveOptionWidget->intOut->setSuffix(outSuffix);
m_curveOptionWidget->curveWidget->setupInOutControls(m_curveOptionWidget->intIn,m_curveOptionWidget->intOut,
inMinValue,inMaxValue,outMinValue,outMaxValue);
}
}
void KisCurveOptionWidget::updateCurve(KisDynamicSensorSP sensor)
{
if (sensor) {
bool blockSignal = m_curveOptionWidget->curveWidget->blockSignals(true);
m_curveOptionWidget->curveWidget->setCurve(sensor->curve());
m_curveOptionWidget->curveWidget->blockSignals(blockSignal);
}
}
void KisCurveOptionWidget::updateLabelsOfCurrentSensor()
{
updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted());
updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted());
}
void KisCurveOptionWidget::updateValues()
{
m_curveOption->setValue(m_curveOptionWidget->strengthSlider->value()/100.0); // convert back to 0-1 for data
m_curveOption->setCurveUsed(m_curveOptionWidget->checkBoxUseCurve->isChecked());
disableWidgets(!m_curveOptionWidget->checkBoxUseCurve->isChecked());
emitSettingChanged();
}
void KisCurveOptionWidget::updateMode()
{
m_curveOption->setCurveMode(m_curveOptionWidget->curveMode->currentIndex());
emitSettingChanged();
}
void KisCurveOptionWidget::changeCurveLinear()
{
QList points;
points.push_back(QPointF(0,0));
points.push_back(QPointF(1,1));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveReverseLinear()
{
QList points;
points.push_back(QPointF(0,1));
points.push_back(QPointF(1,0));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveSShape()
{
QList points;
points.push_back(QPointF(0,0));
points.push_back(QPointF(0.25,0.1));
points.push_back(QPointF(0.75,0.9));
points.push_back(QPointF(1, 1));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveReverseSShape()
{
QList points;
points.push_back(QPointF(0,1));
points.push_back(QPointF(0.25,0.9));
points.push_back(QPointF(0.75,0.1));
points.push_back(QPointF(1,0));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveJShape()
{
QList points;
points.push_back(QPointF(0,0));
points.push_back(QPointF(0.35,0.1));
points.push_back(QPointF(1,1));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveLShape()
{
QList points;
points.push_back(QPointF(0,1));
points.push_back(QPointF(0.25,0.48));
points.push_back(QPointF(1,0));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveUShape()
{
QList points;
points.push_back(QPointF(0,1));
points.push_back(QPointF(0.5,0));
points.push_back(QPointF(1,1));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveArchShape()
{
QList points;
points.push_back(QPointF(0,0));
points.push_back(QPointF(0.5,1));
points.push_back(QPointF(1,0));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::disableWidgets(bool disable)
{
m_curveOptionWidget->checkBoxUseSameCurve->setDisabled(disable);
m_curveOptionWidget->curveWidget->setDisabled(disable);
m_curveOptionWidget->sensorSelector->setDisabled(disable);
m_curveOptionWidget->label_xmax->setDisabled(disable);
m_curveOptionWidget->label_xmin->setDisabled(disable);
m_curveOptionWidget->label_ymax->setDisabled(disable);
m_curveOptionWidget->label_ymin->setDisabled(disable);
}
void KisCurveOptionWidget::updateThemedIcons()
{
// set all the icons for the curve preset shapes
m_curveOptionWidget->linearCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear"));
m_curveOptionWidget->revLinearButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear-reverse"));
m_curveOptionWidget->jCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-j"));
m_curveOptionWidget->lCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-l"));
m_curveOptionWidget->sCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s"));
m_curveOptionWidget->reverseSCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s-reverse"));
m_curveOptionWidget->uCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-u"));
m_curveOptionWidget->revUCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-arch"));
// this helps make the checkboxes show themselves on the dark color themes
QPalette pal = m_curveOptionWidget->sensorSelector->palette();
QPalette newPalette = pal;
newPalette.setColor(QPalette::Active, QPalette::Background, pal.text().color() );
m_curveOptionWidget->sensorSelector->setPalette(newPalette);
}
diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.h b/plugins/paintops/libpaintop/kis_curve_option_widget.h
index eae9d1be19..e5ba4a2ccc 100644
--- a/plugins/paintops/libpaintop/kis_curve_option_widget.h
+++ b/plugins/paintops/libpaintop/kis_curve_option_widget.h
@@ -1,84 +1,87 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Boudewijn Rempt
* Copyright (C) 2009 Sven Langkamp
* Copyright (C) 2011 Silvio Heinrich
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CURVE_OPTION_WIDGET_H
#define KIS_CURVE_OPTION_WIDGET_H
#include
class Ui_WdgCurveOption;
class KisCurveOption;
class QComboBox;
#include
class PAINTOP_EXPORT KisCurveOptionWidget : public KisPaintOpOption
{
Q_OBJECT
public:
KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider = false);
~KisCurveOptionWidget() override;
void writeOptionSetting(KisPropertiesConfigurationSP setting) const override;
void readOptionSetting(const KisPropertiesConfigurationSP setting) override;
void lodLimitations(KisPaintopLodLimitations *l) const override;
bool isCheckable() const override;
bool isChecked() const override;
void setChecked(bool checked) override;
void show();
protected:
KisCurveOption* curveOption();
QWidget* curveWidget();
private Q_SLOTS:
+ void slotModified();
+ void slotStateChanged();
+
void transferCurve();
void updateSensorCurveLabels(KisDynamicSensorSP sensor);
void updateCurve(KisDynamicSensorSP sensor);
void updateValues();
void updateMode();
void updateLabelsOfCurrentSensor();
void disableWidgets(bool disable);
void updateThemedIcons();
// curve shape preset buttons
void changeCurveLinear();
void changeCurveReverseLinear();
void changeCurveSShape();
void changeCurveReverseSShape();
void changeCurveJShape();
void changeCurveLShape();
void changeCurveUShape();
void changeCurveArchShape();
private:
QWidget* m_widget;
Ui_WdgCurveOption* m_curveOptionWidget;
QComboBox* m_curveMode;
KisCurveOption* m_curveOption;
};
#endif // KIS_CURVE_OPTION_WIDGET_H
diff --git a/plugins/paintops/libpaintop/kis_pressure_hsv_option.cpp b/plugins/paintops/libpaintop/kis_pressure_hsv_option.cpp
index d6a7f748c7..22d45c2ba7 100644
--- a/plugins/paintops/libpaintop/kis_pressure_hsv_option.cpp
+++ b/plugins/paintops/libpaintop/kis_pressure_hsv_option.cpp
@@ -1,175 +1,174 @@
/* This file is part of the KDE project
* Copyright (c) 2010 Cyrille Berger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_pressure_hsv_option.h"
#include
#include
KisPressureHSVOption* KisPressureHSVOption::createHueOption() {
return new KisPressureHSVOption("h");
}
QString KisPressureHSVOption::hueMinLabel()
{
// xgettext: no-c-format
QString activeColorMsg = i18n("(0° is active color)");
QString br("
");
QString fullPercent = i18n("+180°");
QString zeroPercent = i18n("-180°");
return QString(zeroPercent + br + i18n("CCW hue") + br + activeColorMsg);
}
QString KisPressureHSVOption::huemaxLabel()
{
// xgettext: no-c-format
QString activeColorMsg = i18n("(0° is active color)");
QString br("
");
QString fullPercent = i18n("+180°");
QString zeroPercent = i18n("-180°");
return QString(fullPercent + br + i18n("CW hue"));
}
KisPressureHSVOption* KisPressureHSVOption::createSaturationOption() {
return new KisPressureHSVOption("s");
}
QString KisPressureHSVOption::saturationMinLabel()
{
// xgettext: no-c-format
QString activeColorMsg = i18n("(50% is active color)");
QString br("
");
QString fullPercent = i18n("+100%");
QString zeroPercent = i18n("-100%");
return QString(zeroPercent + br + i18n("Less saturation ") + br + activeColorMsg);
}
QString KisPressureHSVOption::saturationmaxLabel()
{
// xgettext: no-c-format
QString activeColorMsg = i18n("(50% is active color)");
QString br("
");
QString fullPercent = i18n("+100%");
QString zeroPercent = i18n("-100%");
return QString(fullPercent + br + i18n("More saturation"));
}
KisPressureHSVOption* KisPressureHSVOption::createValueOption() {
return new KisPressureHSVOption("v");
}
QString KisPressureHSVOption::valueMinLabel()
{
// xgettext: no-c-format
QString activeColorMsg = i18n("(50% is active color)");
QString br("
");
QString fullPercent = i18n("+100%");
QString zeroPercent = i18n("-100%");
return QString(zeroPercent + br + i18n("Lower value ") + br + activeColorMsg);
}
QString KisPressureHSVOption::valuemaxLabel()
{
// xgettext: no-c-format
QString activeColorMsg = i18n("(50% is active color)");
QString br("
");
QString fullPercent = i18n("+100%");
QString zeroPercent = i18n("-100%");
return QString(fullPercent + br + i18n("Higher value"));
}
struct KisPressureHSVOption::Private
{
QString parameterName;
int paramId;
};
KisPressureHSVOption::KisPressureHSVOption(const QString& parameterName)
: KisCurveOption(parameterName, KisPaintOpOption::COLOR, false)
, d(new Private())
{
d->parameterName = parameterName;
d->paramId = -1;
}
KisPressureHSVOption::~KisPressureHSVOption()
{
delete d;
}
void KisPressureHSVOption::apply(KoColorTransformation* transfo, const KisPaintInformation& info) const
{
if (!isChecked()) {
return;
}
if (d->paramId == -1) {
d->paramId = transfo->parameterId(d->parameterName);
}
qreal v = computeSizeLikeValue(info);
if (d->parameterName == "h") {
v = computeRotationLikeValue(info, 0, false);
- v = (v) * (this->value());//multiply by strength slider.
} else {
qreal halfValue = this->value()*0.5;
v = (v*this->value()) + (0.5-halfValue);
v = (v*2)-1;
}
transfo->setParameter(d->paramId, v);
transfo->setParameter(3, 0); //sets the type to HSV.
transfo->setParameter(4, false); //sets the colorize to false.
}
int KisPressureHSVOption::intMinValue() const
{
if (name() == "h") {
return -180;
} else {
return -100;
}
}
int KisPressureHSVOption::intMaxValue() const
{
if (name() == "h") {
return 180;
} else {
return 100;
}
}
QString KisPressureHSVOption::valueSuffix() const
{
if (name() == "h") {
return i18n("°");
} else {
return i18n("%");
}
}