KoShape.cpp 70.6 KB
Newer Older
Thomas Zander's avatar
Thomas Zander committed
1
/* This file is part of the KDE project
C. Boemann's avatar
C. Boemann committed
2
   Copyright (C) 2006 C. Boemann Rasmussen <cbo@boemann.dk>
Thomas Zander's avatar
Thomas Zander committed
3
   Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
4
   Copyright (C) 2006-2010 Thorsten Zachmann <zachmann@kde.org>
5
   Copyright (C) 2007-2009,2011 Jan Hambrecht <jaham@gmx.net>
Boudewijn Rempt's avatar
Boudewijn Rempt committed
6
   CopyRight (C) 2010 Boudewijn Rempt <boud@kogmbh.com>
Thomas Zander's avatar
Thomas Zander committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20

   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,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
21
   Boston, MA 02110-1301, USA.
Thomas Zander's avatar
Thomas Zander committed
22 23 24
*/

#include "KoShape.h"
25
#include "KoShape_p.h"
Thomas Zander's avatar
Thomas Zander committed
26
#include "KoShapeContainer.h"
27
#include "KoShapeLayer.h"
28
#include "KoShapeContainerModel.h"
Thomas Zander's avatar
Thomas Zander committed
29
#include "KoSelection.h"
30
#include "KoPointerEvent.h"
Thomas Zander's avatar
Thomas Zander committed
31
#include "KoInsets.h"
32
#include "KoShapeStrokeModel.h"
33 34
#include "KoShapeBackground.h"
#include "KoColorBackground.h"
35
#include "KoHatchBackground.h"
36 37
#include "KoGradientBackground.h"
#include "KoPatternBackground.h"
38
#include "KoShapeManager.h"
39
#include "KoShapeUserData.h"
40
#include "KoShapeApplicationData.h"
41
#include "KoShapeSavingContext.h"
42
#include "KoShapeLoadingContext.h"
43
#include "KoViewConverter.h"
44
#include "KoShapeStroke.h"
45
#include "KoShapeShadow.h"
46
#include "KoClipPath.h"
47
#include "KoPathShape.h"
48
#include "KoOdfWorkaround.h"
49
#include "KoFilterEffectStack.h"
50
#include <KoSnapData.h>
51
#include <KoElementReference.h>
Thomas Zander's avatar
Thomas Zander committed
52

53
#include <KoXmlReader.h>
54
#include <KoXmlWriter.h>
55
#include <KoXmlNS.h>
56
#include <KoGenStyle.h>
57
#include <KoGenStyles.h>
58
#include <KoUnit.h>
59
#include <KoOdfStylesReader.h>
60
#include <KoOdfGraphicStyles.h>
61
#include <KoOdfLoadingContext.h>
62
#include <KoStyleStack.h>
63
#include <KoBorder.h>
64

Thomas Zander's avatar
Thomas Zander committed
65
#include <QPainter>
66
#include <QVariant>
Thomas Zander's avatar
Thomas Zander committed
67
#include <QPainterPath>
68
#include <QList>
69
#include <QMap>
70
#include <QByteArray>
Boudewijn Rempt's avatar
Boudewijn Rempt committed
71
#include <FlakeDebug.h>
72

73
#include <limits>
74
#include "KoOdfGradientBackground.h"
75

Boudewijn Rempt's avatar
Boudewijn Rempt committed
76 77
// KoShapePrivate

78
KoShapePrivate::KoShapePrivate(KoShape *shape)
79 80
    : q_ptr(shape),
      size(50, 50),
Boudewijn Rempt's avatar
Boudewijn Rempt committed
81 82 83
      parent(0),
      userData(0),
      appData(0),
84
      stroke(0),
Boudewijn Rempt's avatar
Boudewijn Rempt committed
85
      shadow(0),
86
      border(0),
87
      clipPath(0),
Boudewijn Rempt's avatar
Boudewijn Rempt committed
88 89 90
      filterEffectStack(0),
      transparency(0.0),
      zIndex(0),
91
      runThrough(0),
