1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com>
4     SPDX-FileCopyrightText: 2016-2021 Alexander Semke <alexander.semke@web.de>
5 */
6 
7 #include "worksheetentry.h"
8 #include "commandentry.h"
9 #include "textentry.h"
10 #include "markdownentry.h"
11 #include "latexentry.h"
12 #include "imageentry.h"
13 #include "pagebreakentry.h"
14 #include "horizontalruleentry.h"
15 #include "hierarchyentry.h"
16 #include "settings.h"
17 #include "actionbar.h"
18 #include "worksheettoolbutton.h"
19 #include "worksheetview.h"
20 
21 #include <QDrag>
22 #include <QIcon>
23 #include <QPropertyAnimation>
24 #include <QParallelAnimationGroup>
25 #include <QMetaMethod>
26 #include <QMimeData>
27 #include <QGraphicsProxyWidget>
28 #include <QBitmap>
29 #include <QJsonArray>
30 #include <QJsonObject>
31 #include <QPainter>
32 #include <QGraphicsSceneMouseEvent>
33 
34 #include <KColorScheme>
35 #include <KLocalizedString>
36 #include <KMessageBox>
37 #include <QDebug>
38 
39 struct AnimationData
40 {
41     QAnimationGroup* animation;
42     QPropertyAnimation* sizeAnimation;
43     QPropertyAnimation* opacAnimation;
44     QPropertyAnimation* posAnimation;
45     const char* slot;
46     QGraphicsObject* item;
47 };
48 
49 const qreal WorksheetEntry::VerticalMargin = 4;
50 const qreal WorksheetEntry::ControlElementWidth = 12;
51 const qreal WorksheetEntry::ControlElementBorder = 4;
52 const qreal WorksheetEntry::RightMargin = ControlElementWidth + 2*ControlElementBorder;
53 const qreal WorksheetEntry::HorizontalSpacing = 4;
54 
55 QColor WorksheetEntry::colors[] = {QColor(255,255,255), QColor(0,0,0),
56                                                     QColor(192,0,0), QColor(255,0,0), QColor(255,192,192), //red
57                                                     QColor(0,192,0), QColor(0,255,0), QColor(192,255,192), //green
58                                                     QColor(0,0,192), QColor(0,0,255), QColor(192,192,255), //blue
59                                                     QColor(192,192,0), QColor(255,255,0), QColor(255,255,192), //yellow
60                                                     QColor(0,192,192), QColor(0,255,255), QColor(192,255,255), //cyan
61                                                     QColor(192,0,192), QColor(255,0,255), QColor(255,192,255), //magenta
62                                                     QColor(192,88,0), QColor(255,128,0), QColor(255,168,88), //orange
63                                                     QColor(128,128,128), QColor(160,160,160), QColor(195,195,195) //grey
64                                                     };
65 
66 QString WorksheetEntry::colorNames[] = {i18n("White"), i18n("Black"),
67                                          i18n("Dark Red"), i18n("Red"), i18n("Light Red"),
68                                          i18n("Dark Green"), i18n("Green"), i18n("Light Green"),
69                                          i18n("Dark Blue"), i18n("Blue"), i18n("Light Blue"),
70                                          i18n("Dark Yellow"), i18n("Yellow"), i18n("Light Yellow"),
71                                          i18n("Dark Cyan"), i18n("Cyan"), i18n("Light Cyan"),
72                                          i18n("Dark Magenta"), i18n("Magenta"), i18n("Light Magenta"),
73                                          i18n("Dark Orange"), i18n("Orange"), i18n("Light Orange"),
74                                          i18n("Dark Grey"), i18n("Grey"), i18n("Light Grey")
75                                          };
76 
WorksheetEntry(Worksheet * worksheet)77 WorksheetEntry::WorksheetEntry(Worksheet* worksheet) : QGraphicsObject(), m_controlElement(worksheet, this)
78 {
79     worksheet->addItem(this);
80     setAcceptHoverEvents(true);
81     connect(&m_controlElement, &WorksheetControlItem::drag, this, &WorksheetEntry::startDrag);
82 }
83 
~WorksheetEntry()84 WorksheetEntry::~WorksheetEntry()
85 {
86     emit aboutToBeDeleted();
87     if (next())
88         next()->setPrevious(previous());
89     if (previous())
90         previous()->setNext(next());
91     if (m_animation) {
92         m_animation->animation->deleteLater();
93         delete m_animation;
94     }
95     if (m_jupyterMetadata)
96         delete m_jupyterMetadata;
97     if (type() == HierarchyEntry::Type)
98         worksheet()->updateHierarchyLayout();
99 }
100 
type() const101 int WorksheetEntry::type() const
102 {
103     return Type;
104 }
105 
create(int t,Worksheet * worksheet)106 WorksheetEntry* WorksheetEntry::create(int t, Worksheet* worksheet)
107 {
108     switch(t)
109     {
110     case TextEntry::Type:
111         return new TextEntry(worksheet);
112     case MarkdownEntry::Type:
113         return new MarkdownEntry(worksheet);
114     case CommandEntry::Type:
115         return new CommandEntry(worksheet);
116     case ImageEntry::Type:
117         return new ImageEntry(worksheet);
118     case PageBreakEntry::Type:
119         return new PageBreakEntry(worksheet);
120     case LatexEntry::Type:
121         return new LatexEntry(worksheet);
122     case HorizontalRuleEntry::Type:
123         return new HorizontalRuleEntry(worksheet);
124     case HierarchyEntry::Type:
125         return new HierarchyEntry(worksheet);
126     default:
127         return nullptr;
128     }
129 }
130 
insertCommandEntry()131 void WorksheetEntry::insertCommandEntry()
132 {
133     worksheet()->insertCommandEntry(this);
134 }
135 
insertTextEntry()136 void WorksheetEntry::insertTextEntry()
137 {
138     worksheet()->insertTextEntry(this);
139 }
140 
insertMarkdownEntry()141 void WorksheetEntry::insertMarkdownEntry()
142 {
143     worksheet()->insertMarkdownEntry(this);
144 }
145 
insertLatexEntry()146 void WorksheetEntry::insertLatexEntry()
147 {
148     worksheet()->insertLatexEntry(this);
149 }
150 
insertImageEntry()151 void WorksheetEntry::insertImageEntry()
152 {
153     worksheet()->insertImageEntry(this);
154 }
155 
insertPageBreakEntry()156 void WorksheetEntry::insertPageBreakEntry()
157 {
158     worksheet()->insertPageBreakEntry(this);
159 }
160 
insertHorizontalRuleEntry()161 void WorksheetEntry::insertHorizontalRuleEntry()
162 {
163     worksheet()->insertHorizontalRuleEntry(this);
164 }
165 
insertHierarchyEntry()166 void WorksheetEntry::insertHierarchyEntry()
167 {
168     worksheet()->insertHierarchyEntry(this);
169 }
170 
insertCommandEntryBefore()171 void WorksheetEntry::insertCommandEntryBefore()
172 {
173     worksheet()->insertCommandEntryBefore(this);
174 }
175 
insertTextEntryBefore()176 void WorksheetEntry::insertTextEntryBefore()
177 {
178     worksheet()->insertTextEntryBefore(this);
179 }
180 
insertMarkdownEntryBefore()181 void WorksheetEntry::insertMarkdownEntryBefore()
182 {
183     worksheet()->insertMarkdownEntryBefore(this);
184 }
185 
insertLatexEntryBefore()186 void WorksheetEntry::insertLatexEntryBefore()
187 {
188     worksheet()->insertLatexEntryBefore(this);
189 }
190 
insertImageEntryBefore()191 void WorksheetEntry::insertImageEntryBefore()
192 {
193     worksheet()->insertImageEntryBefore(this);
194 }
195 
insertPageBreakEntryBefore()196 void WorksheetEntry::insertPageBreakEntryBefore()
197 {
198     worksheet()->insertPageBreakEntryBefore(this);
199 }
200 
insertHorizontalRuleEntryBefore()201 void WorksheetEntry::insertHorizontalRuleEntryBefore()
202 {
203     worksheet()->insertHorizontalRuleEntryBefore(this);
204 }
205 
insertHierarchyEntryBefore()206 void WorksheetEntry::insertHierarchyEntryBefore()
207 {
208     worksheet()->insertHierarchyEntryBefore(this);
209 }
210 
convertToCommandEntry()211 void WorksheetEntry::convertToCommandEntry()
212 {
213     worksheet()->changeEntryType(this, CommandEntry::Type);
214 }
215 
convertToTextEntry()216 void WorksheetEntry::convertToTextEntry()
217 {
218     worksheet()->changeEntryType(this, TextEntry::Type);
219 }
220 
convertToMarkdownEntry()221 void WorksheetEntry::convertToMarkdownEntry()
222 {
223     worksheet()->changeEntryType(this, MarkdownEntry::Type);
224 }
225 
convertToLatexEntry()226 void WorksheetEntry::convertToLatexEntry()
227 {
228     worksheet()->changeEntryType(this, LatexEntry::Type);
229 }
230 
convertToImageEntry()231 void WorksheetEntry::convertToImageEntry()
232 {
233     worksheet()->changeEntryType(this, ImageEntry::Type);
234 }
235 
converToPageBreakEntry()236 void WorksheetEntry::converToPageBreakEntry()
237 {
238     worksheet()->changeEntryType(this, PageBreakEntry::Type);
239 }
240 
convertToHorizontalRuleEntry()241 void WorksheetEntry::convertToHorizontalRuleEntry()
242 {
243     worksheet()->changeEntryType(this, HorizontalRuleEntry::Type);
244 }
245 
convertToHierarchyEntry()246 void WorksheetEntry::convertToHierarchyEntry()
247 {
248     worksheet()->changeEntryType(this, HierarchyEntry::Type);
249 }
250 
showCompletion()251 void WorksheetEntry::showCompletion()
252 {
253 }
254 
next() const255 WorksheetEntry* WorksheetEntry::next() const
256 {
257     return m_next;
258 }
259 
previous() const260 WorksheetEntry* WorksheetEntry::previous() const
261 {
262     return m_prev;
263 }
264 
setNext(WorksheetEntry * n)265 void WorksheetEntry::setNext(WorksheetEntry* n)
266 {
267     m_next = n;
268 }
269 
setPrevious(WorksheetEntry * p)270 void WorksheetEntry::setPrevious(WorksheetEntry* p)
271 {
272     m_prev = p;
273 }
274 
startDrag(QPointF grabPos)275 void WorksheetEntry::startDrag(QPointF grabPos)
276 {
277     // We need reset entry cursor manually, because otherwise the entry cursor will be visible on dragable item
278     worksheet()->resetEntryCursor();
279 
280     QDrag* drag = new QDrag(worksheetView());
281     qDebug() << size();
282     const qreal scale = worksheet()->renderer()->scale();
283     QPixmap pixmap((size()*scale).toSize());
284     pixmap.fill(QColor(255, 255, 255, 0));
285     QPainter painter(&pixmap);
286     const QRectF sceneRect = mapRectToScene(boundingRect());
287     worksheet()->render(&painter, pixmap.rect(), sceneRect);
288     painter.end();
289     QBitmap mask = pixmap.createMaskFromColor(QColor(255, 255, 255),
290                                               Qt::MaskInColor);
291     pixmap.setMask(mask);
292 
293     drag->setPixmap(pixmap);
294     if (grabPos.isNull()) {
295         const QPointF scenePos = worksheetView()->sceneCursorPos();
296         drag->setHotSpot((mapFromScene(scenePos) * scale).toPoint());
297     } else {
298         drag->setHotSpot((grabPos * scale).toPoint());
299     }
300     drag->setMimeData(new QMimeData());
301 
302     worksheet()->startDrag(this, drag);
303 }
304 
305 
boundingRect() const306 QRectF WorksheetEntry::boundingRect() const
307 {
308     return QRectF(QPointF(0,0), m_size);
309 }
310 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)311 void WorksheetEntry::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
312 {
313     Q_UNUSED(painter);
314     Q_UNUSED(option);
315     Q_UNUSED(widget);
316 }
317 
focusEntry(int pos,qreal xCoord)318 bool WorksheetEntry::focusEntry(int pos, qreal xCoord)
319 {
320     Q_UNUSED(pos);
321     Q_UNUSED(xCoord);
322 
323     if (flags() & QGraphicsItem::ItemIsFocusable) {
324         setFocus();
325         return true;
326     }
327     return false;
328 }
329 
moveToPreviousEntry(int pos,qreal x)330 void WorksheetEntry::moveToPreviousEntry(int pos, qreal x)
331 {
332     WorksheetEntry* entry = previous();
333     while (entry && !(entry->wantFocus() && entry->focusEntry(pos, x)))
334         entry = entry->previous();
335 }
336 
moveToNextEntry(int pos,qreal x)337 void WorksheetEntry::moveToNextEntry(int pos, qreal x)
338 {
339     WorksheetEntry* entry = next();
340     while (entry && !(entry->wantFocus() && entry->focusEntry(pos, x)))
341         entry = entry->next();
342 }
343 
worksheet()344 Worksheet* WorksheetEntry::worksheet()
345 {
346     return qobject_cast<Worksheet*>(scene());
347 }
348 
worksheetView()349 WorksheetView* WorksheetEntry::worksheetView()
350 {
351     return worksheet()->worksheetView();
352 }
353 
search(const QString & pattern,unsigned flags,QTextDocument::FindFlags qt_flags,const WorksheetCursor & pos)354 WorksheetCursor WorksheetEntry::search(const QString& pattern, unsigned flags,
355                                    QTextDocument::FindFlags qt_flags,
356                                    const WorksheetCursor& pos)
357 {
358     Q_UNUSED(pattern);
359     Q_UNUSED(flags);
360     Q_UNUSED(qt_flags);
361     Q_UNUSED(pos);
362 
363     return WorksheetCursor();
364 }
365 
keyPressEvent(QKeyEvent * event)366 void WorksheetEntry::keyPressEvent(QKeyEvent* event)
367 {
368     // This event is used in Entries that set the ItemIsFocusable flag
369     switch(event->key()) {
370     case Qt::Key_Left:
371     case Qt::Key_Up:
372         if (event->modifiers() == Qt::NoModifier)
373             moveToPreviousEntry(WorksheetTextItem::BottomRight, 0);
374         else if (event->modifiers() == Qt::CTRL)
375             moveToPrevious();
376         break;
377     case Qt::Key_Right:
378     case Qt::Key_Down:
379         if (event->modifiers() == Qt::NoModifier)
380             moveToNextEntry(WorksheetTextItem::TopLeft, 0);
381         else if (event->modifiers() == Qt::CTRL)
382             moveToNext();
383         break;
384         /*case Qt::Key_Enter:
385     case Qt::Key_Return:
386         if (event->modifiers() == Qt::ShiftModifier)
387             evaluate();
388         else if (event->modifiers() == Qt::ControlModifier)
389             worksheet()->insertCommandEntry();
390         break;*/
391     default:
392         event->ignore();
393     }
394 }
395 
contextMenuEvent(QGraphicsSceneContextMenuEvent * event)396 void WorksheetEntry::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
397 {
398     QMenu *menu = worksheet()->createContextMenu();
399     populateMenu(menu, event->pos());
400 
401     menu->popup(event->screenPos());
402 }
403 
populateMenu(QMenu * menu,QPointF pos)404 void WorksheetEntry::populateMenu(QMenu* menu, QPointF pos)
405 {
406     QAction* firstAction = nullptr;
407     if (!menu->actions().isEmpty()) //action() can be empty, s.a. WorksheetTextItem::populateMenu() where this function is called
408         firstAction = menu->actions().first();
409 
410     QAction* action;
411     if (!worksheet()->isRunning() && wantToEvaluate())
412     {
413         action = new QAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate"));
414         connect(action, SIGNAL(triggered()), this, SLOT(evaluate()));
415         menu->insertAction(firstAction, action);
416         menu->insertSeparator(firstAction);
417     }
418 
419     if (m_prev) {
420         action = new QAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Up"));
421     //     connect(action, &QAction::triggered, this, &WorksheetEntry::moveToPrevious); //TODO: doesn't work
422         connect(action, SIGNAL(triggered()), this, SLOT(moveToPrevious()));
423         action->setShortcut(Qt::CTRL + Qt::Key_Up);
424         menu->insertAction(firstAction, action);
425     }
426 
427     if (m_next) {
428         action = new QAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Down"));
429     //     connect(action, &QAction::triggered, this, &WorksheetEntry::moveToNext); //TODO: doesn't work
430         connect(action, SIGNAL(triggered()), this, SLOT(moveToNext()));
431         action->setShortcut(Qt::CTRL + Qt::Key_Down);
432         menu->insertAction(firstAction, action);
433         menu->insertSeparator(firstAction);
434     }
435 
436     action = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove"));
437     connect(action, &QAction::triggered, this, &WorksheetEntry::startRemoving);
438     action->setShortcut(Qt::ShiftModifier + Qt::Key_Delete);
439     menu->insertAction(firstAction, action);
440     menu->insertSeparator(firstAction);
441 
442     worksheet()->populateMenu(menu, mapToScene(pos));
443 }
444 
evaluateCurrentItem()445 bool WorksheetEntry::evaluateCurrentItem()
446 {
447     // A default implementation that works well for most entries,
448     // because they have only one item.
449     return evaluate();
450 }
451 
evaluateNext(EvaluationOption opt)452 void WorksheetEntry::evaluateNext(EvaluationOption opt)
453 {
454     // For cases, when code want *just* evaluate
455     // the entry, for example, on load stage.
456     // This internal evaluation shouldn't marked as
457     // modifying change.
458     if (opt == InternalEvaluation)
459         return;
460 
461     WorksheetEntry* entry = next();
462 
463     while (entry && !entry->wantFocus())
464         entry = entry->next();
465 
466     if (entry) {
467         if (opt == EvaluateNext || Settings::self()->autoEval()) {
468             entry->evaluate(EvaluateNext);
469         } else if (opt == FocusNext) {
470             worksheet()->setModified();
471             entry->focusEntry(WorksheetTextItem::BottomRight);
472         } else {
473             worksheet()->setModified();
474         }
475     } else if (opt != DoNothing) {
476         if (!worksheet()->isLoadingFromFile() && (!isEmpty() || type() != CommandEntry::Type))
477             worksheet()->appendCommandEntry();
478         else
479             focusEntry();
480         worksheet()->setModified();
481     }
482 }
483 
setGeometry(qreal x,qreal x1,qreal y,qreal w)484 qreal WorksheetEntry::setGeometry(qreal x, qreal x1, qreal y, qreal w)
485 {
486     setPos(x, y);
487     m_entry_zone_x = x1;
488     layOutForWidth(x1, w);
489 
490     recalculateControlGeometry();
491 
492     return size().height();
493 }
494 
recalculateSize()495 void WorksheetEntry::recalculateSize()
496 {
497     qreal height = size().height();
498     layOutForWidth(m_entry_zone_x, size().width(), true);
499     if (height != size().height())
500     {
501         recalculateControlGeometry();
502         worksheet()->updateEntrySize(this);
503     }
504 }
505 
sizeChangeAnimation(QSizeF s)506 QPropertyAnimation* WorksheetEntry::sizeChangeAnimation(QSizeF s)
507 {
508     QSizeF oldSize;
509     QSizeF newSize;
510     if (s.isValid()) {
511         oldSize = size();
512         newSize = s;
513     } else {
514         oldSize = size();
515         layOutForWidth(m_entry_zone_x, size().width(), true);
516         newSize = size();
517     }
518 
519     QPropertyAnimation* sizeAn = new QPropertyAnimation(this, "size", this);
520     sizeAn->setDuration(200);
521     sizeAn->setStartValue(oldSize);
522     sizeAn->setEndValue(newSize);
523     sizeAn->setEasingCurve(QEasingCurve::InOutQuad);
524     connect(sizeAn, &QPropertyAnimation::valueChanged, this, &WorksheetEntry::sizeAnimated);
525     return sizeAn;
526 }
527 
sizeAnimated()528 void WorksheetEntry::sizeAnimated()
529 {
530     recalculateControlGeometry();
531     worksheet()->updateEntrySize(this);
532 }
533 
animateSizeChange()534 void WorksheetEntry::animateSizeChange()
535 {
536     if (!worksheet()->animationsEnabled()) {
537         recalculateSize();
538         return;
539     }
540     if (m_animation) {
541         layOutForWidth(m_entry_zone_x, size().width(), true);
542         return;
543     }
544     QPropertyAnimation* sizeAn = sizeChangeAnimation();
545     m_animation = new AnimationData;
546     m_animation->item = nullptr;
547     m_animation->slot = nullptr;
548     m_animation->opacAnimation = nullptr;
549     m_animation->posAnimation = nullptr;
550     m_animation->sizeAnimation = sizeAn;
551     m_animation->sizeAnimation->setEasingCurve(QEasingCurve::OutCubic);
552     m_animation->animation = new QParallelAnimationGroup(this);
553     m_animation->animation->addAnimation(m_animation->sizeAnimation);
554     connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
555     m_animation->animation->start();
556 }
557 
fadeInItem(QGraphicsObject * item,const char * slot)558 void WorksheetEntry::fadeInItem(QGraphicsObject* item, const char* slot)
559 {
560     if (!worksheet()->animationsEnabled()) {
561         recalculateSize();
562         if (slot)
563             invokeSlotOnObject(slot, item);
564         return;
565     }
566     if (m_animation) {
567         // this calculates the new size and calls updateSizeAnimation
568         layOutForWidth(m_entry_zone_x, size().width(), true);
569         if (slot)
570             invokeSlotOnObject(slot, item);
571         return;
572     }
573     QPropertyAnimation* sizeAn = sizeChangeAnimation();
574     m_animation = new AnimationData;
575     m_animation->sizeAnimation = sizeAn;
576     m_animation->sizeAnimation->setEasingCurve(QEasingCurve::OutCubic);
577     m_animation->opacAnimation = new QPropertyAnimation(item, "opacity", this);
578     m_animation->opacAnimation->setDuration(200);
579     m_animation->opacAnimation->setStartValue(0);
580     m_animation->opacAnimation->setEndValue(1);
581     m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
582     m_animation->posAnimation = nullptr;
583 
584     m_animation->animation = new QParallelAnimationGroup(this);
585     m_animation->item = item;
586     m_animation->slot = slot;
587 
588     m_animation->animation->addAnimation(m_animation->sizeAnimation);
589     m_animation->animation->addAnimation(m_animation->opacAnimation);
590 
591     connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
592 
593     m_animation->animation->start();
594 }
595 
fadeOutItem(QGraphicsObject * item,const char * slot)596 void WorksheetEntry::fadeOutItem(QGraphicsObject* item, const char* slot)
597 {
598     // Note: The default value for slot is SLOT(deleteLater()), so item
599     // will be deleted after the animation.
600     if (!worksheet()->animationsEnabled()) {
601         recalculateSize();
602         if (slot)
603             invokeSlotOnObject(slot, item);
604         return;
605     }
606     if (m_animation) {
607         // this calculates the new size and calls updateSizeAnimation
608         layOutForWidth(m_entry_zone_x, size().width(), true);
609         if (slot)
610             invokeSlotOnObject(slot, item);
611         return;
612     }
613     QPropertyAnimation* sizeAn = sizeChangeAnimation();
614     m_animation = new AnimationData;
615     m_animation->sizeAnimation = sizeAn;
616     m_animation->opacAnimation = new QPropertyAnimation(item, "opacity", this);
617     m_animation->opacAnimation->setDuration(200);
618     m_animation->opacAnimation->setStartValue(1);
619     m_animation->opacAnimation->setEndValue(0);
620     m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
621     m_animation->posAnimation = nullptr;
622 
623     m_animation->animation = new QParallelAnimationGroup(this);
624     m_animation->item = item;
625     m_animation->slot = slot;
626 
627     m_animation->animation->addAnimation(m_animation->sizeAnimation);
628     m_animation->animation->addAnimation(m_animation->opacAnimation);
629 
630     connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
631 
632     m_animation->animation->start();
633 }
634 
endAnimation()635 void WorksheetEntry::endAnimation()
636 {
637     if (!m_animation)
638         return;
639     QAnimationGroup* anim = m_animation->animation;
640     if (anim->state() == QAbstractAnimation::Running) {
641         anim->stop();
642         if (m_animation->sizeAnimation)
643             setSize(m_animation->sizeAnimation->endValue().toSizeF());
644         if (m_animation->opacAnimation) {
645             qreal opac = m_animation->opacAnimation->endValue().value<qreal>();
646             m_animation->item->setOpacity(opac);
647         }
648         if (m_animation->posAnimation) {
649             const QPointF& pos = m_animation->posAnimation->endValue().toPointF();
650             m_animation->item->setPos(pos);
651         }
652 
653         // If the animation was connected to a slot, call it
654         if (m_animation->slot)
655             invokeSlotOnObject(m_animation->slot, m_animation->item);
656     }
657     m_animation->animation->deleteLater();
658     delete m_animation;
659     m_animation = nullptr;
660 }
661 
animationActive()662 bool WorksheetEntry::animationActive()
663 {
664     return m_animation;
665 }
666 
updateSizeAnimation(QSizeF size)667 void WorksheetEntry::updateSizeAnimation(QSizeF size)
668 {
669     // Update the current animation, so that the new ending will be size
670 
671     if (!m_animation)
672         return;
673 
674     if (m_aboutToBeRemoved)
675         // do not modify the remove-animation
676         return;
677     if (m_animation->sizeAnimation) {
678         QPropertyAnimation* sizeAn = m_animation->sizeAnimation;
679         qreal progress = static_cast<qreal>(sizeAn->currentTime()) /
680             sizeAn->totalDuration();
681         QEasingCurve curve = sizeAn->easingCurve();
682         qreal value = curve.valueForProgress(progress);
683         sizeAn->setEndValue(size);
684         QSizeF newStart = 1/(1-value)*(sizeAn->currentValue().toSizeF() - value*size);
685         sizeAn->setStartValue(newStart);
686     } else {
687         m_animation->sizeAnimation = sizeChangeAnimation(size);
688         int d = m_animation->animation->duration() -
689             m_animation->animation->currentTime();
690         m_animation->sizeAnimation->setDuration(d);
691         m_animation->animation->addAnimation(m_animation->sizeAnimation);
692     }
693 }
694 
invokeSlotOnObject(const char * slot,QObject * obj)695 void WorksheetEntry::invokeSlotOnObject(const char* slot, QObject* obj)
696 {
697     const QMetaObject* metaObj = obj->metaObject();
698     const QByteArray normSlot = QMetaObject::normalizedSignature(slot);
699     const int slotIndex = metaObj->indexOfSlot(normSlot.constData());
700     if (slotIndex == -1)
701         qDebug() << "Warning: Tried to invoke an invalid slot:" << slot;
702     const QMetaMethod method = metaObj->method(slotIndex);
703     method.invoke(obj, Qt::DirectConnection);
704 }
705 
aboutToBeRemoved()706 bool WorksheetEntry::aboutToBeRemoved()
707 {
708     return m_aboutToBeRemoved;
709 }
710 
startRemoving()711 void WorksheetEntry::startRemoving()
712 {
713     int rc = KMessageBox::warningYesNo(nullptr, i18n("Do you really want to remove this entry?"), i18n("Remove Entry"));
714     if (rc == KMessageBox::No)
715         return;
716 
717     if (!worksheet()->animationsEnabled()) {
718         m_aboutToBeRemoved = true;
719         remove();
720         return;
721     }
722     if (m_aboutToBeRemoved)
723         return;
724 
725     if (focusItem()) {
726         if (!next()) {
727             if (previous() && previous()->isEmpty() &&
728                 !previous()->aboutToBeRemoved()) {
729                 previous()->focusEntry();
730             } else {
731                 WorksheetEntry* next = worksheet()->appendCommandEntry();
732                 setNext(next);
733                 next->focusEntry();
734             }
735         } else {
736             next()->focusEntry();
737         }
738     }
739 
740     if (m_animation) {
741         endAnimation();
742     }
743 
744     m_aboutToBeRemoved = true;
745     m_animation = new AnimationData;
746     m_animation->sizeAnimation = new QPropertyAnimation(this, "size", this);
747     m_animation->sizeAnimation->setDuration(300);
748     m_animation->sizeAnimation->setEndValue(QSizeF(size().width(), 0));
749     m_animation->sizeAnimation->setEasingCurve(QEasingCurve::InOutQuad);
750 
751     connect(m_animation->sizeAnimation, &QPropertyAnimation::valueChanged, this, &WorksheetEntry::sizeAnimated);
752     connect(m_animation->sizeAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::remove);
753 
754     m_animation->opacAnimation = new QPropertyAnimation(this, "opacity", this);
755     m_animation->opacAnimation->setDuration(300);
756     m_animation->opacAnimation->setEndValue(0);
757     m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
758     m_animation->posAnimation = nullptr;
759 
760     m_animation->animation = new QParallelAnimationGroup(this);
761     m_animation->animation->addAnimation(m_animation->sizeAnimation);
762     m_animation->animation->addAnimation(m_animation->opacAnimation);
763 
764     m_animation->animation->start();
765 }
766 
stopRemoving()767 bool WorksheetEntry::stopRemoving()
768 {
769     if (!m_aboutToBeRemoved)
770         return true;
771 
772     if (m_animation->animation->state() == QAbstractAnimation::Stopped)
773         // we are too late to stop the deletion
774         return false;
775 
776     m_aboutToBeRemoved = false;
777     m_animation->animation->stop();
778     m_animation->animation->deleteLater();
779     delete m_animation;
780     m_animation = nullptr;
781     return true;
782 }
783 
remove()784 void WorksheetEntry::remove()
785 {
786     if (!m_aboutToBeRemoved)
787         return;
788 
789     if (previous() && previous()->next() == this)
790         previous()->setNext(next());
791     else
792         worksheet()->setFirstEntry(next());
793     if (next() && next()->previous() == this)
794         next()->setPrevious(previous());
795     else
796         worksheet()->setLastEntry(previous());
797 
798     if (type() == HierarchyEntry::Type)
799         worksheet()->updateHierarchyLayout();
800 
801     // make the entry invisible to QGraphicsScene's itemAt() function
802     forceRemove();
803 
804     worksheet()->setModified();
805 }
806 
setSize(QSizeF size)807 void WorksheetEntry::setSize(QSizeF size)
808 {
809     prepareGeometryChange();
810     if (m_actionBar && size != m_size)
811         m_actionBar->updatePosition();
812     m_size = size;
813 }
814 
size()815 QSizeF WorksheetEntry::size()
816 {
817     return m_size;
818 }
819 
hasActionBar()820 bool WorksheetEntry::hasActionBar()
821 {
822     return m_actionBar;
823 }
824 
showActionBar()825 void WorksheetEntry::showActionBar()
826 {
827     if (m_actionBar && !m_actionBarAnimation)
828         return;
829 
830     if (m_actionBarAnimation) {
831         if (m_actionBarAnimation->endValue().toReal() == 1)
832             return;
833         m_actionBarAnimation->stop();
834         delete m_actionBarAnimation;
835         m_actionBarAnimation = nullptr;
836     }
837 
838     if (!m_actionBar) {
839         m_actionBar = new ActionBar(this);
840 
841         m_actionBar->addButton(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entry"),
842                                this, SLOT(startRemoving()));
843 
844         WorksheetToolButton* dragButton;
845         dragButton = m_actionBar->addButton(QIcon::fromTheme(QLatin1String("transform-move")),
846                                             i18n("Drag Entry"));
847         connect(dragButton, SIGNAL(pressed()), this, SLOT(startDrag()));
848 
849         if (wantToEvaluate()) {
850             QString toolTip = i18n("Evaluate Entry");
851             m_actionBar->addButton(QIcon::fromTheme(QLatin1String("media-playback-start")), toolTip,
852                                    this, SLOT(evaluate()));
853         }
854 
855         m_actionBar->addSpace();
856 
857         addActionsToBar(m_actionBar);
858     }
859 
860     if (worksheet()->animationsEnabled()) {
861         m_actionBarAnimation = new QPropertyAnimation(m_actionBar, "opacity",
862                                                       this);
863         m_actionBarAnimation->setStartValue(0);
864         m_actionBarAnimation->setKeyValueAt(0.666, 0);
865         m_actionBarAnimation->setEndValue(1);
866         m_actionBarAnimation->setDuration(600);
867         connect(m_actionBarAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::deleteActionBarAnimation);
868 
869         m_actionBarAnimation->start();
870     }
871 }
872 
hideActionBar()873 void WorksheetEntry::hideActionBar()
874 {
875     if (!m_actionBar)
876         return;
877 
878     if (m_actionBarAnimation) {
879         if (m_actionBarAnimation->endValue().toReal() == 0)
880             return;
881         m_actionBarAnimation->stop();
882         delete m_actionBarAnimation;
883         m_actionBarAnimation = nullptr;
884     }
885 
886     if (worksheet()->animationsEnabled()) {
887         m_actionBarAnimation = new QPropertyAnimation(m_actionBar, "opacity",
888                                                       this);
889         m_actionBarAnimation->setEndValue(0);
890         m_actionBarAnimation->setEasingCurve(QEasingCurve::Linear);
891         m_actionBarAnimation->setDuration(200);
892         connect(m_actionBarAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::deleteActionBar);
893 
894         m_actionBarAnimation->start();
895     } else {
896         deleteActionBar();
897     }
898 }
899 
deleteActionBarAnimation()900 void WorksheetEntry::deleteActionBarAnimation()
901 {
902     if (m_actionBarAnimation) {
903         delete m_actionBarAnimation;
904         m_actionBarAnimation = nullptr;
905     }
906 }
907 
deleteActionBar()908 void WorksheetEntry::deleteActionBar()
909 {
910     if (m_actionBar) {
911         delete m_actionBar;
912         m_actionBar = nullptr;
913     }
914 
915     deleteActionBarAnimation();
916 }
917 
addActionsToBar(ActionBar *)918 void WorksheetEntry::addActionsToBar(ActionBar*)
919 {
920 }
921 
hoverEnterEvent(QGraphicsSceneHoverEvent * event)922 void WorksheetEntry::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
923 {
924     Q_UNUSED(event);
925     showActionBar();
926 }
927 
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)928 void WorksheetEntry::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
929 {
930     Q_UNUSED(event);
931     hideActionBar();
932 }
933 
highlightItem()934 WorksheetTextItem* WorksheetEntry::highlightItem()
935 {
936     return nullptr;
937 }
938 
wantFocus()939 bool WorksheetEntry::wantFocus()
940 {
941     return true;
942 }
943 
jupyterMetadata() const944 QJsonObject WorksheetEntry::jupyterMetadata() const
945 {
946     return m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject();
947 }
948 
setJupyterMetadata(QJsonObject metadata)949 void WorksheetEntry::setJupyterMetadata(QJsonObject metadata)
950 {
951     if (m_jupyterMetadata == nullptr)
952         m_jupyterMetadata = new QJsonObject();
953     *m_jupyterMetadata = metadata;
954 }
955 
forceRemove()956 void WorksheetEntry::forceRemove()
957 {
958     hide();
959     worksheet()->updateLayout();
960     deleteLater();
961 }
962 
isCellSelected()963 bool WorksheetEntry::isCellSelected()
964 {
965     return m_controlElement.isSelected;
966 }
967 
setCellSelected(bool val)968 void WorksheetEntry::setCellSelected(bool val)
969 {
970     m_controlElement.isSelected = val;
971 }
972 
moveToNext(bool updateLayout)973 void WorksheetEntry::moveToNext(bool updateLayout)
974 {
975     WorksheetEntry* next = this->next();
976     if (next)
977     {
978         if (next->next())
979         {
980             next->next()->setPrevious(this);
981             this->setNext(next->next());
982         }
983         else
984         {
985             worksheet()->setLastEntry(this);
986             this->setNext(nullptr);
987         }
988 
989         next->setPrevious(this->previous());
990         next->setNext(this);
991 
992         this->setPrevious(next);
993         if (next->previous())
994             next->previous()->setNext(next);
995         else
996             worksheet()->setFirstEntry(next);
997 
998         if (updateLayout)
999             worksheet()->updateLayout();
1000 
1001         worksheet()->setModified();
1002     }
1003 }
1004 
moveToPrevious(bool updateLayout)1005 void WorksheetEntry::moveToPrevious(bool updateLayout)
1006 {
1007     WorksheetEntry* previous = this->previous();
1008     if (previous)
1009     {
1010         if (previous->previous())
1011         {
1012             previous->previous()->setNext(this);
1013             this->setPrevious(previous->previous());
1014         }
1015         else
1016         {
1017             worksheet()->setFirstEntry(this);
1018             this->setPrevious(nullptr);
1019         }
1020 
1021         previous->setNext(this->next());
1022         previous->setPrevious(this);
1023 
1024         this->setNext(previous);
1025         if (previous->next())
1026             previous->next()->setPrevious(previous);
1027         else
1028             worksheet()->setLastEntry(previous);
1029 
1030         if (updateLayout)
1031             worksheet()->updateLayout();
1032 
1033         worksheet()->setModified();
1034     }
1035 }
1036 
recalculateControlGeometry()1037 void WorksheetEntry::recalculateControlGeometry()
1038 {
1039     m_controlElement.setRect(
1040         size().width() - ControlElementWidth - ControlElementBorder, 0, // x,y
1041         ControlElementWidth, size().height() - VerticalMargin // w,h
1042     );
1043     m_controlElement.update();
1044 }
1045 
updateAfterSettingsChanges()1046 void WorksheetEntry::updateAfterSettingsChanges()
1047 {
1048     // do nothing;
1049 }
1050