Changeset View
Changeset View
Standalone View
Standalone View
libs/flake/KoSelection.cpp
Show All 21 Lines | |||||
22 | */ | 22 | */ | ||
23 | 23 | | |||
24 | #include "KoSelection.h" | 24 | #include "KoSelection.h" | ||
25 | #include "KoSelection_p.h" | 25 | #include "KoSelection_p.h" | ||
26 | #include "KoShapeContainer.h" | 26 | #include "KoShapeContainer.h" | ||
27 | #include "KoShapeGroup.h" | 27 | #include "KoShapeGroup.h" | ||
28 | #include "KoPointerEvent.h" | 28 | #include "KoPointerEvent.h" | ||
29 | #include "KoShapePaintingContext.h" | 29 | #include "KoShapePaintingContext.h" | ||
30 | #include "kis_algebra_2d.h" | ||||
31 | #include "krita_container_utils.h" | ||||
30 | 32 | | |||
31 | #include <QPainter> | 33 | #include <QPainter> | ||
32 | #include <QTimer> | | |||
33 | 34 | | |||
34 | QRectF KoSelectionPrivate::sizeRect() | 35 | #include "kis_debug.h" | ||
35 | { | | |||
36 | bool first = true; | | |||
37 | QRectF bb; | | |||
38 | | ||||
39 | QTransform invSelectionTransform = q->absoluteTransformation(0).inverted(); | | |||
40 | | ||||
41 | QRectF bound; | | |||
42 | | ||||
43 | if (!selectedShapes.isEmpty()) { | | |||
44 | QList<KoShape*>::const_iterator it = selectedShapes.constBegin(); | | |||
45 | for (; it != selectedShapes.constEnd(); ++it) { | | |||
46 | if (dynamic_cast<KoShapeGroup*>(*it)) | | |||
47 | continue; | | |||
48 | 36 | | |||
49 | const QTransform shapeTransform = (*it)->absoluteTransformation(0); | 37 | KoSelection::KoSelection() | ||
50 | const QRectF shapeRect(QRectF(QPointF(), (*it)->size())); | 38 | : KoShape(new KoSelectionPrivate(this)) | ||
51 | 39 | { | |||
52 | if (first) { | 40 | Q_D(KoSelection); | ||
53 | bb = (shapeTransform * invSelectionTransform).mapRect(shapeRect); | 41 | connect(&d->selectionChangedCompressor, SIGNAL(timeout()), SIGNAL(selectionChanged())); | ||
54 | bound = shapeTransform.mapRect(shapeRect); | | |||
55 | first = false; | | |||
56 | } else { | | |||
57 | bb = bb.united((shapeTransform * invSelectionTransform).mapRect(shapeRect)); | | |||
58 | bound = bound.united(shapeTransform.mapRect(shapeRect)); | | |||
59 | } | | |||
60 | } | | |||
61 | } | 42 | } | ||
62 | 43 | | |||
63 | globalBound = bound; | 44 | KoSelection::~KoSelection() | ||
64 | return bb; | 45 | { | ||
65 | } | 46 | } | ||
66 | 47 | | |||
67 | void KoSelectionPrivate::requestSelectionChangedEvent() | 48 | void KoSelection::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) | ||
68 | { | 49 | { | ||
69 | if (eventTriggered) | 50 | Q_UNUSED(painter); | ||
70 | return; | 51 | Q_UNUSED(converter); | ||
71 | eventTriggered = true; | 52 | Q_UNUSED(paintcontext); | ||
72 | QTimer::singleShot(0, q, SLOT(selectionChangedEvent())); | | |||
73 | } | 53 | } | ||
74 | 54 | | |||
75 | void KoSelectionPrivate::selectionChangedEvent() | 55 | void KoSelection::setSize(const QSizeF &size) | ||
76 | { | 56 | { | ||
77 | eventTriggered = false; | 57 | Q_UNUSED(size); | ||
78 | emit q->selectionChanged(); | 58 | qWarning() << "WARNING: KoSelection::setSize() should never be used!"; | ||
79 | } | 59 | } | ||
80 | 60 | | |||
81 | void KoSelectionPrivate::selectGroupChildren(KoShapeGroup *group) | 61 | QSizeF KoSelection::size() const | ||
82 | { | 62 | { | ||
83 | if (! group) | 63 | return outlineRect().size(); | ||
84 | return; | | |||
85 | | ||||
86 | Q_FOREACH (KoShape *shape, group->shapes()) { | | |||
87 | if (selectedShapes.contains(shape)) | | |||
88 | continue; | | |||
89 | selectedShapes << shape; | | |||
90 | | ||||
91 | KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(shape); | | |||
92 | if (childGroup) | | |||
93 | selectGroupChildren(childGroup); | | |||
94 | } | | |||
95 | } | 64 | } | ||
96 | 65 | | |||
97 | void KoSelectionPrivate::deselectGroupChildren(KoShapeGroup *group) | 66 | QRectF KoSelection::outlineRect() const | ||
98 | { | 67 | { | ||
99 | if (! group) | 68 | Q_D(const KoSelection); | ||
100 | return; | | |||
101 | | ||||
102 | Q_FOREACH (KoShape *shape, group->shapes()) { | | |||
103 | if (selectedShapes.contains(shape)) | | |||
104 | selectedShapes.removeAll(shape); | | |||
105 | 69 | | |||
106 | KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(shape); | 70 | QPolygonF globalPolygon; | ||
107 | if (childGroup) | 71 | Q_FOREACH (KoShape *shape, d->selectedShapes) { | ||
108 | deselectGroupChildren(childGroup); | 72 | globalPolygon = globalPolygon.united( | ||
109 | } | 73 | shape->absoluteTransformation(0).map(QPolygonF(shape->outlineRect()))); | ||
110 | } | 74 | } | ||
75 | const QPolygonF localPolygon = transformation().inverted().map(globalPolygon); | ||||
111 | 76 | | |||
112 | //////////// | 77 | return localPolygon.boundingRect(); | ||
113 | | ||||
114 | KoSelection::KoSelection() | | |||
115 | : KoShape(*(new KoSelectionPrivate(this))) | | |||
116 | { | | |||
117 | } | 78 | } | ||
118 | 79 | | |||
119 | KoSelection::~KoSelection() | 80 | QRectF KoSelection::boundingRect() const | ||
120 | { | 81 | { | ||
82 | Q_D(const KoSelection); | ||||
83 | return KoShape::boundingRect(d->selectedShapes); | ||||
121 | } | 84 | } | ||
122 | 85 | | |||
123 | void KoSelection::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) | 86 | void KoSelection::select(KoShape *shape) | ||
124 | { | 87 | { | ||
125 | Q_UNUSED(painter); | 88 | Q_D(KoSelection); | ||
126 | Q_UNUSED(converter); | 89 | KIS_SAFE_ASSERT_RECOVER_RETURN(shape != this); | ||
127 | Q_UNUSED(paintcontext); | 90 | KIS_SAFE_ASSERT_RECOVER_RETURN(shape); | ||
91 | | ||||
92 | if (!shape->isSelectable() || !shape->isVisible(true)) { | ||||
93 | return; | ||||
128 | } | 94 | } | ||
129 | 95 | | |||
130 | void KoSelection::select(KoShape *shape, bool recursive) | 96 | // check recursively | ||
131 | { | 97 | if (isSelected(shape)) { | ||
132 | Q_D(KoSelection); | | |||
133 | Q_ASSERT(shape != this); | | |||
134 | Q_ASSERT(shape); | | |||
135 | if (!shape->isSelectable() || !shape->isVisible(true)) | | |||
136 | return; | 98 | return; | ||
99 | } | ||||
137 | 100 | | |||
138 | // save old number of selected shapes | 101 | // find the topmost parent to select | ||
139 | int oldSelectionCount = d->selectedShapes.count(); | 102 | while (KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(shape->parent())) { | ||
103 | shape = parentGroup; | ||||
104 | } | ||||
140 | 105 | | |||
141 | if (!d->selectedShapes.contains(shape)) | | |||
142 | d->selectedShapes << shape; | 106 | d->selectedShapes << shape; | ||
107 | shape->addShapeChangeListener(this); | ||||
143 | 108 | | |||
144 | // automatically recursively select all child shapes downwards in the hierarchy | 109 | d->savedMatrices = d->fetchShapesMatrices(); | ||
145 | KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape); | | |||
146 | if (group) | | |||
147 | d->selectGroupChildren(group); | | |||
148 | | ||||
149 | if (recursive) { | | |||
150 | // recursively select all parents and their children upwards the hierarchy | | |||
151 | KoShapeContainer *parent = shape->parent(); | | |||
152 | while (parent) { | | |||
153 | KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(parent); | | |||
154 | if (! parentGroup) break; | | |||
155 | if (! d->selectedShapes.contains(parentGroup)) { | | |||
156 | d->selectedShapes << parentGroup; | | |||
157 | d->selectGroupChildren(parentGroup); | | |||
158 | } | | |||
159 | parent = parentGroup->parent(); | | |||
160 | } | | |||
161 | } | | |||
162 | 110 | | |||
163 | if (d->selectedShapes.count() == 1) { | 111 | if (d->selectedShapes.size() == 1) { | ||
164 | setTransformation(shape->absoluteTransformation(0)); | 112 | setTransformation(shape->absoluteTransformation(0)); | ||
165 | updateSizeAndPosition(); | | |||
166 | } else { | 113 | } else { | ||
167 | // reset global bound if there were no shapes selected before | | |||
168 | if (!oldSelectionCount) | | |||
169 | d->globalBound = QRectF(); | | |||
170 | | ||||
171 | setTransformation(QTransform()); | 114 | setTransformation(QTransform()); | ||
172 | // we are resetting the transformation here anyway, | | |||
173 | // so we can just add the newly selected shapes to the bounding box | | |||
174 | // in document coordinates and then use that size and position | | |||
175 | int newSelectionCount = d->selectedShapes.count(); | | |||
176 | for (int i = oldSelectionCount; i < newSelectionCount; ++i) { | | |||
177 | KoShape *shape = d->selectedShapes[i]; | | |||
178 | | ||||
179 | // don't add the rect of the group rect, as it can be invalid | | |||
180 | if (dynamic_cast<KoShapeGroup*>(shape)) { | | |||
181 | continue; | | |||
182 | } | 115 | } | ||
183 | const QTransform shapeTransform = shape->absoluteTransformation(0); | | |||
184 | const QRectF shapeRect(QRectF(QPointF(), shape->size())); | | |||
185 | 116 | | |||
186 | d->globalBound = d->globalBound.united(shapeTransform.mapRect(shapeRect)); | 117 | d->selectionChangedCompressor.start(); | ||
187 | } | | |||
188 | setSize(d->globalBound.size()); | | |||
189 | setPosition(d->globalBound.topLeft()); | | |||
190 | } | | |||
191 | | ||||
192 | d->requestSelectionChangedEvent(); | | |||
193 | } | 118 | } | ||
194 | 119 | | |||
195 | void KoSelection::deselect(KoShape *shape, bool recursive) | 120 | void KoSelection::deselect(KoShape *shape) | ||
196 | { | 121 | { | ||
197 | Q_D(KoSelection); | 122 | Q_D(KoSelection); | ||
198 | if (! d->selectedShapes.contains(shape)) | 123 | if (!d->selectedShapes.contains(shape)) | ||
199 | return; | 124 | return; | ||
200 | 125 | | |||
201 | d->selectedShapes.removeAll(shape); | 126 | d->selectedShapes.removeAll(shape); | ||
127 | shape->removeShapeChangeListener(this); | ||||
128 | d->savedMatrices = d->fetchShapesMatrices(); | ||||
202 | 129 | | |||
203 | KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape); | 130 | if (d->selectedShapes.size() == 1) { | ||
204 | if (recursive) { | 131 | setTransformation(d->selectedShapes.first()->absoluteTransformation(0)); | ||
205 | // recursively find the top group upwards int the hierarchy | | |||
206 | KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(shape->parent()); | | |||
207 | while (parentGroup) { | | |||
208 | group = parentGroup; | | |||
209 | parentGroup = dynamic_cast<KoShapeGroup*>(parentGroup->parent()); | | |||
210 | } | | |||
211 | } | 132 | } | ||
212 | if (group) | | |||
213 | d->deselectGroupChildren(group); | | |||
214 | 133 | | |||
215 | if (count() == 1) | 134 | d->selectionChangedCompressor.start(); | ||
216 | setTransformation(firstSelectedShape()->absoluteTransformation(0)); | | |||
217 | | ||||
218 | updateSizeAndPosition(); | | |||
219 | | ||||
220 | d->requestSelectionChangedEvent(); | | |||
221 | } | 135 | } | ||
222 | 136 | | |||
223 | void KoSelection::deselectAll() | 137 | void KoSelection::deselectAll() | ||
224 | { | 138 | { | ||
225 | Q_D(KoSelection); | 139 | Q_D(KoSelection); | ||
226 | // reset the transformation matrix of the selection | | |||
227 | setTransformation(QTransform()); | | |||
228 | 140 | | |||
229 | if (d->selectedShapes.isEmpty()) | 141 | if (d->selectedShapes.isEmpty()) | ||
230 | return; | 142 | return; | ||
143 | | ||||
144 | Q_FOREACH (KoShape *shape, d->selectedShapes) { | ||||
145 | shape->removeShapeChangeListener(this); | ||||
146 | } | ||||
147 | d->savedMatrices = d->fetchShapesMatrices(); | ||||
148 | | ||||
149 | // reset the transformation matrix of the selection | ||||
150 | setTransformation(QTransform()); | ||||
151 | | ||||
231 | d->selectedShapes.clear(); | 152 | d->selectedShapes.clear(); | ||
232 | d->requestSelectionChangedEvent(); | 153 | d->selectionChangedCompressor.start(); | ||
233 | } | 154 | } | ||
234 | 155 | | |||
235 | int KoSelection::count() const | 156 | int KoSelection::count() const | ||
236 | { | 157 | { | ||
237 | Q_D(const KoSelection); | 158 | Q_D(const KoSelection); | ||
238 | int count = 0; | 159 | return d->selectedShapes.size(); | ||
239 | Q_FOREACH (KoShape *shape, d->selectedShapes) | | |||
240 | if (dynamic_cast<KoShapeGroup*>(shape) == 0) | | |||
241 | ++count; | | |||
242 | return count; | | |||
243 | } | 160 | } | ||
244 | 161 | | |||
245 | bool KoSelection::hitTest(const QPointF &position) const | 162 | bool KoSelection::hitTest(const QPointF &position) const | ||
246 | { | 163 | { | ||
247 | Q_D(const KoSelection); | 164 | Q_D(const KoSelection); | ||
248 | if (count() > 1) { | 165 | | ||
249 | QRectF bb(boundingRect()); | 166 | Q_FOREACH (KoShape *shape, d->selectedShapes) { | ||
250 | return bb.contains(position); | 167 | if (shape->hitTest(position)) return true; | ||
251 | } else if (count() == 1) { | | |||
252 | return (*d->selectedShapes.begin())->hitTest(position); | | |||
253 | } else { // count == 0 | | |||
254 | return false; | | |||
255 | } | 168 | } | ||
169 | | ||||
170 | return false; | ||||
256 | } | 171 | } | ||
257 | void KoSelection::updateSizeAndPosition() | 172 | | ||
173 | const QList<KoShape*> KoSelection::selectedShapes() const | ||||
258 | { | 174 | { | ||
259 | Q_D(KoSelection); | 175 | Q_D(const KoSelection); | ||
260 | QRectF bb = d->sizeRect(); | 176 | return d->selectedShapes; | ||
261 | QTransform matrix = absoluteTransformation(0); | | |||
262 | setSize(bb.size()); | | |||
263 | QPointF p = matrix.map(bb.topLeft() + matrix.inverted().map(position())); | | |||
264 | setPosition(p); | | |||
265 | } | 177 | } | ||
266 | 178 | | |||
267 | QRectF KoSelection::boundingRect() const | 179 | const QList<KoShape *> KoSelection::selectedEditableShapes() const | ||
268 | { | 180 | { | ||
269 | return absoluteTransformation(0).mapRect(QRectF(QPointF(), size())); | 181 | Q_D(const KoSelection); | ||
182 | | ||||
183 | QList<KoShape*> shapes = selectedShapes(); | ||||
184 | | ||||
185 | KritaUtils::filterContainer (shapes, [](KoShape *shape) { | ||||
186 | return shape->isEditable(); | ||||
187 | }); | ||||
188 | | ||||
189 | return shapes; | ||||
270 | } | 190 | } | ||
271 | 191 | | |||
272 | const QList<KoShape*> KoSelection::selectedShapes(KoFlake::SelectionType strip) const | 192 | const QList<KoShape *> KoSelection::selectedEditableShapesAndDelegates() const | ||
273 | { | 193 | { | ||
274 | Q_D(const KoSelection); | 194 | QList<KoShape*> shapes; | ||
275 | QList<KoShape*> answer; | 195 | Q_FOREACH (KoShape *shape, selectedShapes()) { | ||
276 | // strip the child objects when there is also a parent included. | 196 | QSet<KoShape *> delegates = shape->toolDelegates(); | ||
277 | bool doStripping = strip == KoFlake::StrippedSelection; | 197 | if (delegates.isEmpty()) { | ||
278 | Q_FOREACH (KoShape *shape, d->selectedShapes) { | 198 | shapes.append(shape); | ||
279 | KoShapeContainer *container = shape->parent(); | 199 | } else { | ||
280 | if (strip != KoFlake::TopLevelSelection && dynamic_cast<KoShapeGroup*>(shape)) | 200 | Q_FOREACH (KoShape *delegatedShape, delegates) { | ||
281 | // since a KoShapeGroup | 201 | shapes.append(delegatedShape); | ||
282 | // guarentees all its children are selected at the same time as itself | 202 | } | ||
283 | // is selected we will only return its children. | 203 | } | ||
284 | continue; | | |||
285 | bool add = true; | | |||
286 | while (doStripping && add && container) { | | |||
287 | if (dynamic_cast<KoShapeGroup*>(container) == 0 && d->selectedShapes.contains(container)) | | |||
288 | add = false; | | |||
289 | container = container->parent(); | | |||
290 | } | | |||
291 | if (strip == KoFlake::TopLevelSelection && container && d->selectedShapes.contains(container)) | | |||
292 | add = false; | | |||
293 | if (add) | | |||
294 | answer << shape; | | |||
295 | } | 204 | } | ||
296 | return answer; | 205 | return shapes; | ||
297 | } | 206 | } | ||
298 | 207 | | |||
299 | bool KoSelection::isSelected(const KoShape *shape) const | 208 | bool KoSelection::isSelected(const KoShape *shape) const | ||
300 | { | 209 | { | ||
301 | Q_D(const KoSelection); | 210 | Q_D(const KoSelection); | ||
302 | if (shape == this) | 211 | if (shape == this) | ||
303 | return true; | 212 | return true; | ||
304 | 213 | | |||
305 | foreach (KoShape *s, d->selectedShapes) { | 214 | const KoShape *tmpShape = shape; | ||
306 | if (s == shape) | 215 | while (tmpShape && std::find(d->selectedShapes.begin(), d->selectedShapes.end(), tmpShape) == d->selectedShapes.end()/*d->selectedShapes.contains(tmpShape)*/) { | ||
307 | return true; | 216 | tmpShape = tmpShape->parent(); | ||
308 | } | 217 | } | ||
309 | 218 | | |||
310 | return false; | 219 | return tmpShape; | ||
311 | } | 220 | } | ||
312 | 221 | | |||
313 | KoShape *KoSelection::firstSelectedShape(KoFlake::SelectionType strip) const | 222 | KoShape *KoSelection::firstSelectedShape() const | ||
314 | { | 223 | { | ||
315 | QList<KoShape*> set = selectedShapes(strip); | 224 | Q_D(const KoSelection); | ||
316 | if (set.isEmpty()) | 225 | return !d->selectedShapes.isEmpty() ? d->selectedShapes.first() : 0; | ||
317 | return 0; | | |||
318 | return *(set.begin()); | | |||
319 | } | 226 | } | ||
320 | 227 | | |||
321 | void KoSelection::setActiveLayer(KoShapeLayer *layer) | 228 | void KoSelection::setActiveLayer(KoShapeLayer *layer) | ||
322 | { | 229 | { | ||
323 | Q_D(KoSelection); | 230 | Q_D(KoSelection); | ||
324 | d->activeLayer = layer; | 231 | d->activeLayer = layer; | ||
325 | emit currentLayerChanged(layer); | 232 | emit currentLayerChanged(layer); | ||
326 | } | 233 | } | ||
327 | 234 | | |||
328 | KoShapeLayer* KoSelection::activeLayer() const | 235 | KoShapeLayer* KoSelection::activeLayer() const | ||
329 | { | 236 | { | ||
330 | Q_D(const KoSelection); | 237 | Q_D(const KoSelection); | ||
331 | return d->activeLayer; | 238 | return d->activeLayer; | ||
332 | } | 239 | } | ||
333 | 240 | | |||
241 | void KoSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) | ||||
242 | { | ||||
243 | Q_UNUSED(shape); | ||||
244 | Q_D(KoSelection); | ||||
245 | | ||||
246 | if (type == KoShape::Deleted) { | ||||
247 | deselect(shape); | ||||
248 | // HACK ALERT: the caller will also remove the listener, so re-add it here | ||||
249 | shape->addShapeChangeListener(this); | ||||
250 | | ||||
251 | } else if (type >= KoShape::PositionChanged && type <= KoShape::GenericMatrixChange) { | ||||
252 | QList<QTransform> matrices = d->fetchShapesMatrices(); | ||||
253 | | ||||
254 | QTransform newTransform; | ||||
255 | if (d->checkMatricesConsistent(matrices, &newTransform)) { | ||||
256 | d->savedMatrices = matrices; | ||||
257 | setTransformation(newTransform); | ||||
258 | } else { | ||||
259 | d->savedMatrices = matrices; | ||||
260 | setTransformation(QTransform()); | ||||
261 | } | ||||
262 | } | ||||
263 | } | ||||
264 | | ||||
334 | void KoSelection::saveOdf(KoShapeSavingContext &) const | 265 | void KoSelection::saveOdf(KoShapeSavingContext &) const | ||
335 | { | 266 | { | ||
336 | } | 267 | } | ||
337 | 268 | | |||
338 | bool KoSelection::loadOdf(const KoXmlElement &, KoShapeLoadingContext &) | 269 | bool KoSelection::loadOdf(const KoXmlElement &, KoShapeLoadingContext &) | ||
339 | { | 270 | { | ||
340 | return true; | 271 | return true; | ||
341 | } | 272 | } | ||
342 | 273 | | |||
343 | //have to include this because of Q_PRIVATE_SLOT | 274 | | ||
344 | #include "moc_KoSelection.cpp" | 275 | QList<QTransform> KoSelectionPrivate::fetchShapesMatrices() const | ||
276 | { | ||||
277 | QList<QTransform> result; | ||||
278 | Q_FOREACH (KoShape *shape, selectedShapes) { | ||||
279 | result << shape->absoluteTransformation(0); | ||||
280 | } | ||||
281 | return result; | ||||
282 | } | ||||
283 | | ||||
284 | bool KoSelectionPrivate::checkMatricesConsistent(const QList<QTransform> &matrices, QTransform *newTransform) | ||||
285 | { | ||||
286 | Q_Q(KoSelection); | ||||
287 | | ||||
288 | QTransform commonDiff; | ||||
289 | bool haveCommonDiff = false; | ||||
290 | | ||||
291 | KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(matrices.size() == selectedShapes.size(), false); | ||||
292 | | ||||
293 | for (int i = 0; i < matrices.size(); i++) { | ||||
294 | QTransform t = savedMatrices[i]; | ||||
295 | QTransform diff = t.inverted() * matrices[i]; | ||||
296 | | ||||
297 | | ||||
298 | if (haveCommonDiff) { | ||||
299 | if (!KisAlgebra2D::fuzzyMatrixCompare(commonDiff, diff, 1e-5)) { | ||||
300 | return false; | ||||
301 | } | ||||
302 | } else { | ||||
303 | commonDiff = diff; | ||||
304 | } | ||||
305 | } | ||||
306 | | ||||
307 | *newTransform = q->transformation() * commonDiff; | ||||
308 | return true; | ||||
309 | } |