Boudewijn Rempt's avatar
Boudewijn Rempt committed
92 93 94 95 96 97 98
      visible(true),
      printable(true),
      geometryProtected(false),
      keepAspect(false),
      selectable(true),
      detectCollision(false),
      protectContent(false),
99
      textRunAroundSide(KoShape::BiggestRunAroundSide),
100
      textRunAroundDistanceLeft(0.0),
101
      textRunAroundDistanceTop(0.0),
102 103
      textRunAroundDistanceRight(0.0),
      textRunAroundDistanceBottom(0.0),
104
      textRunAroundThreshold(0.0),
105
      textRunAroundContour(KoShape::ContourFull),
106 107
      anchor(0),
      minimumHeight(0.0)
108
{
109 110 111 112
    connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint);
    connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint);
    connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint);
    connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint);
113
    connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter);
114
}
Thomas Zander's avatar
Thomas Zander committed
115

116 117
KoShapePrivate::~KoShapePrivate()
{
118
    Q_Q(KoShape);
119
    if (parent)
Thomas Zander's avatar
Thomas Zander committed
120
        parent->removeShape(q);
121
    Q_FOREACH (KoShapeManager *manager, shapeManagers) {
122 123
        manager->remove(q);
        manager->removeAdditional(q);
Thomas Zander's avatar
Thomas Zander committed
124
    }
125 126
    delete userData;
    delete appData;
127 128
    if (stroke && !stroke->deref())
        delete stroke;
129
    if (shadow && !shadow->deref())
130
        delete shadow;
131
    if (filterEffectStack && !filterEffectStack->deref())
132
        delete filterEffectStack;
Jan Hambrecht's avatar
Jan Hambrecht committed
133
    delete clipPath;
134
}
Thomas Zander's avatar
Thomas Zander committed
135

136 137
void KoShapePrivate::shapeChanged(KoShape::ChangeType type)
{
138
    Q_Q(KoShape);
139
    if (parent)
140 141
        parent->model()->childChanged(q, type);
    q->shapeChanged(type);
142
    Q_FOREACH (KoShape * shape, dependees)
143
        shape->shapeChanged(type, q);
144
}
145

146
void KoShapePrivate::updateStroke()
147
{
148
    Q_Q(KoShape);
149
    if (stroke == 0)
150 151
        return;
    KoInsets insets;
152
    stroke->strokeInsets(q, insets);
153 154 155
    QSizeF inner = q->size();
    // update left
    q->update(QRectF(-insets.left, -insets.top, insets.left,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
156
                     inner.height() + insets.top + insets.bottom));
157 158
    // update top
    q->update(QRectF(-insets.left, -insets.top,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
159
                     inner.width() + insets.left + insets.right, insets.top));
160 161
    // update right
    q->update(QRectF(inner.width(), -insets.top, insets.right,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
162
                     inner.height() + insets.top + insets.bottom));
163 164
    // update bottom
    q->update(QRectF(-insets.left, inner.height(),
Boudewijn Rempt's avatar
Boudewijn Rempt committed
165
                     inner.width() + insets.left + insets.right, insets.bottom));
166
}
Thomas Zander's avatar
Thomas Zander committed
167

168 169 170 171 172 173 174 175
void KoShapePrivate::addShapeManager(KoShapeManager *manager)
{
    shapeManagers.insert(manager);
}

void KoShapePrivate::removeShapeManager(KoShapeManager *manager)
{
    shapeManagers.remove(manager);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
176 177
}

178
void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
179
{
Jan Hambrecht's avatar
Jan Hambrecht committed
180
    switch(point.alignment) {
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
        case KoConnectionPoint::AlignNone:
            point.position = KoFlake::toRelative(point.position, shapeSize);
            point.position.rx() = qBound<qreal>(0.0, point.position.x(), 1.0);
            point.position.ry() = qBound<qreal>(0.0, point.position.y(), 1.0);
            break;
        case KoConnectionPoint::AlignRight:
            point.position.rx() -= shapeSize.width();
        case KoConnectionPoint::AlignLeft:
            point.position.ry() = 0.5*shapeSize.height();
            break;
        case KoConnectionPoint::AlignBottom:
            point.position.ry() -= shapeSize.height();
        case KoConnectionPoint::AlignTop:
            point.position.rx() = 0.5*shapeSize.width();
            break;
        case KoConnectionPoint::AlignTopLeft:
            // nothing to do here
            break;
        case KoConnectionPoint::AlignTopRight:
            point.position.rx() -= shapeSize.width();
            break;
        case KoConnectionPoint::AlignBottomLeft:
            point.position.ry() -= shapeSize.height();
            break;
        case KoConnectionPoint::AlignBottomRight:
            point.position.rx() -= shapeSize.width();
            point.position.ry() -= shapeSize.height();
            break;
        case KoConnectionPoint::AlignCenter:
            point.position.rx() -= 0.5 * shapeSize.width();
            point.position.ry() -= 0.5 * shapeSize.height();
            break;
    }
}

