File indexing completed on 2024-04-28 11:21:05

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com>
0004 */
0005 
0006 #include "worksheettextitem.h"
0007 #include "worksheet.h"
0008 #include "worksheetentry.h"
0009 #include "lib/renderer.h"
0010 #include "worksheetcursor.h"
0011 #include "worksheetview.h"
0012 
0013 #include <QApplication>
0014 #include <QClipboard>
0015 #include <QDebug>
0016 #include <QMimeData>
0017 #include <QAbstractTextDocumentLayout>
0018 #include <QTextBlock>
0019 #include <QTextLine>
0020 #include <QGraphicsSceneResizeEvent>
0021 #include <QPainter>
0022 
0023 #include <QAction>
0024 #include <QColorDialog>
0025 #include <KColorScheme>
0026 #include <QFontDatabase>
0027 
0028 WorksheetTextItem::WorksheetTextItem(WorksheetEntry* parent, Qt::TextInteractionFlags ti)
0029     : QGraphicsTextItem(parent)
0030 {
0031     setTextInteractionFlags(ti);
0032     if (ti & Qt::TextEditable) {
0033         setCursor(Qt::IBeamCursor);
0034         connect(this, &WorksheetTextItem::sizeChanged, parent, &WorksheetEntry::recalculateSize);
0035     }
0036 
0037     m_size = document()->size();;
0038     setAcceptDrops(true);
0039     setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0040 
0041     connect(this, &QGraphicsTextItem::linkHovered, [=](const QString& link) {
0042         if (!link.isEmpty())
0043             QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
0044         else
0045             QApplication::restoreOverrideCursor();
0046     });
0047 
0048     connect(document(), &QTextDocument::contentsChanged, this, &WorksheetTextItem::testSize);
0049     connect(document(), &QTextDocument::undoAvailable, this, &WorksheetTextItem::undoAvailable);
0050     connect(document(), &QTextDocument::redoAvailable, this, &WorksheetTextItem::redoAvailable);
0051     connect(this, &WorksheetTextItem::menuCreated, parent, &WorksheetEntry::populateMenu, Qt::DirectConnection);
0052     connect(this, &WorksheetTextItem::deleteEntry, parent, &WorksheetEntry::startRemoving);
0053     connect(this, &WorksheetTextItem::cursorPositionChanged, this, &WorksheetTextItem::updateRichTextActions);
0054 }
0055 
0056 WorksheetTextItem::~WorksheetTextItem()
0057 {
0058     if (worksheet() && this == worksheet()->lastFocusedTextItem())
0059         worksheet()->updateFocusedTextItem(nullptr);
0060 
0061     if (worksheet())
0062         worksheet()->removeRequestedWidth(this);
0063 }
0064 
0065 int WorksheetTextItem::type() const
0066 {
0067     return Type;
0068 }
0069 
0070 /*
0071 void WorksheetTextItem::setHeight()
0072 {
0073     m_height = height();
0074 }
0075 */
0076 
0077 void WorksheetTextItem::testSize()
0078 {
0079     qreal h = document()->size().height();
0080     if (h != m_size.height()) {
0081         emit sizeChanged();
0082         m_size.setHeight(h);
0083     }
0084 
0085     qreal w = document()->size().width();
0086     if (w != m_size.width()) {
0087         m_size.setWidth(w);
0088         emit sizeChanged();
0089 
0090         qreal newWidth = scenePos().x() + m_size.width() - 10;
0091         worksheet()->setRequestedWidth(this, newWidth);
0092     }
0093 }
0094 
0095 qreal WorksheetTextItem::setGeometry(qreal x, qreal y, qreal w, bool centered)
0096 {
0097     if (m_size.width() < w && centered)
0098         setPos(x + w/2 - m_size.width()/2, y);
0099     else
0100         setPos(x,y);
0101 
0102     // Strange: if I use the same logic as for ImageItem (with scenePos.x() + width)
0103     // Cantor always have scrollbar for a few pixels
0104     // So I always subtract the few pixels
0105     setTextWidth(w);
0106     m_size = document()->size();
0107 
0108     qreal newWidth = scenePos().x() + m_size.width() - 10;
0109     worksheet()->setRequestedWidth(this, newWidth);
0110 
0111     return m_size.height();
0112 }
0113 
0114 void WorksheetTextItem::populateMenu(QMenu* menu, QPointF pos)
0115 {
0116     auto* cut = KStandardAction::cut(this, &WorksheetTextItem::cut, menu);
0117     auto* copy = KStandardAction::copy(this, &WorksheetTextItem::copy, menu);
0118     auto* paste = KStandardAction::paste(this, &WorksheetTextItem::paste, menu);
0119     if (!textCursor().hasSelection()) {
0120         cut->setEnabled(false);
0121         copy->setEnabled(false);
0122     }
0123     if (QApplication::clipboard()->text().isEmpty()) {
0124         paste->setEnabled(false);
0125     }
0126     bool actionAdded = false;
0127     if (isEditable()) {
0128         menu->addAction(cut);
0129         actionAdded = true;
0130     }
0131     if (!m_itemDragable && (flags() & Qt::TextSelectableByMouse)) {
0132         menu->addAction(copy);
0133         actionAdded = true;
0134     }
0135     if (isEditable()) {
0136         menu->addAction(paste);
0137         actionAdded = true;
0138     }
0139     if (actionAdded)
0140         menu->addSeparator();
0141 
0142     emit menuCreated(menu, mapToParent(pos));
0143 }
0144 
0145 QKeyEvent* WorksheetTextItem::eventForStandardAction(KStandardAction::StandardAction actionID)
0146 {
0147     // there must be a better way to get the shortcut...
0148     auto* action = KStandardAction::create(actionID, this, &WorksheetTextItem::copy, this);
0149     QKeySequence keySeq = action->shortcut();
0150     // we do not support key sequences with multiple keys here
0151     int code = keySeq[0];
0152     const int ModMask = Qt::ShiftModifier | Qt::ControlModifier |
0153         Qt::AltModifier | Qt::MetaModifier;
0154     const int KeyMask = ~ModMask;
0155     QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, code & KeyMask,
0156                                      QFlags<Qt::KeyboardModifier>(code & ModMask));
0157     delete action;
0158     return event;
0159 }
0160 
0161 void WorksheetTextItem::cut()
0162 {
0163     if (richTextEnabled()) {
0164         QKeyEvent* event = eventForStandardAction(KStandardAction::Cut);
0165         QApplication::sendEvent(worksheet(), event);
0166         delete event;
0167     } else {
0168         copy();
0169         textCursor().removeSelectedText();
0170     }
0171 }
0172 
0173 void WorksheetTextItem::paste()
0174 {
0175     if (richTextEnabled()) {
0176         QKeyEvent* event = eventForStandardAction(KStandardAction::Paste);
0177         QApplication::sendEvent(worksheet(), event);
0178         delete event;
0179     } else {
0180         textCursor().insertText(QApplication::clipboard()->text());
0181     }
0182 }
0183 
0184 void WorksheetTextItem::copy()
0185 {
0186     if (richTextEnabled()) {
0187         QKeyEvent* event = eventForStandardAction(KStandardAction::Copy);
0188         QApplication::sendEvent(worksheet(), event);
0189         delete event;
0190     } else {
0191         auto cursor = textCursor();
0192         if (!cursor.hasSelection())
0193             cursor.select(QTextCursor::Document);
0194         QString text = resolveImages(cursor);
0195         text.replace(QChar::ParagraphSeparator, QLatin1Char('\n'));
0196         text.replace(QChar::LineSeparator, QLatin1Char('\n'));
0197         QApplication::clipboard()->setText(text);
0198     }
0199 }
0200 
0201 void WorksheetTextItem::undo()
0202 {
0203     document()->undo();
0204 }
0205 
0206 void WorksheetTextItem::redo()
0207 {
0208     document()->redo();
0209 }
0210 
0211 void WorksheetTextItem::clipboardChanged()
0212 {
0213     if (isEditable())
0214         emit pasteAvailable(!QApplication::clipboard()->text().isEmpty());
0215 }
0216 
0217 void WorksheetTextItem::selectionChanged()
0218 {
0219     emit copyAvailable(textCursor().hasSelection());
0220     if (isEditable())
0221         emit cutAvailable(textCursor().hasSelection());
0222 }
0223 
0224 QString WorksheetTextItem::resolveImages(const QTextCursor& cursor)
0225 {
0226     int start = cursor.selectionStart();
0227     int end = cursor.selectionEnd();
0228 
0229     const QString repl = QString(QChar::ObjectReplacementCharacter);
0230     QString result;
0231     QTextCursor cursor1 = textCursor();
0232     cursor1.setPosition(start);
0233     QTextCursor cursor2 = document()->find(repl, cursor1);
0234 
0235     for (; !cursor2.isNull() && cursor2.selectionEnd() <= end;
0236          cursor2 = document()->find(repl, cursor1)) {
0237         cursor1.setPosition(cursor2.selectionStart(), QTextCursor::KeepAnchor);
0238         result += cursor1.selectedText();
0239         QVariant var = cursor2.charFormat().property(Cantor::Renderer::Delimiter);
0240         QString delim;
0241         if (var.isValid())
0242             delim = var.value<QString>();
0243         else
0244             delim = QLatin1String("");
0245         result += delim + cursor2.charFormat().property(Cantor::Renderer::Code).value<QString>() + delim;
0246         cursor1.setPosition(cursor2.selectionEnd());
0247     }
0248 
0249     cursor1.setPosition(end, QTextCursor::KeepAnchor);
0250     result += cursor1.selectedText();
0251     return result;
0252 }
0253 
0254 void WorksheetTextItem::setCursorPosition(QPointF pos)
0255 {
0256     QTextCursor cursor = cursorForPosition(pos);
0257     setTextCursor(cursor);
0258     emit cursorPositionChanged(cursor);
0259     //setLocalCursorPosition(mapFromParent(pos));
0260 }
0261 
0262 QPointF WorksheetTextItem::cursorPosition() const
0263 {
0264     return mapToParent(localCursorPosition());
0265 }
0266 
0267 void WorksheetTextItem::setLocalCursorPosition(QPointF pos)
0268 {
0269     int p = document()->documentLayout()->hitTest(pos, Qt::FuzzyHit);
0270     QTextCursor cursor = textCursor();
0271     cursor.setPosition(p);
0272     setTextCursor(cursor);
0273     emit cursorPositionChanged(cursor);
0274 }
0275 
0276 QPointF WorksheetTextItem::localCursorPosition() const
0277 {
0278     QTextCursor cursor = textCursor();
0279     QTextBlock block = cursor.block();
0280     int p = cursor.position() - block.position();
0281     QTextLine line = block.layout()->lineForTextPosition(p);
0282     if (!line.isValid()) // can this happen?
0283         return block.layout()->position();
0284     return QPointF(line.cursorToX(p), line.y() + line.height());
0285 }
0286 
0287 QRectF WorksheetTextItem::sceneCursorRect(QTextCursor cursor) const
0288 {
0289     return mapRectToScene(cursorRect(cursor));
0290 }
0291 
0292 QRectF WorksheetTextItem::cursorRect(QTextCursor cursor) const
0293 {
0294     if (cursor.isNull())
0295         cursor = textCursor();
0296     QTextCursor startCursor = cursor;
0297     startCursor.setPosition(cursor.selectionStart());
0298     QTextBlock block = startCursor.block();
0299     if (!block.layout())
0300         return mapRectToScene(boundingRect());
0301     int p = startCursor.position() - block.position();
0302     QTextLine line = block.layout()->lineForTextPosition(p);
0303     QRectF r1(line.cursorToX(p), line.y(), 1, line.height()+line.leading());
0304 
0305     if (!cursor.hasSelection())
0306         return r1;
0307 
0308     QTextCursor endCursor = cursor;
0309     endCursor.setPosition(cursor.selectionEnd());
0310     block = endCursor.block();
0311     p = endCursor.position() - block.position();
0312     line = block.layout()->lineForTextPosition(p);
0313     QRectF r2(line.cursorToX(p), line.y(), 1, line.height()+line.leading());
0314 
0315     if (r1.y() == r2.y())
0316         return r1.united(r2);
0317     else
0318         return QRectF(x(), qMin(r1.y(), r2.y()), boundingRect().width(),
0319                       qMax(r1.y() + r1.height(), r2.y() + r2.height()));
0320 }
0321 
0322 QTextCursor WorksheetTextItem::cursorForPosition(QPointF pos) const
0323 {
0324     QPointF lpos = mapFromParent(pos);
0325     int p = document()->documentLayout()->hitTest(lpos, Qt::FuzzyHit);
0326     QTextCursor cursor = textCursor();
0327     cursor.setPosition(p);
0328     return cursor;
0329 }
0330 
0331 bool WorksheetTextItem::isEditable()
0332 {
0333     return textInteractionFlags() & Qt::TextEditable;
0334 }
0335 
0336 void WorksheetTextItem::setBackgroundColor(const QColor& color)
0337 {
0338     m_backgroundColor = color;
0339 }
0340 
0341 const QColor& WorksheetTextItem::backgroundColor() const
0342 {
0343     return m_backgroundColor;
0344 }
0345 
0346 bool WorksheetTextItem::richTextEnabled()
0347 {
0348     return m_richTextEnabled;
0349 }
0350 
0351 void WorksheetTextItem::enableCompletion(bool b)
0352 {
0353     m_completionEnabled = b;
0354 }
0355 
0356 void WorksheetTextItem::activateCompletion(bool b)
0357 {
0358     m_completionActive = b;
0359 }
0360 
0361 void WorksheetTextItem::setItemDragable(bool b)
0362 {
0363     m_itemDragable = b;
0364 }
0365 
0366 void WorksheetTextItem::enableRichText(bool b)
0367 {
0368     m_richTextEnabled = b;
0369 }
0370 
0371 void WorksheetTextItem::setFocusAt(int pos, qreal xCoord)
0372 {
0373     QTextCursor cursor = textCursor();
0374     if (pos == TopLeft) {
0375         cursor.movePosition(QTextCursor::Start);
0376     } else if (pos == BottomRight) {
0377         cursor.movePosition(QTextCursor::End);
0378     } else {
0379         QTextLine line;
0380         if (pos == TopCoord) {
0381             line = document()->firstBlock().layout()->lineAt(0);
0382         } else {
0383             QTextLayout* layout = document()->lastBlock().layout();
0384             qDebug() << document()->blockCount() << "blocks";
0385             qDebug() << document()->lastBlock().lineCount() << "lines in last block";
0386             line = layout->lineAt(document()->lastBlock().lineCount()-1);
0387         }
0388         qreal x = mapFromScene(xCoord, 0).x();
0389         int p = line.xToCursor(x);
0390         cursor.setPosition(p);
0391         // Hack: The code for selecting the last line above does not work.
0392         // This is a workaround
0393         if (pos == BottomCoord)
0394             while (cursor.movePosition(QTextCursor::Down))
0395                 ;
0396     }
0397     setTextCursor(cursor);
0398     emit cursorPositionChanged(cursor);
0399     setFocus();
0400 }
0401 
0402 Cantor::Session* WorksheetTextItem::session()
0403 {
0404     return worksheet()->session();
0405 }
0406 
0407 void WorksheetTextItem::keyPressEvent(QKeyEvent *event)
0408 {
0409     switch (event->key()) {
0410     case Qt::Key_Left:
0411         if (event->modifiers() == Qt::NoModifier && textCursor().atStart()) {
0412             emit moveToPrevious(BottomRight, 0);
0413             qDebug()<<"Reached leftmost valid position";
0414             return;
0415         }
0416         break;
0417     case Qt::Key_Right:
0418         if (event->modifiers() == Qt::NoModifier && textCursor().atEnd()) {
0419             emit moveToNext(TopLeft, 0);
0420             qDebug()<<"Reached rightmost valid position";
0421             return;
0422         }
0423         break;
0424     case Qt::Key_Up:
0425         if (event->modifiers() == Qt::NoModifier && !textCursor().movePosition(QTextCursor::Up)) {
0426             qreal x = mapToScene(localCursorPosition()).x();
0427             emit moveToPrevious(BottomCoord, x);
0428             qDebug()<<"Reached topmost valid position" << localCursorPosition().x();
0429             return;
0430         }
0431         break;
0432     case Qt::Key_Down:
0433         if (event->modifiers() == Qt::NoModifier && !textCursor().movePosition(QTextCursor::Down)) {
0434             qreal x = mapToScene(localCursorPosition()).x();
0435             emit moveToNext(TopCoord, x);
0436             qDebug()<<"Reached bottommost valid position" << localCursorPosition().x();
0437             return;
0438         }
0439         break;
0440     case Qt::Key_Enter:
0441     case Qt::Key_Return:
0442         if (event->modifiers() == Qt::NoModifier && m_completionActive) {
0443             emit applyCompletion();
0444             return;
0445         }
0446         break;
0447     case Qt::Key_Tab:
0448         qDebug() << "Tab";
0449         break;
0450     case Qt::Key_F2:
0451         if(textCursor().hasSelection())
0452         {
0453             QString keyword = textCursor().selectedText();
0454             emit worksheet()->requestDocumentation(keyword);
0455         }
0456         break;
0457     default:
0458         break;
0459     }
0460 
0461     int p = textCursor().position();
0462     bool b = textCursor().hasSelection();
0463     QGraphicsTextItem::keyPressEvent(event);
0464 
0465     if (p != textCursor().position())
0466         emit cursorPositionChanged(textCursor());
0467 
0468     if (b != textCursor().hasSelection())
0469         selectionChanged();
0470 }
0471 
0472 bool WorksheetTextItem::sceneEvent(QEvent *event)
0473 {
0474     if (event->type() == QEvent::KeyPress) {
0475         // QGraphicsTextItem's TabChangesFocus feature prevents calls to
0476         // keyPressEvent for Tab, even when it's turned off. So we got to catch
0477         // that here.
0478         QKeyEvent* kev = static_cast<QKeyEvent*>(event);
0479         if (kev->key() == Qt::Key_Tab && kev->modifiers() == Qt::NoModifier) {
0480             emit tabPressed();
0481             return true;
0482         } else if ((kev->key() == Qt::Key_Tab &&
0483                     kev->modifiers() == Qt::ShiftModifier) ||
0484                    kev->key() == Qt::Key_Backtab) {
0485             emit backtabPressed();
0486             return true;
0487         }
0488     } else if (event->type() == QEvent::ShortcutOverride) {
0489         QKeyEvent* kev = static_cast<QKeyEvent*>(event);
0490         QKeySequence seq(kev->key() + kev->modifiers());
0491         if (worksheet()->isShortcut(seq)) {
0492             qDebug() << "ShortcutOverride" << kev->key() << kev->modifiers();
0493             kev->ignore();
0494             return false;
0495         }
0496     }
0497     return QGraphicsTextItem::sceneEvent(event);
0498 }
0499 
0500 void WorksheetTextItem::focusInEvent(QFocusEvent *event)
0501 {
0502     QGraphicsTextItem::focusInEvent(event);
0503     //parentItem()->ensureVisible(QRectF(), 0, 0);
0504     WorksheetEntry* entry = qobject_cast<WorksheetEntry*>(parentObject());
0505     WorksheetCursor c(entry, this, textCursor());
0506     // No need make the text item visible
0507     // if we just hide/show window, it it not necessary
0508     if (event->reason() != Qt::ActiveWindowFocusReason)
0509         worksheet()->makeVisible(c);
0510     worksheet()->updateFocusedTextItem(this);
0511     connect(QApplication::clipboard(), &QClipboard::dataChanged, this,
0512             &WorksheetTextItem::clipboardChanged);
0513     emit receivedFocus(this);
0514     emit cursorPositionChanged(textCursor());
0515 }
0516 
0517 void WorksheetTextItem::focusOutEvent(QFocusEvent *event)
0518 {
0519     QGraphicsTextItem::focusOutEvent(event);
0520     emit cursorPositionChanged(QTextCursor());
0521 }
0522 
0523 void WorksheetTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
0524 {
0525     int p = textCursor().position();
0526     bool b = textCursor().hasSelection();
0527 
0528     QGraphicsTextItem::mousePressEvent(event);
0529 
0530     if (isEditable() && event->button() == Qt::MiddleButton &&
0531         QApplication::clipboard()->supportsSelection() &&
0532         !event->isAccepted())
0533         event->accept();
0534 
0535     if (m_itemDragable && event->button() == Qt::LeftButton)
0536         event->accept();
0537 
0538     if (p != textCursor().position())
0539         emit cursorPositionChanged(textCursor());
0540     if (b != textCursor().hasSelection())
0541         selectionChanged();
0542 }
0543 
0544 void WorksheetTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
0545 {
0546     const QPointF buttonDownPos = event->buttonDownPos(Qt::LeftButton);
0547     if (m_itemDragable && event->buttons() == Qt::LeftButton &&
0548         contains(buttonDownPos) &&
0549         (event->pos() - buttonDownPos).manhattanLength() >= QApplication::startDragDistance()) {
0550         ungrabMouse();
0551         emit drag(mapToParent(buttonDownPos), mapToParent(event->pos()));
0552         event->accept();
0553     } else {
0554         bool b = textCursor().hasSelection();
0555         QGraphicsTextItem::mouseMoveEvent(event);
0556         if (b != textCursor().hasSelection())
0557             selectionChanged();
0558     }
0559 }
0560 
0561 void WorksheetTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
0562 {
0563     int p = textCursor().position();
0564 
0565     // custom middle-click paste that does not copy rich text
0566     if (isEditable() && event->button() == Qt::MiddleButton &&
0567         QApplication::clipboard()->supportsSelection() &&
0568         !richTextEnabled()) {
0569         setLocalCursorPosition(mapFromScene(event->scenePos()));
0570         const QString& text = QApplication::clipboard()->text(QClipboard::Selection);
0571         textCursor().insertText(text);
0572     } else {
0573         QGraphicsTextItem::mouseReleaseEvent(event);
0574     }
0575 
0576     if (p != textCursor().position())
0577         emit cursorPositionChanged(textCursor());
0578 }
0579 
0580 void WorksheetTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
0581 {
0582     QTextCursor cursor = textCursor();
0583     const QChar repl = QChar::ObjectReplacementCharacter;
0584 
0585     if (m_eventBehaviour == DoubleClickEventBehaviour::ImageReplacement)
0586     {
0587         if (!cursor.hasSelection()) {
0588             // We look at the current cursor and the next cursor for a
0589             // ObjectReplacementCharacter
0590             for (int i = 2; i; --i) {
0591                 if (document()->characterAt(cursor.position()-1) == repl) {
0592                     setTextCursor(cursor);
0593                     emit doubleClick();
0594                     return;
0595                 }
0596                 cursor.movePosition(QTextCursor::NextCharacter);
0597             }
0598         } else if (cursor.selectedText().contains(repl)) {
0599             emit doubleClick();
0600             return;
0601         }
0602     }
0603     else if (m_eventBehaviour == DoubleClickEventBehaviour::Simple)
0604     {
0605         emit doubleClick();
0606         return;
0607     }
0608 
0609     QGraphicsTextItem::mouseDoubleClickEvent(event);
0610 }
0611 
0612 void WorksheetTextItem::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
0613 {
0614     if (isEditable() && event->mimeData()->hasFormat(QLatin1String("text/plain"))) {
0615         if (event->proposedAction() & (Qt::CopyAction | Qt::MoveAction)) {
0616             event->acceptProposedAction();
0617         } else if (event->possibleActions() & Qt::CopyAction) {
0618             event->setDropAction(Qt::CopyAction);
0619             event->accept();
0620         } else if (event->possibleActions() & Qt::MoveAction) {
0621             event->setDropAction(Qt::MoveAction);
0622             event->accept();
0623         } else {
0624             event->ignore();
0625         }
0626     } else {
0627         event->ignore();
0628     }
0629 }
0630 
0631 void WorksheetTextItem::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
0632 {
0633     if (isEditable() && event->mimeData()->hasFormat(QLatin1String("text/plain")))
0634         setLocalCursorPosition(mapFromScene(event->scenePos()));
0635 }
0636 
0637 void WorksheetTextItem::dropEvent(QGraphicsSceneDragDropEvent* event)
0638 {
0639     if (isEditable()) {
0640         if (richTextEnabled() && event->mimeData()->hasFormat(QLatin1String("text/html")))
0641             textCursor().insertHtml(event->mimeData()->html());
0642         else
0643             textCursor().insertText(event->mimeData()->text());
0644         event->accept();
0645     }
0646 }
0647 
0648 void WorksheetTextItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
0649 {
0650     QMenu *menu = worksheet()->createContextMenu();
0651     populateMenu(menu, event->pos());
0652 
0653     menu->popup(event->screenPos());
0654 }
0655 
0656 void WorksheetTextItem::wheelEvent(QGraphicsSceneWheelEvent* event)
0657 {
0658     //restore the cursor when scrolling with the mouse wheel since we
0659     //might be using the pointer cursor set after an URL was hovered
0660     QApplication::restoreOverrideCursor();
0661     QGraphicsItem::wheelEvent(event);
0662 }
0663 
0664 void WorksheetTextItem::insertTab()
0665 {
0666     QTextCursor cursor = textCursor();
0667     cursor.clearSelection();
0668     cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
0669     QString sel = cursor.selectedText();
0670     bool spacesOnly = true;
0671     qDebug() << sel;
0672     for (QString::iterator it = sel.begin(); it != sel.end(); ++it) {
0673         if (! it->isSpace()) {
0674             spacesOnly = false;
0675             break;
0676         }
0677     }
0678 
0679     cursor.setPosition(cursor.selectionEnd());
0680     if (spacesOnly) {
0681         while (document()->characterAt(cursor.position()) == QLatin1Char(' '))
0682             cursor.movePosition(QTextCursor::NextCharacter);
0683     }
0684 
0685     QTextLayout *layout = textCursor().block().layout();
0686     if (!layout) {
0687         cursor.insertText(QLatin1String("    "));
0688     } else {
0689         cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
0690         int i = cursor.selectionEnd() - cursor.selectionStart();
0691         i = ((i+4) & (~3)) - i;
0692         cursor.setPosition(cursor.selectionEnd());
0693 
0694         QString insertBlankSpace = QLatin1String(" ");
0695         cursor.insertText(insertBlankSpace.repeated(i));
0696     }
0697     setTextCursor(cursor);
0698     emit cursorPositionChanged(textCursor());
0699 }
0700 
0701 void WorksheetTextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* o, QWidget* w) {
0702     if (m_backgroundColor.isValid()) {
0703         painter->setPen(QPen(Qt::NoPen));
0704         painter->setBrush(m_backgroundColor);
0705         painter->drawRect(boundingRect());
0706     }
0707     QGraphicsTextItem::paint(painter, o, w);
0708 }
0709 
0710 double WorksheetTextItem::width() const
0711 {
0712     return m_size.width();
0713 }
0714 
0715 double WorksheetTextItem::height() const
0716 {
0717     return m_size.height();
0718 }
0719 
0720 Worksheet* WorksheetTextItem::worksheet()
0721 {
0722     return qobject_cast<Worksheet*>(scene());
0723 }
0724 
0725 WorksheetView* WorksheetTextItem::worksheetView()
0726 {
0727     return worksheet()->worksheetView();
0728 }
0729 
0730 void WorksheetTextItem::clearSelection()
0731 {
0732     QTextCursor cursor = textCursor();
0733     cursor.clearSelection();
0734     setTextCursor(cursor);
0735     selectionChanged();
0736 }
0737 
0738 bool WorksheetTextItem::isUndoAvailable()
0739 {
0740     return document()->isUndoAvailable();
0741 }
0742 
0743 bool WorksheetTextItem::isRedoAvailable()
0744 {
0745     return document()->isRedoAvailable();
0746 }
0747 
0748 bool WorksheetTextItem::isCutAvailable()
0749 {
0750     return isEditable() && textCursor().hasSelection();
0751 }
0752 
0753 bool WorksheetTextItem::isCopyAvailable()
0754 {
0755     return !m_itemDragable && textCursor().hasSelection();
0756 }
0757 
0758 bool WorksheetTextItem::isPasteAvailable()
0759 {
0760     return isEditable() && !QApplication::clipboard()->text().isEmpty();
0761 }
0762 
0763 QTextCursor WorksheetTextItem::search(QString pattern,
0764                                       QTextDocument::FindFlags qt_flags,
0765                                       const WorksheetCursor& pos)
0766 {
0767     if (pos.isValid() && pos.textItem() != this)
0768         return QTextCursor();
0769 
0770     QTextDocument* doc = document();
0771     QTextCursor cursor;
0772     if (pos.isValid()) {
0773         cursor = doc->find(pattern, pos.textCursor(), qt_flags);
0774     } else {
0775         cursor = textCursor();
0776         if (qt_flags & QTextDocument::FindBackward)
0777             cursor.movePosition(QTextCursor::End);
0778         else
0779             cursor.movePosition(QTextCursor::Start);
0780         cursor = doc->find(pattern, cursor, qt_flags);
0781     }
0782 
0783     return cursor;
0784 }
0785 
0786 // RichText
0787 
0788 void WorksheetTextItem::updateRichTextActions(QTextCursor cursor)
0789 {
0790     if (cursor.isNull())
0791         return;
0792     Worksheet::RichTextInfo info;
0793     QTextCharFormat fmt = cursor.charFormat();
0794     info.bold = (fmt.fontWeight() == QFont::Bold);
0795     info.italic = fmt.fontItalic();
0796     info.underline = fmt.fontUnderline();
0797     info.strikeOut = fmt.fontStrikeOut();
0798     info.font = fmt.fontFamily();
0799     info.fontSize = fmt.font().pointSize();
0800 
0801     QTextBlockFormat bfmt = cursor.blockFormat();
0802     info.align = bfmt.alignment();
0803 
0804     worksheet()->setRichTextInformation(info);
0805 }
0806 
0807 void WorksheetTextItem::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
0808 {
0809     QTextCursor cursor = textCursor();
0810     QTextCursor wordStart(cursor);
0811     QTextCursor wordEnd(cursor);
0812 
0813     wordStart.movePosition(QTextCursor::StartOfWord);
0814     wordEnd.movePosition(QTextCursor::EndOfWord);
0815 
0816     //cursor.beginEditBlock();
0817     if (!cursor.hasSelection() && cursor.position() != wordStart.position() && cursor.position() != wordEnd.position())
0818         cursor.select(QTextCursor::WordUnderCursor);
0819     cursor.mergeCharFormat(format);
0820     //q->mergeCurrentCharFormat(format);
0821     //cursor.endEditBlock();
0822     setTextCursor(cursor);
0823 }
0824 
0825 void WorksheetTextItem::setTextForegroundColor()
0826 {
0827     QTextCharFormat fmt = textCursor().charFormat();
0828     QColor color = fmt.foreground().color();
0829 
0830     color = QColorDialog::getColor(color, worksheetView());
0831     if (!color.isValid())
0832         color = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
0833 
0834     QTextCharFormat newFmt;
0835     newFmt.setForeground(color);
0836     mergeFormatOnWordOrSelection(newFmt);
0837 }
0838 
0839 void WorksheetTextItem::setTextBackgroundColor()
0840 {
0841     QTextCharFormat fmt = textCursor().charFormat();
0842     QColor color = fmt.background().color();
0843 
0844     color = QColorDialog::getColor(color, worksheetView());
0845     if (!color.isValid())
0846         color = KColorScheme(QPalette::Active, KColorScheme::View).background().color();
0847 
0848     QTextCharFormat newFmt;
0849     newFmt.setBackground(color);
0850     mergeFormatOnWordOrSelection(newFmt);
0851 }
0852 
0853 void WorksheetTextItem::setTextBold(bool b)
0854 {
0855     QTextCharFormat fmt;
0856     fmt.setFontWeight(b ? QFont::Bold : QFont::Normal);
0857     mergeFormatOnWordOrSelection(fmt);
0858 }
0859 
0860 void WorksheetTextItem::setTextItalic(bool b)
0861 {
0862     QTextCharFormat fmt;
0863     fmt.setFontItalic(b);
0864     mergeFormatOnWordOrSelection(fmt);
0865 }
0866 
0867 void WorksheetTextItem::setTextUnderline(bool b)
0868 {
0869     QTextCharFormat fmt;
0870     fmt.setFontUnderline(b);
0871     mergeFormatOnWordOrSelection(fmt);
0872 }
0873 
0874 void WorksheetTextItem::setTextStrikeOut(bool b)
0875 {
0876     QTextCharFormat fmt;
0877     fmt.setFontStrikeOut(b);
0878     mergeFormatOnWordOrSelection(fmt);
0879 }
0880 
0881 void WorksheetTextItem::setAlignment(Qt::Alignment a)
0882 {
0883     QTextBlockFormat fmt;
0884     fmt.setAlignment(a);
0885     QTextCursor cursor = textCursor();
0886     cursor.mergeBlockFormat(fmt);
0887     setTextCursor(cursor);
0888 }
0889 
0890 void WorksheetTextItem::setFontFamily(const QString& font)
0891 {
0892     if (!richTextEnabled())
0893         return;
0894     QTextCharFormat fmt;
0895     fmt.setFontFamily(font);
0896     mergeFormatOnWordOrSelection(fmt);
0897 }
0898 
0899 void WorksheetTextItem::setFontSize(int size)
0900 {
0901     if (!richTextEnabled())
0902         return;
0903     QTextCharFormat fmt;
0904     fmt.setFontPointSize(size);
0905     mergeFormatOnWordOrSelection(fmt);
0906 }
0907 
0908 void WorksheetTextItem::allowEditing()
0909 {
0910     setTextInteractionFlags(Qt::TextEditorInteraction);
0911 }
0912 
0913 void WorksheetTextItem::denyEditing()
0914 {
0915     setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard);
0916 }
0917 
0918 WorksheetTextItem::DoubleClickEventBehaviour WorksheetTextItem::doubleClickBehaviour()
0919 {
0920     return m_eventBehaviour;
0921 }
0922 
0923 void WorksheetTextItem::setDoubleClickBehaviour(WorksheetTextItem::DoubleClickEventBehaviour behaviour)
0924 {
0925     m_eventBehaviour = behaviour;
0926 }