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

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     if (Settings::warnAboutEntryDelete())
0718     {
0719         int rc = KMessageBox::warningYesNo(nullptr, i18n("Do you really want to remove this entry?"), i18n("Remove Entry"));
0720         if (rc == KMessageBox::No)
0721             return;
0722     }
0723 
0724     if (!worksheet()->animationsEnabled()) {
0725         m_aboutToBeRemoved = true;
0726         remove();
0727         return;
0728     }
0729 
0730     if (m_aboutToBeRemoved)
0731         return;
0732 
0733     if (focusItem()) {
0734         if (!next()) {
0735             if (previous() && previous()->isEmpty() &&
0736                 !previous()->aboutToBeRemoved()) {
0737                 previous()->focusEntry();
0738             } else {
0739                 WorksheetEntry* next = worksheet()->appendCommandEntry();
0740                 setNext(next);
0741                 next->focusEntry();
0742             }
0743         } else {
0744             next()->focusEntry();
0745         }
0746     }
0747 
0748     if (m_animation) {
0749         endAnimation();
0750     }
0751 
0752     m_aboutToBeRemoved = true;
0753     m_animation = new AnimationData;
0754     m_animation->sizeAnimation = new QPropertyAnimation(this, "size", this);
0755     m_animation->sizeAnimation->setDuration(300);
0756     m_animation->sizeAnimation->setEndValue(QSizeF(size().width(), 0));
0757     m_animation->sizeAnimation->setEasingCurve(QEasingCurve::InOutQuad);
0758 
0759     connect(m_animation->sizeAnimation, &QPropertyAnimation::valueChanged, this, &WorksheetEntry::sizeAnimated);
0760     connect(m_animation->sizeAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::remove);
0761 
0762     m_animation->opacAnimation = new QPropertyAnimation(this, "opacity", this);
0763     m_animation->opacAnimation->setDuration(300);
0764     m_animation->opacAnimation->setEndValue(0);
0765     m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
0766     m_animation->posAnimation = nullptr;
0767 
0768     m_animation->animation = new QParallelAnimationGroup(this);
0769     m_animation->animation->addAnimation(m_animation->sizeAnimation);
0770     m_animation->animation->addAnimation(m_animation->opacAnimation);
0771 
0772     m_animation->animation->start();
0773 }
0774 
0775 bool WorksheetEntry::stopRemoving()
0776 {
0777     if (!m_aboutToBeRemoved)
0778         return true;
0779 
0780     if (m_animation->animation->state() == QAbstractAnimation::Stopped)
0781         // we are too late to stop the deletion
0782         return false;
0783 
0784     m_aboutToBeRemoved = false;
0785     m_animation->animation->stop();
0786     m_animation->animation->deleteLater();
0787     delete m_animation;
0788     m_animation = nullptr;
0789     return true;
0790 }
0791 
0792 void WorksheetEntry::remove()
0793 {
0794     if (!m_aboutToBeRemoved)
0795         return;
0796 
0797     if (previous() && previous()->next() == this)
0798         previous()->setNext(next());
0799     else
0800         worksheet()->setFirstEntry(next());
0801     if (next() && next()->previous() == this)
0802         next()->setPrevious(previous());
0803     else
0804         worksheet()->setLastEntry(previous());
0805 
0806     if (type() == HierarchyEntry::Type)
0807         worksheet()->updateHierarchyLayout();
0808 
0809     // make the entry invisible to QGraphicsScene's itemAt() function
0810     forceRemove();
0811 
0812     worksheet()->setModified();
0813 }
0814 
0815 void WorksheetEntry::setSize(QSizeF size)
0816 {
0817     prepareGeometryChange();
0818     if (m_actionBar && size != m_size)
0819         m_actionBar->updatePosition();
0820     m_size = size;
0821 }
0822 
0823 QSizeF WorksheetEntry::size()
0824 {
0825     return m_size;
0826 }
0827 
0828 bool WorksheetEntry::hasActionBar()
0829 {
0830     return m_actionBar;
0831 }
0832 
0833 void WorksheetEntry::showActionBar()
0834 {
0835     if (m_actionBar && !m_actionBarAnimation)
0836         return;
0837 
0838     if (m_actionBarAnimation) {
0839         if (m_actionBarAnimation->endValue().toReal() == 1)
0840             return;
0841         m_actionBarAnimation->stop();
0842         delete m_actionBarAnimation;
0843         m_actionBarAnimation = nullptr;
0844     }
0845 
0846     if (!m_actionBar) {
0847         m_actionBar = new ActionBar(this);
0848 
0849         m_actionBar->addButton(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entry"),
0850                                this, SLOT(startRemoving()));
0851 
0852         WorksheetToolButton* dragButton;
0853         dragButton = m_actionBar->addButton(QIcon::fromTheme(QLatin1String("transform-move")),
0854                                             i18n("Drag Entry"));
0855         connect(dragButton, SIGNAL(pressed()), this, SLOT(startDrag()));
0856 
0857         if (wantToEvaluate()) {
0858             QString toolTip = i18n("Evaluate Entry");
0859             m_actionBar->addButton(QIcon::fromTheme(QLatin1String("media-playback-start")), toolTip,
0860                                    this, SLOT(evaluate()));
0861         }
0862 
0863         m_actionBar->addSpace();
0864 
0865         addActionsToBar(m_actionBar);
0866     }
0867 
0868     if (worksheet()->animationsEnabled()) {
0869         m_actionBarAnimation = new QPropertyAnimation(m_actionBar, "opacity",
0870                                                       this);
0871         m_actionBarAnimation->setStartValue(0);
0872         m_actionBarAnimation->setKeyValueAt(0.666, 0);
0873         m_actionBarAnimation->setEndValue(1);
0874         m_actionBarAnimation->setDuration(600);
0875         connect(m_actionBarAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::deleteActionBarAnimation);
0876 
0877         m_actionBarAnimation->start();
0878     }
0879 }
0880 
0881 void WorksheetEntry::hideActionBar()
0882 {
0883     if (!m_actionBar)
0884         return;
0885 
0886     if (m_actionBarAnimation) {
0887         if (m_actionBarAnimation->endValue().toReal() == 0)
0888             return;
0889         m_actionBarAnimation->stop();
0890         delete m_actionBarAnimation;
0891         m_actionBarAnimation = nullptr;
0892     }
0893 
0894     if (worksheet()->animationsEnabled()) {
0895         m_actionBarAnimation = new QPropertyAnimation(m_actionBar, "opacity",
0896                                                       this);
0897         m_actionBarAnimation->setEndValue(0);
0898         m_actionBarAnimation->setEasingCurve(QEasingCurve::Linear);
0899         m_actionBarAnimation->setDuration(200);
0900         connect(m_actionBarAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::deleteActionBar);
0901 
0902         m_actionBarAnimation->start();
0903     } else {
0904         deleteActionBar();
0905     }
0906 }
0907 
0908 void WorksheetEntry::deleteActionBarAnimation()
0909 {
0910     if (m_actionBarAnimation) {
0911         delete m_actionBarAnimation;
0912         m_actionBarAnimation = nullptr;
0913     }
0914 }
0915 
0916 void WorksheetEntry::deleteActionBar()
0917 {
0918     if (m_actionBar) {
0919         delete m_actionBar;
0920         m_actionBar = nullptr;
0921     }
0922 
0923     deleteActionBarAnimation();
0924 }
0925 
0926 void WorksheetEntry::addActionsToBar(ActionBar*)
0927 {
0928 }
0929 
0930 void WorksheetEntry::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
0931 {
0932     Q_UNUSED(event);
0933     showActionBar();
0934 }
0935 
0936 void WorksheetEntry::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
0937 {
0938     Q_UNUSED(event);
0939     hideActionBar();
0940 }
0941 
0942 WorksheetTextItem* WorksheetEntry::highlightItem()
0943 {
0944     return nullptr;
0945 }
0946 
0947 bool WorksheetEntry::wantFocus()
0948 {
0949     return true;
0950 }
0951 
0952 QJsonObject WorksheetEntry::jupyterMetadata() const
0953 {
0954     return m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject();
0955 }
0956 
0957 void WorksheetEntry::setJupyterMetadata(QJsonObject metadata)
0958 {
0959     if (m_jupyterMetadata == nullptr)
0960         m_jupyterMetadata = new QJsonObject();
0961     *m_jupyterMetadata = metadata;
0962 }
0963 
0964 void WorksheetEntry::forceRemove()
0965 {
0966     hide();
0967     worksheet()->updateLayout();
0968     deleteLater();
0969 }
0970 
0971 bool WorksheetEntry::isCellSelected()
0972 {
0973     return m_controlElement.isSelected;
0974 }
0975 
0976 void WorksheetEntry::setCellSelected(bool val)
0977 {
0978     m_controlElement.isSelected = val;
0979 }
0980 
0981 void WorksheetEntry::moveToNext(bool updateLayout)
0982 {
0983     WorksheetEntry* next = this->next();
0984     if (next)
0985     {
0986         if (next->next())
0987         {
0988             next->next()->setPrevious(this);
0989             this->setNext(next->next());
0990         }
0991         else
0992         {
0993             worksheet()->setLastEntry(this);
0994             this->setNext(nullptr);
0995         }
0996 
0997         next->setPrevious(this->previous());
0998         next->setNext(this);
0999 
1000         this->setPrevious(next);
1001         if (next->previous())
1002             next->previous()->setNext(next);
1003         else
1004             worksheet()->setFirstEntry(next);
1005 
1006         if (updateLayout)
1007             worksheet()->updateLayout();
1008 
1009         worksheet()->setModified();
1010     }
1011 }
1012 
1013 void WorksheetEntry::moveToPrevious(bool updateLayout)
1014 {
1015     WorksheetEntry* previous = this->previous();
1016     if (previous)
1017     {
1018         if (previous->previous())
1019         {
1020             previous->previous()->setNext(this);
1021             this->setPrevious(previous->previous());
1022         }
1023         else
1024         {
1025             worksheet()->setFirstEntry(this);
1026             this->setPrevious(nullptr);
1027         }
1028 
1029         previous->setNext(this->next());
1030         previous->setPrevious(this);
1031 
1032         this->setNext(previous);
1033         if (previous->next())
1034             previous->next()->setPrevious(previous);
1035         else
1036             worksheet()->setLastEntry(previous);
1037 
1038         if (updateLayout)
1039             worksheet()->updateLayout();
1040 
1041         worksheet()->setModified();
1042     }
1043 }
1044 
1045 void WorksheetEntry::recalculateControlGeometry()
1046 {
1047     m_controlElement.setRect(
1048         size().width() - ControlElementWidth - ControlElementBorder, 0, // x,y
1049         ControlElementWidth, size().height() - VerticalMargin // w,h
1050     );
1051     m_controlElement.update();
1052 }
1053 
1054 void WorksheetEntry::updateAfterSettingsChanges()
1055 {
1056     // do nothing;
1057 }