void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
{
Jan Hambrecht's avatar
Jan Hambrecht committed
218
    switch(point.alignment) {
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
        case KoConnectionPoint::AlignNone:
            point.position = KoFlake::toAbsolute(point.position, shapeSize);
            break;
        case KoConnectionPoint::AlignRight:
            point.position.rx() += shapeSize.width();
        case KoConnectionPoint::AlignLeft:
            point.position.ry() = 0.5*shapeSize.height();
            break;
        case KoConnectionPoint::AlignBottom:
            point.position.ry() += shapeSize.height();
        case KoConnectionPoint::AlignTop:
            point.position.rx() = 0.5*shapeSize.width();
            break;
        case KoConnectionPoint::AlignTopLeft:
            // nothing to do here
            break;
        case KoConnectionPoint::AlignTopRight:
            point.position.rx() += shapeSize.width();
            break;
        case KoConnectionPoint::AlignBottomLeft:
            point.position.ry() += shapeSize.height();
            break;
        case KoConnectionPoint::AlignBottomRight:
            point.position.rx() += shapeSize.width();
            point.position.ry() += shapeSize.height();
            break;
        case KoConnectionPoint::AlignCenter:
            point.position.rx() += 0.5 * shapeSize.width();
            point.position.ry() += 0.5 * shapeSize.height();
            break;
    }
250 251
}

252
// static
253
QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context)
254 255 256 257 258 259 260 261 262 263 264
{
    KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
    QString value;

    if (styleStack.hasProperty(KoXmlNS::draw, property)) {
        value = styleStack.property(KoXmlNS::draw, property);
    }

    return value;
}

265 266


267
// ======== KoShape
Thomas Zander's avatar
Thomas Zander committed
268
KoShape::KoShape()
Boudewijn Rempt's avatar
Boudewijn Rempt committed
269
    : d_ptr(new KoShapePrivate(this))
Thomas Zander's avatar
Thomas Zander committed
270
{
271
    notifyChanged();
Thomas Zander's avatar
Thomas Zander committed
272 273
}

274 275 276 277 278
KoShape::KoShape(KoShapePrivate &dd)
    : d_ptr(&dd)
{
}

Thomas Zander's avatar
Thomas Zander committed
279 280
KoShape::~KoShape()
{
281
    Q_D(KoShape);
282
    d->shapeChanged(Deleted);
283
    delete d_ptr;
Thomas Zander's avatar
Thomas Zander committed
284 285
}

Thomas Zander's avatar
Thomas Zander committed
286
void KoShape::scale(qreal sx, qreal sy)
Thomas Zander's avatar
Thomas Zander committed
287
{
288
    Q_D(KoShape);
289
    QPointF pos = position();
290
    QTransform scaleMatrix;
291 292 293
    scaleMatrix.translate(pos.x(), pos.y());
    scaleMatrix.scale(sx, sy);
    scaleMatrix.translate(-pos.x(), -pos.y());
294
    d->localMatrix = d->localMatrix * scaleMatrix;
295

296
    notifyChanged();
297
    d->shapeChanged(ScaleChanged);
Thomas Zander's avatar
Thomas Zander committed
298 299
}

300
void KoShape::rotate(qreal angle)
Thomas Zander's avatar
Thomas Zander committed
301
{
302
    Q_D(KoShape);
303
    QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height()));
304
    QTransform rotateMatrix;
