All Classes Namespaces Functions Variables Enumerations Properties Pages
basetool.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 
18 #include "basetool.h"
19 
20 #include <array>
21 #include <QtMath>
22 #include <QPixmap>
23 #include "editor.h"
24 #include "viewmanager.h"
25 #include "toolmanager.h"
26 #include "scribblearea.h"
27 #include "strokemanager.h"
28 #include "pointerevent.h"
29 
30 // ---- shared static variables ---- ( only one instance for all the tools )
31 qreal BaseTool::msOriginalPropertyValue; // start value (width, feather ..)
32 bool BaseTool::msIsAdjusting = false;
33 
34 
35 QString BaseTool::TypeName(ToolType type)
36 {
37  static std::array<QString, TOOL_TYPE_COUNT> map;
38 
39  if (map[0].isEmpty())
40  {
41  map[PENCIL] = tr("Pencil");
42  map[ERASER] = tr("Eraser");
43  map[SELECT] = tr("Select");
44  map[MOVE] = tr("Move");
45  map[HAND] = tr("Hand");
46  map[SMUDGE] = tr("Smudge");
47  map[PEN] = tr("Pen");
48  map[POLYLINE] = tr("Polyline");
49  map[BUCKET] = tr("Bucket");
50  map[EYEDROPPER] = tr("Eyedropper");
51  map[BRUSH] = tr("Brush");
52  }
53  return map.at(type);
54 }
55 
56 BaseTool::BaseTool(QObject* parent) : QObject(parent)
57 {
58  mPropertyEnabled.insert(WIDTH, false);
59  mPropertyEnabled.insert(FEATHER, false);
60  mPropertyEnabled.insert(USEFEATHER, false);
61  mPropertyEnabled.insert(PRESSURE, false);
62  mPropertyEnabled.insert(INVISIBILITY, false);
63  mPropertyEnabled.insert(PRESERVEALPHA, false);
64  mPropertyEnabled.insert(BEZIER, false);
65  mPropertyEnabled.insert(ANTI_ALIASING, false);
66  mPropertyEnabled.insert(STABILIZATION, false);
67 }
68 
69 QCursor BaseTool::cursor()
70 {
71  return Qt::ArrowCursor;
72 }
73 
74 void BaseTool::initialize(Editor* editor)
75 {
76  Q_ASSERT(editor);
77  mEditor = editor;
78  mScribbleArea = editor->getScribbleArea();
79  Q_ASSERT(mScribbleArea);
80 
81  mStrokeManager = mEditor->getScribbleArea()->getStrokeManager();
82  loadSettings();
83 }
84 
85 void BaseTool::pointerPressEvent(PointerEvent* event)
86 {
87  event->accept();
88 }
89 
90 void BaseTool::pointerMoveEvent(PointerEvent* event)
91 {
92  event->accept();
93 }
94 
95 void BaseTool::pointerReleaseEvent(PointerEvent* event)
96 {
97  event->accept();
98 }
99 
100 void BaseTool::pointerDoubleClickEvent(PointerEvent* event)
101 {
102  pointerPressEvent(event);
103 }
104 
111 {
112  if (type() == ToolType::HAND || type() == ToolType::MOVE || type() == ToolType::SELECT )
113  {
114  return false;
115  }
116  return true;
117 }
118 
123 QPixmap BaseTool::canvasCursor(float width, float feather, bool useFeather, float scalingFac, int windowWidth)
124 {
125  float propWidth = width * scalingFac;
126  float propFeather = feather * scalingFac;
127 
128  float cursorWidth = 0.0f;
129  float xyA = 0.0f;
130  float xyB = 0.0f;
131  float whA = 0.0f;
132  float whB = 0.0f;
133 
134  if (useFeather)
135  {
136  cursorWidth = propWidth + 0.5 * propFeather;
137  xyA = 1 + propFeather / 2;
138  xyB = 1 + propFeather / 8;
139  whA = qMax<float>(0, propWidth - xyA - 1);
140  whB = qMax<float>(0, cursorWidth - propFeather / 4 - 2);
141  }
142  else
143  {
144  cursorWidth = (propWidth + 0.5);
145  whA = qMax<float>(0, propWidth - 1);
146  whB = qMax<float>(0, cursorWidth / 4 - 2);
147  }
148 
149  float radius = cursorWidth / 2;
150 
151  // deallocate when cursor width gets some value larger than the widget
152  if (cursorWidth > windowWidth * 2)
153  {
154  return QPixmap(0, 0);
155  }
156 
157  if (cursorWidth < 1) { cursorWidth = 1; }
158 
159  QPixmap cursorPixmap = QPixmap(cursorWidth, cursorWidth);
160  if (!cursorPixmap.isNull())
161  {
162  cursorPixmap.fill(QColor(255, 255, 255, 0));
163  QPainter cursorPainter(&cursorPixmap);
164  QPen cursorPen = cursorPainter.pen();
166 
167  // Draw cross in center
168  cursorPen.setStyle(Qt::SolidLine);
169  cursorPen.setColor(QColor(0, 0, 0, 127));
170  cursorPainter.setPen(cursorPen);
171  cursorPainter.drawLine(QPointF(radius - 2, radius), QPointF(radius + 2, radius));
172  cursorPainter.drawLine(QPointF(radius, radius - 2), QPointF(radius, radius + 2));
173 
174  // Draw outer circle
175  if (useFeather)
176  {
177  cursorPen.setStyle(Qt::DotLine);
178  cursorPen.setColor(QColor(0, 0, 0, 255));
179  cursorPainter.setPen(cursorPen);
180  cursorPainter.drawEllipse(QRectF(xyB, xyB, whB, whB));
181  cursorPen.setDashOffset(4);
182  cursorPen.setColor(QColor(255, 255, 255, 255));
183  cursorPainter.setPen(cursorPen);
184  cursorPainter.drawEllipse(QRectF(xyB, xyB, whB, whB));
185  }
186 
187  // Draw inner circle
188  cursorPen.setStyle(Qt::DotLine);
189  cursorPen.setColor(QColor(0, 0, 0, 255));
190  cursorPainter.setPen(cursorPen);
191  cursorPainter.drawEllipse(QRectF(xyA, xyA, whA, whA));
192  cursorPen.setDashOffset(4);
193  cursorPen.setColor(QColor(255, 255, 255, 255));
194  cursorPainter.setPen(cursorPen);
195  cursorPainter.drawEllipse(QRectF(xyA, xyA, whA, whA));
196 
197  cursorPainter.end();
198  }
199  return cursorPixmap;
200 }
201 
202 QCursor BaseTool::selectMoveCursor(MoveMode mode, ToolType type)
203 {
204  QPixmap cursorPixmap = QPixmap(24, 24);
205  if (!cursorPixmap.isNull())
206  {
207  cursorPixmap.fill(QColor(255, 255, 255, 0));
208  QPainter cursorPainter(&cursorPixmap);
209  cursorPainter.setRenderHint(QPainter::HighQualityAntialiasing);
210 
211  switch(mode)
212  {
213  case MoveMode::MIDDLE:
214  {
215  if (type == SELECT) {
216  cursorPainter.drawImage(QPoint(6,6),QImage("://icons/new/arrow-selectmove.png"));
217  } else {
218  return Qt::ArrowCursor;
219  }
220  break;
221  }
222  case MoveMode::TOPLEFT:
223  case MoveMode::BOTTOMRIGHT:
224  {
225  cursorPainter.drawImage(QPoint(6,6),QImage("://icons/new/arrow-diagonalleft.png"));
226  break;
227  }
228  case MoveMode::TOPRIGHT:
229  case MoveMode::BOTTOMLEFT:
230  {
231  cursorPainter.drawImage(QPoint(6,6),QImage("://icons/new/arrow-diagonalright.png"));
232  break;
233  }
234  default:
235  return (type == SELECT) ? QCursor(QPixmap(":icons/cross.png"), 10, 10) : Qt::ArrowCursor;
236  break;
237  }
238  cursorPainter.end();
239  }
240  return QCursor(cursorPixmap);
241 }
242 
244 {
245  return strokeManager()->isActive();
246 }
247 
253 {
254  qreal propSize = qMax(0., properties.width) * scalingFac;
255  qreal propFeather = qMax(0., properties.feather) * scalingFac;
256  QRectF cursorRect(0, 0, propSize+2, propSize+2);
257 
258  QRectF sizeRect = cursorRect.adjusted(1, 1, -1, -1);
259  qreal featherRadius = (1 - propFeather / 100) * propSize / 2.;
260 
261  QPixmap cursorPixmap = QPixmap(cursorRect.size().toSize());
262  if (!cursorPixmap.isNull())
263  {
264  cursorPixmap.fill(QColor(255, 255, 255, 0));
265  QPainter cursorPainter(&cursorPixmap);
266  cursorPainter.setRenderHints(QPainter::Antialiasing, true);
267 
268  // Draw width (outside circle)
269  cursorPainter.setPen(QColor(255, 127, 127, 127));
270  cursorPainter.setBrush(QColor(0, 255, 127, 127));
271  cursorPainter.drawEllipse(sizeRect);
272 
273  // Draw feather (inside circle)
275  cursorPainter.setPen(QColor(0, 0, 0, 0));
276  cursorPainter.setBrush(QColor(0, 191, 95, 127));
277  cursorPainter.drawEllipse(cursorRect.center(), featherRadius, featherRadius);
278 
279  // Draw cursor in center
280  cursorPainter.setRenderHints(QPainter::Antialiasing, false);
281  cursorPainter.setPen(QColor(0, 0, 0, 255));
282  cursorPainter.drawLine(cursorRect.center() - QPoint(2, 0), cursorRect.center() + QPoint(2, 0));
283  cursorPainter.drawLine(cursorRect.center() - QPoint(0, 2), cursorRect.center() + QPoint(0, 2));
284 
285  cursorPainter.end();
286  }
287  return cursorPixmap;
288 }
289 
290 bool BaseTool::startAdjusting(Qt::KeyboardModifiers modifiers, qreal step)
291 {
292  if (mQuickSizingProperties.contains(modifiers))
293  {
294  switch (mQuickSizingProperties.value(modifiers)) {
295  case WIDTH:
296  msOriginalPropertyValue = properties.width;
297  break;
298  case FEATHER:
299  msOriginalPropertyValue = properties.feather;
300  break;
301  case TOLERANCE:
302  msOriginalPropertyValue = properties.tolerance;
303  break;
304  default:
305  qDebug() << "Unhandled quick sizing property for tool" << typeName();
306  Q_ASSERT(false);
307  return false;
308  }
309 
310  msIsAdjusting = true;
311  mAdjustmentStep = step;
312  mScribbleArea->updateCanvasCursor();
313  return true;
314  }
315  return false;
316 }
317 
318 void BaseTool::stopAdjusting()
319 {
320  msIsAdjusting = false;
321  mAdjustmentStep = 0;
322  msOriginalPropertyValue = 0;
323  mEditor->getScribbleArea()->updateCanvasCursor();
324 }
325 
326 void BaseTool::adjustCursor(Qt::KeyboardModifiers modifiers)
327 {
328  qreal inc = qPow(msOriginalPropertyValue * 100, 0.5);
329  qreal newValue = inc + getCurrentPoint().x();
330 
331  if (newValue < 0)
332  {
333  newValue = 0;
334  }
335 
336  newValue = qPow(newValue, 2) / 100;
337  if (mAdjustmentStep > 0)
338  {
339  int tempValue = (int)(newValue / mAdjustmentStep); // + 0.5 ?
340  newValue = tempValue * mAdjustmentStep;
341  }
342 
343  switch (mQuickSizingProperties.value(modifiers))
344  {
345  case WIDTH:
346  mEditor->tools()->setWidth(qBound(1., newValue, 200.));
347  break;
348  case FEATHER:
349  mEditor->tools()->setFeather(qBound(2., newValue, 200.));
350  break;
351  case TOLERANCE:
352  mEditor->tools()->setTolerance(qBound(0., newValue, 100.));
353  break;
354  default:
355  qDebug() << "Unhandled quick sizing property for tool" << typeName();
356  Q_ASSERT(false);
357  break;
358  };
359 }
360 
361 QPointF BaseTool::getCurrentPressPixel()
362 {
363  return strokeManager()->getCurrentPressPixel();
364 }
365 
366 QPointF BaseTool::getCurrentPressPoint()
367 {
368  return mEditor->view()->mapScreenToCanvas(strokeManager()->getCurrentPressPixel());
369 }
370 
371 QPointF BaseTool::getCurrentPixel()
372 {
373  return strokeManager()->getCurrentPixel();
374 }
375 
376 QPointF BaseTool::getCurrentPoint()
377 {
378  return mEditor->view()->mapScreenToCanvas(getCurrentPixel());
379 }
380 
381 QPointF BaseTool::getLastPixel()
382 {
383  return strokeManager()->getLastPixel();
384 }
385 
386 QPointF BaseTool::getLastPoint()
387 {
388  return mEditor->view()->mapScreenToCanvas(getLastPixel());
389 }
390 
391 QPointF BaseTool::getLastPressPixel()
392 {
393  return strokeManager()->getLastPressPixel();
394 }
395 
396 QPointF BaseTool::getLastPressPoint()
397 {
398  return mEditor->view()->mapScreenToCanvas(getLastPressPixel());
399 }
400 
401 void BaseTool::setWidth(const qreal width)
402 {
403  properties.width = width;
404 }
405 
406 void BaseTool::setFeather(const qreal feather)
407 {
408  properties.feather = feather;
409 }
410 
411 void BaseTool::setUseFeather(const bool usingFeather)
412 {
413  properties.useFeather = usingFeather;
414 }
415 
416 void BaseTool::setInvisibility(const bool invisibility)
417 {
418  properties.invisibility = invisibility;
419 }
420 
421 void BaseTool::setBezier(const bool _bezier_state)
422 {
423  properties.bezier_state = _bezier_state;
424 }
425 
426 void BaseTool::setPressure(const bool pressure)
427 {
428  properties.pressure = pressure;
429 }
430 
431 void BaseTool::setPreserveAlpha(const bool preserveAlpha)
432 {
433  properties.preserveAlpha = preserveAlpha;
434 }
435 
436 void BaseTool::setVectorMergeEnabled(const bool vectorMergeEnabled)
437 {
438  properties.vectorMergeEnabled = vectorMergeEnabled;
439 }
440 
441 void BaseTool::setAA(const int useAA)
442 {
443  properties.useAA = useAA;
444 }
445 
446 void BaseTool::setStabilizerLevel(const int level)
447 {
448  properties.stabilizerLevel = level;
449 }
450 
451 void BaseTool::setTolerance(const int tolerance)
452 {
453  properties.tolerance = tolerance;
454 }
455 
456 void BaseTool::setUseFillContour(const bool useFillContour)
457 {
458  properties.useFillContour = useFillContour;
459 }
static QPixmap canvasCursor(float brushWidth, float brushFeather, bool useFeather, float scalingFac, int windowWidth)
precision circular cursor: used for drawing a cursor within scribble area.
Definition: basetool.cpp:123
typedef KeyboardModifiers
void setStyle(Qt::PenStyle style)
bool end()
void setCompositionMode(QPainter::CompositionMode mode)
QPixmap quickSizeCursor(qreal scalingFac)
precision circular cursor: used for drawing stroke size while adjusting
Definition: basetool.cpp:252
void setRenderHint(QPainter::RenderHint hint, bool on)
SolidLine
void fill(const QColor &color)
QSizeF size() const const
void drawLine(const QLineF &line)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool isDrawingTool()
BaseTool::isDrawingTool - A drawing tool is anything that applies something to the canvas...
Definition: basetool.cpp:110
qreal x() const const
void setPen(const QColor &color)
void drawEllipse(const QRectF &rectangle)
QSize toSize() const const
void setBrush(const QBrush &brush)
ArrowCursor
QPointF center() const const
void setColor(const QColor &color)
const T value(const Key &key) const const
CompositionMode_Darken
bool isNull() const const
void setRenderHints(QPainter::RenderHints hints, bool on)
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
bool contains(const Key &key) const const
Definition: editor.h:51
HighQualityAntialiasing
const QPen & pen() const const
virtual bool isActive()
Check if the tool is active.
Definition: basetool.cpp:243
void setDashOffset(qreal offset)