1 /* This file is part of the KDE project
2    Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
3    Copyright 2009 Thomas Zander <zander@kde.org>
4    Copyright 2006-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
5    Copyright 2006 Robert Knight <robertknight@gmail.com>
6    Copyright 2006 Inge Wallin <inge@lysator.liu.se>
7    Copyright 1999-2002,2004 Laurent Montel <montel@kde.org>
8    Copyright 2002-2005 Ariya Hidayat <ariya@kde.org>
9    Copyright 1999-2004 David Faure <faure@kde.org>
10    Copyright 2004-2005 Meni Livne <livne@kde.org>
11    Copyright 2001-2003 Philipp Mueller <philipp.mueller@gmx.de>
12    Copyright 2002-2003 Norbert Andres <nandres@web.de>
13    Copyright 2003 Hamish Rodda <rodda@kde.org>
14    Copyright 2003 Joseph Wenninger <jowenn@kde.org>
15    Copyright 2003 Lukas Tinkl <lukas@kde.org>
16    Copyright 2000-2002 Werner Trobin <trobin@kde.org>
17    Copyright 2002 Harri Porten <porten@kde.org>
18    Copyright 2002 John Dailey <dailey@vt.edu>
19    Copyright 2002 Daniel Naber <daniel.naber@t-online.de>
20    Copyright 1999-2000 Torben Weis <weis@kde.org>
21    Copyright 1999-2000 Stephan Kulow <coolo@kde.org>
22    Copyright 2000 Bernd Wuebben <wuebben@kde.org>
23    Copyright 2000 Wilco Greven <greven@kde.org>
24    Copyright 2000 Simon Hausmann <hausmann@kde.org
25    Copyright 1999 Michael Reiher <michael.reiher@gmx.de>
26    Copyright 1999 Boris Wedl <boris.wedl@kfunigraz.ac.at>
27    Copyright 1999 Reginald Stadlbauer <reggie@kde.org>
28 
29    This library is free software; you can redistribute it and/or
30    modify it under the terms of the GNU Library General Public
31    License as published by the Free Software Foundation; either
32    version 2 of the License, or (at your option) any later version.
33 
34    This library is distributed in the hope that it will be useful,
35    but WITHOUT ANY WARRANTY; without even the implied warranty of
36    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
37    Library General Public License for more details.
38 
39    You should have received a copy of the GNU Library General Public License
40    along with this library; see the file COPYING.LIB.  If not, write to
41    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
42    Boston, MA 02110-1301, USA.
43 */
44 
45 // Local
46 #include "CanvasBase.h"
47 #include "CanvasBase_p.h"
48 
49 // std
50 #include <assert.h>
51 #include <float.h>
52 #include <stdlib.h>
53 
54 // Qt
55 #include <QApplication>
56 #include <QBuffer>
57 #include <QByteArray>
58 #include <QClipboard>
59 #include <QDragLeaveEvent>
60 #include <QDragMoveEvent>
61 #include <QDropEvent>
62 #include <QEvent>
63 #include <QFocusEvent>
64 #include <QKeyEvent>
65 #include <QLabel>
66 #include <QList>
67 #include <QMenu>
68 #include <QMouseEvent>
69 #include <QPainter>
70 #include <QPaintEvent>
71 #include <QPixmap>
72 #include <QPoint>
73 #include <QScrollBar>
74 #include <QTextStream>
75 #include <QToolTip>
76 
77 // Calligra
78 #include <KoCanvasController.h>
79 #include <KoShapeManager.h>
80 #include <KoToolProxy.h>
81 #include <KoZoomHandler.h>
82 #include <KoPointerEvent.h>
83 #include <KoUnit.h>
84 
85 // Sheets
86 #include "SheetsDebug.h"
87 #include "CellStorage.h"
88 #include "Doc.h"
89 #include "Global.h"
90 #include "HeaderWidgets.h"
91 #include "Localization.h"
92 #include "Map.h"
93 #include "RowColumnFormat.h"
94 #include "RowFormatStorage.h"
95 #include "Sheet.h"
96 #include "Util.h"
97 #include "Validity.h"
98 #include "ElapsedTime_p.h"
99 
100 // commands
101 #include "commands/CopyCommand.h"
102 #include "commands/DeleteCommand.h"
103 #include "commands/PasteCommand.h"
104 #include "commands/StyleCommand.h"
105 
106 // ui
107 #include "ui/CellView.h"
108 #include "ui/Selection.h"
109 #include "ui/SheetView.h"
110 
111 #define MIN_SIZE 10
112 
113 using namespace Calligra::Sheets;
114 
115 /****************************************************************
116  *
117  * CanvasBase
118  *
119  ****************************************************************/
120 
CanvasBase(Doc * doc)121 CanvasBase::CanvasBase(Doc* doc)
122         : KoCanvasBase(0)
123         , d(new Private)
124 {
125     d->validationInfo = 0;
126     d->offset = QPointF(0.0, 0.0);
127     d->doc = doc;
128 
129     // flake
130     d->shapeManager = new KoShapeManager(this);
131     d->toolProxy = new KoToolProxy(this);
132 }
133 
~CanvasBase()134 CanvasBase::~CanvasBase()
135 {
136     delete d->shapeManager;
137     delete d->toolProxy;
138     delete d->validationInfo;
139     delete d;
140 }
141 
doc() const142 Doc* CanvasBase::doc() const
143 {
144     return d->doc;
145 }
146 
gridSize(qreal * horizontal,qreal * vertical) const147 void CanvasBase::gridSize(qreal* horizontal, qreal* vertical) const
148 {
149     *horizontal = doc()->map()->defaultColumnFormat()->width();
150     *vertical = doc()->map()->defaultRowFormat()->height();
151 }
152 
snapToGrid() const153 bool CanvasBase::snapToGrid() const
154 {
155     return false; // FIXME
156 }
157 
addCommand(KUndo2Command * command)158 void CanvasBase::addCommand(KUndo2Command* command)
159 {
160     doc()->addCommand(command);
161 }
162 
shapeManager() const163 KoShapeManager* CanvasBase::shapeManager() const
164 {
165     return d->shapeManager;
166 }
167 
updateCanvas(const QRectF & rc)168 void CanvasBase::updateCanvas(const QRectF& rc)
169 {
170     QRectF clipRect(viewConverter()->documentToView(rc.translated(-offset())));
171     clipRect.adjust(-2, -2, 2, 2);   // Resize to fit anti-aliasing
172     update(clipRect);
173 }
174 
unit() const175 KoUnit CanvasBase::unit() const
176 {
177     return doc()->unit();
178 }
179 
toolProxy() const180 KoToolProxy* CanvasBase::toolProxy() const
181 {
182     return d->toolProxy;
183 }
184 
offset() const185 QPointF CanvasBase::offset() const
186 {
187     return d->offset;
188 }
189 
xOffset() const190 double CanvasBase::xOffset() const
191 {
192     return d->offset.x();
193 }
194 
yOffset() const195 double CanvasBase::yOffset() const
196 {
197     return d->offset.y();
198 }
199 
eventFilter(QObject * o,QEvent * e)200 bool CanvasBase::eventFilter(QObject *o, QEvent *e)
201 {
202     /* this canvas event filter acts on events sent to the line edit as well
203        as events to this filter itself.
204     */
205     if (!o || !e)
206         return true;
207     switch (e->type()) {
208     case QEvent::KeyPress: {
209         QKeyEvent * keyev = static_cast<QKeyEvent *>(e);
210         if ((keyev->key() == Qt::Key_Tab) || (keyev->key() == Qt::Key_Backtab)) {
211             keyPressed(keyev);
212             return true;
213         }
214         break;
215     }
216     case QEvent::InputMethod: {
217         //QIMEvent * imev = static_cast<QIMEvent *>(e);
218         //processIMEvent( imev );
219         //break;
220     }
221     case QEvent::ToolTip: {
222         QHelpEvent* helpEvent = static_cast<QHelpEvent*>(e);
223         showToolTip(helpEvent->pos());
224     }
225     default:
226         break;
227     }
228     return false;
229 }
230 
validateSelection()231 void CanvasBase::validateSelection()
232 {
233     register Sheet * const sheet = activeSheet();
234     if (!sheet)
235         return;
236 #if 0
237 XXX TODO
238     if (selection()->isSingular()) {
239         const Cell cell = Cell(sheet, selection()->marker()).masterCell();
240         Validity validity = cell.validity();
241         if (validity.displayValidationInformation()) {
242             const QString title = validity.titleInfo();
243             QString message = validity.messageInfo();
244             if (title.isEmpty() && message.isEmpty())
245                 return;
246 
247             if (!d->validationInfo) {
248                 d->validationInfo = new QLabel(this);
249                 QPalette palette = d->validationInfo->palette();
250                 palette.setBrush(QPalette::Window, palette.toolTipBase());
251                 palette.setBrush(QPalette::WindowText, palette.toolTipText());
252                 d->validationInfo->setPalette(palette);
253 //                 d->validationInfo->setWindowFlags(Qt::ToolTip);
254                 d->validationInfo->setFrameShape(QFrame::Box);
255                 d->validationInfo->setAlignment(Qt::AlignVCenter);
256                 d->validationInfo->setTextFormat(Qt::RichText);
257             }
258 
259             QString resultText("<html><body>");
260             if (!title.isEmpty()) {
261                 resultText += "<h2>" + title + "</h2>";
262             }
263             if (!message.isEmpty()) {
264                 message.replace(QChar('\n'), QString("<br>"));
265                 resultText += "<p>" + message + "</p>";
266             }
267             resultText += "</body></html>";
268             d->validationInfo->setText(resultText);
269 
270             const double xpos = sheet->columnPosition(cell.column()) + cell.width();
271             const double ypos = sheet->rowPosition(cell.row()) + cell.height();
272             const QPointF position = QPointF(xpos, ypos) - offset();
273             const QPoint viewPosition = viewConverter()->documentToView(position).toPoint();
274             d->validationInfo->move(/*mapToGlobal*/(viewPosition)); // Qt::ToolTip!
275             d->validationInfo->show();
276         } else {
277             delete d->validationInfo;
278             d->validationInfo = 0;
279         }
280     } else {
281         delete d->validationInfo;
282         d->validationInfo = 0;
283     }
284 #endif
285 }
286 
setDocumentOffset(const QPoint & offset)287 void CanvasBase::setDocumentOffset(const QPoint& offset)
288 {
289     const QPoint delta = offset - viewConverter()->documentToView(d->offset).toPoint();
290     d->offset = viewConverter()->viewToDocument(offset);
291 
292     ColumnHeader* ch = columnHeader();
293     if (ch) ch->scroll(-delta.x(), 0);
294     RowHeader* rh = rowHeader();
295     if (rh) rh->scroll(0, -delta.y());
296 }
297 
setDocumentSize(const QSizeF & size)298 void CanvasBase::setDocumentSize(const QSizeF& size)
299 {
300     const QSize s = viewConverter()->documentToView(size).toSize();
301     documentSizeChanged(s);
302 }
303 
mousePressed(KoPointerEvent * event)304 void CanvasBase::mousePressed(KoPointerEvent* event)
305 {
306     KoPointerEvent *const origEvent = event;
307     QPointF documentPosition;
308     if (layoutDirection() == Qt::LeftToRight) {
309         documentPosition = viewConverter()->viewToDocument(event->pos()) + offset();
310     } else {
311         const QPoint position(width() - event->x(), event->y());
312         const QPointF offset(this->offset().x(), this->offset().y());
313         documentPosition = viewConverter()->viewToDocument(position) + offset;
314         /*XXX TODO
315         debugSheets << "----------------------------";
316         debugSheets << "event->pos():" << event->pos();
317         debugSheets << "event->globalPos():" << event->globalPos();
318         debugSheets << "position:" << position;
319         debugSheets << "offset:" << offset;
320         debugSheets << "documentPosition:" << documentPosition;
321         event = new QMouseEvent(QEvent::MouseButtonPress, position, mapToGlobal(position), event->button(), event->buttons(), event->modifiers());
322         debugSheets << "newEvent->pos():" << event->pos();
323         debugSheets << "newEvent->globalPos():" << event->globalPos();*/
324     }
325 
326     event = new KoPointerEvent(event, documentPosition);
327 
328     // flake
329     if(d->toolProxy) {
330         d->toolProxy->mousePressEvent(event);
331         if (!event->isAccepted() && event->button() == Qt::RightButton) {
332             showContextMenu(origEvent->globalPos());
333             origEvent->accept();
334         }
335     }
336     if (layoutDirection() == Qt::RightToLeft) {
337         //delete event;
338     }
339     delete event;
340 }
341 
mouseReleased(KoPointerEvent * event)342 void CanvasBase::mouseReleased(KoPointerEvent* event)
343 {
344     QPointF documentPosition;
345     if (layoutDirection() == Qt::LeftToRight) {
346         documentPosition = viewConverter()->viewToDocument(event->pos()) + offset();
347     } else {
348         const QPoint position(width() - event->x(), event->y());
349         const QPointF offset(this->offset().x(), this->offset().y());
350         documentPosition = viewConverter()->viewToDocument(position) + offset;
351         // XXX TODO event = new QMouseEvent(QEvent::MouseButtonRelease, position, mapToGlobal(position), event->button(), event->buttons(), event->modifiers());
352     }
353 
354     event = new KoPointerEvent(event, documentPosition);
355 
356     // flake
357     if(d->toolProxy)
358         d->toolProxy->mouseReleaseEvent(event);
359 
360     if (layoutDirection() == Qt::RightToLeft) {
361        // delete event;
362     }
363     delete event;
364 }
365 
mouseMoved(KoPointerEvent * event)366 void CanvasBase::mouseMoved(KoPointerEvent* event)
367 {
368     QPointF documentPosition;
369     if (layoutDirection() == Qt::LeftToRight) {
370         documentPosition = viewConverter()->viewToDocument(event->pos()) + offset();
371     } else {
372         const QPoint position(width() - event->x(), event->y());
373         const QPointF offset(this->offset().x(), this->offset().y());
374         documentPosition = viewConverter()->viewToDocument(position) + offset;
375         // XXX TODO event = new QMouseEvent(QEvent::MouseMove, position, mapToGlobal(position), event->button(), event->buttons(), event->modifiers());
376     }
377 
378     event = new KoPointerEvent(event, documentPosition);
379 
380     // flake
381     if(d->toolProxy)
382         d->toolProxy->mouseMoveEvent(event);
383 
384     if (layoutDirection() == Qt::RightToLeft) {
385        // delete event;
386     }
387     delete event;
388 }
389 
mouseDoubleClicked(KoPointerEvent * event)390 void CanvasBase::mouseDoubleClicked(KoPointerEvent* event)
391 {
392     QPointF documentPosition;
393     if (layoutDirection() == Qt::LeftToRight) {
394         documentPosition = viewConverter()->viewToDocument(event->pos()) + offset();
395     } else {
396         const QPoint position(width() - event->x(), event->y());
397         const QPointF offset(this->offset().x(), this->offset().y());
398         documentPosition = viewConverter()->viewToDocument(position) + offset;
399         // XXX TODO event = new QMouseEvent(QEvent::MouseButtonDblClick, position, mapToGlobal(position), event->button(), event->buttons(), event->modifiers());
400     }
401 
402     event = new KoPointerEvent(event, documentPosition);
403 
404     // flake
405     if(d->toolProxy)
406         d->toolProxy->mouseDoubleClickEvent(event);
407 
408     if (layoutDirection() == Qt::RightToLeft) {
409        // delete event;
410     }
411     delete event;
412 }
413 
keyPressed(QKeyEvent * event)414 void CanvasBase::keyPressed(QKeyEvent* event)
415 {
416     // flake
417     if(d->toolProxy)
418         d->toolProxy->keyPressEvent(event);
419 }
420 
tabletEvent(QTabletEvent * e)421 void CanvasBase::tabletEvent(QTabletEvent *e)
422 {
423     // flake
424     if(d->toolProxy)
425         d->toolProxy->tabletEvent(e, viewConverter()->viewToDocument(e->pos() + offset()));
426 }
427 
inputMethodQuery(Qt::InputMethodQuery query) const428 QVariant CanvasBase::inputMethodQuery(Qt::InputMethodQuery query) const
429 {
430     // flake
431     return d->toolProxy ? d->toolProxy->inputMethodQuery(query, *(viewConverter())) : 0;
432 }
433 
inputMethodEvent(QInputMethodEvent * event)434 void CanvasBase::inputMethodEvent(QInputMethodEvent *event)
435 {
436     // flake
437     if(d->toolProxy)
438         d->toolProxy->inputMethodEvent(event);
439 }
440 
paint(QPainter * painter,const QRectF & painterRect)441 void CanvasBase::paint(QPainter* painter, const QRectF& painterRect)
442 {
443     if (doc()->map()->isLoading() || isViewLoading())
444         return;
445 
446     register Sheet * const sheet = activeSheet();
447     if (!sheet)
448         return;
449 
450 //     ElapsedTime et("Painting cells", ElapsedTime::PrintOnlyTime);
451 
452     painter->setClipRect(painterRect);
453     painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
454     painter->save();
455 
456     // After the scaling, the painter methods need document coordinates!
457     qreal zoomX, zoomY;
458     viewConverter()->zoom(&zoomX, &zoomY);
459     painter->scale(zoomX, zoomY);
460 
461     const bool layoutReversed = sheet->layoutDirection() == Qt::RightToLeft;
462     const QPointF offset(layoutReversed ? -this->offset().x() : this->offset().x(), this->offset().y());
463     painter->translate(-offset);
464 
465     // erase background
466     const QRectF paintRect(viewConverter()->viewToDocument(rect()).translated(offset));
467     painter->fillRect(paintRect, painter->background());
468 
469     // paint visible cells
470     const QRect visibleRect = visibleCells();
471     const QPointF topLeft(sheet->columnPosition(visibleRect.left()), sheet->rowPosition(visibleRect.top()));
472     sheetView(sheet)->setPaintCellRange(visibleRect);
473     sheetView(sheet)->paintCells(*painter, paintRect, topLeft, this);
474 
475     // flake
476     painter->restore();
477     // d->offset is the negated CanvasController offset in document coordinates.
478 //     painter.save();
479     painter->translate(-viewConverter()->documentToView(offset));
480     d->shapeManager->paint(*painter, *viewConverter(), false);
481 //     painter.restore();
482 //     const QPointF p = -viewConverter()->documentToView(this->offset());
483 //     painter.translate(p.x() /*+ width()*/, p.y());
484     painter->setRenderHint(QPainter::Antialiasing, false);
485     if(d->toolProxy)
486         d->toolProxy->paint(*painter, *viewConverter());
487 }
488 
focusIn(QFocusEvent * event)489 void CanvasBase::focusIn(QFocusEvent *event)
490 {
491     Q_UNUSED(event);
492     // If we are in editing mode, we redirect the
493     // focus to the CellEditor or ExternalEditor.
494     // Using a focus proxy does not work here, because in reference selection
495     // mode clicking on the canvas to select a reference should end up in the
496     // editor, which got the focus before. This is determined by storing the
497     // last editor with focus. It is set by the editors on getting focus by user
498     // interaction. Setting a focus proxy would always result in the proxy being
499     // the last editor, because clicking the canvas is a user interaction.
500     // This screws up <Tab> though (David)
501     selection()->emitRequestFocusEditor();
502     //XXX TODO QWidget::focusInEvent(event);
503 }
504 
dragEnter(const QMimeData * mimeData)505 bool CanvasBase::dragEnter(const QMimeData* mimeData)
506 {
507     if (mimeData->hasText() ||
508             mimeData->hasFormat("application/x-kspread-snippet")) {
509         return true;
510     }
511     return false;
512 }
513 
dragMove(const QMimeData * mimeData,const QPointF & eventPos,const QObject * source)514 bool CanvasBase::dragMove(const QMimeData* mimeData, const QPointF& eventPos, const QObject *source)
515 {
516     register Sheet * const sheet = activeSheet();
517     if (!sheet) {
518         return false;
519     }
520 
521     if (mimeData->hasText() || mimeData->hasFormat("application/x-kspread-snippet")) {
522         // acceptProposedAction
523     } else {
524         return false;
525     }
526 #if 0 // TODO Stefan: implement drag marking rectangle
527     QRect dragMarkingRect;
528     if (mimeData->hasFormat("application/x-kspread-snippet")) {
529         if (source == canvasWidget()) {
530             debugSheetsUI << "source == this";
531             dragMarkingRect = selection()->boundingRect();
532         } else {
533             debugSheetsUI << "source != this";
534             QByteArray data = mimeData->data("application/x-kspread-snippet");
535             QString errorMsg;
536             int errorLine;
537             int errorColumn;
538             QDomDocument doc;
539             if (!doc.setContent(data, false, &errorMsg, &errorLine, &errorColumn)) {
540                 // an error occurred
541                 debugSheetsUI << "CanvasBase::daragMoveEvent: an error occurred" << endl
542                 << "line: " << errorLine << " col: " << errorColumn
543                 << ' ' << errorMsg << endl;
544                 dragMarkingRect = QRect(1, 1, 1, 1);
545             } else {
546                 QDomElement root = doc.documentElement(); // "spreadsheet-snippet"
547                 dragMarkingRect = QRect(1, 1,
548                                         root.attribute("columns").toInt(),
549                                         root.attribute("rows").toInt());
550             }
551         }
552     } else { // if ( mimeData->hasText() )
553         debugSheetsUI << "has text";
554         dragMarkingRect = QRect(1, 1, 1, 1);
555     }
556 #else
557     Q_UNUSED(source);
558 #endif
559     const QPoint dragAnchor = selection()->boundingRect().topLeft();
560     double xpos = sheet->columnPosition(dragAnchor.x());
561     double ypos = sheet->rowPosition(dragAnchor.y());
562     double width  = sheet->columnFormat(dragAnchor.x())->width();
563     double height = sheet->rowFormats()->rowHeight(dragAnchor.y());
564 
565     // consider also the selection rectangle
566     const QRectF noGoArea(xpos - 1, ypos - 1, width + 3, height + 3);
567 
568     // determine the current position
569     double eventPosX;
570     if (sheet->layoutDirection() == Qt::RightToLeft) {
571         eventPosX = viewConverter()->viewToDocumentX(this->width() - eventPos.x()) + xOffset();
572     } else {
573         eventPosX = viewConverter()->viewToDocumentX(eventPos.x()) + xOffset();
574     }
575     double eventPosY = viewConverter()->viewToDocumentY(eventPos.y()) + yOffset();
576 
577     if (noGoArea.contains(QPointF(eventPosX, eventPosY))) {
578         return false;
579         // XXX TODO event->ignore(noGoArea.toRect());
580     }
581 
582 #if 0 // TODO Stefan: implement drag marking rectangle
583     // determine the cell position under the mouse
584     qreal tmp;
585     const int col = sheet->leftColumn(eventPosX, tmp);
586     const int row = sheet->topRow(eventPosY, tmp);
587     dragMarkingRect.moveTo(QPoint(col, row));
588     debugSheetsUI << "MARKING RECT =" << dragMarkingRect;
589 #endif
590     return true;
591 }
592 
dragLeave()593 void CanvasBase::dragLeave()
594 {
595 }
596 
drop(const QMimeData * mimeData,const QPointF & eventPos,const QObject * source)597 bool CanvasBase::drop(const QMimeData* mimeData, const QPointF& eventPos, const QObject *source)
598 {
599     register Sheet * const sheet = activeSheet();
600     // FIXME Sheet protection: Not all cells have to be protected.
601     if (!sheet || sheet->isProtected()) {
602         return false;
603     }
604 
605     if (!PasteCommand::supports(mimeData)) {
606         return false;
607     }
608 
609     // Do not allow dropping onto the same position.
610     const QPoint topLeft(selection()->boundingRect().topLeft());
611     const double xpos = sheet->columnPosition(topLeft.x());
612     const double ypos = sheet->rowPosition(topLeft.y());
613     const double width  = sheet->columnFormat(topLeft.x())->width();
614     const double height = sheet->rowFormats()->rowHeight(topLeft.y());
615 
616     const QRectF noGoArea(xpos - 1, ypos - 1, width + 3, height + 3);
617 
618     double ev_PosX;
619     if (sheet->layoutDirection() == Qt::RightToLeft) {
620         ev_PosX = viewConverter()->viewToDocumentX(this->width() - eventPos.x()) + xOffset();
621     } else {
622         ev_PosX = viewConverter()->viewToDocumentX(eventPos.x()) + xOffset();
623     }
624     double ev_PosY = viewConverter()->viewToDocumentY(eventPos.y()) + yOffset();
625 
626     if (noGoArea.contains(QPointF(ev_PosX, ev_PosY))) {
627         return false;
628     }
629 
630     // The destination cell location.
631     qreal tmp;
632     const int col = sheet->leftColumn(ev_PosX, tmp);
633     const int row = sheet->topRow(ev_PosY, tmp);
634 
635     PasteCommand *const command = new PasteCommand();
636     command->setSheet(sheet);
637     command->add(Region(col, row, 1, 1, sheet));
638     command->setMimeData(mimeData);
639 
640     if (source == canvasWidget()) {
641         DeleteCommand *const deleteCommand = new DeleteCommand(command);
642         deleteCommand->setSheet(sheet);
643         deleteCommand->add(*selection()); // selection is still, where the drag started
644         deleteCommand->setRegisterUndo(false);
645     }
646 
647     command->execute();
648 
649     // Select the pasted cells
650     const int columns = selection()->boundingRect().width();
651     const int rows = selection()->boundingRect().height();
652     selection()->initialize(QRect(col, row, columns, rows), sheet);
653 
654     return true;
655 }
656 
viewToCellCoordinates(const QRectF & viewRect) const657 QRect CanvasBase::viewToCellCoordinates(const QRectF& viewRect) const
658 {
659     register Sheet * const sheet = activeSheet();
660     if (!sheet)
661         return QRect();
662 
663     // NOTE Stefan: Do not consider the layout direction in this case.
664     const QRectF rect = viewConverter()->viewToDocument(viewRect.normalized()).translated(offset());
665 
666     qreal tmp;
667     const int left = sheet->leftColumn(rect.left(), tmp);
668     const int right = sheet->rightColumn(rect.right());
669     const int top = sheet->topRow(rect.top(), tmp);
670     const int bottom = sheet->bottomRow(rect.bottom());
671 
672     return QRect(left, top, right - left + 1, bottom - top + 1);
673 }
674 
visibleCells() const675 QRect CanvasBase::visibleCells() const
676 {
677     return viewToCellCoordinates(rect());
678 }
679 
680 //---------------------------------------------
681 //
682 // Drawing Engine
683 //
684 //---------------------------------------------
685 
cellCoordinatesToView(const QRect & cellRange) const686 QRectF CanvasBase::cellCoordinatesToView(const QRect& cellRange) const
687 {
688     register Sheet * const sheet = activeSheet();
689     if (!sheet)
690         return QRectF();
691 
692     QRectF rect = sheet->cellCoordinatesToDocument(cellRange);
693     // apply scrolling offset
694     rect.translate(-xOffset(), -yOffset());
695     // convert it to view coordinates
696     rect = viewConverter()->documentToView(rect);
697     // apply layout direction
698     if (sheet->layoutDirection() == Qt::RightToLeft) {
699         const double left = rect.left();
700         const double right = rect.right();
701         rect.setLeft(width() - right);
702         rect.setRight(width() - left);
703     }
704     return rect;
705 }
706 
showToolTip(const QPoint & p)707 void CanvasBase::showToolTip(const QPoint& p)
708 {
709     register Sheet * const sheet = activeSheet();
710     if (!sheet)
711         return;
712     SheetView * const sheetView = this->sheetView(sheet);
713 
714     // Over which cell is the mouse ?
715     qreal ypos, xpos;
716     qreal dwidth = viewConverter()->viewToDocumentX(width());
717     int col;
718     if (sheet->layoutDirection() == Qt::RightToLeft)
719         col = sheet->leftColumn((dwidth - viewConverter()->viewToDocumentX(p.x()) +
720                                  xOffset()), xpos);
721     else
722         col = sheet->leftColumn((viewConverter()->viewToDocumentX(p.x()) +
723                                  xOffset()), xpos);
724 
725 
726     int row = sheet->topRow((viewConverter()->viewToDocumentY(p.y()) +
727                              yOffset()), ypos);
728 
729     Cell cell = Cell(sheet, col, row).masterCell();
730     const CellView& baseCellView = sheetView->cellView(cell.column(), cell.row());
731     const bool baseIsObscured = sheetView->isObscured(cell.cellPosition());
732     const QPoint cellPos = baseIsObscured ? sheetView->obscuringCell(cell.cellPosition())
733                                           : cell.cellPosition();
734     const CellView& cellView = baseIsObscured
735                ? sheetView->cellView(cellPos)
736                : baseCellView;
737     if (sheetView->isObscured(cellPos)) {
738         cell = Cell(sheet, sheetView->obscuringCell(cellPos));
739     }
740 
741     // displayed tool tip, which has the following priorities:
742     //  - cell content if the cell dimension is too small
743     //  - cell comment
744     //  - hyperlink
745     // Ensure that it is plain text.
746     // Not funny if (intentional or not) <a> appears as hyperlink.
747     QString tipText;
748     // If cell is too small, show the content
749     if (!cellView.dimensionFits())
750         tipText = cell.displayText().replace('<', "&lt;");
751 
752     // Show hyperlink, if any
753     if (tipText.isEmpty())
754         tipText = cell.link().replace('<', "&lt;");
755 
756     // Nothing to display, bail out
757     if (tipText.isEmpty() && cell.comment().isEmpty())
758         return;
759 
760     // Cut if the tip is ridiculously long
761     const int maxLen = 256;
762     if (tipText.length() > maxLen)
763         tipText = tipText.left(maxLen).append("...");
764 
765     // Determine position and width of the current cell.
766     const double cellWidth = cellView.cellWidth();
767     const double cellHeight = cellView.cellHeight();
768 
769     // Get the cell dimensions
770     QRect cellRect;
771     bool insideCellRect = false;
772     if (sheet->layoutDirection() == Qt::RightToLeft) {
773         const QRectF rect(dwidth - cellWidth - xpos + xOffset(), ypos - yOffset(), cellWidth, cellHeight);
774         cellRect = viewConverter()->documentToView(rect).toRect();
775         insideCellRect = cellRect.contains(p);
776     } else {
777         QRectF rect(xpos - xOffset(), ypos - yOffset(), cellWidth, cellHeight);
778         cellRect = viewConverter()->documentToView(rect).toRect();
779         insideCellRect = cellRect.contains(p);
780     }
781 
782     // No use if mouse is somewhere else
783     if (!insideCellRect)
784         return;
785 
786     // Show comment, if any.
787     if (tipText.isEmpty())
788         tipText = cell.comment().replace('<', "&lt;");
789     else if (!cell.comment().isEmpty())
790         tipText += "</p><h4>" + i18n("Comment:") + "</h4><p>" + cell.comment().replace('<', "&lt;");
791 
792     // Now we show the tip
793     QToolTip::showText(mapToGlobal(cellRect.bottomRight()),
794                        "<p>" + tipText.replace('\n', "<br>") + "</p>");
795                        // TODO XXX this, cellRect.translated(-mapToGlobal(cellRect.topLeft())));
796 }
797 
updateInputMethodInfo()798 void CanvasBase::updateInputMethodInfo()
799 {
800     updateMicroFocus();
801 }
802 
viewConverter() const803 KoViewConverter* CanvasBase::viewConverter() const
804 {
805     return zoomHandler();
806 }
807