305 306 307
    rotateMatrix.translate(center.x(), center.y());
    rotateMatrix.rotate(angle);
    rotateMatrix.translate(-center.x(), -center.y());
308
    d->localMatrix = d->localMatrix * rotateMatrix;
309

310
    notifyChanged();
311
    d->shapeChanged(RotationChanged);
Thomas Zander's avatar
Thomas Zander committed
312 313
}

Thomas Zander's avatar
Thomas Zander committed
314
void KoShape::shear(qreal sx, qreal sy)
Thomas Zander's avatar
Thomas Zander committed
315
{
316
    Q_D(KoShape);
317
    QPointF pos = position();
318
    QTransform shearMatrix;
319 320 321
    shearMatrix.translate(pos.x(), pos.y());
    shearMatrix.shear(sx, sy);
    shearMatrix.translate(-pos.x(), -pos.y());
322
    d->localMatrix = d->localMatrix * shearMatrix;
323

324
    notifyChanged();
325
    d->shapeChanged(ShearChanged);
Thomas Zander's avatar
Thomas Zander committed
326 327
}

328
void KoShape::setSize(const QSizeF &newSize)
Thomas Zander's avatar
Thomas Zander committed
329
{
330
    Q_D(KoShape);
331
    QSizeF oldSize(size());
332

333
    // always set size, as d->size and size() may vary
Thomas Zander's avatar
Thomas Zander committed
334
    d->size = newSize;
335

336 337 338
    if (oldSize == newSize)
        return;

339
    notifyChanged();
340
    d->shapeChanged(SizeChanged);
Thomas Zander's avatar
Thomas Zander committed
341 342
}

343
void KoShape::setPosition(const QPointF &newPosition)
Thomas Zander's avatar
Thomas Zander committed
344
{
345
    Q_D(KoShape);
346
    QPointF currentPos = position();
347
    if (newPosition == currentPos)
Thomas Zander's avatar
Thomas Zander committed
348
        return;
349
    QTransform translateMatrix;
350
    translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y());
351 352 353
    d->localMatrix = d->localMatrix * translateMatrix;

    notifyChanged();
354
    d->shapeChanged(PositionChanged);
Thomas Zander's avatar
Thomas Zander committed
355 356
}

357
bool KoShape::hitTest(const QPointF &position) const
Thomas Zander's avatar
Thomas Zander committed
358
{
359
    Q_D(const KoShape);
Thomas Zander's avatar
Thomas Zander committed
360
    if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position))
Thomas Zander's avatar
Thomas Zander committed
361 362
        return false;

363
    QPointF point = absoluteTransformation(0).inverted().map(position);
364
    QRectF bb(QPointF(), size());
365
    if (d->stroke) {
366
        KoInsets insets;
367
        d->stroke->strokeInsets(this, insets);
368
        bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
369
    }
370 371 372
    if (bb.contains(point))
        return true;

373
    // if there is no shadow we can as well just leave
374
    if (! d->shadow)
375
        return false;
Thomas Zander's avatar
Thomas Zander committed
376

377 378
    // the shadow has an offset to the shape, so we simply
    // check if the position minus the shadow offset hits the shape
379
    point = absoluteTransformation(0).inverted().map(position - d->shadow->offset());
380

381
    return bb.contains(point);
Thomas Zander's avatar
Thomas Zander committed
382 383
}

384
QRectF KoShape::boundingRect() const
Thomas Zander's avatar
Thomas Zander committed
385
{
386
    Q_D(const KoShape);
Jan Hambrecht's avatar
Jan Hambrecht committed
387

388
    QTransform transform = absoluteTransformation(0);
389
    QRectF bb = outlineRect();
390
    if (d->stroke) {
391
        KoInsets insets;
392
        d->stroke->strokeInsets(this, insets);
393 394
        bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
    }
395
    bb = transform.mapRect(bb);
396
    if (d->shadow) {
397
        KoInsets insets;
Thomas Zander's avatar
Thomas Zander committed
398
        d->shadow->insets(insets);
399 400
        bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
    }
401
    if (d->filterEffectStack) {
402
        QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect());
403 404
        bb |= transform.mapRect(clipRect);
    }
