All Classes Namespaces Functions Variables Enumerations Properties Pages
selectionmanager.cpp
1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2020 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 
16 */
17 #include "selectionmanager.h"
18 #include "viewmanager.h"
19 #include "editor.h"
20 
21 #include "layerbitmap.h"
22 #include "vectorimage.h"
23 #include "bitmapimage.h"
24 
25 #include "layervector.h"
26 #include "mathutils.h"
27 
28 //#ifdef QT_DEBUG
29 #include <QDebug>
30 //#endif
31 
32 
33 SelectionManager::SelectionManager(Editor* editor) : BaseManager(editor, __FUNCTION__)
34 {
35 }
36 
37 SelectionManager::~SelectionManager()
38 {
39 }
40 
41 bool SelectionManager::init()
42 {
43  return true;
44 }
45 
46 Status SelectionManager::load(Object*)
47 {
48  resetSelectionProperties();
49  return Status::OK;
50 }
51 
52 Status SelectionManager::save(Object*)
53 {
54  return Status::OK;
55 }
56 
57 void SelectionManager::workingLayerChanged(Layer *)
58 {
59 }
60 
62 {
63  mOffset = QPointF(0, 0);
64  mRotatedAngle = 0;
65  mSelectionTransform.reset();
66 }
67 
68 void SelectionManager::updatePolygons()
69 {
70  mCurrentSelectionPolygonF = mTempTransformedSelection;
71  mLastSelectionPolygonF = mTransformedSelection;
72 }
73 
74 void SelectionManager::resetSelectionTransform()
75 {
76  mSelectionTransform.reset();
77 }
78 
79 bool SelectionManager::isOutsideSelectionArea(const QPointF point)
80 {
81  return (!mTransformedSelection.contains(point)
82  && validateMoveMode(point) == MoveMode::NONE);
83 }
84 
85 bool SelectionManager::transformHasBeenModified() const
86 {
87  return (mSelection != mTempTransformedSelection) || rotationHasBeenModified();
88 }
89 
90 bool SelectionManager::rotationHasBeenModified() const
91 {
92  return !qFuzzyCompare(mRotatedAngle, 0);
93 }
94 
95 void SelectionManager::deleteSelection()
96 {
97  emit needDeleteSelection();
98 }
99 
100 void SelectionManager::clearCurves()
101 {
102  mClosestCurves.clear();
103 }
104 
105 void SelectionManager::clearVertices()
106 {
107  mClosestVertices.clear();
108 }
109 
110 qreal SelectionManager::selectionTolerance() const
111 {
112  return qAbs(mSelectionTolerance * editor()->view()->getViewScaleInverse());
113 }
114 
115 MoveMode SelectionManager::validateMoveMode(const QPointF pos)
116 {
117  return moveModeForAnchorInRange(pos);
118 }
119 
120 MoveMode SelectionManager::moveModeForAnchorInRange(const QPointF lastPos)
121 {
122  const QRectF transformRect = mTempTransformedSelection;
123  const QPointF lastPoint = lastPos;
124 
125  const double calculatedSelectionTol = selectionTolerance();
126 
127  MoveMode mode;
128  if (QLineF(lastPoint, transformRect.topLeft()).length() < calculatedSelectionTol)
129  {
130  mode = MoveMode::TOPLEFT;
131  }
132  else if (QLineF(lastPoint, transformRect.topRight()).length() < calculatedSelectionTol)
133  {
134  mode = MoveMode::TOPRIGHT;
135  }
136  else if (QLineF(lastPoint, transformRect.bottomLeft()).length() < calculatedSelectionTol)
137  {
138  mode = MoveMode::BOTTOMLEFT;
139 
140  }
141  else if (QLineF(lastPoint, transformRect.bottomRight()).length() < calculatedSelectionTol)
142  {
143  mode = MoveMode::BOTTOMRIGHT;
144  }
145  else if (mTransformedSelection.contains(lastPoint))
146  {
147  mode = MoveMode::MIDDLE;
148  }
149  else {
150  mode = MoveMode::NONE;
151  }
152  mMoveMode = mode;
153  return mode;
154 }
155 
156 MoveMode SelectionManager::getMoveModeForSelectionAnchor(const QPointF pos) const
157 {
158  const double calculatedSelectionTol = selectionTolerance();
159 
160  if (mCurrentSelectionPolygonF.count() < 4) { return MoveMode::NONE; }
161 
162  QPointF topLeftCorner = mCurrentSelectionPolygonF[0];
163 
164  QPointF topRightCorner = mCurrentSelectionPolygonF[1];
165 
166  QPointF bottomRightCorner = mCurrentSelectionPolygonF[2];
167 
168  QPointF bottomLeftCorner = mCurrentSelectionPolygonF[3];
169 
170  QPointF currentPos = pos;
171 
172  if (QLineF(currentPos, topLeftCorner).length() < calculatedSelectionTol)
173  {
174  return MoveMode::TOPLEFT;
175  }
176  else if (QLineF(currentPos, topRightCorner).length() < calculatedSelectionTol)
177  {
178  return MoveMode::TOPRIGHT;
179  }
180  else if (QLineF(currentPos, bottomLeftCorner).length() < calculatedSelectionTol)
181  {
182  return MoveMode::BOTTOMLEFT;
183 
184  }
185  else if (QLineF(currentPos, bottomRightCorner).length() < calculatedSelectionTol)
186  {
187  return MoveMode::BOTTOMRIGHT;
188  }
189  else if (mTempTransformedSelection.contains(currentPos))
190  {
191  return MoveMode::MIDDLE;
192  }
193 
194  return MoveMode::NONE;
195 }
196 
197 QPointF SelectionManager::whichAnchorPoint(const QPointF currentPoint) const
198 {
199  QPointF anchorPoint;
200  MoveMode mode = getMoveModeForSelectionAnchor(currentPoint);
201  if (mode == MoveMode::TOPLEFT)
202  {
203  anchorPoint = mSelection.bottomRight();
204  }
205  else if (mode == MoveMode::TOPRIGHT)
206  {
207  anchorPoint = mSelection.bottomLeft();
208  }
209  else if (mode == MoveMode::BOTTOMLEFT)
210  {
211  anchorPoint = mSelection.topRight();
212  }
213  else if (mode == MoveMode::BOTTOMRIGHT)
214  {
215  anchorPoint = mSelection.topLeft();
216  }
217  return anchorPoint;
218 }
219 
220 void SelectionManager::adjustSelection(const QPointF& currentPoint, qreal offsetX, qreal offsetY, qreal rotationOffset, int rotationIncrement)
221 {
222  offsetX = qRound(offsetX);
223  offsetY = qRound(offsetY);
224  QRectF& transformedSelection = mTransformedSelection;
225 
226  switch (mMoveMode)
227  {
228  case MoveMode::MIDDLE:
229  {
230  mTempTransformedSelection = transformedSelection.translated(QPointF(offsetX, offsetY));
231  break;
232  }
233  case MoveMode::TOPRIGHT:
234  {
235  mTempTransformedSelection = transformedSelection.adjusted(0, offsetY, offsetX, 0);
236  break;
237  }
238  case MoveMode::TOPLEFT:
239  {
240  mTempTransformedSelection = transformedSelection.adjusted(offsetX, offsetY, 0, 0);
241  break;
242  }
243  case MoveMode::BOTTOMLEFT:
244  {
245  mTempTransformedSelection = transformedSelection.adjusted(offsetX, 0, 0, offsetY);
246  break;
247  }
248  case MoveMode::BOTTOMRIGHT:
249  {
250  mTempTransformedSelection = transformedSelection.adjusted(0, 0, offsetX, offsetY);
251  break;
252 
253  }
254  case MoveMode::ROTATION:
255  {
256  mTempTransformedSelection = transformedSelection;
257  QPointF anchorPoint = transformedSelection.center();
258  qreal rotatedAngle = qRadiansToDegrees(MathUtils::getDifferenceAngle(anchorPoint, currentPoint)) - rotationOffset;
259  if (rotationIncrement > 0) {
260  mRotatedAngle = constrainRotationToAngle(rotatedAngle, rotationIncrement);
261  } else {
262  mRotatedAngle = rotatedAngle;
263  }
264  break;
265  }
266  default:
267  break;
268  }
269 }
270 
271 int SelectionManager::constrainRotationToAngle(const qreal rotatedAngle, const int rotationIncrement) const
272 {
273  return qRound(rotatedAngle / rotationIncrement) * rotationIncrement;
274 }
275 
276 void SelectionManager::setSelection(QRectF rect, bool roundPixels)
277 {
279  if (roundPixels)
280  {
281  rect = QRect(rect.topLeft().toPoint(), rect.bottomRight().toPoint() - QPoint(1,1));
282  }
283  mSelection = rect;
284  mTransformedSelection = rect;
285  mTempTransformedSelection = rect;
286  mSomethingSelected = (mSelection.isNull() ? false : true);
287 
288  emit selectionChanged();
289 }
290 
291 void SelectionManager::calculateSelectionTransformation()
292 {
293  QVector<QPointF> centerPoints = calcSelectionCenterPoints();
294 
295  mSelectionTransform.reset();
296 
297  mSelectionTransform.translate(centerPoints[0].x(), centerPoints[0].y());
298  mSelectionTransform.rotate(mRotatedAngle);
299 
300  if (mSelection.width() > 0 && mSelection.height() > 0) // can't divide by 0
301  {
302  qreal scaleX = mTempTransformedSelection.width() / mSelection.width();
303  qreal scaleY = mTempTransformedSelection.height() / mSelection.height();
304  mSelectionTransform.scale(scaleX, scaleY);
305  }
306  mSelectionTransform.translate(-centerPoints[1].x(), -centerPoints[1].y());
307 }
308 
309 QVector<QPointF> SelectionManager::calcSelectionCenterPoints() const
310 {
311  QVector<QPointF> centerPoints;
312  qreal selectionCenterX,
313  selectionCenterY,
314  tempSelectionCenterX,
315  tempSelectionCenterY;
316 
317  tempSelectionCenterX = mTempTransformedSelection.center().x();
318  tempSelectionCenterY = mTempTransformedSelection.center().y();
319  selectionCenterX = mSelection.center().x();
320  selectionCenterY = mSelection.center().y();
321  centerPoints.append(QPointF(tempSelectionCenterX, tempSelectionCenterY));
322  centerPoints.append(QPointF(selectionCenterX, selectionCenterY));
323  return centerPoints;
324 }
325 
326 
327 QPointF SelectionManager::offsetFromAspectRatio(qreal offsetX, qreal offsetY) const
328 {
329  qreal factor = mTransformedSelection.width() / mTransformedSelection.height();
330 
331  if (mMoveMode == MoveMode::TOPLEFT || mMoveMode == MoveMode::BOTTOMRIGHT)
332  {
333  offsetY = offsetX / factor;
334  }
335  else if (mMoveMode == MoveMode::TOPRIGHT || mMoveMode == MoveMode::BOTTOMLEFT)
336  {
337  offsetY = -(offsetX / factor);
338  }
339  else if (mMoveMode == MoveMode::MIDDLE)
340  {
341  qreal absX = offsetX;
342  if (absX < 0) { absX = -absX; }
343 
344  qreal absY = offsetY;
345  if (absY < 0) { absY = -absY; }
346 
347  if (absX > absY) { offsetY = 0; }
348  if (absY > absX) { offsetX = 0; }
349  }
350  return QPointF(offsetX, offsetY);
351 }
352 
357 void SelectionManager::flipSelection(bool flipVertical)
358 {
359  if (flipVertical)
360  {
361  editor()->backup(tr("Flip selection vertically"));
362  }
363  else
364  {
365  editor()->backup(tr("Flip selection horizontally"));
366  }
367 
368  qreal scaleX = mTempTransformedSelection.width() / mSelection.width();
369  qreal scaleY = mTempTransformedSelection.height() / mSelection.height();
370  QVector<QPointF> centerPoints = calcSelectionCenterPoints();
371 
372  QTransform translate = QTransform::fromTranslate(centerPoints[0].x(), centerPoints[0].y());
373  QTransform _translate = QTransform::fromTranslate(-centerPoints[1].x(), -centerPoints[1].y());
374  QTransform scale = QTransform::fromScale(-scaleX, scaleY);
375 
376  if (flipVertical)
377  {
378  scale = QTransform::fromScale(scaleX, -scaleY);
379  }
380 
381  // reset transformation for vector selections
382  mSelectionTransform.reset();
383  mSelectionTransform *= _translate * scale * translate;
384 
385  emit needPaintAndApply();
386 }
387 
388 void SelectionManager::translate(QPointF point)
389 {
390  mTempTransformedSelection.translate(point);
391  mTransformedSelection = mTempTransformedSelection;
392  calculateSelectionTransformation();
393 }
394 
395 void SelectionManager::resetSelectionProperties()
396 {
398  mSelection = QRectF();
399  mTransformedSelection = QRectF();
400  mTempTransformedSelection = QRectF();
401  mCurrentSelectionPolygonF = QPolygonF();
402  mLastSelectionPolygonF = QPolygonF();
403 
404  mSomethingSelected = false;
405  vectorSelection.clear();
406  emit selectionReset();
407  emit selectionChanged();
408 }
409 
QTransform fromTranslate(qreal dx, qreal dy)
void clear()
QTransform fromScale(qreal sx, qreal sy)
void reset()
void append(const T &value)
bool contains(const QRectF &rectangle) const const
void resetSelectionTransformProperties()
SelectionManager::resetSelectionTransformProperties should be used whenever translate, rotate, transform, scale has been applied to a selection, but don't want to reset size nor position.
QString tr(const char *sourceText, const char *disambiguation, int n)
qreal length() const const
QTransform & translate(qreal dx, qreal dy)
qreal x() const const
qreal y() const const
void translate(qreal dx, qreal dy)
QTransform & scale(qreal sx, qreal sy)
QPointF topLeft() const const
Definition: layer.h:38
QPointF center() const const
QPointF topRight() const const
bool isNull() const const
QTransform & rotate(qreal angle, Qt::Axis axis)
qreal width() const const
QPoint toPoint() const const
QRectF translated(qreal dx, qreal dy) const const
QPointF bottomLeft() const const
int count(const T &value) const const
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
qreal height() const const
Definition: object.h:41
void flipSelection(bool flipVertical)
ScribbleArea::flipSelection flip selection along the X or Y axis.
QPointF bottomRight() const const
Definition: editor.h:55