File indexing completed on 2025-02-02 04:11:34

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "timeline_widget.hpp"
0008 
0009 #include <QScrollBar>
0010 #include <QWheelEvent>
0011 #include <QDebug>
0012 #include <QTreeView>
0013 
0014 #include "timeline_items.hpp"
0015 #include "model/shapes/precomp_layer.hpp"
0016 #include "model/shapes/styler.hpp"
0017 
0018 using namespace glaxnimate::gui;
0019 using namespace glaxnimate;
0020 
0021 
0022 using namespace glaxnimate::gui::timeline;
0023 
0024 class TimelineWidget::Private
0025 {
0026 public:
0027     enum class DragMode
0028     {
0029         None,
0030         Frame,
0031         Pan,
0032     };
0033 
0034     TimelineWidget* parent;
0035     QGraphicsScene scene;
0036     int start_time = 0;
0037     int end_time = 0;
0038     int row_height = 24;
0039     int header_height = 24;
0040 
0041     qreal min_scale = 1;
0042     int frame_skip = 1;
0043     int min_gap = 32;
0044     int mouse_frame = -1;
0045     model::Document* document = nullptr;
0046     DragMode drag_mode = DragMode::None;
0047     model::AnimationContainer* limit = nullptr;
0048     bool keep_highlight = false;
0049     QPointF drag_start;
0050 
0051     LineItem* root = nullptr;
0052     std::unordered_map<quintptr, LineItem*> line_items;
0053 
0054     item_models::PropertyModelFull* base_model = nullptr;
0055     item_models::CompFilterModel* model = nullptr;
0056     QTreeView* expander = nullptr;
0057     model::Composition* comp = nullptr;
0058 
0059 
0060     int rounded_end_time()
0061     {
0062         return time_round_to_ticks(end_time);
0063     }
0064 
0065     int time_round_to_ticks(int time)
0066     {
0067         return (time/frame_skip + 1) * frame_skip;
0068     }
0069 
0070     QRectF scene_rect()
0071     {
0072         return QRectF(
0073             QPointF(start_time, -header_height),
0074             QPointF(rounded_end_time(), std::max(row_height*root->visible_rows(), parent->height()))
0075         );
0076     }
0077 
0078     LineItem* add_item(quintptr id, const item_models::PropertyModelFull::Item& item, LineItem* parent_item, int index)
0079     {
0080         LineItem* line_item = nullptr;
0081 
0082         if ( item.property )
0083             line_item = add_property(id, item.property);
0084         else if ( item.object )
0085             line_item = add_object_without_properties(id, item.object);
0086 
0087         if ( line_item )
0088         {
0089             line_items[id] = line_item;
0090             parent_item->add_row(line_item, index);
0091             connect(line_item, &LineItem::removed, parent, &TimelineWidget::on_item_removed);
0092         }
0093 
0094         return line_item;
0095     }
0096 
0097     LineItem* add_property(quintptr id, model::BaseProperty* prop)
0098     {
0099         LineItem* item;
0100 
0101         if ( prop->traits().flags & model::PropertyTraits::Animated )
0102             item = add_animatable(id, static_cast<model::AnimatableBase*>(prop));
0103         else if ( (prop->traits().flags & model::PropertyTraits::List) && prop->traits().is_object() )
0104             item = add_property_list(id, static_cast<model::ObjectListPropertyBase*>(prop));
0105         else
0106             item = add_property_plain(id, prop);
0107 
0108         return item;
0109     }
0110 
0111     LineItem* add_animatable(quintptr id, model::AnimatableBase* anim)
0112     {
0113         AnimatableItem* item = new AnimatableItem(id, anim->object(), anim, start_time, end_time, row_height);
0114         connect(item, &LineItem::clicked, parent, &TimelineWidget::line_clicked);
0115         return item;
0116     }
0117 
0118     LineItem* add_property_plain(quintptr id, model::BaseProperty* prop)
0119     {
0120         PropertyLineItem* item = new PropertyLineItem(id, prop->object(), prop, start_time, end_time, row_height);
0121         connect(item, &LineItem::clicked, parent, &TimelineWidget::line_clicked);
0122         return item;
0123     }
0124 
0125     LineItem* add_property_list(quintptr id, model::ObjectListPropertyBase* prop)
0126     {
0127         auto obj = prop->object();
0128         ObjectListLineItem* item = new ObjectListLineItem(id, obj, prop, start_time, end_time, row_height);
0129         if ( auto layer = obj->cast<model::Layer>() )
0130         {
0131             auto anim_item = new AnimationContainerItem(layer, layer->animation.get(), row_height - 8, item);
0132             anim_item->setPos(0, row_height/2.0);
0133         }
0134         else if ( auto comp = obj->cast<model::Composition>() )
0135         {
0136             auto anim_item = new AnimationContainerItem(comp, comp->animation.get(), row_height - 8, item);
0137             anim_item->setPos(0, row_height/2.0);
0138         }
0139         connect(item, &LineItem::clicked, parent, &TimelineWidget::line_clicked);
0140         return item;
0141     }
0142 
0143     ObjectLineItem* add_object_without_properties(quintptr id, model::Object* obj)
0144     {
0145         ObjectLineItem* item = new ObjectLineItem(id, obj, start_time, end_time, row_height);
0146         if ( auto layer = obj->cast<model::PreCompLayer>() )
0147         {
0148             auto anim_item = new StretchableTimeItem(layer, row_height - 8, item);
0149             anim_item->setPos(0, row_height/2.0);
0150         }
0151 
0152         connect(item, &LineItem::clicked, parent, &TimelineWidget::line_clicked);
0153 
0154         return item;
0155     }
0156 
0157     void adjust_min_scale(int wpw)
0158     {
0159         if ( min_scale == 0 || scene_rect().width() == 0 )
0160             min_scale = 1;
0161         else
0162             min_scale = wpw / scene_rect().width();
0163     }
0164 
0165     void update_frame_skip(const QTransform& tr)
0166     {
0167         frame_skip = qCeil(min_gap / tr.m11());
0168         update_end_time();
0169     }
0170 
0171     void update_end_time()
0172     {
0173         root->set_time_end(end_time);
0174     }
0175 
0176     void paint_highligted_frame(int frame, QPainter& painter, const QBrush& color)
0177     {
0178         QPointF framep = parent->mapFromScene(QPoint(frame, 0));
0179         QPointF framep1 = parent->mapFromScene(QPoint(frame+1, 0));
0180         painter.fillRect(
0181             framep.x() + 1,
0182             0,
0183             framep1.x() - framep.x() - 0.5,
0184             header_height - 0.5,
0185             color
0186         );
0187     }
0188 
0189     void clear()
0190     {
0191         line_items.clear();
0192         root->raw_clear();
0193     }
0194 
0195     model::AnimationContainer* anim(model::DocumentNode* node)
0196     {
0197         while ( node )
0198         {
0199             const QMetaObject* mo = node->metaObject();
0200             if ( mo->inherits(&model::Layer::staticMetaObject) )
0201                 return static_cast<model::Layer*>(node)->animation.get();
0202             else if ( mo->inherits(&model::Composition::staticMetaObject) )
0203                 return static_cast<model::Composition*>(node)->animation.get();
0204 
0205             node = node->docnode_parent();
0206         }
0207 
0208         return comp->animation.get();
0209     }
0210 
0211     QRectF frame_text_rect(int f, TimelineWidget* parent)
0212     {
0213 
0214         int fs = f / frame_skip * frame_skip;
0215         int box_x1 = parent->mapFromScene(fs, 0).x();
0216         int box_x2 = parent->mapFromScene(fs+frame_skip, 0).x();
0217         return QRectF(
0218             QPointF(box_x1+1, header_height / 4),
0219             QPointF(box_x2-0.5, header_height-0.5)
0220         );
0221     }
0222 
0223     void paint_frame_rect(TimelineWidget* parent, QPainter& painter, int f, const QBrush& brush, const QPen& pen)
0224     {
0225         QRectF text_rect = frame_text_rect(f, parent);
0226         painter.fillRect(text_rect, brush);
0227         painter.setPen(pen);
0228         painter.drawText(text_rect, Qt::AlignLeft|Qt::AlignBottom,  QString::number(f));
0229     }
0230 
0231     LineItem* index_to_line(const QModelIndex& index)
0232     {
0233         auto it = line_items.find(index.internalId());
0234         if ( it == line_items.end() )
0235             return nullptr;
0236         return it->second;
0237     }
0238 
0239     void insert_index(const QModelIndex& parent_index, LineItem* parent, int index)
0240     {
0241         auto item = add_item(parent_index.internalId(), base_model->item(parent_index), parent, index);
0242         insert_children(parent_index, item);
0243     }
0244 
0245     void insert_children(const QModelIndex& parent_index, LineItem* parent_item)
0246     {
0247         int row_count = model->rowCount(parent_index);
0248         for ( int i = 0; i < row_count; i++ )
0249             insert_index(model->index(i, 0, parent_index), parent_item, i);
0250     }
0251 
0252     /**
0253      * \brief QTreeView isn't reliable with expanded/collapsed signals, so we check every time ;_;
0254      */
0255     void adjust_expand(const QModelIndex& parent_index, LineItem* parent_item, bool recursive = false)
0256     {
0257         int row_count = model->rowCount(parent_index);
0258         for ( int i = 0; i < row_count; i++ )
0259         {
0260             QModelIndex index = model->index(i, 0, parent_index);
0261             auto row = parent_item->rows()[i];
0262             bool expanded = expander->isExpanded(index);
0263             row->set_expanded(expanded);
0264             if ( recursive && expanded )
0265                 adjust_expand(index, row, true);
0266         }
0267     }
0268 
0269     int frame_at_point(const QPoint& view_pos)
0270     {
0271         return qBound(start_time, qRound(parent->mapToScene(view_pos).x()), end_time - 1);
0272     }
0273 
0274     int max_scroll_row()
0275     {
0276         return (parent->verticalScrollBar()->maximum() + header_height) / row_height;
0277     }
0278 
0279     int scroll_row()
0280     {
0281         return (parent->verticalScrollBar()->value() + header_height) / row_height;
0282     }
0283 
0284     bool horizontal_scroll(Qt::KeyboardModifiers modifiers)
0285     {
0286         bool horizontal = modifiers & (Qt::ShiftModifier|Qt::AltModifier);
0287         if ( app::settings::get<bool>("ui", "timeline_scroll_horizontal") )
0288             return !horizontal;
0289         return horizontal;
0290     }
0291 };
0292 
0293 TimelineWidget::TimelineWidget(QWidget* parent)
0294     : QGraphicsView(parent), d(std::make_unique<Private>())
0295 {
0296     d->parent = this;
0297     d->root = new LineItem(0, nullptr, 0, 0, d->row_height);
0298     d->root->setPos(0, -d->row_height);
0299     d->root->expand();
0300     d->scene.addItem(d->root);
0301     setMouseTracking(true);
0302     setInteractive(true);
0303     setRenderHint(QPainter::Antialiasing);
0304     setScene(&d->scene);
0305     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0306     setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0307     setTransformationAnchor(AnchorUnderMouse);
0308     setCursor(Qt::ArrowCursor);
0309 }
0310 
0311 TimelineWidget::~TimelineWidget()
0312 {
0313 }
0314 
0315 int TimelineWidget::row_height() const
0316 {
0317     return d->row_height;
0318 }
0319 
0320 void TimelineWidget::set_row_height(int w)
0321 {
0322     d->row_height = w;
0323 }
0324 
0325 void TimelineWidget::set_document(model::Document* document)
0326 {
0327     if ( d->document )
0328     {
0329         disconnect(this, nullptr, d->document, nullptr);
0330         disconnect(d->document, nullptr, viewport(), nullptr);
0331     }
0332 
0333     d->clear();
0334     d->document = document;
0335     update_comp(nullptr);
0336 
0337     if ( document )
0338     {
0339         connect(this, &TimelineWidget::frame_clicked, document, [document](int frame){document->set_current_time(frame);});
0340         connect(document, &model::Document::current_time_changed, viewport(), (void (QWidget::*)())&QWidget::update);
0341     }
0342 
0343 }
0344 
0345 void TimelineWidget::update_comp(model::Composition* comp)
0346 {
0347     if ( d->comp )
0348         disconnect(d->comp->animation.get(), nullptr, this, nullptr);
0349 
0350     d->comp = comp;
0351 
0352     if ( comp )
0353     {
0354         connect(d->comp->animation.get(), &model::AnimationContainer::first_frame_changed, this, &TimelineWidget::update_timeline_start);
0355         connect(d->comp->animation.get(), &model::AnimationContainer::last_frame_changed, this, &TimelineWidget::update_timeline_end);
0356         update_timeline_end(d->comp->animation->last_frame.get());
0357         update_timeline_start(d->comp->animation->first_frame.get());
0358     }
0359 }
0360 
0361 void TimelineWidget::update_timeline_end(model::FrameTime end)
0362 {
0363     d->end_time = end;
0364     setSceneRect(sceneRect() | d->scene_rect());
0365     d->adjust_min_scale(viewport()->width());
0366     d->update_end_time();
0367 }
0368 
0369 void TimelineWidget::update_timeline_start(model::FrameTime start)
0370 {
0371     d->start_time = start;
0372     setSceneRect(sceneRect() | d->scene_rect());
0373     d->adjust_min_scale(viewport()->width());
0374     d->root->set_time_start(qFloor(start));
0375 }
0376 
0377 void TimelineWidget::wheelEvent(QWheelEvent* event)
0378 {
0379     if ( event->modifiers() & Qt::ControlModifier )
0380     {
0381         if ( event->angleDelta().y() < 0 )
0382         {
0383             qreal scale_by = 0.8;
0384             qreal cs = transform().m11();
0385             if ( cs * scale_by < d->min_scale )
0386                 scale_by = d->min_scale / cs;
0387             scale(scale_by, 1);
0388         }
0389         else
0390         {
0391             scale(1.25, 1);
0392         }
0393 
0394         d->update_frame_skip(transform());
0395     }
0396     else
0397     {
0398         if ( d->horizontal_scroll(event->modifiers()) )
0399         {
0400             QApplication::sendEvent(horizontalScrollBar(), event);
0401         }
0402         else
0403         {
0404             int row = d->scroll_row();
0405             if ( event->angleDelta().y() > 0 && !event->inverted() )
0406                 row -= 1;
0407             else
0408                 row += 1;
0409             Q_EMIT scrolled(row);
0410         }
0411     }
0412 }
0413 
0414 void TimelineWidget::paintEvent(QPaintEvent* event)
0415 {
0416     QPen dark(palette().text(), 1);
0417     int small_height = d->header_height / 4;
0418     QPointF scene_tl = mapToScene(event->rect().topLeft());
0419     QPointF scene_br = mapToScene(event->rect().bottomRight());
0420     QBrush disabled = palette().brush(QPalette::Disabled, QPalette::Base);
0421 
0422     // bg
0423     QPainter painter;
0424     painter.begin(viewport());
0425 
0426     // stripy rows
0427     QPointF viewport_scene_tl = mapFromScene(QPointF(0, 0));
0428     auto layer_top_left = mapFromScene(d->start_time, scene_tl.y());
0429     auto layer_top_right = mapFromScene(d->end_time, scene_tl.y());
0430     std::array<QBrush, 2> brushes = {
0431         palette().base(),
0432         palette().alternateBase(),
0433     };
0434 
0435     int n_rows = d->root->visible_rows();
0436 
0437     for ( int i = 0; i < n_rows - 1; i++ )
0438     {
0439         painter.fillRect(
0440             QRectF(
0441                 QPointF(layer_top_left.x(), i*d->row_height + viewport_scene_tl.y()),
0442                 QPointF(layer_top_right.x(), (i+1)*d->row_height + viewport_scene_tl.y())
0443             ),
0444             brushes[i%2]
0445         );
0446     }
0447 
0448     // gray out the area after the outside the layer range
0449     if ( layer_top_left.x() > 0 )
0450     {
0451         painter.fillRect(
0452             QRectF(
0453                 QPointF(0, event->rect().top()),
0454                 QPointF(layer_top_left.x(), event->rect().bottom())
0455             ),
0456             disabled
0457         );
0458     }
0459     painter.fillRect(
0460         QRectF(
0461             layer_top_right,
0462             QPointF(width(), event->rect().right())
0463         ),
0464         disabled
0465     );
0466     painter.end();
0467 
0468     // scene
0469     QGraphicsView::paintEvent(event);
0470 
0471     // fg
0472     painter.begin(viewport());
0473 
0474 
0475     // Vertical line for the current keyframe
0476     if ( d->document )
0477     {
0478         painter.setPen(dark);
0479         int cur_x = mapFromScene(d->document->current_time(), 0).x();
0480         painter.drawLine(cur_x, event->rect().top(), cur_x, event->rect().bottom());
0481     }
0482 
0483     // Fill the header background
0484     painter.fillRect(event->rect().left(), 0, event->rect().right(), d->header_height, disabled);
0485 
0486     // Gray out ticks outside layer range
0487     painter.fillRect(
0488         QRectF(
0489             QPointF(mapFromScene(d->start_time, 0).x(), 0),
0490             QPointF(mapFromScene(d->end_time, 0).x(), small_height)
0491         ),
0492         palette().base()
0493     );
0494     painter.fillRect(
0495         QRectF(
0496             QPointF(mapFromScene(d->time_round_to_ticks(d->start_time) - d->frame_skip, 0).x(), small_height),
0497             QPointF(mapFromScene(d->time_round_to_ticks(d->end_time-1), 0).x(), d->header_height)
0498         ),
0499         palette().base()
0500     );
0501 
0502     painter.setPen(dark);
0503     painter.drawLine(event->rect().left(), d->header_height, event->rect().right(), d->header_height);
0504 
0505 
0506     // Draw header ticks marks
0507     if ( event->rect().top() < d->header_height )
0508     {
0509         QColor frame_line = palette().color(QPalette::Text);
0510         frame_line.setAlphaF(0.5);
0511         QPen light(frame_line, 1);
0512         painter.setPen(light);
0513         int first_frame = qCeil(scene_tl.x());
0514         int last_frame = qFloor(scene_br.x());
0515         for ( int f = first_frame; f <= last_frame; f++ )
0516         {
0517             QPoint p1 = mapFromScene(f, scene_tl.y());
0518             int height = d->header_height;
0519 
0520             if ( f % d->frame_skip == 0 )
0521             {
0522                 d->paint_frame_rect(this, painter, f, Qt::NoBrush, dark);
0523                 painter.setPen(light);
0524             }
0525             else
0526             {
0527                 height = small_height;
0528             }
0529 
0530             painter.drawLine(QPoint(p1.x(), 0), QPoint(p1.x(), height));
0531         }
0532 
0533         d->paint_frame_rect(this, painter, d->start_time, palette().base(), dark);
0534 
0535         if ( d->document )
0536         {
0537             d->paint_highligted_frame(d->document->current_time(), painter, palette().text());
0538             d->paint_frame_rect(this, painter, d->document->current_time(), palette().text(), QPen(palette().base(), 1));
0539         }
0540 
0541         if ( d->mouse_frame > -1 )
0542         {
0543             d->paint_highligted_frame(d->mouse_frame, painter, palette().highlight());
0544             d->paint_frame_rect(this, painter, d->mouse_frame, palette().highlight(), QPen(palette().highlightedText(), 1));
0545         }
0546     }
0547 }
0548 
0549 void TimelineWidget::reset_view()
0550 {
0551     // scene_rect changes min_scale, which changes transform, which changes frame_skip, which changes scene_rect
0552     setSceneRect(d->scene_rect());
0553     d->adjust_min_scale(viewport()->width());
0554 
0555     d->update_frame_skip(QTransform::fromScale(d->min_scale, 1));
0556 
0557     setSceneRect(d->scene_rect());
0558     d->adjust_min_scale(viewport()->width());
0559     setTransform(QTransform::fromScale(d->min_scale, 1));
0560 }
0561 
0562 void TimelineWidget::resizeEvent(QResizeEvent* event)
0563 {
0564     setSceneRect(d->scene_rect());
0565     QGraphicsView::resizeEvent(event);
0566     d->adjust_min_scale(viewport()->width());
0567     if ( transform().m11() < d->min_scale )
0568         reset_view();
0569 }
0570 
0571 void TimelineWidget::scrollContentsBy(int dx, int dy)
0572 {
0573     QGraphicsView::scrollContentsBy(dx, dy);
0574     viewport()->update();
0575 }
0576 
0577 
0578 void TimelineWidget::mousePressEvent(QMouseEvent* event)
0579 {
0580     d->mouse_frame = d->frame_at_point(event->pos());
0581 
0582     if ( event->button() == Qt::MiddleButton )
0583     {
0584         setCursor(Qt::ClosedHandCursor);
0585         d->drag_mode = Private::DragMode::Pan;
0586         d->drag_start = mapToScene(event->pos());
0587     }
0588 #if QT_VERSION_MAJOR >= 6
0589     else if ( event->position().y() > d->header_height )
0590 #else
0591     else if ( event->y() > d->header_height )
0592 #endif
0593     {
0594         QGraphicsView::mousePressEvent(event);
0595         auto selection = d->scene.selectedItems();
0596         if ( d->scene.selectedItems().empty() )
0597             for ( const auto& s : selection )
0598                 s->setSelected(true);
0599     }
0600     else if ( event->button() == Qt::LeftButton )
0601     {
0602         d->drag_mode = Private::DragMode::Frame;
0603         Q_EMIT frame_clicked(d->mouse_frame);
0604     }
0605 
0606 }
0607 
0608 void TimelineWidget::mouseMoveEvent(QMouseEvent* event)
0609 {
0610     d->mouse_frame = d->frame_at_point(event->pos());
0611 
0612     if ( d->drag_mode == Private::DragMode::Pan )
0613     {
0614         QPointF scene_pos = mapToScene(event->pos());
0615         QPointF drag_delta = scene_pos - d->drag_start;
0616         if ( d->horizontal_scroll(event->modifiers()) )
0617         {
0618             int scroll_by = scene_pos.x() - d->drag_start.x();
0619             int scroll = horizontalScrollBar()->value() - scroll_by * transform().m11();
0620             horizontalScrollBar()->setValue(scroll);
0621             d->drag_start = scene_pos;
0622         }
0623         else
0624         {
0625             int scroll_by = qRound(drag_delta.y() / d->row_height);
0626             if ( scroll_by != 0 )
0627             {
0628                 d->drag_start = scene_pos;
0629                 int row = d->scroll_row() - scroll_by;
0630                 if ( row >= 0 && row <= d->max_scroll_row() )
0631                 {
0632                     Q_EMIT scrolled(row);
0633                     d->drag_start.setY(scene_pos.y() - scroll_by *  d->row_height);
0634                 }
0635             }
0636         }
0637     }
0638     else if ( d->drag_mode == Private::DragMode::Frame )
0639     {
0640         Q_EMIT frame_clicked(d->mouse_frame);
0641     }
0642 #if QT_VERSION_MAJOR >= 6
0643     else if ( event->position().y() > d->header_height )
0644 #else
0645     else if ( event->y() > d->header_height )
0646 #endif
0647     {
0648         QGraphicsView::mouseMoveEvent(event);
0649     }
0650 
0651     viewport()->update();
0652 }
0653 
0654 void TimelineWidget::mouseReleaseEvent(QMouseEvent* event)
0655 {
0656     if ( event->button() == Qt::MiddleButton && d->drag_mode == Private::DragMode::Pan )
0657     {
0658         unsetCursor();
0659         d->drag_mode = Private::DragMode::None;
0660     }
0661     else if ( event->button() == Qt::LeftButton && d->drag_mode == Private::DragMode::Frame )
0662     {
0663         d->drag_mode = Private::DragMode::None;
0664         Q_EMIT frame_clicked(d->mouse_frame);
0665     }
0666     else
0667     {
0668         QGraphicsView::mouseReleaseEvent(event);
0669     }
0670 }
0671 
0672 void TimelineWidget::leaveEvent(QEvent* event)
0673 {
0674     QGraphicsView::leaveEvent(event);
0675     if ( !d->keep_highlight )
0676     {
0677         d->mouse_frame = -1;
0678         viewport()->update();
0679     }
0680     d->keep_highlight = false;
0681 }
0682 
0683 #if QT_VERSION_MAJOR < 6
0684 void TimelineWidget::enterEvent(QEvent* event)
0685 #else
0686 void TimelineWidget::enterEvent(QEnterEvent* event)
0687 #endif
0688 {
0689     QGraphicsView::enterEvent(event);
0690     d->keep_highlight = false;
0691 }
0692 
0693 int TimelineWidget::header_height() const
0694 {
0695     return d->header_height;
0696 }
0697 
0698 void TimelineWidget::select(const QItemSelection& selected, const QItemSelection& deselected)
0699 {
0700     for ( const auto& index : selected.indexes() )
0701     {
0702         if ( auto item = d->index_to_line(index) )
0703         {
0704             item->setVisible(true);
0705             item->setSelected(true);
0706         }
0707     }
0708 
0709     for ( const auto& index : deselected.indexes() )
0710     {
0711         if ( auto item = d->index_to_line(index) )
0712             item->setSelected(false);
0713     }
0714 
0715 
0716     // Sometimes the expand/collapse status gets out of sync, so we force it when selecting a row
0717     auto root_index = d->model->index(0, 0);
0718     if ( auto root_line = d->index_to_line(root_index) )
0719         d->adjust_expand(root_index, root_line, true);
0720 
0721 }
0722 
0723 
0724 item_models::PropertyModelFull::Item TimelineWidget::item_at(const QPoint& viewport_pos)
0725 {
0726     for ( QGraphicsItem* it : items(viewport_pos) )
0727     {
0728         switch ( ItemTypes(it->type()) )
0729         {
0730             case ItemTypes::LineItem:
0731             case ItemTypes::AnimatableItem:
0732             case ItemTypes::ObjectLineItem:
0733             case ItemTypes::PropertyLineItem:
0734             case ItemTypes::ObjectListLineItem:
0735                 return static_cast<LineItem*>(it)->property_item();
0736         }
0737     }
0738     return {};
0739 }
0740 
0741 std::pair<model::KeyframeBase*, model::KeyframeBase*> TimelineWidget::keyframe_at(const QPoint& viewport_pos)
0742 {
0743     for ( QGraphicsItem* it : items(viewport_pos) )
0744     {
0745         if ( auto kfit = dynamic_cast<KeyframeSplitItem*>(it) )
0746         {
0747             auto anit = static_cast<AnimatableItem*>(it->parentItem());
0748             return anit->keyframes(kfit);
0749         }
0750     }
0751     return {nullptr, nullptr};
0752 }
0753 
0754 void TimelineWidget::keyPressEvent(QKeyEvent* event)
0755 {
0756     if ( !d->document )
0757         return;
0758 
0759     /// \todo figure why pageup/end etc aren't received here...
0760     switch ( event->key() )
0761     {
0762         case Qt::Key_PageDown:
0763         case Qt::Key_Left:
0764             if ( d->document->current_time() - 1 >= d->start_time )
0765                 Q_EMIT frame_clicked(d->document->current_time() - 1);
0766             event->accept();
0767             break;
0768         case Qt::Key_PageUp:
0769         case Qt::Key_Right:
0770             if ( d->document->current_time() + 1 <= d->end_time )
0771                 Q_EMIT frame_clicked(d->document->current_time() + 1);
0772             event->accept();
0773             break;
0774         case Qt::Key_Home:
0775             Q_EMIT frame_clicked(d->start_time);
0776             event->accept();
0777             break;
0778         case Qt::Key_End:
0779             Q_EMIT frame_clicked(d->end_time);
0780             event->accept();
0781             break;
0782     }
0783 
0784     QGraphicsView::keyPressEvent(event);
0785 }
0786 
0787 qreal TimelineWidget::highlighted_time() const
0788 {
0789     if ( d->mouse_frame == -1 && d->document )
0790         return d->document->current_time();
0791 
0792     return d->mouse_frame;
0793 }
0794 
0795 void TimelineWidget::set_highlighted_time(int time)
0796 {
0797     d->mouse_frame = time;
0798 }
0799 
0800 void TimelineWidget::keep_highlight()
0801 {
0802     d->keep_highlight = true;
0803 }
0804 
0805 void TimelineWidget::collapse(const QModelIndex& index)
0806 {
0807     if ( auto line = d->index_to_line(index) )
0808         line->collapse();
0809     setSceneRect(d->scene_rect());
0810     viewport()->update();
0811 }
0812 
0813 void TimelineWidget::expand(const QModelIndex& index)
0814 {
0815     if ( auto line = d->index_to_line(index) )
0816         line->expand();
0817     setSceneRect(d->scene_rect());
0818     viewport()->update();
0819 }
0820 
0821 void TimelineWidget::set_model(item_models::CompFilterModel* model, item_models::PropertyModelFull* base_model, QTreeView* expander)
0822 {
0823     d->model = model;
0824     d->base_model = base_model;
0825     d->expander = expander;
0826     connect(model, &QAbstractItemModel::rowsInserted, this, &TimelineWidget::model_rows_added);
0827     connect(model, &QAbstractItemModel::rowsRemoved, this, &TimelineWidget::model_rows_removed);
0828     connect(model, &QAbstractItemModel::rowsMoved, this, &TimelineWidget::model_rows_moved);
0829     connect(model, &QAbstractItemModel::modelReset, this, &TimelineWidget::model_reset);
0830     connect(model, &item_models::CompFilterModel::composition_changed, this, &TimelineWidget::update_comp);
0831 }
0832 
0833 void TimelineWidget::model_reset()
0834 {
0835     d->clear();
0836     d->insert_children(QModelIndex(), d->root);
0837 }
0838 
0839 void TimelineWidget::model_rows_added(const QModelIndex& parent, int first, int last)
0840 {
0841     auto parent_line = d->index_to_line(parent);
0842     if ( !parent_line )
0843         return;
0844 
0845     for ( int i = first; i <= last; i++ )
0846         d->insert_index(d->model->index(first, 0, parent), parent_line, i);
0847 
0848 
0849     d->adjust_expand(parent, parent_line);
0850 
0851     setSceneRect(d->scene_rect());
0852 
0853     // We are better at preserving selection than the treeview, so force a click
0854     emit_clicked();
0855 }
0856 
0857 void TimelineWidget::model_rows_removed(const QModelIndex& parent, int first, int last)
0858 {
0859     auto line = d->index_to_line(parent);
0860     // Shouldn't happen but fail safe
0861     if ( !line || line == d->root )
0862         return;
0863 
0864     line->remove_rows(first, last);
0865 
0866     d->adjust_expand(parent, line);
0867 
0868     setSceneRect(d->scene_rect());
0869 
0870     // We are better at preserving selection than the treeview, so force a click
0871     emit_clicked();
0872 }
0873 
0874 void TimelineWidget::on_item_removed(quintptr id)
0875 {
0876     d->line_items.erase(id);
0877 }
0878 
0879 void TimelineWidget::model_rows_moved(const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row)
0880 {
0881     // Given how the property model does things, I can assume
0882     // start == end && parent == destination && row != start
0883 
0884     Q_UNUSED(end);
0885     Q_UNUSED(destination);
0886 
0887     if ( row > start )
0888         row -= 1;
0889 
0890     if ( LineItem* item = d->index_to_line(parent) )
0891     {
0892         item->move_row(start, row);
0893 
0894         d->adjust_expand(parent, item);
0895     }
0896 
0897     setSceneRect(d->scene_rect());
0898 
0899     // We are better at preserving selection than the treeview, so force a click
0900     emit_clicked();
0901 }
0902 
0903 static void debug_line(timeline::LineItem* line_item, QString indent, int index, bool recursive)
0904 {
0905 
0906     QString item_name = line_item->metaObject()->className();
0907     item_name = item_name.mid(item_name.indexOf("::")+2);
0908 
0909     int effective_index = line_item->pos().y() / line_item->row_height();
0910 
0911 
0912     QString debug_string;
0913     auto item = line_item->property_item();
0914     if ( item.object )
0915     {
0916         debug_string = item.object->object_name();
0917         if ( debug_string.contains(' ') )
0918             debug_string = '"' + debug_string + '"';
0919     }
0920 
0921     if ( item.property )
0922     {
0923         if ( !debug_string.isEmpty() )
0924             debug_string += "->";
0925         debug_string += item.property->name();
0926     }
0927 
0928     if ( debug_string.isEmpty() )
0929         debug_string = "NULL";
0930 
0931     qDebug().noquote() << indent
0932         << index << effective_index << line_item->visible_height() /  line_item->row_height()
0933         << line_item->id() << item_name << debug_string << line_item->is_expanded() << line_item->isVisible();
0934 
0935     if ( recursive )
0936     {
0937         for ( uint i = 0; i < line_item->rows().size(); i++ )
0938             debug_line(line_item->rows()[i], indent + "    ", i, true);
0939     }
0940 }
0941 
0942 void TimelineWidget::debug_lines() const
0943 {
0944     qDebug() << "index" << "effective_index" << "visible_rows" << "id" << "item_class" << "object->property" << "expanded" << "visible";
0945     debug_line(d->root, "", 0, true);
0946 
0947     qDebug() << sceneRect() << d->scene_rect() << d->rounded_end_time();
0948 }
0949 
0950 void TimelineWidget::toggle_debug(bool debug)
0951 {
0952     timeline::enable_debug = debug;
0953     viewport()->update();
0954 }
0955 
0956 void TimelineWidget::emit_clicked()
0957 {
0958     for ( auto item : d->scene.selectedItems() )
0959     {
0960         if ( item->type() >= int(ItemTypes::LineItem) )
0961         {
0962             Q_EMIT line_clicked(static_cast<LineItem*>(item)->id(), true, true);
0963             return;
0964         }
0965     }
0966 }