Thomas Zander's avatar
Thomas Zander committed
405

406
    return bb;
Thomas Zander's avatar
Thomas Zander committed
407 408
}

409
QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const
410
{
411
    Q_D(const KoShape);
412
    QTransform matrix;
Thomas Zander's avatar
Thomas Zander committed
413
    // apply parents matrix to inherit any transformations done there.
414
    KoShapeContainer * container = d->parent;
415
    if (container) {
416
        if (container->inheritsTransform(this)) {
417 418 419 420
            // We do need to pass the converter here, otherwise the parent's
            // translation is not inherited.
            matrix = container->absoluteTransformation(converter);
        } else {
421 422 423 424
            QSizeF containerSize = container->size();
            QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height());
            if (converter)
                containerPos = converter->documentToView(containerPos);
425
            matrix.translate(containerPos.x(), containerPos.y());
Thomas Zander's avatar
Thomas Zander committed
426 427 428
        }
    }

429
    if (converter) {
430 431 432
        QPointF pos = d->localMatrix.map(QPointF());
        QPointF trans = converter->documentToView(pos) - pos;
        matrix.translate(trans.x(), trans.y());
Thomas Zander's avatar
Thomas Zander committed
433
    }
434 435

    return d->localMatrix * matrix;
Thomas Zander's avatar
Thomas Zander committed
436 437
}

438
void KoShape::applyAbsoluteTransformation(const QTransform &matrix)
439
{
440
    QTransform globalMatrix = absoluteTransformation(0);
441 442
    // the transformation is relative to the global coordinate system
    // but we want to change the local matrix, so convert the matrix
Thomas Zander's avatar
Thomas Zander committed
443
    // to be relative to the local coordinate system
444
    QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted();
445
    applyTransformation(transformMatrix);
446 447
}

448
void KoShape::applyTransformation(const QTransform &matrix)
449
{
450
    Q_D(KoShape);
451
    d->localMatrix = matrix * d->localMatrix;
452
    notifyChanged();
453
    d->shapeChanged(GenericMatrixChange);
454 455
}

456
void KoShape::setTransformation(const QTransform &matrix)
457
{
458
    Q_D(KoShape);
459 460
    d->localMatrix = matrix;
    notifyChanged();
461
    d->shapeChanged(GenericMatrixChange);
462
}
Thomas Zander's avatar
Thomas Zander committed
463

464
QTransform KoShape::transformation() const
465
{
466
    Q_D(const KoShape);
467 468 469
    return d->localMatrix;
}

470
KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy()
471
{
472
    return ChildZDefault;
473 474
}

475 476
bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2)
{
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
    // First sort according to runThrough which is sort of a master level
    KoShape *parentShapeS1 = s1->parent();
    KoShape *parentShapeS2 = s2->parent();
    int runThrough1 = s1->runThrough();
    int runThrough2 = s2->runThrough();
    while (parentShapeS1) {
        if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) {
            runThrough1 = parentShapeS1->runThrough();
        } else {
            runThrough1 = runThrough1 + parentShapeS1->runThrough();
        }
        parentShapeS1 = parentShapeS1->parent();
    }

    while (parentShapeS2) {
        if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) {
            runThrough2 = parentShapeS2->runThrough();
        } else {
            runThrough2 = runThrough2 + parentShapeS2->runThrough();
        }
        parentShapeS2 = parentShapeS2->parent();
    }

    if (runThrough1 > runThrough2) {
        return false;
    }
    if (runThrough1 < runThrough2) {
        return true;
    }

    // If on the same runThrough level then the zIndex is all that matters.
    //
    // We basically walk up through the parents until we find a common base parent
    // To do that we need two loops where the inner loop walks up through the parents
    // of s2 every time we step up one parent level on s1
    //
    // We don't update the index value until after we have seen that it's not a common base
    // That way we ensure that two children of a common base are sorted according to their respective
    // z value
516
    bool foundCommonParent = false;
517 518 519 520
    int index1 = s1->zIndex();
    int index2 = s2->zIndex();
    parentShapeS1 = s1;
    parentShapeS2 = s2;
