Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAnimCurvesView.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Jouni Pentikäinen <joupent@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "KisAnimCurvesView.h"
7
8#include <QPaintEvent>
9#include <QMouseEvent>
10#include <QApplication>
11#include <QScrollBar>
12#include <qpainter.h>
13#include <QtMath>
14
15#include "KisAnimCurvesModel.h"
21#include "kis_zoom_button.h"
23#include <kis_painting_tweaks.h>
24#include "kis_zoom_scrollbar.h"
25
57
59 : QAbstractItemView(parent)
60 , m_d(new Private())
61{
62 m_d->horizontalHeader = new KisAnimTimelineTimeHeader(this);
63
64 m_d->verticalHeader = new KisAnimCurvesValuesHeader(this);
65 m_d->itemDelegate = new KisAnimCurvesKeyDelegate(m_d->horizontalHeader, m_d->verticalHeader, this);
66
67 m_d->modifiersCatcher = new KisCustomModifiersCatcher(this);
68 m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space);
69
70 setSelectionMode(QAbstractItemView::ExtendedSelection);
71
72 KisZoomableScrollBar* horiZoomableBar = new KisZoomableScrollBar(this);
73 setHorizontalScrollBar(horiZoomableBar);
74 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
75 setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
76
77 connect(horiZoomableBar, &KisZoomableScrollBar::valueChanged, [this](qreal value){
78 m_d->horizontalHeader->setPixelOffset(value);
80 viewport()->update();
81 });
82
83 connect(horiZoomableBar, &KisZoomableScrollBar::sliderReleased, this, &KisAnimCurvesView::slotUpdateHorizontalScrollbarSize);
84
85 connect(horiZoomableBar, &KisZoomableScrollBar::overscroll, [this](qreal overscroll){
86 m_d->horizontalHeader->setPixelOffset(m_d->horizontalHeader->offset() + overscroll);
89 viewport()->update();
90 });
91
92 connect(horiZoomableBar, &KisZoomableScrollBar::zoom, [this](qreal zoomDelta){
93 qreal currentZoomLevel = m_d->horizontalHeader->zoom();
94 m_d->horizontalHeader->setZoom(currentZoomLevel + zoomDelta);
97 viewport()->update();
98 });
99
100
101 KisZoomableScrollBar* vertZoomableBar = new KisZoomableScrollBar(this);
102 setVerticalScrollBar(vertZoomableBar);
103 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
104 setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
105 vertZoomableBar->setWheelOverscrollSensitivity( 0.04f );
106
107 connect(vertZoomableBar, &KisZoomableScrollBar::zoom, [this](qreal zoomDelta){
108 const qreal currentZoomLevel = m_d->verticalHeader->scale();
109 m_d->verticalHeader->setScale(currentZoomLevel + zoomDelta / m_d->verticalHeader->step());
110 });
111
112 connect(vertZoomableBar, &KisZoomableScrollBar::overscroll, [this](qreal overscroll){
113 qreal currentOffset = m_d->verticalHeader->valueOffset();
114 m_d->verticalHeader->setValueOffset(currentOffset - overscroll * m_d->verticalHeader->step() * 0.25);
115 });
116
117 connect(m_d->verticalHeader, &KisAnimCurvesValuesHeader::scaleChanged, [this](qreal){
118 viewport()->update();
119 });
120
121 connect(m_d->verticalHeader, &KisAnimCurvesValuesHeader::valueOffsetChanged, [this](qreal){
122 viewport()->update();
123 });
124
125 QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
126 if (scroller){
127 connect(scroller, SIGNAL(stateChanged(QScroller::State)),
128 this, SLOT(slotScrollerStateChanged(QScroller::State)));
129
130 QScrollerProperties props = scroller->scrollerProperties();
131 props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
132 props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
133 scroller->setScrollerProperties(props);
134 }
135}
136
139
140void KisAnimCurvesView::setModel(QAbstractItemModel *model)
141{
142 m_d->model = dynamic_cast<KisAnimCurvesModel*>(model);
143
144 QAbstractItemView::setModel(model);
145 m_d->horizontalHeader->setModel(model);
146
147 connect(model, &QAbstractItemModel::rowsInserted,
149
150 connect(model, &QAbstractItemModel::rowsRemoved,
152
153 connect(model, &QAbstractItemModel::dataChanged,
155
156 connect(model, &QAbstractItemModel::headerDataChanged,
158
159 connect(selectionModel(), &QItemSelectionModel::selectionChanged,
160 [this](const QItemSelection& newSelection, const QItemSelection& /*oldSelection*/) {
161 if (newSelection.count() == 0) {
162 activeDataChanged(QModelIndex());
163 } else {
164 activeDataChanged(selectionModel()->currentIndex());
165 }
166 });
167
170}
171
172QRect KisAnimCurvesView::visualRect(const QModelIndex &index) const
173{
174 return m_d->itemDelegate->itemRect(index);
175}
176
177void KisAnimCurvesView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
178{
179 // TODO
180 Q_UNUSED(index);
181 Q_UNUSED(hint);
182}
183
184QModelIndex KisAnimCurvesView::indexAt(const QPoint &point) const
185{
186 if (!model()) return QModelIndex();
187
188 int time = m_d->horizontalHeader->logicalIndexAt(point.x());
189
190 int rows = model()->rowCount();
191 for (int row=0; row < rows; row++) {
192 QModelIndex index = model()->index(row, time);
193
194 if (index.data(KisTimeBasedItemModel::SpecialKeyframeExists).toBool()
195 && index.data(KisAnimCurvesModel::CurveVisibleRole).toBool()) {
196 QRect nodePos = m_d->itemDelegate->itemRect(index);
197
198 if (nodePos.contains(point)) {
199 return index;
200 }
201 }
202 }
203
204 return QModelIndex();
205}
206
207void KisAnimCurvesView::paintEvent(QPaintEvent *event)
208{
209 QPainter painter(viewport());
210
211 QRect rect = event->rect();
212 rect.translate(dirtyRegionOffset());
213
214 int firstFrame = m_d->horizontalHeader->logicalIndexAt(rect.left());
215 int lastFrame = m_d->horizontalHeader->logicalIndexAt(rect.right());
216 if (lastFrame == -1) lastFrame = model()->columnCount();
217
218 paintGrid(painter);
219 paintCurves(painter, firstFrame, lastFrame);
220 paintKeyframes(painter, firstFrame, lastFrame);
221}
222
223void KisAnimCurvesView::paintGrid(QPainter &painter)
224{
225 const QColor backgroundColor = qApp->palette().window().color();
226 const QColor textColor = qApp->palette().text().color();
227 const QColor lineColor = KisPaintingTweaks::blendColors(textColor, backgroundColor, 0.1);
228 const QColor zeroLineColor = qApp->palette().highlight().color();
229 const QColor activeFrameColor = KisAnimTimelineColors::instance()->headerActive().color();
230
231 // Draw vertical lines..
232 const int visibleFrames = m_d->horizontalHeader->estimateLastVisibleColumn() - m_d->horizontalHeader->estimateFirstVisibleColumn() + 1;
233 const int firstVisibleFrame = qMax( m_d->horizontalHeader->estimateFirstVisibleColumn() - 1, 0);
234 for (int time = 0; time <= visibleFrames; time++) {
235 QVariant data = m_d->model->headerData(firstVisibleFrame + time, Qt::Horizontal, KisTimeBasedItemModel::ActiveFrameRole);
236 const bool activeFrame = data.isValid() && data.toBool();
237
238 data = m_d->model->headerData(firstVisibleFrame + time, Qt::Horizontal, KisTimeBasedItemModel::WithinClipRange);
239 const bool withinClipRange = data.isValid() && data.toBool();
240
241 const int offsetHori = m_d->horizontalHeader ? m_d->horizontalHeader->offset() : 0;
242 const int stepHori = m_d->horizontalHeader->defaultSectionSize();
243 const int xPosition = stepHori * (firstVisibleFrame + time) - offsetHori;
244
245 QRect frameRect = QRect(xPosition, -10, stepHori, 9999);
246
247 const QPoint top = frameRect.topLeft() + 0.5 * (frameRect.topRight() - frameRect.topLeft());
248 const QPoint bottom = frameRect.bottomLeft() + 0.5 * (frameRect.bottomRight() - frameRect.bottomLeft());
249
250 QColor fadedLineColor = lineColor;
251 fadedLineColor.setAlphaF(0.33);
252
253 QColor finalColor = withinClipRange ? lineColor : fadedLineColor;
254 finalColor = activeFrame ? activeFrameColor : finalColor;
255
256 painter.setPen(finalColor);
257 painter.drawLine(top, bottom);
258 }
259
260 // Draw horizontal lines..
261 const int visibleSteps = m_d->verticalHeader->visibleValueDifference() / m_d->verticalHeader->step();
262 const qreal stepAmount = m_d->verticalHeader->step();
263 for (int step = 0; step <= visibleSteps; step++) {
264 const qreal value = m_d->verticalHeader->firstVisibleStep() + stepAmount * step;
265 const int CORRECTION = -1;
266 int yPosition = m_d->verticalHeader->valueToWidget(value);
267
268 QRect frameRect = QRect(-10, yPosition + CORRECTION, 9999, 1);
269
270 const QPoint left = frameRect.topLeft();
271 const QPoint right = frameRect.topRight();
272
273 painter.setPen(value == 0 ? zeroLineColor : lineColor);
274 painter.drawLine(left, right);
275 }
276}
277
278void KisAnimCurvesView::paintCurves(QPainter &painter, int firstFrame, int lastFrame)
279{
280 int channels = model()->rowCount();
281 for (int channel = 0; channel < channels; channel++) {
282 QModelIndex index0 = model()->index(channel, 0);
283
284 if (!isIndexHidden(index0)) {
285 QColor color = index0.data(KisAnimCurvesModel::CurveColorRole).value<QColor>();
286 painter.setPen(QPen(color, 1));
287 painter.setRenderHint(QPainter::Antialiasing);
288
289 paintCurve(channel, firstFrame, lastFrame, painter);
290 }
291 }
292}
293
294void KisAnimCurvesView::paintCurve(int channel, int firstFrame, int lastFrame, QPainter &painter)
295{
296 int selectionOffset = m_d->isDraggingKeyframe ? (m_d->dragOffset.x() / m_d->horizontalHeader->defaultSectionSize()) : 0;
297
298 QModelIndex index = findNextKeyframeIndex(channel, firstFrame+1, selectionOffset, true);
299 if (!index.isValid()) {
300 index = findNextKeyframeIndex(channel, firstFrame, selectionOffset, false);
301 }
302 if (!index.isValid()) return;
303
304 QPointF previousKeyPos = m_d->itemDelegate->nodeCenter(index, selectionModel()->isSelected(index));
305 QPointF rightTangent = m_d->itemDelegate->rightHandle(index, index == currentIndex());
306
307 while(index.column() <= lastFrame) {
308 int interpolationMode = index.data(KisAnimCurvesModel::InterpolationModeRole).toInt();
309
310 int time = (m_d->isDraggingKeyframe && selectionModel()->isSelected(index)) ? index.column() + selectionOffset : index.column();
311 index = findNextKeyframeIndex(channel, time, selectionOffset, false);
312 if (!index.isValid()) return;
313
314 bool active = (index == currentIndex());
315 QPointF nextKeyPos = m_d->itemDelegate->nodeCenter(index, selectionModel()->isSelected(index));
316 QPointF leftTangent = m_d->itemDelegate->leftHandle(index, active);
317
318 if (interpolationMode == KisScalarKeyframe::Constant) {
319 painter.drawLine(previousKeyPos, QPointF(nextKeyPos.x(), previousKeyPos.y()));
320 } else if (interpolationMode == KisScalarKeyframe::Linear) {
321 painter.drawLine(previousKeyPos, nextKeyPos);
322 } else {
323 QVariant limitData = m_d->model->data(m_d->model->index(channel, index.column()), KisAnimCurvesModel::ChannelLimits);
324 paintCurveSegment(painter, previousKeyPos, rightTangent, leftTangent, nextKeyPos, limitData);
325 }
326
327 previousKeyPos = nextKeyPos;
328 rightTangent = m_d->itemDelegate->rightHandle(index, active);
329 }
330}
331
332void KisAnimCurvesView::paintCurveSegment(QPainter &painter, QPointF pos1, QPointF rightTangent, QPointF leftTangent, QPointF pos2, QVariant limitData) {
333 const int steps = 32;
334 QPointF previousPos;
335
336 for (int step = 0; step <= steps; step++) {
337 qreal t = 1.0 * step / steps;
338
339 QPointF nextPos = KisScalarKeyframeChannel::interpolate(pos1, rightTangent, leftTangent, pos2, t);
340 if (limitData.isValid()) {
341 ChannelLimitsMetatype limits = limitData.value<ChannelLimitsMetatype>();
342
343 nextPos.setY(qMin(nextPos.y(), m_d->verticalHeader->valueToWidget(limits.first)));
344 nextPos.setY(qMax(nextPos.y(), m_d->verticalHeader->valueToWidget(limits.second)));
345 }
346
347 if (step > 0) {
348 painter.drawLine(previousPos, nextPos);
349 }
350
351 previousPos = nextPos;
352 }
353}
354
355void KisAnimCurvesView::paintKeyframes(QPainter &painter, int firstFrame, int lastFrame)
356{
357 int channels = model()->rowCount();
358 for (int channel = 0; channel < channels; channel++) {
359 for (int time=firstFrame; time <= lastFrame; time++) {
360 QModelIndex index = model()->index(channel, time);
361 bool keyframeExists = model()->data(index, KisAnimCurvesModel::SpecialKeyframeExists).toReal();
362
363 if (keyframeExists && !isIndexHidden(index)) {
364 QStyleOptionViewItem opt;
365
366 if (selectionModel()->isSelected(index)) {
367 opt.state |= QStyle::State_Selected;
368 }
369
370 if (index == selectionModel()->currentIndex()) {
371 opt.state |= QStyle::State_HasFocus;
372 }
373
374 m_d->itemDelegate->paint(&painter, opt, index);
375 }
376 }
377 }
378}
379
380QModelIndex KisAnimCurvesView::findNextKeyframeIndex(int channel, int time, int selectionOffset, bool backward)
381{
384 QModelIndex currentIndex = model()->index(channel, time);
385
386 if (!selectionOffset) {
387 QVariant next = currentIndex.data(role);
388 return (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex();
389 } else {
390 // Find the next unselected index
391 QModelIndex nextIndex = currentIndex;
392 do {
393 QVariant next = nextIndex.data(role);
394 nextIndex = (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex();
395 } while(nextIndex.isValid() && selectionModel()->isSelected(nextIndex));
396
397 // Find the next selected index, accounting for D&D offset
398 QModelIndex draggedIndex = model()->index(channel, qMax(0, time - selectionOffset));
399 do {
400 QVariant next = draggedIndex.data(role);
401 draggedIndex = (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex();
402 } while(draggedIndex.isValid() && !selectionModel()->isSelected(draggedIndex));
403
404 // Choose the earlier of the two
405 if (draggedIndex.isValid() && nextIndex.isValid()) {
406 if (draggedIndex.column() + selectionOffset <= nextIndex.column()) {
407 return draggedIndex;
408 } else {
409 return nextIndex;
410 }
411 } else if (draggedIndex.isValid()) {
412 return draggedIndex;
413 } else {
414 return nextIndex;
415 }
416 }
417}
418
419void KisAnimCurvesView::findExtremes(qreal *minimum, qreal *maximum)
420{
421 if (!model()) return;
422
423 qreal min = qInf();
424 qreal max = -qInf();
425
426 int curveCount = model()->rowCount();
427 for (int curveIndex = 0; curveIndex < curveCount; curveIndex++) {
428 QModelIndex index = model()->index(curveIndex, 0);
429 if (isIndexHidden(index)) continue;
430
431 QVariant nextTime;
432 do {
433 qreal value = index.data(KisAnimCurvesModel::ScalarValueRole).toReal();
434
435 if (value < min) min = value;
436 if (value > max) max = value;
437
438 const int MAX_NUM_TANGENTS = 2;
439 for (int i = 0; i < MAX_NUM_TANGENTS; i++) {
440 QVariant tangent = index.data(KisAnimCurvesModel::LeftTangentRole + i);
441 if (!tangent.isValid())
442 continue;
443
444 QPointF tangentPointF = tangent.toPointF();
445 if (value + tangentPointF.y() < min) min = value + tangentPointF.y();
446 if (value + tangentPointF.y() > max) max = value + tangentPointF.y();
447 }
448
449 nextTime = index.data(KisAnimCurvesModel::NextKeyframeTime);
450 if (nextTime.isValid()) index = model()->index(curveIndex, nextTime.toInt());
451 } while (nextTime.isValid());
452 }
453
454 if (qIsFinite(min)) *minimum = min;
455 if (qIsFinite(max)) *maximum = max;
456}
457
458QModelIndex KisAnimCurvesView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
459{
460 // TODO
461 Q_UNUSED(cursorAction);
462 Q_UNUSED(modifiers);
463 return QModelIndex();
464}
465
467{
468 return m_d->horizontalHeader->offset();
469}
470
472{
473 return m_d->verticalHeader->valueOffset();
474}
475
476bool KisAnimCurvesView::isIndexHidden(const QModelIndex &index) const
477{
478 return !index.data(KisAnimCurvesModel::CurveVisibleRole).toBool();
479}
480
481void KisAnimCurvesView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags)
482{
483 int timeFrom = m_d->horizontalHeader->logicalIndexAt(rect.left());
484 int timeTo = m_d->horizontalHeader->logicalIndexAt(rect.right());
485
486 QItemSelection selection;
487
488 int rows = model()->rowCount();
489 for (int row=0; row < rows; row++) {
490 for (int time = timeFrom; time <= timeTo; time++) {
491
492 QModelIndex index = model()->index(row, time);
493
494 if (isIndexHidden(index)) continue;
495
496 if (index.data(KisTimeBasedItemModel::SpecialKeyframeExists).toBool()) {
497 QRect itemRect = m_d->itemDelegate->itemRect(index);
498
499 if (itemRect.intersects(rect)) {
500 selection.select(index, index);
501 }
502 }
503 }
504 }
505
506 if (!selection.contains(selectionModel()->currentIndex()) && selection.size() > 0) {
507 selectionModel()->setCurrentIndex(selection.first().topLeft(), flags);
508 }
509
510 selectionModel()->select(selection, flags);
511 activated(selectionModel()->currentIndex());
512}
513
514QRegion KisAnimCurvesView::visualRegionForSelection(const QItemSelection &selection) const
515{
516 QRegion region;
517
518 Q_FOREACH(QModelIndex index, selection.indexes()) {
519 region += m_d->itemDelegate->visualRect(index);
520 }
521
522 return region;
523}
524
526{
527 if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
528 if (e->button() == Qt::LeftButton) { // PAN
529 m_d->dragPanning = true;
530 m_d->panAnchor = e->pos();
531
532 } else if (e->button() == Qt::RightButton) { // ZOOM
533 m_d->dragZooming = true;
534 m_d->zoomAnchor = e->pos();
535 }
536 } else if (e->button() == Qt::LeftButton) { // SELECT
537 m_d->dragStart = e->pos();
538
539 const int handleClickRadius = 16;
540
541 Q_FOREACH(QModelIndex index, selectedIndexes()) {
542
543 if (isIndexHidden(index)) continue;
544
545 QPointF center = m_d->itemDelegate->nodeCenter(index, false);
546 bool hasLeftHandle = m_d->itemDelegate->hasHandle(index, 0);
547 bool hasRightHandle = m_d->itemDelegate->hasHandle(index, 1);
548
549 QPointF leftHandle = center + m_d->itemDelegate->leftHandle(index, false);
550 QPointF rightHandle = center + m_d->itemDelegate->rightHandle(index, false);
551
552 if (hasLeftHandle && (e->localPos() - leftHandle).manhattanLength() < handleClickRadius) {
553 m_d->isAdjustingHandle = true;
554 m_d->adjustedHandle = 0;
555 setCurrentIndex(index);
556 continue;
557 } else if (hasRightHandle && (e->localPos() - rightHandle).manhattanLength() < handleClickRadius) {
558 m_d->isAdjustingHandle = true;
559 m_d->adjustedHandle = 1;
560 setCurrentIndex(index);
561 continue;
562 }
563 }
564
565 }
566
567 QModelIndex clickedIndex = indexAt(e->pos());
568 if(indexHasKey(clickedIndex)) {
569 if ((e->modifiers() & Qt::ShiftModifier) == 0 && selectionModel()->currentIndex() != clickedIndex) {
570 clearSelection();
571 }
572
573 if (clickedIndex == selectionModel()->currentIndex() && selectionModel()->hasSelection()) {
574 m_d->deselectIntended = true;
575 m_d->toDeselect = clickedIndex;
576 } else {
577 QModelIndex prevCurrent = selectionModel()->currentIndex();
578 selectionModel()->select(clickedIndex, QItemSelectionModel::Select);
579 selectionModel()->setCurrentIndex(clickedIndex, QItemSelectionModel::NoUpdate);
580 Q_EMIT currentChanged(clickedIndex, prevCurrent);
581 }
582
583 Q_EMIT clicked(clickedIndex);
584 Q_EMIT activeDataChanged(selectionModel()->currentIndex());
585 } else {
586 QAbstractItemView::mousePressEvent(e);
587 }
588}
589
590
592{
593 QModelIndex clicked = indexAt(e->pos());
594
595 if(clicked.isValid() && indexHasKey(clicked)) {
596 selectionModel()->clear();
597 bool firstSelection = true;
598 if (e->modifiers() & Qt::AltModifier) {
599 for (int column = 0; column <= model()->columnCount(); column++) {
600 QModelIndex toSelect = model()->index(clicked.row(), column);
601 const bool hasSpecial = toSelect.data(KisTimeBasedItemModel::SpecialKeyframeExists).toBool();
602 const bool curveVisible = toSelect.data(KisAnimCurvesModel::CurveVisibleRole).toBool();
603 if (toSelect.isValid() && hasSpecial && curveVisible) {
604 selectionModel()->select(toSelect, firstSelection ? QItemSelectionModel::SelectCurrent : QItemSelectionModel::Select);
605 firstSelection = false;
606 }
607 }
608 } else {
609 for (int row = 0; row <= model()->rowCount(); row++) {
610 QModelIndex toSelect = model()->index(row, clicked.column());
611 const bool hasSpecial = toSelect.data(KisTimeBasedItemModel::SpecialKeyframeExists).toBool();
612 const bool curveVisible = toSelect.data(KisAnimCurvesModel::CurveVisibleRole).toBool();
613 if (toSelect.isValid() && hasSpecial && curveVisible) {
614 selectionModel()->select(toSelect, firstSelection ? QItemSelectionModel::SelectCurrent : QItemSelectionModel::Select);
615 firstSelection = false;
616 }
617 }
618 }
619
620 QModelIndex oldCurrent = selectionModel()->currentIndex();
621 selectionModel()->setCurrentIndex(clicked, QItemSelectionModel::NoUpdate);
622 currentChanged(clicked, oldCurrent);
623 } else {
624 QAbstractItemView::mouseDoubleClickEvent(e);
625 }
626}
627
629{
630 if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
631 if (m_d->dragPanning) {
632 const int timelineScrubAmnt = m_d->panAnchor.x() - e->pos().x();
633 const qreal valueScrubAmnt = m_d->verticalHeader->pixelsToValueOffset(m_d->panAnchor.y() - e->pos().y());
634
637 horizontalScrollBar()->setValue(horizontalScrollBar()->value() + timelineScrubAmnt);
638
639 m_d->verticalHeader->setValueOffset(m_d->verticalHeader->valueOffset() + valueScrubAmnt);
640
641 m_d->panAnchor = e->pos();
642 viewport()->update();
643 return;
644 }
645
646 if (m_d->dragZooming) {
647 const qreal zoomScale = 50.0f;
648 const qreal updown = ((m_d->zoomAnchor.y() - e->pos().y())) / zoomScale;
649 const qreal leftright = (m_d->zoomAnchor.x() - e->pos().x()) / zoomScale;
650
651 changeZoom(Qt::Vertical, updown);
652 changeZoom(Qt::Horizontal, leftright * -0.5);
653
654 m_d->zoomAnchor = e->pos();
655 viewport()->update();
656 return;
657 }
658
659 } else if (e->buttons() & Qt::LeftButton) {
660
661 m_d->dragOffset = e->pos() - m_d->dragStart;
662
663 if (m_d->isAdjustingHandle) {
664 m_d->itemDelegate->setHandleAdjustment(m_d->dragOffset, m_d->adjustedHandle);
665 viewport()->update();
666 return;
667 } else if (m_d->isDraggingKeyframe) {
668 const bool axisSnap = (e->modifiers() & Qt::ShiftModifier);
669 m_d->itemDelegate->setSelectedItemVisualOffset(m_d->dragOffset, axisSnap);
670 viewport()->update();
671 return;
672 } else if (selectionModel()->hasSelection()) {
673 if ((e->pos() - m_d->dragStart).manhattanLength() > QApplication::startDragDistance()) {
674 m_d->isDraggingKeyframe = true;
675 }
676 }
677 } else {
678 QAbstractItemView::mouseMoveEvent(e);
679 }
680}
681
683{
684
685 if (e->button() == Qt::LeftButton) {
686 m_d->dragPanning = false;
687 m_d->dragZooming = false;
688
689 if (m_d->isDraggingKeyframe) {
690 const QModelIndexList indices = selectedIndexes();
691 const QPointF largestOffset = qAbs(m_d->dragOffset.y()) > qAbs(m_d->dragOffset.x()) ? QPointF(0.0f, m_d->dragOffset.y()) :
692 QPointF(m_d->dragOffset.x(), 0.0f);
693
694 //Only use the largest offset when axis snap is enabled on mouse button release..
695 const bool axisSnap = (e->modifiers() & Qt::ShiftModifier);
696 const QPointF offset = axisSnap ? largestOffset : m_d->dragOffset;
697 const int timeOffset = qRound( qreal(offset.x()) / m_d->horizontalHeader->defaultSectionSize() );
698 const qreal valueOffset = m_d->verticalHeader->pixelsToValueOffset(offset.y());
699
700 KisAnimCurvesModel *curvesModel = dynamic_cast<KisAnimCurvesModel*>(model());
702 curvesModel->adjustKeyframes(indices, timeOffset, valueOffset);
703
704 //Adjust selection to match new keyframe adjustment.
705 Q_FOREACH(const QModelIndex& index, indices) {
706 const bool wasCurrent = (index == selectionModel()->currentIndex());
707 selectionModel()->select(index, QItemSelectionModel::Deselect);
708 const QModelIndex newIndex = m_d->model->index(index.row(), index.column() + timeOffset);
709 if (wasCurrent) {
710 selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::SelectCurrent);
711 } else {
712 selectionModel()->select(newIndex, QItemSelectionModel::Select);
713 }
714 }
715
716 m_d->isDraggingKeyframe = false;
717 m_d->itemDelegate->setSelectedItemVisualOffset(QPointF());
718 viewport()->update();
719 } else if (m_d->isAdjustingHandle) {
720 QModelIndex index = currentIndex();
721 int mode = index.data(KisAnimCurvesModel::TangentsModeRole).toInt();
722
723 m_d->model->beginCommand(kundo2_i18n("Adjust tangent"));
724
725 if (mode == KisScalarKeyframe::Smooth) {
726 QPointF leftHandle = m_d->itemDelegate->leftHandle(index, true);
727 QPointF rightHandle = m_d->itemDelegate->rightHandle(index, true);
728
729 QPointF leftTangent = m_d->itemDelegate->unscaledTangent(leftHandle);
730 QPointF rightTangent = m_d->itemDelegate->unscaledTangent(rightHandle);
731
732 model()->setData(index, leftTangent, KisAnimCurvesModel::LeftTangentRole);
733 model()->setData(index, rightTangent, KisAnimCurvesModel::RightTangentRole);
734 } else {
735 if (m_d->adjustedHandle == 0) {
736 QPointF leftHandle = m_d->itemDelegate->leftHandle(index, true);
737 model()->setData(index, m_d->itemDelegate->unscaledTangent(leftHandle), KisAnimCurvesModel::LeftTangentRole);
738 } else {
739 QPointF rightHandle = m_d->itemDelegate->rightHandle(index, true);
740 model()->setData(index, m_d->itemDelegate->unscaledTangent(rightHandle), KisAnimCurvesModel::RightTangentRole);
741 }
742 }
743
744 m_d->model->endCommand();
745
746 m_d->isAdjustingHandle = false;
747 m_d->itemDelegate->setHandleAdjustment(QPointF(), m_d->adjustedHandle);
748 } else {
749
750 if (m_d->deselectIntended){
751 selectionModel()->select(m_d->toDeselect, QItemSelectionModel::Deselect);
752 }
753
754 }
755
756 m_d->deselectIntended = false;
757 m_d->toDeselect = QModelIndex();
758 }
759
760 QAbstractItemView::mouseReleaseEvent(e);
761}
762
764{
765 scrollDirtyRegion(dx, dy);
766 viewport()->scroll(dx, dy);
767 viewport()->update();
768}
769
770bool KisAnimCurvesView::indexHasKey(const QModelIndex &index)
771{
772 const QVariant data = m_d->model->data(index, KisAnimCurvesModel::SpecialKeyframeExists);
773 return data.isValid() && data.toBool();
774}
775
777{
778 int topMargin = qMax(m_d->horizontalHeader->minimumHeight(),
779 m_d->horizontalHeader->sizeHint().height());
780
781 int leftMargin = m_d->verticalHeader->sizeHint().width();
782
783 setViewportMargins(leftMargin, topMargin, 0, 0);
784
785 QRect viewRect = viewport()->geometry();
786 m_d->horizontalHeader->setGeometry(leftMargin, 0, viewRect.width(), topMargin);
787 m_d->verticalHeader->setGeometry(0, topMargin, leftMargin, viewRect.height());
788 if (m_d->model) {
790 }
791
792 QAbstractItemView::updateGeometries();
793}
794
795void KisAnimCurvesView::slotRowsChanged(const QModelIndex &parentIndex, int first, int last)
796{
797 Q_UNUSED(parentIndex);
798 Q_UNUSED(first);
799 Q_UNUSED(last);
801 viewport()->update();
802}
803
804void KisAnimCurvesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
805{
806 Q_UNUSED(topLeft);
807 Q_UNUSED(bottomRight);
808
809 viewport()->update();
810
811 // this forces the horizontal ruler to refresh. Repaint() doesn't do it for some reason
812 // If you remove this, scrubbing the timeline will probably stop updating the indicator
813 m_d->horizontalHeader->resize(m_d->horizontalHeader->width()-1, m_d->horizontalHeader->height());
814 m_d->horizontalHeader->resize(m_d->horizontalHeader->width()+1, m_d->horizontalHeader->height());
815
816 if (selectionModel()->selection().count() != 0 &&
817 selectionModel()->currentIndex().isValid()) {
818 Q_EMIT activeDataChanged(selectionModel()->currentIndex());
819 }
820}
821
822void KisAnimCurvesView::slotDataAdded(const QModelIndex &index)
823{
824 const qreal lastVisibleValue = m_d->verticalHeader->visibleValueMax();
825 const qreal firstVisibleValue = m_d->verticalHeader->visibleValueMin();
826
827 qreal value = index.data(KisAnimCurvesModel::ScalarValueRole).toReal();
828 if ( value < firstVisibleValue || value > lastVisibleValue) {
829 qreal min, max;
830 findExtremes(&min, &max);
831 qreal padding = (max - min) * 0.1;
832 m_d->verticalHeader->zoomToFitRange(min - padding, max + padding);
833 viewport()->update();
834 }
835
836 selectionModel()->clear();
837 selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent);
838}
839
840void KisAnimCurvesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last)
841{
842 Q_UNUSED(orientation);
843 Q_UNUSED(first);
844 Q_UNUSED(last);
845 viewport()->update();
846}
847
849{
850 if (m_d->model) {
851 const int lastColumn = m_d->horizontalHeader->estimateLastVisibleColumn();
852 m_d->model->setLastVisibleFrame(lastColumn);
853 }
854}
855
857{
858 if (m_d->model) {
859 const int lastColumn = qMax( m_d->horizontalHeader->estimateLastVisibleColumn(), m_d->model->columnCount());
860 const int numberOfColumnsOnScreen = lastColumn - m_d->horizontalHeader->estimateFirstVisibleColumn();
861 const int overallSize = lastColumn * m_d->horizontalHeader->defaultSectionSize();
862 const int pageStep = overallSize * (qreal(numberOfColumnsOnScreen) / lastColumn);
863 horizontalScrollBar()->setRange(0, overallSize + pageStep);
864 horizontalScrollBar()->setPageStep(pageStep);
865 }
866}
867
869{
870 m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
871 Q_FOREACH(QModelIndex index, selectedIndexes()) {
873 }
874 m_d->model->endCommand();
875}
876
878{
879 m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
880 Q_FOREACH(QModelIndex index, selectedIndexes()) {
882 }
883 m_d->model->endCommand();
884}
885
887{
888 m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
889 Q_FOREACH(QModelIndex index, selectedIndexes()) {
891 }
892 m_d->model->endCommand();
893}
894
896{
897 m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
898 Q_FOREACH(QModelIndex index, selectedIndexes()) {
899 QVector2D leftVisualTangent(m_d->itemDelegate->leftHandle(index, false));
900 QVector2D rightVisualTangent(m_d->itemDelegate->rightHandle(index, false));
901
902 if (leftVisualTangent.lengthSquared() > 0 && rightVisualTangent.lengthSquared() > 0) {
903 float leftAngle = qAtan2(-leftVisualTangent.y(), -leftVisualTangent.x());
904 float rightAngle = qAtan2(rightVisualTangent.y(), rightVisualTangent.x());
905 float angle = (leftAngle + rightAngle) / 2;
906 QVector2D unit(qCos(angle), qSin(angle));
907
908 leftVisualTangent = -unit * QVector2D(leftVisualTangent).length();
909 rightVisualTangent = unit * QVector2D(rightVisualTangent).length();
910
911 QPointF leftTangent = m_d->itemDelegate->unscaledTangent(leftVisualTangent.toPointF());
912 QPointF rightTangent = m_d->itemDelegate->unscaledTangent(rightVisualTangent.toPointF());
913
914 model()->setData(index, leftTangent, KisAnimCurvesModel::LeftTangentRole);
915 model()->setData(index, rightTangent, KisAnimCurvesModel::RightTangentRole);
916 }
917
919 }
920 m_d->model->endCommand();
921}
922
924{
925 m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
926 Q_FOREACH(QModelIndex index, selectedIndexes()) {
928 }
929 m_d->model->endCommand();
930}
931
933{
934 QModelIndex active = currentIndex();
935 int channel = active.isValid() ? active.row() : 0;
936
937 int time = m_d->model->currentTime();
938 QModelIndex index = m_d->model->index(channel, time);
939
940 qreal value = index.data(KisAnimCurvesModel::ScalarValueRole).toReal();
941 m_d->model->setData(index, value, KisAnimCurvesModel::ScalarValueRole);
942}
943
945{
946 m_d->model->removeFrames(selectedIndexes());
947}
948
949
951{
952 if (!model()) return;
953
954 qreal min, max;
955 findExtremes(&min, &max);
956
957 const qreal padding = (min != max) ? (max - min) * 0.1 : 10.0f;
958 m_d->verticalHeader->zoomToFitRange(min - padding, max + padding);
959 viewport()->update();
960}
961
963{
964 if (!model()) return;
965
966 const int channels = model()->rowCount();
967
968 qreal min = 0;
969 qreal max = min;
970
971 for (int channel = 0; channel < channels; channel++) {
972 QModelIndex index = m_d->model->index(channel, 0);
973 QVariant variant = m_d->model->data(index, KisAnimCurvesModel::ChannelLimits);
974
975 if (!variant.isValid())
976 continue;
977
978 ChannelLimitsMetatype limits = variant.value<ChannelLimitsMetatype>();
979 min = qMin(limits.first, min);
980 max = qMax(limits.second, max);
981 }
982
983 if (min == max)
984 {
986 return;
987 }
988
989 const qreal padding = (max - min) * 0.1;
990 m_d->verticalHeader->zoomToFitRange(min - padding, max + padding);
991 viewport()->update();
992}
993
994void KisAnimCurvesView::changeZoom(Qt::Orientation orientation, qreal zoomDelta)
995{
996 if (orientation == Qt::Horizontal) {
997 m_d->horizontalHeader->setZoom( m_d->horizontalHeader->zoom() + zoomDelta);
999 } else {
1000 const qreal currentZoomLevel = m_d->verticalHeader->scale();
1001 m_d->verticalHeader->setScale(currentZoomLevel + zoomDelta / m_d->verticalHeader->step());
1002 }
1003 viewport()->update();
1004}
float value(const T *src, size_t ch)
QPair< qreal, qreal > ChannelLimitsMetatype
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
bool adjustKeyframes(const QModelIndexList &indexes, int timeOffset, qreal valueOffset)
void dataAdded(const QModelIndex &index)
void valueOffsetChanged(qreal offset)
void scaleChanged(qreal scale)
QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override
void paintCurves(QPainter &painter, int firstFrame, int lastFrame)
void paintEvent(QPaintEvent *event) override
void mouseMoveEvent(QMouseEvent *) override
void scrollTo(const QModelIndex &index, ScrollHint hint) override
QModelIndex findNextKeyframeIndex(int channel, int time, int selectionOffset, bool backward)
void mouseReleaseEvent(QMouseEvent *) override
void updateGeometries() override
bool indexHasKey(const QModelIndex &index)
void setModel(QAbstractItemModel *model) override
void slotRowsChanged(const QModelIndex &parentIndex, int first, int last)
void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override
void paintCurve(int channel, int firstFrame, int lastFrame, QPainter &painter)
int verticalOffset() const override
void activeDataChanged(const QModelIndex &index)
bool isIndexHidden(const QModelIndex &index) const override
QModelIndex indexAt(const QPoint &point) const override
void changeZoom(Qt::Orientation orientation, qreal zoomDelta)
const QScopedPointer< Private > m_d
KisAnimCurvesView(QWidget *parent)
void slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
void paintCurveSegment(QPainter &painter, QPointF pos1, QPointF rightTangent, QPointF leftTangent, QPointF pos2, QVariant limitData)
void paintKeyframes(QPainter &painter, int firstFrame, int lastFrame)
void slotScrollerStateChanged(QScroller::State state)
void paintGrid(QPainter &painter)
QRect visualRect(const QModelIndex &index) const override
void scrollContentsBy(int dx, int dy) override
void mousePressEvent(QMouseEvent *) override
void slotDataAdded(const QModelIndex &index)
void slotHeaderDataChanged(Qt::Orientation orientation, int first, int last)
QRegion visualRegionForSelection(const QItemSelection &selection) const override
void findExtremes(qreal *minimum, qreal *maximum)
int horizontalOffset() const override
void mouseDoubleClickEvent(QMouseEvent *) override
static KisAnimTimelineColors * instance()
The KisCustomModifiersCatcher class is a special utility class that tracks custom modifiers pressed....
static QPointF interpolate(QPointF point1, QPointF rightTangent, QPointF leftTangent, QPointF point2, qreal t)
void overscroll(qreal delta)
void zoom(qreal delta)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
KUndo2MagicString kundo2_i18n(const char *text)
KRITAWIDGETUTILS_EXPORT QScroller * createPreconfiguredScroller(QAbstractScrollArea *target)
QColor blendColors(const QColor &c1, const QColor &c2, qreal r1)
KisCustomModifiersCatcher * modifiersCatcher
KisAnimTimelineTimeHeader * horizontalHeader
KisAnimCurvesKeyDelegate * itemDelegate
KisAnimCurvesValuesHeader * verticalHeader