File indexing completed on 2022-12-06 18:50:29

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com>
0004     SPDX-FileCopyrightText: 2016-2021 Alexander Semke <alexander.semke@web.de>
0005 */
0006 
0007 #include "worksheetentry.h"
0008 #include "commandentry.h"
0009 #include "textentry.h"
0010 #include "markdownentry.h"
0011 #include "latexentry.h"
0012 #include "placeholderentry.h"
0013 #include "imageentry.h"
0014 #include "pagebreakentry.h"
0015 #include "horizontalruleentry.h"
0016 #include "hierarchyentry.h"
0017 #include "settings.h"
0018 #include "actionbar.h"
0019 #include "worksheettoolbutton.h"
0020 #include "worksheetview.h"
0021 
0022 #include <QDrag>
0023 #include <QIcon>
0024 #include <QPropertyAnimation>
0025 #include <QParallelAnimationGroup>
0026 #include <QMetaMethod>
0027 #include <QMimeData>
0028 #include <QGraphicsProxyWidget>
0029 #include <QBitmap>
0030 #include <QJsonArray>
0031 #include <QJsonObject>
0032 #include <QPainter>
0033 #include <QGraphicsSceneMouseEvent>
0034 
0035 #include <KColorScheme>
0036 #include <KLocalizedString>
0037 #include <KMessageBox>
0038 #include <QDebug>
0039 
0040 struct AnimationData
0041 {
0042     QAnimationGroup* animation;
0043     QPropertyAnimation* sizeAnimation;
0044     QPropertyAnimation* opacAnimation;
0045     QPropertyAnimation* posAnimation;
0046     const char* slot;
0047     QGraphicsObject* item;
0048 };
0049 
0050 const qreal WorksheetEntry::VerticalMargin = 4;
0051 const qreal WorksheetEntry::ControlElementWidth = 12;
0052 const qreal WorksheetEntry::ControlElementBorder = 4;
0053 const qreal WorksheetEntry::RightMargin = ControlElementWidth + 2*ControlElementBorder;
0054 const qreal WorksheetEntry::HorizontalSpacing = 4;
0055 
0056 QColor WorksheetEntry::colors[] = {QColor(255,255,255), QColor(0,0,0),
0057                                                     QColor(192,0,0), QColor(255,0,0), QColor(255,192,192), //red
0058                                                     QColor(0,192,0), QColor(0,255,0), QColor(192,255,192), //green
0059                                                     QColor(0,0,192), QColor(0,0,255), QColor(192,192,255), //blue
0060                                                     QColor(192,192,0), QColor(255,255,0), QColor(255,255,192), //yellow
0061                                                     QColor(0,192,192), QColor(0,255,255), QColor(192,255,255), //cyan
0062                                                     QColor(192,0,192), QColor(255,0,255), QColor(255,192,255), //magenta
0063                                                     QColor(192,88,0), QColor(255,128,0), QColor(255,168,88), //orange
0064                                                     QColor(128,128,128), QColor(160,160,160), QColor(195,195,195) //grey
0065                                                     };
0066 
0067 QString WorksheetEntry::colorNames[] = {i18n("White"), i18n("Black"),
0068                                          i18n("Dark Red"), i18n("Red"), i18n("Light Red"),
0069                                          i18n("Dark Green"), i18n("Green"), i18n("Light Green"),
0070                                          i18n("Dark Blue"), i18n("Blue"), i18n("Light Blue"),
0071                                          i18n("Dark Yellow"), i18n("Yellow"), i18n("Light Yellow"),
0072                                          i18n("Dark Cyan"), i18n("Cyan"), i18n("Light Cyan"),
0073                                          i18n("Dark Magenta"), i18n("Magenta"), i18n("Light Magenta"),
0074                                          i18n("Dark Orange"), i18n("Orange"), i18n("Light Orange"),
0075                                          i18n("Dark Grey"), i18n("Grey"), i18n("Light Grey")
0076                                          };
0077 
0078 WorksheetEntry::WorksheetEntry(Worksheet* worksheet) : QGraphicsObject(), m_controlElement(worksheet, this)
0079 {
0080     worksheet->addItem(this);
0081     setAcceptHoverEvents(true);
0082     connect(&m_controlElement, &WorksheetControlItem::drag, this, &WorksheetEntry::startDrag);
0083 }
0084 
0085 WorksheetEntry::~WorksheetEntry()
0086 {
0087     emit aboutToBeDeleted();
0088     if (next())
0089         next()->setPrevious(previous());
0090     if (previous())
0091         previous()->setNext(next());
0092     if (m_animation) {
0093         m_animation->animation->deleteLater();
0094         delete m_animation;
0095     }
0096     if (m_jupyterMetadata)
0097         delete m_jupyterMetadata;
0098     if (type() == HierarchyEntry::Type)
0099         worksheet()->updateHierarchyLayout();
0100 }
0101 
0102 int WorksheetEntry::type() const
0103 {
0104     return Type;
0105 }
0106 
0107 WorksheetEntry* WorksheetEntry::create(int t, Worksheet* worksheet)
0108 {
0109     switch(t)
0110     {
0111     case TextEntry::Type:
0112         return new TextEntry(worksheet);
0113     case MarkdownEntry::Type:
0114         return new MarkdownEntry(worksheet);
0115     case CommandEntry::Type:
0116         return new CommandEntry(worksheet);
0117     case ImageEntry::Type:
0118         return new ImageEntry(worksheet);
0119     case PageBreakEntry::Type:
0120         return new PageBreakEntry(worksheet);
0121     case LatexEntry::Type:
0122         return new LatexEntry(worksheet);
0123     case HorizontalRuleEntry::Type:
0124         return new HorizontalRuleEntry(worksheet);
0125     case HierarchyEntry::Type:
0126         return new HierarchyEntry(worksheet);
0127     default:
0128         return nullptr;
0129     }
0130 }
0131 
0132 void WorksheetEntry::insertCommandEntry()
0133 {
0134     worksheet()->insertCommandEntry(this);
0135 }
0136 
0137 void WorksheetEntry::insertTextEntry()
0138 {
0139     worksheet()->insertTextEntry(this);
0140 }
0141 
0142 void WorksheetEntry::insertMarkdownEntry()
0143 {
0144     worksheet()->insertMarkdownEntry(this);
0145 }
0146 
0147 void WorksheetEntry::insertLatexEntry()
0148 {
0149     worksheet()->insertLatexEntry(this);
0150 }
0151 
0152 void WorksheetEntry::insertImageEntry()
0153 {
0154     worksheet()->insertImageEntry(this);
0155 }
0156 
0157 void WorksheetEntry::insertPageBreakEntry()
0158 {
0159     worksheet()->insertPageBreakEntry(this);
0160 }
0161 
0162 void WorksheetEntry::insertHorizontalRuleEntry()
0163 {
0164     worksheet()->insertHorizontalRuleEntry(this);
0165 }
0166 
0167 void WorksheetEntry::insertHierarchyEntry()
0168 {
0169     worksheet()->insertHierarchyEntry(this);
0170 }
0171 
0172 void WorksheetEntry::insertCommandEntryBefore()
0173 {
0174     worksheet()->insertCommandEntryBefore(this);
0175 }
0176 
0177 void WorksheetEntry::insertTextEntryBefore()
0178 {
0179     worksheet()->insertTextEntryBefore(this);
0180 }
0181 
0182 void WorksheetEntry::insertMarkdownEntryBefore()
0183 {
0184     worksheet()->insertMarkdownEntryBefore(this);
0185 }
0186 
0187 void WorksheetEntry::insertLatexEntryBefore()
0188 {
0189     worksheet()->insertLatexEntryBefore(this);
0190 }
0191 
0192 void WorksheetEntry::insertImageEntryBefore()
0193 {
0194     worksheet()->insertImageEntryBefore(this);
0195 }
0196 
0197 void WorksheetEntry::insertPageBreakEntryBefore()
0198 {
0199     worksheet()->insertPageBreakEntryBefore(this);
0200 }
0201 
0202 void WorksheetEntry::insertHorizontalRuleEntryBefore()
0203 {
0204     worksheet()->insertHorizontalRuleEntryBefore(this);
0205 }
0206 
0207 void WorksheetEntry::insertHierarchyEntryBefore()
0208 {
0209     worksheet()->insertHierarchyEntryBefore(this);
0210 }
0211 
0212 void WorksheetEntry::convertToCommandEntry()
0213 {
0214     worksheet()->changeEntryType(this, CommandEntry::Type);
0215 }
0216 
0217 void WorksheetEntry::convertToTextEntry()
0218 {
0219     worksheet()->changeEntryType(this, TextEntry::Type);
0220 }
0221 
0222 void WorksheetEntry::convertToMarkdownEntry()
0223 {
0224     worksheet()->changeEntryType(this, MarkdownEntry::Type);
0225 }
0226 
0227 void WorksheetEntry::convertToLatexEntry()
0228 {
0229     worksheet()->changeEntryType(this, LatexEntry::Type);
0230 }
0231 
0232 void WorksheetEntry::convertToImageEntry()
0233 {
0234     worksheet()->changeEntryType(this, ImageEntry::Type);
0235 }
0236 
0237 void WorksheetEntry::converToPageBreakEntry()
0238 {
0239     worksheet()->changeEntryType(this, PageBreakEntry::Type);
0240 }
0241 
0242 void WorksheetEntry::convertToHorizontalRuleEntry()
0243 {
0244     worksheet()->changeEntryType(this, HorizontalRuleEntry::Type);
0245 }
0246 
0247 void WorksheetEntry::convertToHierarchyEntry()
0248 {
0249     worksheet()->changeEntryType(this, HierarchyEntry::Type);
0250 }
0251 
0252 void WorksheetEntry::showCompletion()
0253 {
0254 }
0255 
0256 WorksheetEntry* WorksheetEntry::next() const
0257 {
0258     return m_next;
0259 }
0260 
0261 WorksheetEntry* WorksheetEntry::previous() const
0262 {
0263     return m_prev;
0264 }
0265 
0266 void WorksheetEntry::setNext(WorksheetEntry* n)
0267 {
0268     m_next = n;
0269 }
0270 
0271 void WorksheetEntry::setPrevious(WorksheetEntry* p)
0272 {
0273     m_prev = p;
0274 }
0275 
0276 void WorksheetEntry::startDrag(QPointF grabPos)
0277 {
0278     // We need reset entry cursor manually, because otherwise the entry cursor will be visible on dragable item
0279     worksheet()->resetEntryCursor();
0280 
0281     QDrag* drag = new QDrag(worksheetView());
0282     qDebug() << size();
0283     const qreal scale = worksheet()->renderer()->scale();
0284     QPixmap pixmap((size()*scale).toSize());
0285     pixmap.fill(QColor(255, 255, 255, 0));
0286     QPainter painter(&pixmap);
0287     const QRectF sceneRect = mapRectToScene(boundingRect());
0288     worksheet()->render(&painter, pixmap.rect(), sceneRect);
0289     painter.end();
0290     QBitmap mask = pixmap.createMaskFromColor(QColor(255, 255, 255),
0291                                               Qt::MaskInColor);
0292     pixmap.setMask(mask);
0293 
0294     drag->setPixmap(pixmap);
0295     if (grabPos.isNull()) {
0296         const QPointF scenePos = worksheetView()->sceneCursorPos();
0297         drag->setHotSpot((mapFromScene(scenePos) * scale).toPoint());
0298     } else {
0299         drag->setHotSpot((grabPos * scale).toPoint());
0300     }
0301     drag->setMimeData(new QMimeData());
0302 
0303     worksheet()->startDrag(this, drag);
0304 }
0305 
0306 
0307 QRectF WorksheetEntry::boundingRect() const
0308 {
0309     return QRectF(QPointF(0,0), m_size);
0310 }
0311 
0312 void WorksheetEntry::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
0313 {
0314     Q_UNUSED(painter);
0315     Q_UNUSED(option);
0316     Q_UNUSED(widget);
0317 }
0318 
0319 bool WorksheetEntry::focusEntry(int pos, qreal xCoord)
0320 {
0321     Q_UNUSED(pos);
0322     Q_UNUSED(xCoord);
0323 
0324     if (flags() & QGraphicsItem::ItemIsFocusable) {
0325         setFocus();
0326         return true;
0327     }
0328     return false;
0329 }
0330 
0331 void WorksheetEntry::moveToPreviousEntry(int pos, qreal x)
0332 {
0333     WorksheetEntry* entry = previous();
0334     while (entry && !(entry->wantFocus() && entry->focusEntry(pos, x)))
0335         entry = entry->previous();
0336 }
0337 
0338 void WorksheetEntry::moveToNextEntry(int pos, qreal x)
0339 {
0340     WorksheetEntry* entry = next();
0341     while (entry && !(entry->wantFocus() && entry->focusEntry(pos, x)))
0342         entry = entry->next();
0343 }
0344 
0345 Worksheet* WorksheetEntry::worksheet()
0346 {
0347     return qobject_cast<Worksheet*>(scene());
0348 }
0349 
0350 WorksheetView* WorksheetEntry::worksheetView()
0351 {
0352     return worksheet()->worksheetView();
0353 }
0354 
0355 WorksheetCursor WorksheetEntry::search(const QString& pattern, unsigned flags,
0356                                    QTextDocument::FindFlags qt_flags,
0357                                    const WorksheetCursor& pos)
0358 {
0359     Q_UNUSED(pattern);
0360     Q_UNUSED(flags);
0361     Q_UNUSED(qt_flags);
0362     Q_UNUSED(pos);
0363 
0364     return WorksheetCursor();
0365 }
0366 
0367 void WorksheetEntry::keyPressEvent(QKeyEvent* event)
0368 {
0369     // This event is used in Entries that set the ItemIsFocusable flag
0370     switch(event->key()) {
0371     case Qt::Key_Left:
0372     case Qt::Key_Up:
0373         if (event->modifiers() == Qt::NoModifier)
0374             moveToPreviousEntry(WorksheetTextItem::BottomRight, 0);
0375         else if (event->modifiers() == Qt::CTRL)
0376             moveToPrevious();
0377         break;
0378     case Qt::Key_Right:
0379     case Qt::Key_Down:
0380         if (event->modifiers() == Qt::NoModifier)
0381             moveToNextEntry(WorksheetTextItem::TopLeft, 0);
0382         else if (event->modifiers() == Qt::CTRL)
0383             moveToNext();
0384         break;
0385         /*case Qt::Key_Enter:
0386     case Qt::Key_Return:
0387         if (event->modifiers() == Qt::ShiftModifier)
0388             evaluate();
0389         else if (event->modifiers() == Qt::ControlModifier)
0390             worksheet()->insertCommandEntry();
0391         break;*/
0392     default:
0393         event->ignore();
0394     }
0395 }
0396 
0397 void WorksheetEntry::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
0398 {
0399     QMenu *menu = worksheet()->createContextMenu();
0400     populateMenu(menu, event->pos());
0401 
0402     menu->popup(event->screenPos());
0403 }
0404 
0405 void WorksheetEntry::populateMenu(QMenu* menu, QPointF pos)
0406 {
0407     QAction* firstAction = nullptr;
0408     if (!menu->actions().isEmpty()) //action() can be empty, s.a. WorksheetTextItem::populateMenu() where this function is called
0409         firstAction = menu->actions().first();
0410 
0411     QAction* action;
0412     if (!worksheet()->isRunning() && wantToEvaluate())
0413     {
0414         action = new QAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate"));
0415         connect(action, SIGNAL(triggered()), this, SLOT(evaluate()));
0416         menu->insertAction(firstAction, action);
0417         menu->insertSeparator(firstAction);
0418     }
0419 
0420     if (m_prev) {
0421         action = new QAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Up"));
0422     //     connect(action, &QAction::triggered, this, &WorksheetEntry::moveToPrevious); //TODO: doesn't work
0423         connect(action, SIGNAL(triggered()), this, SLOT(moveToPrevious()));
0424         action->setShortcut(Qt::CTRL + Qt::Key_Up);
0425         menu->insertAction(firstAction, action);
0426     }
0427 
0428     if (m_next) {
0429         action = new QAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Down"));
0430     //     connect(action, &QAction::triggered, this, &WorksheetEntry::moveToNext); //TODO: doesn't work
0431         connect(action, SIGNAL(triggered()), this, SLOT(moveToNext()));
0432         action->setShortcut(Qt::CTRL + Qt::Key_Down);
0433         menu->insertAction(firstAction, action);
0434         menu->insertSeparator(firstAction);
0435     }
0436 
0437     action = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove"));
0438     connect(action, &QAction::triggered, this, &WorksheetEntry::startRemoving);
0439     action->setShortcut(Qt::ShiftModifier + Qt::Key_Delete);
0440     menu->insertAction(firstAction, action);
0441     menu->insertSeparator(firstAction);
0442 
0443     worksheet()->populateMenu(menu, mapToScene(pos));
0444 }
0445 
0446 bool WorksheetEntry::evaluateCurrentItem()
0447 {
0448     // A default implementation that works well for most entries,
0449     // because they have only one item.
0450     return evaluate();
0451 }
0452 
0453 void WorksheetEntry::evaluateNext(EvaluationOption opt)
0454 {
0455     // For cases, when code want *just* evaluate
0456     // the entry, for example, on load stage.
0457     // This internal evaluation shouldn't marked as
0458     // modifying change.
0459     if (opt == InternalEvaluation)
0460         return;
0461 
0462     WorksheetEntry* entry = next();
0463 
0464     while (entry && !entry->wantFocus())
0465         entry = entry->next();
0466 
0467     if (entry) {
0468         if (opt == EvaluateNext || Settings::self()->autoEval()) {
0469             entry->evaluate(EvaluateNext);
0470         } else if (opt == FocusNext) {
0471             worksheet()->setModified();
0472             entry->focusEntry(WorksheetTextItem::BottomRight);
0473         } else {
0474             worksheet()->setModified();
0475         }
0476     } else if (opt != DoNothing) {
0477         if (!worksheet()->isLoadingFromFile() && (!isEmpty() || type() != CommandEntry::Type))
0478             worksheet()->appendCommandEntry();
0479         else
0480             focusEntry();
0481         worksheet()->setModified();
0482     }
0483 }
0484 
0485 qreal WorksheetEntry::setGeometry(qreal x, qreal x1, qreal y, qreal w)
0486 {
0487     setPos(x, y);
0488     m_entry_zone_x = x1;
0489     layOutForWidth(x1, w);
0490 
0491     recalculateControlGeometry();
0492 
0493     return size().height();
0494 }
0495 
0496 void WorksheetEntry::recalculateSize()
0497 {
0498     qreal height = size().height();
0499     layOutForWidth(m_entry_zone_x, size().width(), true);
0500     if (height != size().height())
0501     {
0502         recalculateControlGeometry();
0503         worksheet()->updateEntrySize(this);
0504     }
0505 }
0506 
0507 QPropertyAnimation* WorksheetEntry::sizeChangeAnimation(QSizeF s)
0508 {
0509     QSizeF oldSize;
0510     QSizeF newSize;
0511     if (s.isValid()) {
0512         oldSize = size();
0513         newSize = s;
0514     } else {
0515         oldSize = size();
0516         layOutForWidth(m_entry_zone_x, size().width(), true);
0517         newSize = size();
0518     }
0519 
0520     QPropertyAnimation* sizeAn = new QPropertyAnimation(this, "size", this);
0521     sizeAn->setDuration(200);
0522     sizeAn->setStartValue(oldSize);
0523     sizeAn->setEndValue(newSize);
0524     sizeAn->setEasingCurve(QEasingCurve::InOutQuad);
0525     connect(sizeAn, &QPropertyAnimation::valueChanged, this, &WorksheetEntry::sizeAnimated);
0526     return sizeAn;
0527 }
0528 
0529 void WorksheetEntry::sizeAnimated()
0530 {
0531     recalculateControlGeometry();
0532     worksheet()->updateEntrySize(this);
0533 }
0534 
0535 void WorksheetEntry::animateSizeChange()
0536 {
0537     if (!worksheet()->animationsEnabled()) {
0538         recalculateSize();
0539         return;
0540     }
0541     if (m_animation) {
0542         layOutForWidth(m_entry_zone_x, size().width(), true);
0543         return;
0544     }
0545     QPropertyAnimation* sizeAn = sizeChangeAnimation();
0546     m_animation = new AnimationData;
0547     m_animation->item = nullptr;
0548     m_animation->slot = nullptr;
0549     m_animation->opacAnimation = nullptr;
0550     m_animation->posAnimation = nullptr;
0551     m_animation->sizeAnimation = sizeAn;
0552     m_animation->sizeAnimation->setEasingCurve(QEasingCurve::OutCubic);
0553     m_animation->animation = new QParallelAnimationGroup(this);
0554     m_animation->animation->addAnimation(m_animation->sizeAnimation);
0555     connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
0556     m_animation->animation->start();
0557 }
0558 
0559 void WorksheetEntry::fadeInItem(QGraphicsObject* item, const char* slot)
0560 {
0561     if (!worksheet()->animationsEnabled()) {
0562         recalculateSize();
0563         if (slot)
0564             invokeSlotOnObject(slot, item);
0565         return;
0566     }
0567     if (m_animation) {
0568         // this calculates the new size and calls updateSizeAnimation
0569         layOutForWidth(m_entry_zone_x, size().width(), true);
0570         if (slot)
0571             invokeSlotOnObject(slot, item);
0572         return;
0573     }
0574     QPropertyAnimation* sizeAn = sizeChangeAnimation();
0575     m_animation = new AnimationData;
0576     m_animation->sizeAnimation = sizeAn;
0577     m_animation->sizeAnimation->setEasingCurve(QEasingCurve::OutCubic);
0578     m_animation->opacAnimation = new QPropertyAnimation(item, "opacity", this);
0579     m_animation->opacAnimation->setDuration(200);
0580     m_animation->opacAnimation->setStartValue(0);
0581     m_animation->opacAnimation->setEndValue(1);
0582     m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
0583     m_animation->posAnimation = nullptr;
0584 
0585     m_animation->animation = new QParallelAnimationGroup(this);
0586     m_animation->item = item;
0587     m_animation->slot = slot;
0588 
0589     m_animation->animation->addAnimation(m_animation->sizeAnimation);
0590     m_animation->animation->addAnimation(m_animation->opacAnimation);
0591 
0592     connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
0593 
0594     m_animation->animation->start();
0595 }
0596 
0597 void WorksheetEntry::fadeOutItem(QGraphicsObject* item, const char* slot)
0598 {
0599     // Note: The default value for slot is SLOT(deleteLater()), so item
0600     // will be deleted after the animation.
0601     if (!worksheet()->animationsEnabled()) {
0602         recalculateSize();
0603         if (slot)
0604             invokeSlotOnObject(slot, item);
0605         return;
0606     }
0607     if (m_animation) {
0608         // this calculates the new size and calls updateSizeAnimation
0609         layOutForWidth(m_entry_zone_x, size().width(), true);
0610         if (slot)
0611             invokeSlotOnObject(slot, item);
0612         return;
0613     }
0614     QPropertyAnimation* sizeAn = sizeChangeAnimation();
0615     m_animation = new AnimationData;
0616     m_animation->sizeAnimation = sizeAn;
0617     m_animation->opacAnimation = new QPropertyAnimation(item, "opacity", this);
0618     m_animation->opacAnimation->setDuration(200);
0619     m_animation->opacAnimation->setStartValue(1);
0620     m_animation->opacAnimation->setEndValue(0);
0621     m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
0622     m_animation->posAnimation = nullptr;
0623 
0624     m_animation->animation = new QParallelAnimationGroup(this);
0625     m_animation->item = item;
0626     m_animation->slot = slot;
0627 
0628     m_animation->animation->addAnimation(m_animation->sizeAnimation);
0629     m_animation->animation->addAnimation(m_animation->opacAnimation);
0630 
0631     connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
0632 
0633     m_animation->animation->start();
0634 }
0635 
0636 void WorksheetEntry::endAnimation()
0637 {
0638     if (!m_animation)
0639         return;
0640     QAnimationGroup* anim = m_animation->animation;
0641     if (anim->state() == QAbstractAnimation::Running) {
0642         anim->stop();
0643         if (m_animation->sizeAnimation)
0644             setSize(m_animation->sizeAnimation->endValue().toSizeF());
0645         if (m_animation->opacAnimation) {
0646             qreal opac = m_animation->opacAnimation->endValue().value<qreal>();
0647             m_animation->item->setOpacity(opac);
0648         }
0649         if (m_animation->posAnimation) {
0650             const QPointF& pos = m_animation->posAnimation->endValue().toPointF();
0651             m_animation->item->setPos(pos);
0652         }
0653 
0654         // If the animation was connected to a slot, call it
0655         if (m_animation->slot)
0656             invokeSlotOnObject(m_animation->slot, m_animation->item);
0657     }
0658     m_animation->animation->deleteLater();
0659     delete m_animation;
0660     m_animation = nullptr;
0661 }
0662 
0663 bool WorksheetEntry::animationActive()
0664 {
0665     return m_animation;
0666 }
0667 
0668 void WorksheetEntry::updateSizeAnimation(QSizeF size)
0669 {
0670     // Update the current animation, so that the new ending will be size
0671 
0672     if (!m_animation)
0673         return;
0674 
0675     if (m_aboutToBeRemoved)
0676         // do not modify the remove-animation
0677         return;
0678     if (m_animation->sizeAnimation) {
0679         QPropertyAnimation* sizeAn = m_animation->sizeAnimation;
0680         qreal progress = static_cast<qreal>(sizeAn->currentTime()) /
0681             sizeAn->totalDuration();
0682         QEasingCurve curve = sizeAn->easingCurve();
0683         qreal value = curve.valueForProgress(progress);
0684         sizeAn->setEndValue(size);
0685         QSizeF newStart = 1/(1-value)*(sizeAn->currentValue().toSizeF() - value*size);
0686         sizeAn->setStartValue(newStart);
0687     } else {
0688         m_animation->sizeAnimation = sizeChangeAnimation(size);
0689         int d = m_animation->animation->duration() -
0690             m_animation->animation->currentTime();
0691         m_animation->sizeAnimation->setDuration(d);
0692         m_animation->animation->addAnimation(m_animation->sizeAnimation);
0693     }
0694 }
0695 
0696 void WorksheetEntry::invokeSlotOnObject(const char* slot, QObject* obj)
0697 {
0698     const QMetaObject* metaObj = obj->metaObject();
0699     const QByteArray normSlot = QMetaObject::normalizedSignature(slot);
0700     const int slotIndex = metaObj->indexOfSlot(normSlot.constData());
0701     if (slotIndex == -1)
0702         qDebug() << "Warning: Tried to invoke an invalid slot:" << slot;
0703     const QMetaMethod method = metaObj->method(slotIndex);
0704     method.invoke(obj, Qt::DirectConnection);
0705 }
0706 
0707 bool WorksheetEntry::aboutToBeRemoved()
0708 {
0709     return m_aboutToBeRemoved;
0710 }
0711 
0712 void WorksheetEntry::startRemoving()
0713 {
0714     if (type() == PlaceHolderEntry::Type) //don't do anything if a PlaceholderEntry is being removed in Worksheet::drageMoveEvent()
0715         return;
0716 
0717     int rc = KMessageBox::warningYesNo(nullptr, i18n("Do you really want to remove this entry?"), i18n("Remove Entry"));
0718     if (rc == KMessageBox::No)
0719         return;
0720 
0721     if (!worksheet()->animationsEnabled()) {
0722         m_aboutToBeRemoved = true;
0723         remove();
0724         return;
0725     }
0726     if (m_aboutToBeRemoved)
0727         return;
0728 
0729     if (focusItem()) {
0730         if (!next()) {
0731             if (previous() && previous()->isEmpty() &&
0732                 !previous()->aboutToBeRemoved()) {
0733                 previous()->focusEntry();
0734             } else {
0735                 WorksheetEntry* next = worksheet()->appendCommandEntry();
0736                 setNext(next);
0737                 next->focusEntry();
0738             }
0739         } else {
0740             next()->focusEntry();
0741         }
0742     }
0743 
0744     if (m_animation) {
0745         endAnimation();
0746     }
0747 
0748     m_aboutToBeRemoved = true;
0749     m_animation = new AnimationData;
0750     m_animation->sizeAnimation = new QPropertyAnimation(this, "size", this);
0751     m_animation->sizeAnimation->setDuration(300);
0752     m_animation->sizeAnimation->setEndValue(QSizeF(size().width(), 0));
0753     m_animation->sizeAnimation->setEasingCurve(QEasingCurve::InOutQuad);
0754 
0755     connect(m_animation->sizeAnimation, &QPropertyAnimation::valueChanged, this, &WorksheetEntry::sizeAnimated);
0756     connect(m_animation->sizeAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::remove);
0757 
0758     m_animation->opacAnimation = new QPropertyAnimation(this, "opacity", this);
0759     m_animation->opacAnimation->setDuration(300);
0760     m_animation->opacAnimation->setEndValue(0);
0761     m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
0762     m_animation->posAnimation = nullptr;
0763 
0764     m_animation->animation = new QParallelAnimationGroup(this);
0765     m_animation->animation->addAnimation(m_animation->sizeAnimation);
0766     m_animation->animation->addAnimation(m_animation->opacAnimation);
0767 
0768     m_animation->animation->start();
0769 }
0770 
0771 bool WorksheetEntry::stopRemoving()
0772 {
0773     if (!m_aboutToBeRemoved)
0774         return true;
0775 
0776     if (m_animation->animation->state() == QAbstractAnimation::Stopped)
0777         // we are too late to stop the deletion
0778         return false;
0779 
0780     m_aboutToBeRemoved = false;
0781     m_animation->animation->stop();
0782     m_animation->animation->deleteLater();
0783     delete m_animation;
0784     m_animation = nullptr;
0785     return true;
0786 }
0787 
0788 void WorksheetEntry::remove()
0789 {
0790     if (!m_aboutToBeRemoved)
0791         return;
0792 
0793     if (previous() && previous()->next() == this)
0794         previous()->setNext(next());
0795     else
0796         worksheet()->setFirstEntry(next());
0797     if (next() && next()->previous() == this)
0798         next()->setPrevious(previous());
0799     else
0800         worksheet()->setLastEntry(previous());
0801 
0802     if (type() == HierarchyEntry::Type)
0803         worksheet()->updateHierarchyLayout();
0804 
0805     // make the entry invisible to QGraphicsScene's itemAt() function
0806     forceRemove();
0807 
0808     worksheet()->setModified();
0809 }
0810 
0811 void WorksheetEntry::setSize(QSizeF size)
0812 {
0813     prepareGeometryChange();
0814     if (m_actionBar && size != m_size)
0815         m_actionBar->updatePosition();
0816     m_size = size;
0817 }
0818 
0819 QSizeF WorksheetEntry::size()
0820 {
0821     return m_size;
0822 }
0823 
0824 bool WorksheetEntry::hasActionBar()
0825 {
0826     return m_actionBar;
0827 }
0828 
0829 void WorksheetEntry::showActionBar()
0830 {
0831     if (m_actionBar && !m_actionBarAnimation)
0832         return;
0833 
0834     if (m_actionBarAnimation) {
0835         if (m_actionBarAnimation->endValue().toReal() == 1)
0836             return;
0837         m_actionBarAnimation->stop();
0838         delete m_actionBarAnimation;
0839         m_actionBarAnimation = nullptr;
0840     }
0841 
0842     if (!m_actionBar) {
0843         m_actionBar = new ActionBar(this);
0844 
0845         m_actionBar->addButton(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entry"),
0846                                this, SLOT(startRemoving()));
0847 
0848         WorksheetToolButton* dragButton;
0849         dragButton = m_actionBar->addButton(QIcon::fromTheme(QLatin1String("transform-move")),
0850                                             i18n("Drag Entry"));
0851         connect(dragButton, SIGNAL(pressed()), this, SLOT(startDrag()));
0852 
0853         if (wantToEvaluate()) {
0854             QString toolTip = i18n("Evaluate Entry");
0855             m_actionBar->addButton(QIcon::fromTheme(QLatin1String("media-playback-start")), toolTip,
0856                                    this, SLOT(evaluate()));
0857         }
0858 
0859         m_actionBar->addSpace();
0860 
0861         addActionsToBar(m_actionBar);
0862     }
0863 
0864     if (worksheet()->animationsEnabled()) {
0865         m_actionBarAnimation = new QPropertyAnimation(m_actionBar, "opacity",
0866                                                       this);
0867         m_actionBarAnimation->setStartValue(0);
0868         m_actionBarAnimation->setKeyValueAt(0.666, 0);
0869         m_actionBarAnimation->setEndValue(1);
0870         m_actionBarAnimation->setDuration(600);
0871         connect(m_actionBarAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::deleteActionBarAnimation);
0872 
0873         m_actionBarAnimation->start();
0874     }
0875 }
0876 
0877 void WorksheetEntry::hideActionBar()
0878 {
0879     if (!m_actionBar)
0880         return;
0881 
0882     if (m_actionBarAnimation) {
0883         if (m_actionBarAnimation->endValue().toReal() == 0)
0884             return;
0885         m_actionBarAnimation->stop();
0886         delete m_actionBarAnimation;
0887         m_actionBarAnimation = nullptr;
0888     }
0889 
0890     if (worksheet()->animationsEnabled()) {
0891         m_actionBarAnimation = new QPropertyAnimation(m_actionBar, "opacity",
0892                                                       this);
0893         m_actionBarAnimation->setEndValue(0);
0894         m_actionBarAnimation->setEasingCurve(QEasingCurve::Linear);
0895         m_actionBarAnimation->setDuration(200);
0896         connect(m_actionBarAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::deleteActionBar);
0897 
0898         m_actionBarAnimation->start();
0899     } else {
0900         deleteActionBar();
0901     }
0902 }
0903 
0904 void WorksheetEntry::deleteActionBarAnimation()
0905 {
0906     if (m_actionBarAnimation) {
0907         delete m_actionBarAnimation;
0908         m_actionBarAnimation = nullptr;
0909     }
0910 }
0911 
0912 void WorksheetEntry::deleteActionBar()
0913 {
0914     if (m_actionBar) {
0915         delete m_actionBar;
0916         m_actionBar = nullptr;
0917     }
0918 
0919     deleteActionBarAnimation();
0920 }
0921 
0922 void WorksheetEntry::addActionsToBar(ActionBar*)
0923 {
0924 }
0925 
0926 void WorksheetEntry::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
0927 {
0928     Q_UNUSED(event);
0929     showActionBar();
0930 }
0931 
0932 void WorksheetEntry::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
0933 {
0934     Q_UNUSED(event);
0935     hideActionBar();
0936 }
0937 
0938 WorksheetTextItem* WorksheetEntry::highlightItem()
0939 {
0940     return nullptr;
0941 }
0942 
0943 bool WorksheetEntry::wantFocus()
0944 {
0945     return true;
0946 }
0947 
0948 QJsonObject WorksheetEntry::jupyterMetadata() const
0949 {
0950     return m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject();
0951 }
0952 
0953 void WorksheetEntry::setJupyterMetadata(QJsonObject metadata)
0954 {
0955     if (m_jupyterMetadata == nullptr)
0956         m_jupyterMetadata = new QJsonObject();
0957     *m_jupyterMetadata = metadata;
0958 }
0959 
0960 void WorksheetEntry::forceRemove()
0961 {
0962     hide();
0963     worksheet()->updateLayout();
0964     deleteLater();
0965 }
0966 
0967 bool WorksheetEntry::isCellSelected()
0968 {
0969     return m_controlElement.isSelected;
0970 }
0971 
0972 void WorksheetEntry::setCellSelected(bool val)
0973 {
0974     m_controlElement.isSelected = val;
0975 }
0976 
0977 void WorksheetEntry::moveToNext(bool updateLayout)
0978 {
0979     WorksheetEntry* next = this->next();
0980     if (next)
0981     {
0982         if (next->next())
0983         {
0984             next->next()->setPrevious(this);
0985             this->setNext(next->next());
0986         }
0987         else
0988         {
0989             worksheet()->setLastEntry(this);
0990             this->setNext(nullptr);
0991         }
0992 
0993         next->setPrevious(this->previous());
0994         next->setNext(this);
0995 
0996         this->setPrevious(next);
0997         if (next->previous())
0998             next->previous()->setNext(next);
0999         else
1000             worksheet()->setFirstEntry(next);
1001 
1002         if (updateLayout)
1003             worksheet()->updateLayout();
1004 
1005         worksheet()->setModified();
1006     }
1007 }
1008 
1009 void WorksheetEntry::moveToPrevious(bool updateLayout)
1010 {
1011     WorksheetEntry* previous = this->previous();
1012     if (previous)
1013     {
1014         if (previous->previous())
1015         {
1016             previous->previous()->setNext(this);
1017             this->setPrevious(previous->previous());
1018         }
1019         else
1020         {
1021             worksheet()->setFirstEntry(this);
1022             this->setPrevious(nullptr);
1023         }
1024 
1025         previous->setNext(this->next());
1026         previous->setPrevious(this);
1027 
1028         this->setNext(previous);
1029         if (previous->next())
1030             previous->next()->setPrevious(previous);
1031         else
1032             worksheet()->setLastEntry(previous);
1033 
1034         if (updateLayout)
1035             worksheet()->updateLayout();
1036 
1037         worksheet()->setModified();
1038     }
1039 }
1040 
1041 void WorksheetEntry::recalculateControlGeometry()
1042 {
1043     m_controlElement.setRect(
1044         size().width() - ControlElementWidth - ControlElementBorder, 0, // x,y
1045         ControlElementWidth, size().height() - VerticalMargin // w,h
1046     );
1047     m_controlElement.update();
1048 }
1049 
1050 void WorksheetEntry::updateAfterSettingsChanges()
1051 {
1052     // do nothing;
1053 }