521 522 523 524 525 526 527 528
    while (parentShapeS1 && !foundCommonParent) {
        parentShapeS2 = s2;
        index2 = parentShapeS2->zIndex();
        while (parentShapeS2) {
            if (parentShapeS2 == parentShapeS1) {
                foundCommonParent = true;
                break;
            }
529
            if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) {
530 531
                index2 = parentShapeS2->zIndex();
            }
532
            parentShapeS2 = parentShapeS2->parent();
533
        }
534 535

        if (!foundCommonParent) {
536
            if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) {
537 538
                index1 = parentShapeS1->zIndex();
            }
539
            parentShapeS1 = parentShapeS1->parent();
540 541
        }
    }
542

543 544 545 546 547 548 549 550
    // If the one shape is a parent/child of the other then sort so.
    if (s1 == parentShapeS2) {
        return true;
    }
    if (s2 == parentShapeS1) {
        return false;
    }

551
    // If we went that far then the z-Index is used for sorting.
552
    return index1 < index2;
Thomas Zander's avatar
Thomas Zander committed
553 554
}

555 556
void KoShape::setParent(KoShapeContainer *parent)
{
557
    Q_D(KoShape);
558
    if (d->parent == parent)
559
        return;
560 561 562
    KoShapeContainer *oldParent = d->parent;
    d->parent = 0; // avoids recursive removing
    if (oldParent)
Thomas Zander's avatar
Thomas Zander committed
563
        oldParent->removeShape(this);
Thomas Zander's avatar
Thomas Zander committed
564
    if (parent && parent != this) {
Thomas Zander's avatar
Thomas Zander committed
565
        d->parent = parent;
Thomas Zander's avatar
Thomas Zander committed
566
        parent->addShape(this);
567
    }
568
    notifyChanged();
569
    d->shapeChanged(ParentChanged);
Thomas Zander's avatar
Thomas Zander committed
570 571
}

572 573
int KoShape::zIndex() const
{
574
    Q_D(const KoShape);
Thomas Zander's avatar
Thomas Zander committed
575
    return d->zIndex;
Thomas Zander's avatar
Thomas Zander committed
576 577
}

578 579
void KoShape::update() const
{
580
    Q_D(const KoShape);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
581

582 583
    if (!d->shapeManagers.empty()) {
        QRectF rect(boundingRect());
584
        Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
Jan Hambrecht's avatar
Jan Hambrecht committed
585
            manager->update(rect, this, true);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
586
        }
Thomas Zander's avatar
Thomas Zander committed
587 588 589
    }
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
590
void KoShape::update(const QRectF &rect) const
591
{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
592 593 594 595 596

    if (rect.isEmpty() && !rect.isNull()) {
        return;
    }

597
    Q_D(const KoShape);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
598

599
    if (!d->shapeManagers.empty() && isVisible()) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
600
        QRectF rc(absoluteTransformation(0).mapRect(rect));
601
        Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
602
            manager->update(rc);
603 604
        }
    }
Thomas Zander's avatar
Thomas Zander committed
605 606
}

607
QPainterPath KoShape::outline() const
608
{
Thomas Zander's avatar
Thomas Zander committed
609
    QPainterPath path;
610
    path.addRect(outlineRect());
Thomas Zander's avatar
Thomas Zander committed
611 612 613
    return path;
}

614 615
QRectF KoShape::outlineRect() const
{
616
    const QSizeF s = size();
617 618 619 620 621 622 623
    return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(),  qreal(0.0001)),
                                        qMax(s.height(), qreal(0.0001))));
}

QPainterPath KoShape::shadowOutline() const
{
    Q_D(const KoShape);
Inge Wallin's avatar
Inge Wallin committed
624
    if (d->fill) {
625
        return outline();
Inge Wallin's avatar
Inge Wallin committed
626
    }
627 628

    return QPainterPath();
629 630
}

