File indexing completed on 2024-12-22 04:13:01
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2009 Boudewijn Rempt <boud@valdyas.org> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "kis_tool_rectangle_base.h" 0008 0009 #include <QtCore/qmath.h> 0010 0011 #include "KisViewManager.h" 0012 #include "kis_canvas2.h" 0013 #include <KisOptionCollectionWidget.h> 0014 #include <KoCanvasBase.h> 0015 #include <KoCanvasController.h> 0016 #include <KoPointerEvent.h> 0017 #include <KoViewConverter.h> 0018 #include <input/kis_extended_modifiers_mapper.h> 0019 #include <kis_icon.h> 0020 0021 #include "kis_rectangle_constraint_widget.h" 0022 0023 KisToolRectangleBase::KisToolRectangleBase(KoCanvasBase * canvas, KisToolRectangleBase::ToolType type, const QCursor & cursor) 0024 : KisToolShape(canvas, cursor) 0025 , m_dragStart(0, 0) 0026 , m_dragEnd(0, 0) 0027 , m_type(type) 0028 , m_isRatioForced(false) 0029 , m_isWidthForced(false) 0030 , m_isHeightForced(false) 0031 , m_rotateActive(false) 0032 , m_forcedRatio(1.0) 0033 , m_forcedWidth(0) 0034 , m_forcedHeight(0) 0035 , m_roundCornersX(0) 0036 , m_roundCornersY(0) 0037 , m_referenceAngle(0) 0038 , m_angle(0) 0039 , m_angleBuffer(0) 0040 , m_currentModifiers(Qt::NoModifier) 0041 { 0042 } 0043 0044 0045 QList<QPointer<QWidget> > KisToolRectangleBase::createOptionWidgets() 0046 { 0047 QList<QPointer<QWidget>> widgetsList = KisToolShape::createOptionWidgets(); 0048 0049 KisRectangleConstraintWidget *widget = 0050 new KisRectangleConstraintWidget(0, this, showRoundCornersGUI()); 0051 0052 if (widgetsList.size() > 0 0053 && dynamic_cast<KisOptionCollectionWidget *>( 0054 widgetsList.first().data())) { 0055 KisOptionCollectionWidget *baseOptions = 0056 dynamic_cast<KisOptionCollectionWidget *>( 0057 widgetsList.first().data()); 0058 KisOptionCollectionWidgetWithHeader *sectionRectangle = 0059 new KisOptionCollectionWidgetWithHeader(widget->windowTitle()); 0060 sectionRectangle->appendWidget("rectangleConstraintWidget", widget); 0061 baseOptions->appendWidget("sectionRectangle", sectionRectangle); 0062 } else { 0063 widget->setContentsMargins(10, 10, 10, 10); 0064 widgetsList.append(widget); 0065 } 0066 0067 return widgetsList; 0068 } 0069 0070 void KisToolRectangleBase::constraintsChanged(bool forceRatio, bool forceWidth, bool forceHeight, float ratio, float width, float height) 0071 { 0072 m_isWidthForced = forceWidth; 0073 m_isHeightForced = forceHeight; 0074 m_isRatioForced = forceRatio; 0075 0076 m_forcedHeight = height; 0077 m_forcedWidth = width; 0078 m_forcedRatio = ratio; 0079 0080 // Avoid division by zero in size calculations 0081 if (ratio < 0.0001f) 0082 m_isRatioForced = false; 0083 } 0084 0085 void KisToolRectangleBase::roundCornersChanged(int rx, int ry) 0086 { 0087 m_roundCornersX = rx; 0088 m_roundCornersY = ry; 0089 } 0090 0091 void KisToolRectangleBase::showSize() 0092 { 0093 KisCanvas2 *kisCanvas =dynamic_cast<KisCanvas2*>(canvas()); 0094 KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas); 0095 kisCanvas->viewManager()->showFloatingMessage(i18n("Width: %1 px\nHeight: %2 px" 0096 , createRect(m_dragStart, m_dragEnd).width() 0097 , createRect(m_dragStart, m_dragEnd).height()), QIcon(), 1000 0098 , KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter); 0099 0100 } 0101 void KisToolRectangleBase::paint(QPainter& gc, const KoViewConverter &converter) 0102 { 0103 if(mode() == KisTool::PAINT_MODE) { 0104 paintRectangle(gc, createRect(m_dragStart, m_dragEnd)); 0105 } 0106 0107 KisToolPaint::paint(gc, converter); 0108 } 0109 0110 void KisToolRectangleBase::activate(const QSet<KoShape *> &shapes) 0111 { 0112 KisToolShape::activate(shapes); 0113 0114 emit sigRequestReloadConfig(); 0115 } 0116 0117 void KisToolRectangleBase::deactivate() 0118 { 0119 cancelStroke(); 0120 KisToolShape::deactivate(); 0121 } 0122 0123 void KisToolRectangleBase::keyPressEvent(QKeyEvent *event) { 0124 const Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(event); 0125 0126 if (key == Qt::Key_Control) { 0127 m_currentModifiers |= Qt::ControlModifier; 0128 } else if (key == Qt::Key_Shift) { 0129 m_currentModifiers |= Qt::ShiftModifier; 0130 } else if (key == Qt::Key_Alt) { 0131 m_currentModifiers |= Qt::AltModifier; 0132 } 0133 0134 KisToolShape::keyPressEvent(event); 0135 } 0136 0137 void KisToolRectangleBase::keyReleaseEvent(QKeyEvent *event) { 0138 const Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(event); 0139 0140 if (key == Qt::Key_Control) { 0141 m_currentModifiers &= ~Qt::ControlModifier; 0142 } else if (key == Qt::Key_Shift) { 0143 m_currentModifiers &= ~Qt::ShiftModifier; 0144 } else if (key == Qt::Key_Alt) { 0145 m_currentModifiers &= ~Qt::AltModifier; 0146 } 0147 0148 KisToolShape::keyReleaseEvent(event); 0149 } 0150 0151 void KisToolRectangleBase::beginPrimaryAction(KoPointerEvent *event) 0152 { 0153 NodePaintAbility paintability = nodePaintAbility(); 0154 if ((m_type == PAINT && (!nodeEditable() || paintability == UNPAINTABLE || paintability == KisToolPaint::CLONE || paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE)) || (m_type == SELECT && !selectionEditable())) { 0155 0156 if (paintability == KisToolPaint::CLONE){ 0157 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); 0158 QString message = i18n("This tool cannot paint on clone layers. Please select a paint or vector layer or mask."); 0159 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); 0160 } 0161 0162 if (paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE) { 0163 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); 0164 QString message = i18n("The MyPaint Brush Engine is not available for this colorspace"); 0165 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); 0166 } 0167 0168 event->ignore(); 0169 return; 0170 } 0171 setMode(KisTool::PAINT_MODE); 0172 beginShape(); 0173 0174 m_currentModifiers = Qt::NoModifier; 0175 0176 QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false); 0177 m_dragStart = m_dragCenter = pos; 0178 m_angle = m_angleBuffer = 0; 0179 m_rotateActive = false; 0180 0181 QSizeF area = QSizeF(0,0); 0182 0183 applyConstraints(area, false); 0184 0185 m_dragEnd.setX(m_dragStart.x() + area.width()); 0186 m_dragEnd.setY(m_dragStart.y() + area.height()); 0187 0188 m_dragCenter = QPointF((m_dragStart.x() + m_dragEnd.x()) / 2, 0189 (m_dragStart.y() + m_dragEnd.y()) / 2); 0190 showSize(); 0191 event->accept(); 0192 } 0193 0194 bool KisToolRectangleBase::isFixedSize() { 0195 if (m_isWidthForced && m_isHeightForced) return true; 0196 if (m_isRatioForced && (m_isWidthForced || m_isHeightForced)) return true; 0197 0198 return false; 0199 } 0200 0201 void KisToolRectangleBase::applyConstraints(QSizeF &area, bool overrideRatio) { 0202 if (m_isWidthForced) { 0203 area.setWidth(m_forcedWidth); 0204 } 0205 if (m_isHeightForced) { 0206 area.setHeight(m_forcedHeight); 0207 } 0208 0209 if (m_isHeightForced && m_isWidthForced) return; 0210 0211 if (m_isRatioForced || overrideRatio) { 0212 float ratio = m_isRatioForced ? m_forcedRatio : 1.0f; 0213 0214 if (m_isWidthForced) { 0215 area.setHeight(area.width() / ratio); 0216 } else { 0217 area.setWidth(area.height() * ratio); 0218 } 0219 } 0220 } 0221 0222 void KisToolRectangleBase::continuePrimaryAction(KoPointerEvent *event) 0223 { 0224 CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); 0225 0226 bool constraintToggle = m_currentModifiers & Qt::ShiftModifier; 0227 bool translateMode = m_currentModifiers & Qt::AltModifier; 0228 bool expandFromCenter = m_currentModifiers & Qt::ControlModifier; 0229 0230 bool rotateMode = expandFromCenter && translateMode; 0231 bool fixedSize = isFixedSize() && !constraintToggle; 0232 0233 QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false); 0234 0235 if (rotateMode) { 0236 QPointF angleVector; 0237 if (!m_rotateActive) { 0238 m_rotateActive = true; 0239 angleVector = (fixedSize)? m_dragEnd: pos; 0240 angleVector -= m_dragStart; 0241 m_referenceAngle = atan2(angleVector.y(), angleVector.x()); 0242 } 0243 angleVector = pos - m_dragStart; 0244 qreal a2 = atan2(angleVector.y(), angleVector.x()); 0245 m_angleBuffer = a2 - m_referenceAngle; 0246 } else { 0247 m_rotateActive = false; 0248 m_angle += m_angleBuffer; 0249 m_angleBuffer = 0; 0250 } 0251 0252 if (fixedSize && !rotateMode) { 0253 m_dragStart = pos; 0254 } else if (translateMode && !rotateMode) { 0255 QPointF trans = pos - m_dragEnd; 0256 m_dragStart += trans; 0257 m_dragEnd += trans; 0258 0259 } 0260 0261 QPointF diag = pos - m_dragStart; 0262 QTransform t1, t2; 0263 t1.rotateRadians(-getRotationAngle()); 0264 QPointF baseDiag = t1.map(diag); 0265 QSizeF area = QSizeF(fabs(baseDiag.x()), fabs(baseDiag.y())); 0266 0267 bool overrideRatio = constraintToggle && !(m_isHeightForced || m_isWidthForced || m_isRatioForced); 0268 if (!constraintToggle || overrideRatio) { 0269 applyConstraints(area, overrideRatio); 0270 } 0271 0272 baseDiag = QPointF( 0273 (baseDiag.x() < 0) ? -area.width() : area.width(), 0274 (baseDiag.y() < 0) ? -area.height() : area.height() 0275 ); 0276 0277 t2.rotateRadians(getRotationAngle()); 0278 diag = t2.map(baseDiag); 0279 0280 // resize around center point? 0281 if (expandFromCenter && !fixedSize && !rotateMode) { 0282 m_dragStart = m_dragCenter - diag / 2; 0283 m_dragEnd = m_dragCenter + diag / 2; 0284 } else { 0285 m_dragEnd = m_dragStart + diag; 0286 } 0287 0288 if(!translateMode) { 0289 showSize(); 0290 } 0291 else { 0292 KisCanvas2 *kisCanvas =dynamic_cast<KisCanvas2*>(canvas()); 0293 KIS_ASSERT(kisCanvas); 0294 kisCanvas->viewManager()->showFloatingMessage(i18n("X: %1 px\nY: %2 px" 0295 , QString::number(m_dragStart.x(), 'f', 1) 0296 , QString::number(m_dragStart.y(), 'f', 1)), QIcon(), 1000 0297 , KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter); 0298 } 0299 updateArea(); 0300 m_dragCenter = QPointF((m_dragStart.x() + m_dragEnd.x()) / 2, 0301 (m_dragStart.y() + m_dragEnd.y()) / 2); 0302 0303 KisToolPaint::requestUpdateOutline(event->point, event); 0304 } 0305 0306 void KisToolRectangleBase::endPrimaryAction(KoPointerEvent *event) 0307 { 0308 CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); 0309 // If the event was not originated by the user releasing the button 0310 // (for example due to the canvas loosing focus), then we just cancel the 0311 // operation. This prevents some issues with shapes being added after 0312 // the image was closed while the shape was being made 0313 if (event->spontaneous()) { 0314 endStroke(); 0315 } else { 0316 cancelStroke(); 0317 } 0318 event->accept(); 0319 } 0320 0321 void KisToolRectangleBase::requestStrokeEnd() 0322 { 0323 if (mode() != KisTool::PAINT_MODE) { 0324 return; 0325 } 0326 endStroke(); 0327 } 0328 0329 void KisToolRectangleBase::requestStrokeCancellation() 0330 { 0331 if (mode() != KisTool::PAINT_MODE) { 0332 return; 0333 } 0334 cancelStroke(); 0335 } 0336 0337 void KisToolRectangleBase::endStroke() 0338 { 0339 setMode(KisTool::HOVER_MODE); 0340 updateArea(); 0341 finishRect(createRect(m_dragStart, m_dragEnd), m_roundCornersX, m_roundCornersY); 0342 endShape(); 0343 } 0344 0345 void KisToolRectangleBase::cancelStroke() 0346 { 0347 setMode(KisTool::HOVER_MODE); 0348 updateArea(); 0349 endShape(); 0350 } 0351 0352 QRectF KisToolRectangleBase::createRect(const QPointF &start, const QPointF &end) 0353 { 0354 QTransform t; 0355 t.translate(start.x(), start.y()); 0356 t.rotateRadians(-getRotationAngle()); 0357 t.translate(-start.x(), -start.y()); 0358 const QTransform tInv = t.inverted(); 0359 0360 const QPointF end1 = t.map(end); 0361 const QPointF newStart(qRound(start.x()), qRound(start.y())); 0362 const QPointF newEnd(qRound(end1.x()), qRound(end1.y())); 0363 const QPointF newCenter = (newStart + newEnd) / 2.0; 0364 0365 QRectF result(newStart, newEnd); 0366 result.moveCenter(tInv.map(newCenter)); 0367 0368 return result.normalized(); 0369 } 0370 0371 bool KisToolRectangleBase::showRoundCornersGUI() const 0372 { 0373 return true; 0374 } 0375 0376 void KisToolRectangleBase::paintRectangle(QPainter &gc, const QRectF &imageRect) 0377 { 0378 KIS_ASSERT_RECOVER_RETURN(canvas()); 0379 0380 const QRect viewRect = pixelToView(imageRect).toAlignedRect(); 0381 0382 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas()); 0383 KIS_SAFE_ASSERT_RECOVER_RETURN(kritaCanvas); 0384 0385 const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter(); 0386 const qreal roundCornersX = converter->effectiveZoom() * m_roundCornersX; 0387 const qreal roundCornersY = converter->effectiveZoom() * m_roundCornersY; 0388 0389 QPainterPath path; 0390 if (m_roundCornersX > 0 || m_roundCornersY > 0) { 0391 path.addRoundedRect(viewRect, 0392 roundCornersX, roundCornersY); 0393 } else { 0394 path.addRect(viewRect); 0395 } 0396 0397 getRotatedPath(path, viewRect.center(), getRotationAngle()); 0398 path.addPath(drawX(pixelToView(m_dragStart))); 0399 path.addPath(drawX(pixelToView(m_dragCenter))); 0400 paintToolOutline(&gc, path); 0401 } 0402 0403 void KisToolRectangleBase::updateArea() { 0404 const QRectF bound = createRect(m_dragStart, m_dragEnd); 0405 0406 canvas()->updateCanvas(convertToPt(bound).adjusted(-100, -100, +200, +200)); 0407 0408 emit rectangleChanged(bound); 0409 } 0410 0411 qreal KisToolRectangleBase::getRotationAngle() { 0412 return m_angle + m_angleBuffer; 0413 } 0414 0415 QPainterPath KisToolRectangleBase::drawX(const QPointF &pt) { 0416 QPainterPath path; 0417 path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0)); 0418 path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0)); 0419 return path; 0420 } 0421 0422 void KisToolRectangleBase::getRotatedPath(QPainterPath &path, const QPointF ¢er, const qreal &angle) { 0423 QTransform t; 0424 t.translate(center.x(), center.y()); 0425 t.rotateRadians(angle); 0426 t.translate(-center.x(), -center.y()); 0427 0428 path = t.map(path); 0429 }