Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_rectangle_base.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2009 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
8
9#include <QtCore/qmath.h>
10
11#include "KisViewManager.h"
12#include "kis_canvas2.h"
14#include <KoCanvasBase.h>
15#include <KoCanvasController.h>
16#include <KoPointerEvent.h>
17#include <KoViewConverter.h>
19#include <kis_icon.h>
20
22
24 : KisToolShape(canvas, cursor)
25 , m_dragStart(0, 0)
26 , m_dragEnd(0, 0)
27 , m_type(type)
28 , m_isRatioForced(false)
29 , m_isWidthForced(false)
30 , m_isHeightForced(false)
31 , m_rotateActive(false)
32 , m_forcedRatio(1.0)
33 , m_forcedWidth(0)
34 , m_forcedHeight(0)
35 , m_roundCornersX(0)
36 , m_roundCornersY(0)
37 , m_referenceAngle(0)
38 , m_angle(0)
39 , m_angleBuffer(0)
40 , m_currentModifiers(Qt::NoModifier)
41{
42}
43
44
46{
48
51
52 if (widgetsList.size() > 0
53 && dynamic_cast<KisOptionCollectionWidget *>(
54 widgetsList.first().data())) {
55 KisOptionCollectionWidget *baseOptions =
56 dynamic_cast<KisOptionCollectionWidget *>(
57 widgetsList.first().data());
58 KisOptionCollectionWidgetWithHeader *sectionRectangle =
59 new KisOptionCollectionWidgetWithHeader(widget->windowTitle());
60 sectionRectangle->appendWidget("rectangleConstraintWidget", widget);
61 baseOptions->appendWidget("sectionRectangle", sectionRectangle);
62 } else {
63 widget->setContentsMargins(10, 10, 10, 10);
64 widgetsList.append(widget);
65 }
66
67 return widgetsList;
68}
69
70void KisToolRectangleBase::constraintsChanged(bool forceRatio, bool forceWidth, bool forceHeight, float ratio, float width, float height)
71{
72 m_isWidthForced = forceWidth;
73 m_isHeightForced = forceHeight;
74 m_isRatioForced = forceRatio;
75
76 m_forcedHeight = height;
77 m_forcedWidth = width;
78 m_forcedRatio = ratio;
79
80 // Avoid division by zero in size calculations
81 if (ratio < 0.0001f)
82 m_isRatioForced = false;
83}
84
86{
87 m_roundCornersX = rx;
88 m_roundCornersY = ry;
89}
90
92{
93 KisCanvas2 *kisCanvas =dynamic_cast<KisCanvas2*>(canvas());
95 kisCanvas->viewManager()->showFloatingMessage(i18n("Width: %1 px\nHeight: %2 px"
97 , createRect(m_dragStart, m_dragEnd).height()), QIcon(), 1000
98 , KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
99
100}
101void KisToolRectangleBase::paint(QPainter& gc, const KoViewConverter &converter)
102{
103 if(mode() == KisTool::PAINT_MODE) {
105 }
106
107 KisToolPaint::paint(gc, converter);
108}
109
110void KisToolRectangleBase::activate(const QSet<KoShape *> &shapes)
111{
113
114 Q_EMIT sigRequestReloadConfig();
115}
116
122
125
126 if (key == Qt::Key_Control) {
127 m_currentModifiers |= Qt::ControlModifier;
128 } else if (key == Qt::Key_Shift) {
129 m_currentModifiers |= Qt::ShiftModifier;
130 } else if (key == Qt::Key_Alt) {
131 m_currentModifiers |= Qt::AltModifier;
132 }
133
135}
136
139
140 if (key == Qt::Key_Control) {
141 m_currentModifiers &= ~Qt::ControlModifier;
142 } else if (key == Qt::Key_Shift) {
143 m_currentModifiers &= ~Qt::ShiftModifier;
144 } else if (key == Qt::Key_Alt) {
145 m_currentModifiers &= ~Qt::AltModifier;
146 }
147
149}
150
152{
153 NodePaintAbility paintability = nodePaintAbility();
154 if ((m_type == PAINT && (!nodeEditable() || paintability == UNPAINTABLE || paintability == KisToolPaint::CLONE || paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE)) || (m_type == SELECT && !selectionEditable())) {
155
156 if (paintability == KisToolPaint::CLONE){
157 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
158 QString message = i18n("This tool cannot paint on clone layers. Please select a paint or vector layer or mask.");
159 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
160 }
161
162 if (paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE) {
163 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
164 QString message = i18n("The MyPaint Brush Engine is not available for this colorspace");
165 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
166 }
167
168 event->ignore();
169 return;
170 }
172 beginShape();
173
174 m_currentModifiers = Qt::NoModifier;
175
176 QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false);
179 m_rotateActive = false;
180
181 QSizeF area = QSizeF(0,0);
182
183 applyConstraints(area, false);
184
185 m_dragEnd.setX(m_dragStart.x() + area.width());
186 m_dragEnd.setY(m_dragStart.y() + area.height());
187
188 m_dragCenter = QPointF((m_dragStart.x() + m_dragEnd.x()) / 2,
189 (m_dragStart.y() + m_dragEnd.y()) / 2);
190 showSize();
191 event->accept();
192}
193
195 if (m_isWidthForced && m_isHeightForced) return true;
196 if (m_isRatioForced && (m_isWidthForced || m_isHeightForced)) return true;
197
198 return false;
199}
200
201void KisToolRectangleBase::applyConstraints(QSizeF &area, bool overrideRatio) {
202 if (m_isWidthForced) {
203 area.setWidth(m_forcedWidth);
204 }
205 if (m_isHeightForced) {
206 area.setHeight(m_forcedHeight);
207 }
208
209 if (m_isHeightForced && m_isWidthForced) return;
210
211 if (m_isRatioForced || overrideRatio) {
212 float ratio = m_isRatioForced ? m_forcedRatio : 1.0f;
213
214 if (m_isWidthForced) {
215 area.setHeight(area.width() / ratio);
216 } else {
217 area.setWidth(area.height() * ratio);
218 }
219 }
220}
221
223{
225
226 bool constraintToggle = m_currentModifiers & Qt::ShiftModifier;
227 bool translateMode = m_currentModifiers & Qt::AltModifier;
228 bool expandFromCenter = m_currentModifiers & Qt::ControlModifier;
229
230 bool rotateMode = expandFromCenter && translateMode;
231 bool fixedSize = isFixedSize() && !constraintToggle;
232
233 QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false);
234
235 if (rotateMode) {
236 QPointF angleVector;
237 if (!m_rotateActive) {
238 m_rotateActive = true;
239 angleVector = (fixedSize)? m_dragEnd: pos;
240 angleVector -= m_dragStart;
241 m_referenceAngle = atan2(angleVector.y(), angleVector.x());
242 }
243 angleVector = pos - m_dragStart;
244 qreal a2 = atan2(angleVector.y(), angleVector.x());
246 } else {
247 m_rotateActive = false;
249 m_angleBuffer = 0;
250 }
251
252 if (fixedSize && !rotateMode) {
253 m_dragStart = pos;
254 } else if (translateMode && !rotateMode) {
255 QPointF trans = pos - m_dragEnd;
256 m_dragStart += trans;
257 m_dragEnd += trans;
258
259 }
260
261 QPointF diag = pos - m_dragStart;
262 QTransform t1, t2;
263 t1.rotateRadians(-getRotationAngle());
264 QPointF baseDiag = t1.map(diag);
265 QSizeF area = QSizeF(fabs(baseDiag.x()), fabs(baseDiag.y()));
266
267 bool overrideRatio = constraintToggle && !(m_isHeightForced || m_isWidthForced || m_isRatioForced);
268 if (!constraintToggle || overrideRatio) {
269 applyConstraints(area, overrideRatio);
270 }
271
272 baseDiag = QPointF(
273 (baseDiag.x() < 0) ? -area.width() : area.width(),
274 (baseDiag.y() < 0) ? -area.height() : area.height()
275 );
276
277 t2.rotateRadians(getRotationAngle());
278 diag = t2.map(baseDiag);
279
280 // resize around center point?
281 if (expandFromCenter && !fixedSize && !rotateMode) {
282 m_dragStart = m_dragCenter - diag / 2;
283 m_dragEnd = m_dragCenter + diag / 2;
284 } else {
285 m_dragEnd = m_dragStart + diag;
286 }
287
288 if(!translateMode) {
289 showSize();
290 }
291 else {
292 KisCanvas2 *kisCanvas =dynamic_cast<KisCanvas2*>(canvas());
293 KIS_ASSERT(kisCanvas);
294 kisCanvas->viewManager()->showFloatingMessage(i18n("X: %1 px\nY: %2 px"
295 , QString::number(m_dragStart.x(), 'f', 1)
296 , QString::number(m_dragStart.y(), 'f', 1)), QIcon(), 1000
297 , KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
298 }
299 updateArea();
300 m_dragCenter = QPointF((m_dragStart.x() + m_dragEnd.x()) / 2,
301 (m_dragStart.y() + m_dragEnd.y()) / 2);
302
304}
305
307{
309 // If the event was not originated by the user releasing the button
310 // (for example due to the canvas loosing focus), then we just cancel the
311 // operation. This prevents some issues with shapes being added after
312 // the image was closed while the shape was being made
313 if (event->spontaneous()) {
314 endStroke();
315 } else {
316 cancelStroke();
317 }
318 event->accept();
319}
320
322{
323 if (mode() != KisTool::PAINT_MODE) {
324 return;
325 }
326 endStroke();
327}
328
330{
331 if (mode() != KisTool::PAINT_MODE) {
332 return;
333 }
334 cancelStroke();
335}
336
344
351
352QRectF KisToolRectangleBase::createRect(const QPointF &start, const QPointF &end)
353{
354 QTransform t;
355 t.translate(start.x(), start.y());
356 t.rotateRadians(-getRotationAngle());
357 t.translate(-start.x(), -start.y());
358 const QTransform tInv = t.inverted();
359
360 const QPointF end1 = t.map(end);
361 const QPointF newStart(qRound(start.x()), qRound(start.y()));
362 const QPointF newEnd(qRound(end1.x()), qRound(end1.y()));
363 const QPointF newCenter = (newStart + newEnd) / 2.0;
364
365 QRectF result(newStart, newEnd);
366 result.moveCenter(tInv.map(newCenter));
367
368 return result.normalized();
369}
370
372{
373 return true;
374}
375
376void KisToolRectangleBase::paintRectangle(QPainter &gc, const QRectF &imageRect)
377{
379
380 const QRect viewRect = pixelToView(imageRect).toAlignedRect();
381
382 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
384
385 const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter();
386 const qreal roundCornersX = converter->effectiveZoom() * m_roundCornersX;
387 const qreal roundCornersY = converter->effectiveZoom() * m_roundCornersY;
388
389 QPainterPath path;
390 if (m_roundCornersX > 0 || m_roundCornersY > 0) {
391 path.addRoundedRect(viewRect,
392 roundCornersX, roundCornersY);
393 } else {
394 path.addRect(viewRect);
395 }
396
397 getRotatedPath(path, viewRect.center(), getRotationAngle());
398 path.addPath(drawX(pixelToView(m_dragStart)));
399 path.addPath(drawX(pixelToView(m_dragCenter)));
400 paintToolOutline(&gc, path);
401}
402
404 const QRectF bound = createRect(m_dragStart, m_dragEnd);
405
406 canvas()->updateCanvas(convertToPt(bound).adjusted(-100, -100, +200, +200));
407
408 Q_EMIT rectangleChanged(bound);
409}
410
414
415QPainterPath KisToolRectangleBase::drawX(const QPointF &pt) {
416 QPainterPath path;
417 path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0));
418 path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0));
419 return path;
420}
421
422void KisToolRectangleBase::getRotatedPath(QPainterPath &path, const QPointF &center, const qreal &angle) {
423 QTransform t;
424 t.translate(center.x(), center.y());
425 t.rotateRadians(angle);
426 t.translate(-center.x(), -center.y());
427
428 path = t.map(path);
429}
KisCoordinatesConverter * coordinatesConverter
void updateCanvas(const QRectF &rc) override
KisViewManager * viewManager() const
static Qt::Key workaroundShiftAltMetaHell(const QKeyEvent *keyEvent)
Wrapper class around a KisOptionCollectionWidget that also provide a header with a title label and an...
void appendWidget(const QString &id, QWidget *widget)
Insert the given widget with the given id at the end of the list. The list widget takes ownership of ...
Class providing a list of widgets with some addons such as separators, orientation or individual widg...
void appendWidget(const QString &id, QWidget *widget)
Insert the given widget with the given id at the end of the list. The list widget takes ownership of ...
virtual void requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event)
void paint(QPainter &gc, const KoViewConverter &converter) override
void setMode(ToolMode mode) override
void requestStrokeCancellation() override
void endPrimaryAction(KoPointerEvent *event) override
void rectangleChanged(const QRectF &newRect)
void keyReleaseEvent(QKeyEvent *event) override
virtual void paintRectangle(QPainter &gc, const QRectF &imageRect)
virtual bool showRoundCornersGUI() const
void getRotatedPath(QPainterPath &path, const QPointF &center, const qreal &angle)
QPainterPath drawX(const QPointF &pt)
Qt::KeyboardModifiers m_currentModifiers
void keyPressEvent(QKeyEvent *event) override
void paint(QPainter &gc, const KoViewConverter &converter) override
void applyConstraints(QSizeF &area, bool overrideRatio)
void roundCornersChanged(int rx, int ry)
void activate(const QSet< KoShape * > &shapes) override
QList< QPointer< QWidget > > createOptionWidgets() override
virtual void finishRect(const QRectF &rect, qreal roundCornersX, qreal roundCornersY)=0
virtual QRectF createRect(const QPointF &start, const QPointF &end)
void continuePrimaryAction(KoPointerEvent *event) override
KisToolRectangleBase(KoCanvasBase *canvas, KisToolRectangleBase::ToolType type, const QCursor &cursor=KisCursor::load("tool_rectangle_cursor.png", 6, 6))
void constraintsChanged(bool forceRatio, bool forceWidth, bool forceHeight, float ratio, float width, float height)
void beginPrimaryAction(KoPointerEvent *event) override
virtual void beginShape()
virtual void endShape()
void activate(const QSet< KoShape * > &shapes) override
void showFloatingMessage(const QString &message, const QIcon &icon, int timeout=4500, KisFloatingMessage::Priority priority=KisFloatingMessage::Medium, int alignment=Qt::AlignCenter|Qt::TextWordWrap)
shows a floating message in the top right corner of the canvas
bool spontaneous() const
return if this event was spontaneous (see QMouseEvent::spontaneous())
QPointF point
The point in document coordinates.
virtual QList< QPointer< QWidget > > createOptionWidgets()
virtual void keyReleaseEvent(QKeyEvent *event)
virtual void keyPressEvent(QKeyEvent *event)
virtual void deactivate()
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define koIcon(name)
Use these macros for icons without any issues.
Definition kis_icon.h:25
#define CHECK_MODE_SANITY_OR_RETURN(_mode)
Definition kis_tool.h:27
virtual ToolMode mode() const
Definition kis_tool.cc:407
KisTool::NodePaintAbility nodePaintAbility()
Definition kis_tool.cc:539
bool nodeEditable()
Checks checks if the current node is editable.
Definition kis_tool.cc:651
QPointF pixelToView(const QPoint &pixelCoord) const
Definition kis_tool.cc:269
void paintToolOutline(QPainter *painter, const KisOptimizedBrushOutline &path)
Definition kis_tool.cc:589
QRectF convertToPt(const QRectF &rect)
Definition kis_tool.cc:252
@ PAINT_MODE
Definition kis_tool.h:300
@ HOVER_MODE
Definition kis_tool.h:299
QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset=QPointF(), bool useModifiers=true)
Definition kis_tool.cc:214
bool selectionEditable()
Checks checks if the selection is editable, only applies to local selection as global selection is al...
Definition kis_tool.cc:696
NodePaintAbility
Definition kis_tool.h:148
@ MYPAINTBRUSH_UNPAINTABLE
Definition kis_tool.h:153
@ UNPAINTABLE
Definition kis_tool.h:152
KisCanvas2 * canvas