File indexing completed on 2024-05-12 15:56:46

0001 /* This file is part of the KDE project
0002 
0003    SPDX-FileCopyrightText: 2006 Boudewijn Rempt <boud@valdyas.org>
0004    SPDX-FileCopyrightText: 2006 Thorsten Zachmann <zachmann@kde.org>
0005    SPDX-FileCopyrightText: 2006 Jan Hambrecht <jaham@gmx.net>
0006    SPDX-FileCopyrightText: 2006-2007, 2009 Thomas Zander <zander@kde.org>
0007 
0008    SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "KoSelection.h"
0012 #include "KoSelection_p.h"
0013 #include "KoShapeContainer.h"
0014 #include "KoShapeGroup.h"
0015 #include "KoPointerEvent.h"
0016 #include "kis_algebra_2d.h"
0017 #include "krita_container_utils.h"
0018 
0019 #include <QPainter>
0020 
0021 #include "kis_debug.h"
0022 KoSelection::KoSelection(QObject *parent)
0023     : QObject(parent)
0024     , KoShape()
0025     , d(new Private)
0026 {
0027     connect(&d->selectionChangedCompressor, SIGNAL(timeout()), SIGNAL(selectionChanged()));
0028 }
0029 
0030 KoSelection::KoSelection(const KoSelection &rhs)
0031     : QObject()
0032     , KoShape(rhs)
0033     , d(rhs.d)
0034 {
0035 }
0036 
0037 KoSelection::~KoSelection()
0038 {
0039 }
0040 
0041 void KoSelection::paint(QPainter &painter) const
0042 {
0043     Q_UNUSED(painter);
0044 }
0045 
0046 void KoSelection::setSize(const QSizeF &size)
0047 {
0048     Q_UNUSED(size);
0049     qWarning() << "WARNING: KoSelection::setSize() should never be used!";
0050 }
0051 
0052 QSizeF KoSelection::size() const
0053 {
0054     return outlineRect().size();
0055 }
0056 
0057 QRectF KoSelection::outlineRect() const
0058 {
0059     const QTransform invertedTransform = transformation().inverted();
0060     QRectF boundingRect;
0061 
0062     Q_FOREACH (KoShape *shape, selectedVisibleShapes()) {
0063         // it is cheaper to invert-transform each outline, than
0064         // to group 300+ rotated rectangles into a polygon
0065         boundingRect |=
0066             invertedTransform.map(
0067                 shape->absoluteTransformation().map(
0068                         QPolygonF(shape->outlineRect()))).boundingRect();
0069     }
0070 
0071     return boundingRect;
0072 }
0073 
0074 QRectF KoSelection::boundingRect() const
0075 {
0076     return KoShape::boundingRect(selectedVisibleShapes());
0077 }
0078 
0079 void KoSelection::select(KoShape *shape)
0080 {
0081     KIS_SAFE_ASSERT_RECOVER_RETURN(shape != this);
0082     KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
0083 
0084     if (!shape->isSelectable() || !shape->isVisible()) {
0085         return;
0086     }
0087 
0088     // check recursively
0089     if (isSelected(shape)) {
0090         return;
0091     }
0092 
0093     // find the topmost parent to select
0094     while (KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(shape->parent())) {
0095         shape = parentGroup;
0096     }
0097 
0098     d->selectedShapes << shape;
0099     shape->addShapeChangeListener(this);
0100 
0101     if (d->selectedShapes.size() == 1) {
0102         setTransformation(shape->absoluteTransformation());
0103     } else {
0104         setTransformation(QTransform());
0105     }
0106 
0107     d->selectionChangedCompressor.start();
0108 }
0109 
0110 void KoSelection::deselect(KoShape *shape)
0111 {
0112     if (!d->selectedShapes.contains(shape))
0113         return;
0114 
0115     d->selectedShapes.removeAll(shape);
0116     shape->removeShapeChangeListener(this);
0117 
0118     if (d->selectedShapes.size() == 1) {
0119         setTransformation(d->selectedShapes.first()->absoluteTransformation());
0120     }
0121 
0122     d->selectionChangedCompressor.start();
0123 }
0124 
0125 void KoSelection::deselectAll()
0126 {
0127 
0128     if (d->selectedShapes.isEmpty())
0129         return;
0130 
0131     Q_FOREACH (KoShape *shape, d->selectedShapes) {
0132         shape->removeShapeChangeListener(this);
0133     }
0134 
0135     // reset the transformation matrix of the selection
0136     setTransformation(QTransform());
0137 
0138     d->selectedShapes.clear();
0139     d->selectionChangedCompressor.start();
0140 }
0141 
0142 int KoSelection::count() const
0143 {
0144     return d->selectedShapes.size();
0145 }
0146 
0147 bool KoSelection::hitTest(const QPointF &position) const
0148 {
0149 
0150     Q_FOREACH (KoShape *shape, d->selectedShapes) {
0151         if (shape->isVisible()) continue;
0152         if (shape->hitTest(position)) return true;
0153     }
0154 
0155     return false;
0156 }
0157 
0158 const QList<KoShape*> KoSelection::selectedShapes() const
0159 {
0160     return d->selectedShapes;
0161 }
0162 
0163 const QList<KoShape *> KoSelection::selectedVisibleShapes() const
0164 {
0165     QList<KoShape*> shapes = selectedShapes();
0166 
0167     KritaUtils::filterContainer (shapes, [](KoShape *shape) {
0168         return shape->isVisible();
0169     });
0170 
0171     return shapes;
0172 }
0173 
0174 const QList<KoShape *> KoSelection::selectedEditableShapes() const
0175 {
0176     QList<KoShape*> shapes = selectedShapes();
0177 
0178     KritaUtils::filterContainer (shapes, [](KoShape *shape) {
0179         return shape->isShapeEditable();
0180     });
0181 
0182     return shapes;
0183 }
0184 
0185 const QList<KoShape *> KoSelection::selectedEditableShapesAndDelegates() const
0186 {
0187     QList<KoShape*> shapes;
0188     Q_FOREACH (KoShape *shape, selectedShapes()) {
0189         QSet<KoShape *> delegates = shape->toolDelegates();
0190         if (delegates.isEmpty()) {
0191             shapes.append(shape);
0192         } else {
0193             Q_FOREACH (KoShape *delegatedShape, delegates) {
0194                 shapes.append(delegatedShape);
0195             }
0196         }
0197     }
0198     return shapes;
0199 }
0200 
0201 bool KoSelection::isSelected(const KoShape *shape) const
0202 {
0203     if (shape == this)
0204         return true;
0205 
0206     const KoShape *tmpShape = shape;
0207     while (tmpShape && std::find(d->selectedShapes.begin(), d->selectedShapes.end(), tmpShape) == d->selectedShapes.end()) {
0208         tmpShape = tmpShape->parent();
0209     }
0210 
0211     return tmpShape;
0212 }
0213 
0214 KoShape *KoSelection::firstSelectedShape() const
0215 {
0216     return !d->selectedShapes.isEmpty() ? d->selectedShapes.first() : 0;
0217 }
0218 
0219 void KoSelection::setActiveLayer(KoShapeLayer *layer)
0220 {
0221     d->activeLayer = layer;
0222     emit currentLayerChanged(layer);
0223 }
0224 
0225 KoShapeLayer* KoSelection::activeLayer() const
0226 {
0227     return d->activeLayer;
0228 }
0229 
0230 void KoSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
0231 {
0232     Q_UNUSED(shape);
0233     if (type == KoShape::Deleted) {
0234         deselect(shape);
0235 
0236         // HACK ALERT: the caller will also remove the listener, which was
0237         // removed in deselect(), so re-add it here
0238         shape->addShapeChangeListener(this);
0239     }
0240 }