631 632
QPointF KoShape::absolutePosition(KoFlake::Position anchor) const
{
633
    QPointF point;
634 635 636 637 638 639
    switch (anchor) {
    case KoFlake::TopLeftCorner: break;
    case KoFlake::TopRightCorner: point = QPointF(size().width(), 0.0); break;
    case KoFlake::BottomLeftCorner: point = QPointF(0.0, size().height()); break;
    case KoFlake::BottomRightCorner: point = QPointF(size().width(), size().height()); break;
    case KoFlake::CenteredPosition: point = QPointF(size().width() / 2.0, size().height() / 2.0); break;
640
    }
641
    return absoluteTransformation(0).map(point);
Thomas Zander's avatar
Thomas Zander committed
642 643
}

Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
644
void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::Position anchor)
645
{
646
    Q_D(KoShape);
647
    QPointF currentAbsPosition = absolutePosition(anchor);
648
    QPointF translate = newPosition - currentAbsPosition;
649
    QTransform translateMatrix;
650 651
    translateMatrix.translate(translate.x(), translate.y());
    applyAbsoluteTransformation(translateMatrix);
652
    notifyChanged();
653
    d->shapeChanged(PositionChanged);
Thomas Zander's avatar
Thomas Zander committed
654 655
}

656 657
void KoShape::copySettings(const KoShape *shape)
{
658
    Q_D(KoShape);
Thomas Zander's avatar
Thomas Zander committed
659 660
    d->size = shape->size();
    d->connectors.clear();
661
    Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints())
Jan Hambrecht's avatar
Jan Hambrecht committed
662
        addConnectionPoint(point);
Thomas Zander's avatar
Thomas Zander committed
663 664
    d->zIndex = shape->zIndex();
    d->visible = shape->isVisible();
665

666 667 668 669 670
    // Ensure printable is true by default
    if (!d->visible)
        d->printable = true;
    else
        d->printable = shape->isPrintable();
671

672
    d->geometryProtected = shape->isGeometryProtected();
Thomas Zander's avatar
Thomas Zander committed
673 674
    d->protectContent = shape->isContentProtected();
    d->selectable = shape->isSelectable();
Thomas Zander's avatar
Thomas Zander committed
675
    d->keepAspect = shape->keepAspectRatio();
676
    d->localMatrix = shape->d_ptr->localMatrix;
Thomas Zander's avatar
Thomas Zander committed
677 678
}

Thomas Zander's avatar
Thomas Zander committed
679
void KoShape::notifyChanged()
680
{
681
    Q_D(KoShape);
682
    Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
683
        manager->notifyShapeChanged(this);
684 685 686
    }
}

687 688
void KoShape::setUserData(KoShapeUserData *userData)
{
689
    Q_D(KoShape);
Thomas Zander's avatar
Thomas Zander committed
690
    delete d->userData;
Thomas Zander's avatar
Thomas Zander committed
691
    d->userData = userData;
692 693
}

694 695
KoShapeUserData *KoShape::userData() const
{
696
    Q_D(const KoShape);
Thomas Zander's avatar
Thomas Zander committed
697
    return d->userData;
698 699
}

700 701
void KoShape::setApplicationData(KoShapeApplicationData *appData)
{
702
    Q_D(KoShape);
Thomas Zander's avatar
Thomas Zander committed
703
    // appdata is deleted by the application.
Thomas Zander's avatar
Thomas Zander committed
704
    d->appData = appData;
705 706
}

707 708
KoShapeApplicationData *KoShape::applicationData() const
{
709
    Q_D(const KoShape);
Thomas Zander's avatar
Thomas Zander committed
710
    return d->appData;
711 712
}

Thomas Zander's avatar
Thomas Zander committed
713
bool KoShape::hasTransparency() const
714
{
Thomas Zander's avatar
Thomas Zander committed
715
    Q_D(const KoShape);
716
    if (! d->fill)
Thomas Zander's avatar
Thomas Zander committed
717
        return true;
718
    else
719 720 721 722 723 724
        return d->fill->hasTransparency() || d->transparency > 0.0;
}

void KoShape::setTransparency(qreal transparency)
{
    Q_D(KoShape);
725
    d->transparency = qBound<qreal>(0.0, transparency, 1.0);
726 727 728 729 730 731 732 733 734 735 736 737
}

qreal KoShape::transparency(bool recursive) const
{
    Q_D(const KoShape);
    if (!recursive || !parent()) {
        return d->transparency;
    } else {
        const qreal parentOpacity = 1.0-parent()->transparency(recursive);
        const qreal childOpacity = 1.0-d->transparency;
        return 1.0-(parentOpacity*childOpacity);
    }
Thomas Zander's avatar
Thomas Zander committed
738 739
}

