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 }