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 }