File indexing completed on 2024-05-12 15:56:39

0001 /* This file is part of the KDE project
0002  *
0003  * SPDX-FileCopyrightText: 2006-2007, 2009 Thomas Zander <zander@kde.org>
0004  * SPDX-FileCopyrightText: 2006 Thorsten Zachmann <zachmann@kde.org>
0005  * SPDX-FileCopyrightText: 2007-2010 Boudewijn Rempt <boud@valdyas.org>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.0-or-later
0008  */
0009 
0010 #include "KoCanvasControllerWidgetViewport_p.h"
0011 
0012 #include <limits.h>
0013 #include <stdlib.h>
0014 
0015 #include <QPainter>
0016 #include <QDragEnterEvent>
0017 #include <QMimeData>
0018 
0019 #include <KoProperties.h>
0020 
0021 #include <FlakeDebug.h>
0022 
0023 #include "KoShape.h"
0024 #include "KoShape_p.h"
0025 #include "KoShapeFactoryBase.h" // for the SHAPE mimetypes
0026 #include "KoShapeRegistry.h"
0027 #include "KoShapeController.h"
0028 #include "KoShapeManager.h"
0029 #include "KoSelection.h"
0030 #include "KoCanvasBase.h"
0031 #include "KoShapeLayer.h"
0032 #include "KoToolProxy.h"
0033 #include "KoCanvasControllerWidget.h"
0034 #include "KoViewConverter.h"
0035 #include "KoSvgPaste.h"
0036 
0037 // ********** Viewport **********
0038 Viewport::Viewport(KoCanvasControllerWidget *parent)
0039     : QWidget(parent)
0040     , m_draggedShape(0)
0041     , m_canvas(0)
0042     , m_documentOffset(QPoint(0, 0))
0043     , m_margin(0)
0044 {
0045     setAutoFillBackground(true);
0046     setAcceptDrops(true);
0047     setMouseTracking(true);
0048     m_parent = parent;
0049 }
0050 
0051 void Viewport::setCanvas(QWidget *canvas)
0052 {
0053     if (m_canvas) {
0054         m_canvas->hide();
0055         delete m_canvas;
0056     }
0057     m_canvas = canvas;
0058     if (!canvas) return;
0059     m_canvas->setParent(this);
0060     m_canvas->show();
0061     if (!m_canvas->minimumSize().isNull()) {
0062         m_documentSize = m_canvas->minimumSize();
0063     }
0064     resetLayout();
0065 }
0066 
0067 void Viewport::setDocumentSize(const QSizeF &size)
0068 {
0069     m_documentSize = size;
0070     resetLayout();
0071 }
0072 
0073 void Viewport::documentOffsetMoved(const QPoint &pt)
0074 {
0075     m_documentOffset = pt;
0076     resetLayout();
0077 }
0078 
0079 void Viewport::handleDragEnterEvent(QDragEnterEvent *event)
0080 {
0081     // if not a canvas set then ignore this, makes it possible to assume
0082     // we have a canvas in all the support methods.
0083     if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) {
0084         event->ignore();
0085         return;
0086     }
0087 
0088     delete m_draggedShape;
0089     m_draggedShape = 0;
0090 
0091     // only allow dropping when active layer is editable
0092     KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
0093     KoShapeLayer *activeLayer = selection->activeLayer();
0094     if (activeLayer && (!activeLayer->isShapeEditable() || activeLayer->isGeometryProtected())) {
0095         event->ignore();
0096         return;
0097     }
0098 
0099     const QMimeData *data = event->mimeData();
0100 
0101     if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) ||
0102             data->hasFormat(SHAPEID_MIMETYPE) ||
0103             data->hasFormat("image/svg+xml"))
0104     {
0105         if (data->hasFormat("image/svg+xml")) {
0106             KoCanvasBase *canvas = m_parent->canvas();
0107             QSizeF fragmentSize;
0108 
0109             QList<KoShape*> shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"),
0110                                                                      canvas->shapeController()->documentRectInPixels(),
0111                                                                      canvas->shapeController()->pixelsPerInch(),
0112                                                                      &fragmentSize);
0113 
0114             if (!shapes.isEmpty()) {
0115                 m_draggedShape = shapes[0];
0116             }
0117         }
0118         else {
0119             QByteArray itemData;
0120             bool isTemplate = true;
0121 
0122             if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) {
0123                 itemData = data->data(SHAPETEMPLATE_MIMETYPE);
0124             }
0125             else if (data->hasFormat(SHAPEID_MIMETYPE)) {
0126                 isTemplate = false;
0127                 itemData = data->data(SHAPEID_MIMETYPE);
0128             }
0129 
0130 
0131             QDataStream dataStream(&itemData, QIODevice::ReadOnly);
0132             QString id;
0133             dataStream >> id;
0134             QString properties;
0135             if (isTemplate)
0136                 dataStream >> properties;
0137 
0138             // and finally, there is a point.
0139             QPointF offset;
0140             dataStream >> offset;
0141 
0142             // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy
0143             // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave)
0144             KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id);
0145             if (! factory) {
0146                 warnFlake << "Application requested a shape that is not registered '" <<
0147                              id << "', Ignoring";
0148                 event->ignore();
0149                 return;
0150             }
0151             if (isTemplate) {
0152                 KoProperties props;
0153                 props.load(properties);
0154                 m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager());
0155             }
0156             else {
0157                 m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager());
0158             }
0159 
0160             if (m_draggedShape->shapeId().isEmpty()) {
0161                 m_draggedShape->setShapeId(factory->id());
0162             }
0163         }
0164 
0165         event->setDropAction(Qt::CopyAction);
0166         event->accept();
0167 
0168         Q_ASSERT(m_draggedShape);
0169         if (!m_draggedShape) return;
0170 
0171         // calculate maximum existing shape zIndex
0172 
0173         int pasteZIndex = 0;
0174 
0175         {
0176             QList<KoShape*> allShapes = m_parent->canvas()->shapeManager()->topLevelShapes();
0177 
0178             if (!allShapes.isEmpty()) {
0179                 std::sort(allShapes.begin(), allShapes.end(), KoShape::compareShapeZIndex);
0180                 pasteZIndex = qMin(int(KoShape::maxZIndex), allShapes.last()->zIndex() + 1);
0181             }
0182         }
0183 
0184         m_draggedShape->setZIndex(pasteZIndex);
0185         m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
0186 
0187         m_parent->canvas()->shapeManager()->addShape(m_draggedShape);
0188     } else {
0189         event->ignore();
0190     }
0191 }
0192 
0193 void Viewport::handleDropEvent(QDropEvent *event)
0194 {
0195     if (!m_draggedShape) {
0196         m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos()));
0197         return;
0198     }
0199 
0200     repaint(m_draggedShape);
0201     m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc.
0202 
0203     m_draggedShape->setPosition(QPointF(0, 0));  // always save position.
0204     QPointF newPos = correctPosition(event->pos());
0205     m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document.
0206     m_draggedShape->setAbsolutePosition(newPos);
0207 
0208 
0209     KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0);
0210 
0211     if (cmd) {
0212         m_parent->canvas()->addCommand(cmd);
0213         KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
0214 
0215         // repaint selection before selecting newly create shape
0216         Q_FOREACH (KoShape * shape, selection->selectedShapes()) {
0217             shape->update();
0218         }
0219 
0220         selection->deselectAll();
0221         selection->select(m_draggedShape);
0222     } else {
0223 
0224         delete m_draggedShape;
0225     }
0226 
0227     m_draggedShape = 0;
0228 }
0229 
0230 QPointF Viewport::correctPosition(const QPoint &point) const
0231 {
0232     QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
0233     Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
0234     QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y());
0235     correctedPos += m_documentOffset;
0236     return m_parent->canvas()->viewToDocument(correctedPos);
0237 }
0238 
0239 void Viewport::handleDragMoveEvent(QDragMoveEvent *event)
0240 {
0241     if (!m_draggedShape) {
0242         m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos()));
0243         return;
0244     }
0245 
0246     m_draggedShape->update();
0247     repaint(m_draggedShape);
0248     m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
0249     m_draggedShape->update();
0250     repaint(m_draggedShape);
0251 }
0252 
0253 void Viewport::repaint(KoShape *shape)
0254 {
0255     QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect();
0256     QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
0257     Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
0258     rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x());
0259     rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y());
0260     rect.adjust(-2, -2, 2, 2); // adjust for antialias
0261     update(rect);
0262 }
0263 
0264 void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event)
0265 {
0266     if (m_draggedShape) {
0267         repaint(m_draggedShape);
0268         m_parent->canvas()->shapeManager()->remove(m_draggedShape);
0269         delete m_draggedShape;
0270         m_draggedShape = 0;
0271     } else {
0272         m_parent->canvas()->toolProxy()->dragLeaveEvent(event);
0273     }
0274 }
0275 
0276 void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event)
0277 {
0278     Q_UNUSED(event);
0279     if (m_draggedShape) {
0280         const KoViewConverter *vc = m_parent->canvas()->viewConverter();
0281 
0282         painter.save();
0283         QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
0284         Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
0285         painter.translate(canvasWidget->x() - m_documentOffset.x(),
0286                           canvasWidget->y() - m_documentOffset.y());
0287         QPointF offset = vc->documentToView(m_draggedShape->position());
0288         painter.setOpacity(0.6);
0289         painter.translate(offset.x(), offset.y());
0290         painter.setRenderHint(QPainter::Antialiasing);
0291         painter.setTransform(vc->documentToView());
0292         m_draggedShape->paint(painter);
0293         painter.restore();
0294     }
0295 }
0296 
0297 void Viewport::resetLayout()
0298 {
0299     // Determine the area we have to show
0300     QRect viewRect(m_documentOffset, size());
0301 
0302     const int viewH = viewRect.height();
0303     const int viewW = viewRect.width();
0304 
0305     if (m_canvas) {
0306         QRect geom = QRect(0, 0, viewW, viewH);
0307         if (m_canvas->geometry() != geom) {
0308             m_canvas->setGeometry(geom);
0309             m_canvas->update();
0310         }
0311     }
0312     emit sizeChanged();
0313 #if 0
0314     debugFlake <<"View port geom:" << geometry();
0315     if (m_canvas)
0316         debugFlake <<"Canvas widget geom:" << m_canvas->geometry();
0317 #endif
0318 }