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