/* timeline-layout-helper.cpp - Implementation of the timeline layout helper class Copyright (C) Lumiera.org 2008, Joel Holdsworth This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****************************************************/ #include #include "timeline-layout-helper.hpp" #include "../timeline-widget.hpp" #include "../../model/sequence.hpp" using namespace Gtk; using namespace std; using namespace boost; using namespace lumiera; using namespace util; namespace gui { namespace widgets { namespace timeline { const int TimelineLayoutHelper::AnimationTimeout = 20; // 20ms TimelineLayoutHelper::TimelineLayoutHelper(TimelineWidget &owner) : timelineWidget(owner), totalHeight(0), animation_state(Track::NoAnimationState) { } void TimelineLayoutHelper::clone_tree_from_sequence() { const shared_ptr &sequence = timelineWidget.sequence; REQUIRE(sequence); layoutTree.clear(); TrackTree::iterator_base iterator = layoutTree.set_head(sequence); add_branch(iterator, sequence); } TimelineLayoutHelper::TrackTree& TimelineLayoutHelper::get_layout_tree() { return layoutTree; } void TimelineLayoutHelper::add_branch( TrackTree::iterator_base parent_iterator, shared_ptr parent) { BOOST_FOREACH(shared_ptr child, parent->get_child_tracks()) { TrackTree::iterator_base child_iterator = layoutTree.append_child(parent_iterator, child); add_branch(child_iterator, child); } } optional TimelineLayoutHelper::get_track_header_rect( boost::weak_ptr track) { if(contains(headerBoxes, track)) { Gdk::Rectangle rect(headerBoxes[track]); rect.set_y(rect.get_y() - timelineWidget.get_y_scroll_offset()); return optional(rect); } return optional(); } shared_ptr TimelineLayoutHelper::header_from_point(Gdk::Point point) { // Apply the scroll offset point.set_y(point.get_y() + timelineWidget.get_y_scroll_offset()); // Search the headers std::pair, Gdk::Rectangle> pair; BOOST_FOREACH( pair, headerBoxes ) { // Hit test the rectangle const Gdk::Rectangle &rect = pair.second; if(point.get_x() >= rect.get_x() && point.get_x() < rect.get_x() + rect.get_width() && point.get_y() >= rect.get_y() && point.get_y() < rect.get_y() + rect.get_height()) return shared_ptr(pair.first); } // No track was found - return an empty pointer return shared_ptr(); } boost::shared_ptr TimelineLayoutHelper::track_from_y(int y) { // Apply the scroll offset y += timelineWidget.get_y_scroll_offset(); // Search the tracks std::pair, Gdk::Rectangle> pair; BOOST_FOREACH( pair, headerBoxes ) { // Hit test the rectangle const Gdk::Rectangle &rect = pair.second; if(y >= rect.get_y() && y < rect.get_y() + rect.get_height()) return shared_ptr(pair.first); } // No track was found - return an empty pointer return shared_ptr(); } int TimelineLayoutHelper::get_total_height() const { ENSURE(totalHeight >= 0); return totalHeight; } void TimelineLayoutHelper::update_layout() { int offset = 0; // Reset the animation state value, before it gets recalculated animation_state = Track::NoAnimationState; // Clear previously cached layout headerBoxes.clear(); // Do the layout const int header_width = TimelineWidget::HeaderWidth; const int indent_width = TimelineWidget::HeaderIndentWidth; layout_headers_recursive(layoutTree.begin(), offset, animation_state, header_width, indent_width, 0, true); totalHeight = offset; // Signal that the layout has changed timelineWidget.on_layout_changed(); // Begin animating as necessary if(animation_state != Track::NoAnimationState && !animationTimer) begin_animation(); } void TimelineLayoutHelper::layout_headers_recursive( TrackTree::iterator_base parent_iterator, int &offset, int &common_animation_state, const int header_width, const int indent_width, const int depth, const bool parent_expanded) { REQUIRE(depth >= 0); TrackTree::sibling_iterator iterator; for(iterator = layoutTree.begin(parent_iterator); iterator != layoutTree.end(parent_iterator); iterator++) { const shared_ptr &model_track = *iterator; REQUIRE(model_track); shared_ptr timeline_track = lookup_timeline_track(model_track); // Is the track animating? const int animation_state = timeline_track->get_expand_animation_state(); // Is the track going to be shown? if(parent_expanded) { // Calculate and store the box of the header const int track_height = timeline_track->get_height(); const int indent = depth * indent_width; headerBoxes[timeline_track] = Gdk::Rectangle( indent, // x offset, // y max( header_width - indent, 0 ), // width track_height); // height // Offset for the next header offset += track_height + TimelineWidget::TrackPadding; } // Recurse to children const bool expand_child = ((animation_state != Track::NoAnimationState) || timeline_track->get_expanded()) && parent_expanded; layout_headers_recursive(iterator, offset, common_animation_state, header_width, indent_width, depth + 1, expand_child); // Do collapse animation as necessary if(animation_state != Track::NoAnimationState) { timeline_track->tick_expand_animation(); // Calculate the total height of the branch // Get the top and bottom descendants, and use them to get the // total height const shared_ptr &first_descendant_track = lookup_timeline_track( *(++TrackTree::pre_order_iterator(iterator))); const shared_ptr &last_descendant_track = lookup_timeline_track( *(--TrackTree::pre_order_iterator( ++TrackTree::sibling_iterator(iterator)))); const Gdk::Rectangle &first_rect = headerBoxes[first_descendant_track]; const Gdk::Rectangle &last_rect = headerBoxes[last_descendant_track]; const int branch_height = (last_rect.get_y() + last_rect.get_height()) - first_rect.get_y(); // Now we have the branch_height, obscure tracks according to // the animation state offset = offset - branch_height + branch_height * animation_state / Track::MaxExpandAnimation; TrackTree::pre_order_iterator descendant_iterator(iterator); descendant_iterator++; TrackTree::sibling_iterator end_iterator(iterator); end_iterator++; for(descendant_iterator = layoutTree.begin(parent_iterator); descendant_iterator != end_iterator; descendant_iterator++) { const weak_ptr &track = lookup_timeline_track(*descendant_iterator); const Gdk::Rectangle &rect = headerBoxes[track]; if(rect.get_y() + rect.get_height() > offset) headerBoxes.erase(track); } // Make sure the global animation state includes this branch's // animation state common_animation_state = max( common_animation_state, animation_state); } } } shared_ptr TimelineLayoutHelper::lookup_timeline_track( shared_ptr model_track) { REQUIRE(model_track != NULL); shared_ptr timeline_track = timelineWidget.lookup_timeline_track(model_track); ENSURE(timeline_track); return timeline_track; } void TimelineLayoutHelper::begin_animation() { animationTimer = Glib::signal_timeout().connect( sigc::mem_fun(this, &TimelineLayoutHelper::on_animation_tick), AnimationTimeout); } bool TimelineLayoutHelper::on_animation_tick() { update_layout(); return animation_state != Track::NoAnimationState; } } // namespace timeline } // namespace widgets } // namespace gui