740
KoInsets KoShape::strokeInsets() const
741
{
742
    Q_D(const KoShape);
743
    KoInsets answer;
744 745
    if (d->stroke)
        d->stroke->strokeInsets(this, answer);
746 747 748
    return answer;
}

749 750
qreal KoShape::rotation() const
{
751
    Q_D(const KoShape);
752
    // try to extract the rotation angle out of the local matrix
753 754 755
    // if it is a pure rotation matrix

    // check if the matrix has shearing mixed in
756
    if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10)
757
        return std::numeric_limits<qreal>::quiet_NaN();
758
    // check if the matrix has scaling mixed in
759
    if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10)
760
        return std::numeric_limits<qreal>::quiet_NaN();
761 762

    // calculate the angle from the matrix elements
763 764
    qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI;
    if (angle < 0.0)
765 766 767
        angle += 360.0;

    return angle;
Thomas Zander's avatar
Thomas Zander committed
768 769
}

770
QSizeF KoShape::size() const
771
{
772
    Q_D(const KoShape);
Thomas Zander's avatar
Thomas Zander committed
773 774 775
    return d->size;
}

776 777
QPointF KoShape::position() const
{
778
    Q_D(const KoShape);
779 780
    QPointF center(0.5*size().width(), 0.5*size().height());
    return d->localMatrix.map(center) - center;
Thomas Zander's avatar
Thomas Zander committed
781 782
}

783
int KoShape::addConnectionPoint(const KoConnectionPoint &point)
784
{
785
    Q_D(KoShape);
786

787
    // get next glue point id
788
    int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint;
Jan Hambrecht's avatar
Jan Hambrecht committed
789
    if (d->connectors.size())
790 791
        nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1);

792
    KoConnectionPoint p = point;
793 794
    d->convertFromShapeCoordinates(p, size());
    d->connectors[nextConnectionPointId] = p;
795 796 797 798

    return nextConnectionPointId;
}

799
bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point)
800 801
{
    Q_D(KoShape);
802
    if (connectionPointId < 0)
803 804
        return false;

805 806
    const bool insertPoint = !hasConnectionPoint(connectionPointId);

807
    switch(connectionPointId) {
808 809 810 811 812 813
        case KoConnectionPoint::TopConnectionPoint:
        case KoConnectionPoint::RightConnectionPoint:
        case KoConnectionPoint::BottomConnectionPoint:
        case KoConnectionPoint::LeftConnectionPoint:
        {
            KoConnectionPoint::PointId id = static_cast<KoConnectionPoint::PointId>(connectionPointId);
814
            d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id);
815
            break;
816
        }
817
        default:
818
        {
819
            KoConnectionPoint p = point;
820 821
            d->convertFromShapeCoordinates(p, size());
            d->connectors[connectionPointId] = p;
822
            break;
823
        }
824 825
    }

826 827
    if(!insertPoint)
        d->shapeChanged(ConnectionPointChanged);
828 829 830 831

    return true;
}

832 833 834 835 836 837
bool KoShape::hasConnectionPoint(int connectionPointId) const
{
    Q_D(const KoShape);
    return d->connectors.contains(connectionPointId);
}

838
KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const
839 840
{
    Q_D(const KoShape);
841
    KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint());
842
    // convert glue point to shape coordinates
843
    d->convertToShapeCoordinates(p, size());
844
    return p;
Thomas Zander's avatar
Thomas Zander committed
845 846
}

847
KoConnectionPoints KoShape::connectionPoints() const
848
{
849
    Q_D(const KoShape);
850
    QSizeF s = size();
851 852 853
    KoConnectionPoints points = d->connectors;
    KoConnectionPoints::iterator point = points.begin();
    KoConnectionPoints::iterator lastPoint = points.end();
854
    // convert glue points to shape coordinates
855
    for(; point != lastPoint; ++point) {
856
        d->convertToShapeCoordinates(point.value(), s);
857
    }
858 859

    return points;
860 861
}