diff --git a/src/gui/model/track.cpp b/src/gui/model/track.cpp index a0287efd9..c8d1c3356 100644 --- a/src/gui/model/track.cpp +++ b/src/gui/model/track.cpp @@ -72,6 +72,25 @@ Track::print_branch() return print_branch_recursive(0); } +shared_ptr +Track::find_parent(shared_ptr root, shared_ptr child) +{ + REQUIRE(root != NULL); + REQUIRE(child != NULL); + const list< shared_ptr > children = root->get_child_tracks(); + BOOST_FOREACH(shared_ptr track, children) + { + if(track == child) + return root; + + shared_ptr result = find_parent(track, child); + if(result) + return result; + } + + return shared_ptr(); +} + string Track::print_branch_recursive(const unsigned int indentation) { diff --git a/src/gui/model/track.hpp b/src/gui/model/track.hpp index 300e0f9b8..37bbe955e 100644 --- a/src/gui/model/track.hpp +++ b/src/gui/model/track.hpp @@ -53,7 +53,9 @@ public: virtual std::string print_track() = 0; - + static boost::shared_ptr + find_parent(boost::shared_ptr root, + boost::shared_ptr child); protected: std::string print_branch_recursive(const unsigned int indentation); diff --git a/src/gui/widgets/timeline-widget.cpp b/src/gui/widgets/timeline-widget.cpp index 0e589bba0..3aec39992 100644 --- a/src/gui/widgets/timeline-widget.cpp +++ b/src/gui/widgets/timeline-widget.cpp @@ -54,7 +54,8 @@ TimelineWidget::TimelineWidget( horizontalAdjustment(0, 0, 0), verticalAdjustment(0, 0, 0), horizontalScroll(horizontalAdjustment), - verticalScroll(verticalAdjustment) + verticalScroll(verticalAdjustment), + update_tracks_frozen(false) { REQUIRE(sequence); @@ -288,6 +289,9 @@ TimelineWidget::on_add_track_command() void TimelineWidget::update_tracks() { + if(update_tracks_frozen) + return; + REQUIRE(sequence); // Remove any tracks which are no longer present in the model @@ -301,6 +305,18 @@ TimelineWidget::update_tracks() layoutHelper.update_layout(); } +void +TimelineWidget::freeze_update_tracks() +{ + update_tracks_frozen = true; +} + +void +TimelineWidget::thaw_update_tracks() +{ + update_tracks_frozen = false; +} + void TimelineWidget::create_timeline_tracks() { diff --git a/src/gui/widgets/timeline-widget.hpp b/src/gui/widgets/timeline-widget.hpp index 7113d100f..d79547935 100644 --- a/src/gui/widgets/timeline-widget.hpp +++ b/src/gui/widgets/timeline-widget.hpp @@ -171,6 +171,10 @@ private: **/ void update_tracks(); + void freeze_update_tracks(); + + void thaw_update_tracks(); + /** * Ensures timeline UI tracks have been created for every model track * present in sequence. @@ -297,6 +301,8 @@ protected: sigc::signal playbackPeriodDragReleasedSignal; sigc::signal > hoveringTrackChangedSignal; + + bool update_tracks_frozen; /* ===== Constants ===== */ public: diff --git a/src/gui/widgets/timeline/timeline-header-container.cpp b/src/gui/widgets/timeline/timeline-header-container.cpp index 4497be6bc..dfc81bef4 100644 --- a/src/gui/widgets/timeline/timeline-header-container.cpp +++ b/src/gui/widgets/timeline/timeline-header-container.cpp @@ -411,13 +411,13 @@ TimelineHeaderContainer::begin_drag() } void -TimelineHeaderContainer::end_drag() +TimelineHeaderContainer::end_drag(bool apply) { TimelineLayoutHelper &layout = timelineWidget.layoutHelper; // Has the user been dragging? if(layout.get_dragging_track()) - layout.end_dragging_track(); + layout.end_dragging_track(apply); // Reset the arrow as a cursor REQUIRE(gdkWindow); diff --git a/src/gui/widgets/timeline/timeline-header-container.hpp b/src/gui/widgets/timeline/timeline-header-container.hpp index fe1006b1d..6787d7984 100644 --- a/src/gui/widgets/timeline/timeline-header-container.hpp +++ b/src/gui/widgets/timeline/timeline-header-container.hpp @@ -185,7 +185,7 @@ private: void begin_drag(); - void end_drag(); + void end_drag(bool apply = true); /** * Recusively raises all the header widget windows in a branch to the diff --git a/src/gui/widgets/timeline/timeline-layout-helper.cpp b/src/gui/widgets/timeline/timeline-layout-helper.cpp index d4cefec4a..cb22de581 100644 --- a/src/gui/widgets/timeline/timeline-layout-helper.cpp +++ b/src/gui/widgets/timeline/timeline-layout-helper.cpp @@ -150,13 +150,18 @@ TimelineLayoutHelper::begin_dragging_track( draggingTrack->get_model_track(); draggingTrackIter = iterator_from_track(model_track); dragBranchHeight = measure_branch_height(draggingTrackIter); + + draggingDrop.relation = None; return draggingTrack; } void -TimelineLayoutHelper::end_dragging_track() +TimelineLayoutHelper::end_dragging_track(bool apply) { + if(apply) + apply_drop_to_model_tree(draggingDrop); + draggingTrack.reset(); clone_tree_from_sequence(); update_layout(); @@ -177,6 +182,8 @@ TimelineLayoutHelper::get_dragging_track_iter() const void TimelineLayoutHelper::drag_to_point(const Gdk::Point &point) { + optional drop; + // Apply the scroll offset const Gdk::Point last_point(dragPoint); dragPoint = Gdk::Point(point.get_x(), @@ -221,34 +228,42 @@ TimelineLayoutHelper::drag_to_point(const Gdk::Point &point) const int full_width = rect.get_x() + rect.get_width(); const int x_mid = rect.get_x() + rect.get_width() / 2; - // Do hit test - if(attempt_drop_upper(iterator, test_point, y, - full_width, half_height)) - break; + // Do hit test + drop = attempt_drop_upper(iterator, test_point, y, + full_width, half_height); + if(drop) break; - if(attempt_drop_lower(iterator, test_point, - x_mid, full_width, y_mid, half_height)) - break; + drop = attempt_drop_lower(iterator, test_point, + x_mid, full_width, y_mid, half_height); + if(drop) break; } - + + // Did we get a drop point? + if(drop) + { + apply_drop_to_layout_tree(*drop); + draggingDrop = *drop; + } + update_layout(); } -bool +optional TimelineLayoutHelper::attempt_drop_upper( TrackTree::pre_order_iterator target, const Gdk::Point &point, const int y, const int full_width, const int half_height) { if(pt_in_rect(point, Gdk::Rectangle(0, y, full_width, half_height))) { - draggingTrackIter = layoutTree.move_before( - target, draggingTrackIter); - return true; + Drop drop; + drop.target = target; + drop.relation = Before; + return drop; } - return false; + return optional(); } -bool +optional TimelineLayoutHelper::attempt_drop_lower( TrackTree::pre_order_iterator target, const Gdk::Point &point, const int x_mid, const int full_width, const int y_mid, @@ -259,7 +274,9 @@ TimelineLayoutHelper::attempt_drop_lower( if(!pt_in_rect(point, Gdk::Rectangle(0, y_mid, full_width, half_height))) - return false; + return optional(); + + Drop drop = {target, None}; if(model_track->can_host_children()) { @@ -267,47 +284,135 @@ TimelineLayoutHelper::attempt_drop_lower( { // Is our track being dragged after this header? if(dragPoint.get_x() < x_mid) - { - draggingTrackIter = layoutTree.move_after( - target, draggingTrackIter); - } + drop.relation = After; else - { - if(draggingTrackIter.node->parent != target.node) - { - // Insert a place-holder in the tree - const TrackTree::pre_order_iterator placeholder = - layoutTree.prepend_child(target); - - // Replace it with the relocated branch - draggingTrackIter = layoutTree.move_ontop( - placeholder, draggingTrackIter); - } - } + drop.relation = FirstChild; } else - { - if(draggingTrackIter.node->parent != target.node) - { - // Insert a place-holder in the tree - const TrackTree::pre_order_iterator placeholder = - layoutTree.append_child(target); - - // Replace it with the relocated branch - draggingTrackIter = layoutTree.move_ontop( - placeholder, draggingTrackIter); - } - } + drop.relation = LastChild; } else { // When this track cannot be a parent, the dragging track is - // simply dropped after - draggingTrackIter = layoutTree.move_after( - target, draggingTrackIter); + // simply dropped after + drop.relation = After; } - return true; + return drop; +} + +void +TimelineLayoutHelper::apply_drop_to_layout_tree( + const TimelineLayoutHelper::Drop &drop) +{ + switch(drop.relation) + { + case None: + break; + + case Before: + draggingTrackIter = layoutTree.move_before( + drop.target, draggingTrackIter); + break; + + case After: + draggingTrackIter = layoutTree.move_after( + drop.target, draggingTrackIter); + break; + + case FirstChild: + if(draggingTrackIter.node->parent != drop.target.node) + { + draggingTrackIter = layoutTree.move_ontop( + layoutTree.prepend_child(drop.target), draggingTrackIter); + } + break; + + case LastChild: + if(draggingTrackIter.node->parent != drop.target.node) + { + draggingTrackIter = layoutTree.move_ontop( + layoutTree.append_child(drop.target), draggingTrackIter); + } + break; + + default: + ASSERT(0); // Unexpected value of relation + break; + } +} + +void +TimelineLayoutHelper::apply_drop_to_model_tree( + const TimelineLayoutHelper::Drop &drop) +{ + if(drop.relation == None) + return; + + // Freeze the timeline widget - it must be done manually later + timelineWidget.freeze_update_tracks(); + + // Get the tracks + shared_ptr &dragging_track = *draggingTrackIter; + REQUIRE(dragging_track); + REQUIRE(dragging_track != timelineWidget.sequence); + + shared_ptr &target_track = *drop.target; + REQUIRE(target_track); + REQUIRE(target_track != timelineWidget.sequence); + + // Detach the track from the old parent + shared_ptr old_parent = + dynamic_pointer_cast( + model::Track::find_parent( + timelineWidget.sequence, dragging_track)); + REQUIRE(old_parent); // The track must have a parent + old_parent->get_child_track_list().remove(dragging_track); + + if(drop.relation == Before || drop.relation == After) + { + // Find the new parent track + shared_ptr new_parent = + dynamic_pointer_cast( + model::Track::find_parent( + timelineWidget.sequence, target_track)); + REQUIRE(new_parent); // The track must have a parent + + // Find the destination point + observable_list< shared_ptr > &dest = + new_parent->get_child_track_list(); + list< shared_ptr >::iterator iter; + for(iter = dest.begin(); iter != dest.end(); iter++) + { + if(*iter == target_track) + break; + } + REQUIRE(iter != dest.end()); // The target must be + // in the destination + + // We have to jump on 1 if we want to insert after + if(drop.relation == After) + iter++; + + // Insert at this point + dest.insert(iter, dragging_track); + } + else if(drop.relation == FirstChild || drop.relation == LastChild) + { + shared_ptr new_parent = + dynamic_pointer_cast( + target_track); + REQUIRE(new_parent); // The track must have a parent + + if(drop.relation == FirstChild) + new_parent->get_child_track_list().push_front(dragging_track); + else if(drop.relation == LastChild) + new_parent->get_child_track_list().push_back(dragging_track); + } + else ASSERT(0); // Unexpected value of relation + + // Freeze the timeline widget - we will do it manually + timelineWidget.freeze_update_tracks(); } int diff --git a/src/gui/widgets/timeline/timeline-layout-helper.hpp b/src/gui/widgets/timeline/timeline-layout-helper.hpp index 4f7acf0b5..afecb6aab 100644 --- a/src/gui/widgets/timeline/timeline-layout-helper.hpp +++ b/src/gui/widgets/timeline/timeline-layout-helper.hpp @@ -130,7 +130,7 @@ public: boost::shared_ptr begin_dragging_track(const Gdk::Point &mouse_point); - void end_dragging_track(); + void end_dragging_track(bool apply); boost::shared_ptr get_dragging_track() const; @@ -159,7 +159,23 @@ public: boost::shared_ptr model_track); int measure_branch_height(TrackTree::iterator_base parent_iterator); + +protected: + enum TreeRelation + { + None, + Before, + After, + FirstChild, + LastChild + }; + struct Drop + { + TrackTree::pre_order_iterator target; + TreeRelation relation; + }; + protected: /** @@ -221,16 +237,19 @@ protected: * The animation timer tick callback. **/ bool on_animation_tick(); - -bool + +boost::optional attempt_drop_upper(TrackTree::pre_order_iterator target, const Gdk::Point &point, const int y, const int full_width, const int half_height); -bool +boost::optional attempt_drop_lower(TrackTree::pre_order_iterator target, const Gdk::Point &point, const int x_mid, const int full_width, const int y_mid, const int half_height); - -protected: + void apply_drop_to_layout_tree(const Drop &drop); + + void apply_drop_to_model_tree(const Drop &drop); + +protected: /** * The owner timeline widget as provided to the constructor. **/ @@ -268,6 +287,8 @@ protected: int dragBranchHeight; + Drop draggingDrop; + /** * The connection to the animation timer. * @see begin_animation()