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