1 /* This file is part of the KDE project
2  *
3  * Copyright (C) 2006-2007, 2009 Thomas Zander <zander@kde.org>
4  * Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
5  * Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #include "KoCanvasControllerWidgetViewport_p.h"
24 #include "KoShape.h"
25 #include "KoShape_p.h"
26 #include "KoShapeFactoryBase.h" // for the SHAPE mimetypes
27 #include "KoShapeRegistry.h"
28 #include "KoShapeController.h"
29 #include "KoShapeManager.h"
30 #include "KoSelection.h"
31 #include "KoCanvasBase.h"
32 #include "KoShapeLayer.h"
33 #include "KoShapePaste.h"
34 #include "KoShapePaintingContext.h"
35 #include "KoToolProxy.h"
36 
37 #include <KoProperties.h>
38 
39 #include <FlakeDebug.h>
40 
41 #include <QPainter>
42 #include <QDragEnterEvent>
43 
44 #include <limits.h>
45 #include <stdlib.h>
46 
47 // ********** Viewport **********
Viewport(KoCanvasControllerWidget * parent)48 Viewport::Viewport(KoCanvasControllerWidget *parent)
49         : QWidget(parent)
50         , m_draggedShape(0)
51         , m_drawShadow(false)
52         , m_canvas(0)
53         , m_documentOffset(QPoint(0, 0))
54         , m_margin(0)
55 {
56     setAutoFillBackground(true);
57     setAcceptDrops(true);
58     setMouseTracking(true);
59     m_parent = parent;
60 }
61 
setCanvas(QWidget * canvas)62 void Viewport::setCanvas(QWidget *canvas)
63 {
64     if (m_canvas) {
65         m_canvas->hide();
66         delete m_canvas;
67     }
68     m_canvas = canvas;
69     if (!canvas) return;
70     m_canvas->setParent(this);
71     m_canvas->show();
72     if (!m_canvas->minimumSize().isNull()) {
73         m_documentSize = m_canvas->minimumSize();
74     }
75     resetLayout();
76 }
77 
setDocumentSize(const QSize & size)78 void Viewport::setDocumentSize(const QSize &size)
79 {
80     m_documentSize = size;
81     resetLayout();
82 }
83 
documentOffsetMoved(const QPoint & pt)84 void Viewport::documentOffsetMoved(const QPoint &pt)
85 {
86     m_documentOffset = pt;
87     resetLayout();
88 }
89 
setDrawShadow(bool drawShadow)90 void Viewport::setDrawShadow(bool drawShadow)
91 {
92     m_drawShadow = drawShadow;
93 }
94 
95 
handleDragEnterEvent(QDragEnterEvent * event)96 void Viewport::handleDragEnterEvent(QDragEnterEvent *event)
97 {
98     // if not a canvas set then ignore this, makes it possible to assume
99     // we have a canvas in all the support methods.
100     if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget()))
101         return;
102 
103     // only allow dropping when active layer is editable
104     KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
105     KoShapeLayer *activeLayer = selection->activeLayer();
106     if (activeLayer && (!activeLayer->isEditable() || activeLayer->isGeometryProtected()))
107         return;
108 
109     const QMimeData *data = event->mimeData();
110     if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) ||
111             data->hasFormat(SHAPEID_MIMETYPE)) {
112         QByteArray itemData;
113         bool isTemplate = true;
114         if (data->hasFormat(SHAPETEMPLATE_MIMETYPE))
115             itemData = data->data(SHAPETEMPLATE_MIMETYPE);
116         else {
117             isTemplate = false;
118             itemData = data->data(SHAPEID_MIMETYPE);
119         }
120         QDataStream dataStream(&itemData, QIODevice::ReadOnly);
121         QString id;
122         dataStream >> id;
123         QString properties;
124         if (isTemplate)
125             dataStream >> properties;
126 
127         // and finally, there is a point.
128         QPointF offset;
129         dataStream >> offset;
130 
131         // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy
132         // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave)
133         KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id);
134         if (! factory) {
135             warnFlake << "Application requested a shape that is not registered '" <<
136             id << "', Ignoring";
137             event->ignore();
138             return;
139         }
140         event->setDropAction(Qt::CopyAction);
141         event->accept();
142 
143         if (isTemplate) {
144             KoProperties props;
145             props.load(properties);
146             m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager());
147         } else
148             m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager());
149 
150         Q_ASSERT(m_draggedShape);
151         if (!m_draggedShape) return;
152 
153         if (m_draggedShape->shapeId().isEmpty())
154             m_draggedShape->setShapeId(factory->id());
155         m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex);
156         m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
157 
158         m_parent->canvas()->shapeManager()->addShape(m_draggedShape);
159     }
160     else if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) {
161         KoShapeManager *sm = m_parent->canvas()->shapeManager();
162         KoShapePaste paste(m_parent->canvas(), sm->selection()->activeLayer());
163         if (paste.paste(KoOdf::Text, data)) {
164             QList<KoShape *> shapes = paste.pastedShapes();
165             if (shapes.count() == 1) {
166                 m_draggedShape = shapes.first();
167                 m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex);
168                 event->setDropAction(Qt::CopyAction);
169             }
170             event->accept();
171         }
172     } else {
173         event->ignore();
174     }
175 }
176 
handleDropEvent(QDropEvent * event)177 void Viewport::handleDropEvent(QDropEvent *event)
178 {
179     if (!m_draggedShape) {
180         m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos()));
181         return;
182     }
183 
184     repaint(m_draggedShape);
185     m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc.
186 
187     m_draggedShape->setPosition(QPointF(0, 0));  // always save position.
188     QPointF newPos = correctPosition(event->pos());
189     m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document.
190     m_draggedShape->setAbsolutePosition(newPos);
191     KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape);
192     if (cmd) {
193         m_parent->canvas()->addCommand(cmd);
194         KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
195 
196         // repaint selection before selecting newly create shape
197         foreach(KoShape * shape, selection->selectedShapes())
198             shape->update();
199 
200         selection->deselectAll();
201         selection->select(m_draggedShape);
202     } else
203         delete m_draggedShape;
204 
205     m_draggedShape = 0;
206 }
207 
correctPosition(const QPoint & point) const208 QPointF Viewport::correctPosition(const QPoint &point) const
209 {
210     QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
211     Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
212     QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y());
213     correctedPos += m_documentOffset;
214     return m_parent->canvas()->viewToDocument(correctedPos);
215 }
216 
handleDragMoveEvent(QDragMoveEvent * event)217 void Viewport::handleDragMoveEvent(QDragMoveEvent *event)
218 {
219     if (!m_draggedShape) {
220         m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos()));
221         return;
222     }
223 
224     m_draggedShape->update();
225     repaint(m_draggedShape);
226     m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
227     m_draggedShape->update();
228     repaint(m_draggedShape);
229 }
230 
repaint(KoShape * shape)231 void Viewport::repaint(KoShape *shape)
232 {
233     QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect();
234     QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
235     Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
236     rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x());
237     rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y());
238     rect.adjust(-2, -2, 2, 2); // adjust for antialias
239     update(rect);
240 }
241 
handleDragLeaveEvent(QDragLeaveEvent * event)242 void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event)
243 {
244     if (m_draggedShape) {
245         repaint(m_draggedShape);
246         m_parent->canvas()->shapeManager()->remove(m_draggedShape);
247         delete m_draggedShape;
248         m_draggedShape = 0;
249     } else {
250         m_parent->canvas()->toolProxy()->dragLeaveEvent(event);
251     }
252 }
253 
handlePaintEvent(QPainter & painter,QPaintEvent * event)254 void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event)
255 {
256     Q_UNUSED(event);
257     // Draw the shadow around the canvas.
258     if (m_parent->canvas() && m_parent->canvas()->canvasWidget() && m_drawShadow) {
259         QWidget *canvas = m_parent->canvas()->canvasWidget();
260         painter.setPen(QPen(Qt::black, 0));
261         QRect rect(canvas->x(), canvas->y(), canvas->width(), canvas->height());
262         rect.adjust(-1, -1, 0, 0);
263         painter.drawRect(rect);
264         painter.drawLine(rect.right() + 2, rect.top() + 2, rect.right() + 2, rect.bottom() + 2);
265         painter.drawLine(rect.left() + 2, rect.bottom() + 2, rect.right() + 2, rect.bottom() + 2);
266     }
267     if (m_draggedShape) {
268         const KoViewConverter *vc = m_parent->canvas()->viewConverter();
269 
270         painter.save();
271         QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
272         Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
273         painter.translate(canvasWidget->x() - m_documentOffset.x(),
274                 canvasWidget->y() - m_documentOffset.y());
275         QPointF offset = vc->documentToView(m_draggedShape->position());
276         painter.setOpacity(0.6);
277         painter.translate(offset.x(), offset.y());
278         painter.setRenderHint(QPainter::Antialiasing);
279         KoShapePaintingContext paintContext; //FIXME
280         m_draggedShape->paint(painter, *vc, paintContext);
281         painter.restore();
282     }
283 }
284 
resetLayout()285 void Viewport::resetLayout()
286 {
287     // Determine the area we have to show
288     QRect viewRect(m_documentOffset, size());
289 
290     const int viewH = viewRect.height();
291     const int viewW = viewRect.width();
292 
293     const int docH = m_documentSize.height();
294     const int docW = m_documentSize.width();
295 
296     int moveX = 0;
297     int moveY = 0;
298 
299     int resizeW = viewW;
300     int resizeH = viewH;
301 
302 //     debugFlake <<"viewH:" << viewH << endl
303 //              << "docH: " << docH << endl
304 //              << "viewW: " << viewW << endl
305 //              << "docW: " << docW << endl;
306 
307     if (viewH == docH && viewW == docW) {
308         // Do nothing
309         resizeW = docW;
310         resizeH = docH;
311     } else if (viewH > docH && viewW > docW) {
312         // Show entire canvas centered
313         moveX = (viewW - docW) / 2;
314         moveY = (viewH - docH) / 2;
315         resizeW = docW;
316         resizeH = docH;
317     } else  if (viewW > docW) {
318         // Center canvas horizontally
319         moveX = (viewW - docW) / 2;
320         resizeW = docW;
321 
322         int marginTop = m_margin - m_documentOffset.y();
323         int marginBottom = viewH  - (m_documentSize.height() - m_documentOffset.y());
324 
325         if (marginTop > 0) moveY = marginTop;
326         if (marginTop > 0) resizeH = viewH - marginTop;
327         if (marginBottom > 0) resizeH = viewH - marginBottom;
328     } else  if (viewH > docH) {
329         // Center canvas vertically
330         moveY = (viewH - docH) / 2;
331         resizeH = docH;
332 
333         int marginLeft = m_margin - m_documentOffset.x();
334         int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x());
335 
336         if (marginLeft > 0) moveX = marginLeft;
337         if (marginLeft > 0) resizeW = viewW - marginLeft;
338         if (marginRight > 0) resizeW = viewW - marginRight;
339     } else {
340         // Take care of the margin around the canvas
341         int marginTop = m_margin - m_documentOffset.y();
342         int marginLeft = m_margin - m_documentOffset.x();
343         int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x());
344         int marginBottom = viewH  - (m_documentSize.height() - m_documentOffset.y());
345 
346         if (marginTop > 0) moveY = marginTop;
347         if (marginLeft > 0) moveX = marginLeft;
348 
349         if (marginTop > 0) resizeH = viewH - marginTop;
350         if (marginLeft > 0) resizeW = viewW - marginLeft;
351         if (marginRight > 0) resizeW = viewW - marginRight;
352         if (marginBottom > 0) resizeH = viewH - marginBottom;
353     }
354     if (m_parent->canvasMode() == KoCanvasController::AlignTop) {
355         // have up to m_margin pixels at top.
356         moveY = qMin(m_margin, moveY);
357     }
358     if (m_canvas) {
359         QRect geom;
360         if (m_parent->canvasMode() == KoCanvasController::Infinite)
361             geom = QRect(0, 0, viewW, viewH);
362         else
363             geom = QRect(moveX, moveY, resizeW, resizeH);
364         if (m_canvas->geometry() != geom) {
365             m_canvas->setGeometry(geom);
366             m_canvas->update();
367         }
368     }
369     if (m_drawShadow) {
370         update();
371     }
372 
373     emit sizeChanged();
374 #if 0
375      debugFlake <<"View port geom:" << geometry();
376      if (m_canvas)
377         debugFlake <<"Canvas widget geom:" << m_canvas->geometry();
378 #endif
379 }
380