Merge branch 'newlayout' into gui

This commit is contained in:
Joel Holdsworth 2009-01-02 16:07:58 +00:00
commit eb6f364084
14 changed files with 3326 additions and 311 deletions

View file

@ -144,6 +144,8 @@ libgui_la_SOURCES = \
$(lumigui_srcdir)/widgets/timeline/timeline-group-track.hpp \
$(lumigui_srcdir)/widgets/timeline/timeline-clip.cpp \
$(lumigui_srcdir)/widgets/timeline/timeline-clip.hpp \
$(lumigui_srcdir)/widgets/timeline/timeline-layout-helper.cpp \
$(lumigui_srcdir)/widgets/timeline/timeline-layout-helper.hpp \
$(lumigui_srcdir)/model/project.cpp \
$(lumigui_srcdir)/model/project.hpp \
$(lumigui_srcdir)/model/track.cpp \

View file

@ -32,6 +32,7 @@
#include <nobug.h>
#include <vector>
#include <boost/utility.hpp>
#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include "lib/util.hpp"

View file

@ -36,6 +36,7 @@ namespace widgets {
const int TimelineWidget::TrackPadding = 1;
const int TimelineWidget::HeaderWidth = 150;
const int TimelineWidget::HeaderIndentWidth = 10;
const double TimelineWidget::ZoomIncrement = 1.25;
const int64_t TimelineWidget::MaxScale = 30000000;
@ -43,13 +44,13 @@ TimelineWidget::TimelineWidget(
shared_ptr<model::Sequence> source_sequence) :
Table(2, 2),
sequence(source_sequence),
layoutHelper(*this),
viewWindow(this, 0, 1),
selectionStart(0),
selectionEnd(0),
playbackPeriodStart(0),
playbackPeriodEnd(0),
playbackPoint(GAVL_TIME_UNDEFINED),
totalHeight(0),
horizontalAdjustment(0, 0, 0),
verticalAdjustment(0, 0, 0),
horizontalScroll(horizontalAdjustment),
@ -300,17 +301,9 @@ TimelineWidget::update_tracks()
headerContainer->show_all_children();
headerContainer->update_headers();
// Update the body
body->queue_draw();
// Recalculate the total height of the timeline scrolled area
totalHeight = 0;
BOOST_FOREACH(shared_ptr<model::Track> track,
sequence->get_child_tracks())
{
REQUIRE(track);
totalHeight += measure_branch_height(track);
}
// Update the layout helper
layoutHelper.clone_tree_from_sequence();
layoutHelper.update_layout();
}
void
@ -338,9 +331,6 @@ TimelineWidget::create_timeline_tracks_from_branch(
// We will need to create one
trackMap[model_track] =
create_timeline_track_from_model_track(model_track);
// Hook up
}
// Recurse to child tracks
@ -418,6 +408,9 @@ TimelineWidget::lookup_timeline_track(
shared_ptr<model::Track> model_track) const
{
REQUIRE(sequence);
REQUIRE(model_track);
REQUIRE(model_track != sequence); // The sequence isn't really a track
std::map<shared_ptr<model::Track>, shared_ptr<timeline::Track> >::
const_iterator iterator = trackMap.find(model_track);
if(iterator == trackMap.end())
@ -433,30 +426,15 @@ TimelineWidget::lookup_timeline_track(
return iterator->second;
}
boost::shared_ptr<model::Track>
TimelineWidget::lookup_model_track(
const timeline::Track *timeline_track) const
void
TimelineWidget::on_layout_changed()
{
REQUIRE(sequence);
REQUIRE(headerContainer != NULL);
REQUIRE(body != NULL);
std::pair<shared_ptr<model::Track>, shared_ptr<timeline::Track> >
pair;
BOOST_FOREACH( pair, trackMap )
{
if(pair.second.get() == timeline_track)
{
ENSURE(pair.first);
return pair.first;
}
}
// The track is not present in the map
// We are in an error condition if the timeline track is not found
// - the timeline tracks must always be synchronous with the model
// tracks.
ENSURE(0);
return shared_ptr<model::Track>();
}
headerContainer->layout_headers();
body->queue_draw();
}
void
TimelineWidget::update_scroll()
@ -478,7 +456,8 @@ TimelineWidget::update_scroll()
// Calculate the vertical length that can be scrolled:
// the total height of all the tracks minus one screenful
int y_scroll_length = totalHeight - body_allocation.get_height();
int y_scroll_length = layoutHelper.get_total_height() -
body_allocation.get_height();
if(y_scroll_length < 0) y_scroll_length = 0;
// If by resizing we're now over-scrolled, scroll back to
@ -500,29 +479,6 @@ TimelineWidget::update_scroll()
}
int
TimelineWidget::measure_branch_height(
shared_ptr<model::Track> model_track)
{
REQUIRE(model_track);
const shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(model_track);
ENSURE(timeline_track);
int height = timeline_track->get_height() + TrackPadding;
// Recurse through all the children
BOOST_FOREACH( shared_ptr<model::Track> child,
model_track->get_child_tracks() )
{
REQUIRE(child);
height += measure_branch_height(child);
}
return height;
}
int
TimelineWidget::get_y_scroll_offset() const
{

View file

@ -35,6 +35,7 @@
#include "timeline/timeline-ibeam-tool.hpp"
#include "timeline/timeline-group-track.hpp"
#include "timeline/timeline-clip-track.hpp"
#include "timeline/timeline-layout-helper.hpp"
#include "../model/sequence.hpp"
@ -216,24 +217,12 @@ private:
**/
boost::shared_ptr<timeline::Track> lookup_timeline_track(
boost::shared_ptr<model::Track> model_track) const;
/**
* Looks up a model track in trackMap that corresponds to a
* given timeline track.
* @param timeline_track The timeline UI track to look up.
* @returns The model track found, or an empty shared_ptr if
* timeline_track has no corresponding timeline UI track (this is an
* error condition).
**/
boost::shared_ptr<model::Track> lookup_model_track(
const timeline::Track *timeline_track) const;
// ----- Layout Functions ----- //
void update_scroll();
void on_layout_changed();
int measure_branch_height(
boost::shared_ptr<model::Track> model_track);
void update_scroll();
int get_y_scroll_offset() const;
@ -276,6 +265,9 @@ protected:
std::map<boost::shared_ptr<model::Track>,
boost::shared_ptr<timeline::Track> >
trackMap;
// Helper Classes
timeline::TimelineLayoutHelper layoutHelper;
// View State
timeline::TimelineViewWindow viewWindow;
@ -289,8 +281,6 @@ protected:
boost::shared_ptr<timeline::Track> hoveringTrack;
int totalHeight;
// Child Widgets
timeline::TimelineHeaderContainer *headerContainer;
timeline::TimelineBody *body;
@ -318,11 +308,13 @@ public:
protected:
static const int TrackPadding;
static const int HeaderWidth;
static const int HeaderIndentWidth;
static const double ZoomIncrement;
friend class timeline::TimelineViewWindow;
friend class timeline::TimelineBody;
friend class timeline::TimelineHeaderContainer;
friend class timeline::TimelineLayoutHelper;
friend class timeline::TimelineRuler;
friend class timeline::Tool;
friend class timeline::ArrowTool;

View file

@ -244,6 +244,7 @@ bool
TimelineBody::on_motion_notify_event(GdkEventMotion *event)
{
REQUIRE(event != NULL);
REQUIRE(timelineWidget != NULL);
// Handle a middle-mouse drag if one is occuring
switch(dragType)
@ -270,8 +271,8 @@ TimelineBody::on_motion_notify_event(GdkEventMotion *event)
tool->on_motion_notify_event(event);
// See if the track that we're hovering over has changed
shared_ptr<timeline::Track> new_hovering_track =
track_from_point(event->y);
shared_ptr<timeline::Track> new_hovering_track(
timelineWidget->layoutHelper.track_from_y(event->y));
if(timelineWidget->get_hovering_track() != new_hovering_track)
timelineWidget->set_hovering_track(new_hovering_track);
@ -287,35 +288,53 @@ TimelineBody::draw_tracks(Cairo::RefPtr<Cairo::Context> cr)
REQUIRE(timelineWidget->sequence);
// Prepare
TimelineLayoutHelper &layout_helper = timelineWidget->layoutHelper;
const TimelineLayoutHelper::TrackTree &layout_tree =
layout_helper.get_layout_tree();
const Allocation allocation = get_allocation();
// Save the view matrix
Cairo::Matrix view_matrix;
cr->get_matrix(view_matrix);
// Translate the view by the scroll distance
cr->translate(0, -get_vertical_offset());
// Interate drawing each track
BOOST_FOREACH( shared_ptr<model::Track> model_track,
timelineWidget->sequence->get_child_tracks() )
draw_track_recursive(cr, model_track, allocation.get_width());
// Iterate drawing each track
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so we skip the sequence root
iterator != layout_tree.end();
iterator++)
{
const shared_ptr<model::Track> model_track(*iterator);
const shared_ptr<timeline::Track> timeline_track =
timelineWidget->lookup_timeline_track(*iterator);
optional<Gdk::Rectangle> rect =
layout_helper.get_track_header_rect(timeline_track);
// Is this track visible?
if(rect)
{
// Translate to the top of the track
cr->set_matrix(view_matrix);
cr->translate(0, rect->get_y());
// Draw the track
draw_track(cr, timeline_track, allocation.get_width());
}
}
// Restore the view matrix
cr->set_matrix(view_matrix);
}
void
TimelineBody::draw_track_recursive(Cairo::RefPtr<Cairo::Context> cr,
shared_ptr<model::Track> model_track, const int view_width) const
TimelineBody::draw_track(Cairo::RefPtr<Cairo::Context> cr,
shared_ptr<timeline::Track> timeline_track,
const int view_width) const
{
REQUIRE(cr);
REQUIRE(model_track != NULL);
REQUIRE(timeline_track != NULL);
REQUIRE(timelineWidget != NULL);
shared_ptr<timeline::Track> timeline_track = timelineWidget->
lookup_timeline_track(model_track);
const int height = timeline_track->get_height();
REQUIRE(height >= 0);
@ -329,14 +348,6 @@ TimelineBody::draw_track_recursive(Cairo::RefPtr<Cairo::Context> cr,
cr->save();
timeline_track->draw_track(cr, &timelineWidget->get_view_window());
cr->restore();
// Shift for the next track
cr->translate(0, height + TimelineWidget::TrackPadding);
// Recurse drawing into children
BOOST_FOREACH( shared_ptr<model::Track> child,
model_track->get_child_tracks() )
draw_track_recursive(cr, child, view_width);
}
void
@ -436,60 +447,6 @@ TimelineBody::set_vertical_offset(int offset)
timelineWidget->verticalAdjustment.set_value(offset);
}
shared_ptr<timeline::Track>
TimelineBody::track_from_point(const int y) const
{
REQUIRE(timelineWidget != NULL);
REQUIRE(timelineWidget->sequence);
int offset = -get_vertical_offset();
BOOST_FOREACH( shared_ptr<model::Track> model_track,
timelineWidget->sequence->get_child_tracks() )
{
shared_ptr<timeline::Track> result = track_from_branch(
model_track, y, offset);
if(result)
return result;
}
// No track has been found with this point in it
return boost::shared_ptr<timeline::Track>();
}
shared_ptr<timeline::Track> TimelineBody::track_from_branch(
shared_ptr<model::Track> model_track,
const int y, int &offset) const
{
REQUIRE(timelineWidget != NULL);
shared_ptr<timeline::Track> timeline_track = timelineWidget->
lookup_timeline_track(model_track);
const int height = timeline_track->get_height();
REQUIRE(height >= 0);
// Does the point fall in this track?
if(offset <= y && y < offset + height)
return timeline_track;
// Add the height of this track to the accumulation
offset += height;
// Recurse drawing into children
BOOST_FOREACH( shared_ptr<model::Track> child,
model_track->get_child_tracks() )
{
shared_ptr<timeline::Track> result =
track_from_branch(child, y, offset);
if(result != NULL)
return result;
}
// No track has been found in this branch
return shared_ptr<timeline::Track>();
}
void
TimelineBody::register_styles() const
{

View file

@ -118,8 +118,9 @@ private:
*/
void draw_tracks(Cairo::RefPtr<Cairo::Context> cr);
void draw_track_recursive(Cairo::RefPtr<Cairo::Context> cr,
boost::shared_ptr<model::Track> track, const int view_width) const;
void draw_track(Cairo::RefPtr<Cairo::Context> cr,
boost::shared_ptr<timeline::Track> timeline_track,
const int view_width) const;
/**
* Draws the selected timeline period.
@ -138,14 +139,7 @@ private:
int get_vertical_offset() const;
void set_vertical_offset(int offset);
boost::shared_ptr<timeline::Track> track_from_point(const int y)
const;
boost::shared_ptr<timeline::Track> track_from_branch(
boost::shared_ptr<model::Track> model_track,
const int y, int &offset) const;
/**
* Registers all the styles that this class will respond to.
*/

View file

@ -69,6 +69,9 @@ TimelineHeaderContainer::TimelineHeaderContainer(
// Install style properties
register_styles();
// Load the styles up
read_styles();
}
void
@ -135,6 +138,7 @@ TimelineHeaderContainer::on_unrealize()
bool TimelineHeaderContainer::on_button_press_event (
GdkEventButton* event)
{
REQUIRE(timelineWidget != NULL);
REQUIRE(event != NULL);
switch(event->button)
@ -152,8 +156,9 @@ bool TimelineHeaderContainer::on_button_press_event (
case 3: // Right Click
{
// Popup the context menu
shared_ptr<Track> header = header_from_point(
Gdk::Point(event->x, event->y));
shared_ptr<Track> header(
timelineWidget->layoutHelper.header_from_point(
Gdk::Point(event->x, event->y)));
// Are we hovering on a header?
if(header)
@ -183,7 +188,8 @@ bool TimelineHeaderContainer::on_button_release_event (
// Yes? The toggle the expanding
clickedExpander->set_expanded(!clickedExpander->get_expanded());
clickedExpander.reset();
layout_headers();
timelineWidget->layoutHelper.update_layout();
}
return Container::on_button_release_event(event);
@ -265,8 +271,6 @@ TimelineHeaderContainer::on_expose_event(GdkEventExpose *event)
if(gdkWindow)
{
const Allocation container_allocation = get_allocation();
read_styles();
// Paint a border underneath all the root headers
BOOST_FOREACH( shared_ptr<model::Track> model_track,
@ -304,88 +308,59 @@ TimelineHeaderContainer::on_hovering_track_changed(
void
TimelineHeaderContainer::layout_headers()
{
{
REQUIRE(timelineWidget != NULL);
REQUIRE(margin >= 0); // read_styles must have been called before now
// We can't layout before the widget has been set up
if(!gdkWindow)
return;
// Make sure the style are loaded
read_styles();
// Clear previously cached layout
headerBoxes.clear();
// Start at minus-the-scroll offset
int offset = -timelineWidget->get_y_scroll_offset();
const Allocation container_allocation = get_allocation();
const int header_width = container_allocation.get_width();
TimelineLayoutHelper &layout_helper =
timelineWidget->layoutHelper;
const TimelineLayoutHelper::TrackTree &layout_tree =
layout_helper.get_layout_tree();
BOOST_FOREACH( shared_ptr<model::Track> model_track, get_tracks() )
layout_headers_recursive(
model_track, offset, header_width, 0, true);
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root
iterator != layout_tree.end();
iterator++)
{
const shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(*iterator);
Widget &widget = timeline_track->get_header_widget();
optional<Gdk::Rectangle> header_rect =
layout_helper.get_track_header_rect(timeline_track);
if(header_rect)
{
REQUIRE(header_rect->get_width() >= 0);
REQUIRE(header_rect->get_height() >= 0);
// Calculate the allocation of the header widget
Allocation header_allocation(
header_rect->get_x() + margin + expand_button_size, // x
header_rect->get_y() + margin, // y
max( header_rect->get_width() - expand_button_size -
margin * 2, 0 ), // width
header_rect->get_height() - margin * 2); // height
// Apply the allocation to the header
widget.size_allocate (header_allocation);
if(!widget.is_visible())
widget.show();
}
else // No header rect, so the track must be hidden
if(widget.is_visible())
widget.hide();
}
// Repaint the background of our parenting
queue_draw ();
}
void
TimelineHeaderContainer::layout_headers_recursive(
shared_ptr<model::Track> model_track, int &offset,
const int header_width, const int depth, bool parent_expanded)
{
REQUIRE(depth >= 0);
REQUIRE(model_track != NULL);
shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(model_track);
const int indent = depth * 10;
Widget &widget = timeline_track->get_header_widget();
if(parent_expanded)
{
const int track_height = timeline_track->get_height();
// Calculate the box of the header
Gdk::Rectangle header_box(
indent, // x
offset, // y
max( header_width - indent, 0 ), // width
track_height); // height
REQUIRE(header_box.get_height() >= 0);
// Cache the bounding box
headerBoxes[timeline_track] = header_box;
// Calculate the allocation of the header widget
Allocation header_allocation(
header_box.get_x() + margin + expand_button_size, // x
header_box.get_y() + margin, // y
max( header_box.get_width() - expand_button_size -
margin * 2, 0 ), // width
header_box.get_height() - margin * 2); // height
// Apply the allocation to the header
widget.size_allocate (header_allocation);
if(!widget.is_visible())
widget.show();
// Offset for the next header
offset += track_height + TimelineWidget::TrackPadding;
}
else
if(widget.is_visible())
widget.hide();
// Recurse through all the children
BOOST_FOREACH( boost::shared_ptr<model::Track> child,
model_track->get_child_tracks() )
layout_headers_recursive(
child, offset, header_width, depth + 1,
timeline_track->get_expanded() && parent_expanded);
}
void
TimelineHeaderContainer::set_parent_recursive(
boost::shared_ptr<model::Track> model_track)
@ -442,6 +417,7 @@ TimelineHeaderContainer::draw_header_decoration(
shared_ptr<model::Track> model_track,
const Gdk::Rectangle &clip_rect)
{
REQUIRE(timelineWidget != NULL);
REQUIRE(model_track != NULL);
REQUIRE(clip_rect.get_width() > 0);
REQUIRE(clip_rect.get_height() > 0);
@ -452,10 +428,11 @@ TimelineHeaderContainer::draw_header_decoration(
shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(model_track);
// Get the cached header box
weak_ptr<timeline::Track> ptr(timeline_track);
REQUIRE(contains(headerBoxes, ptr));
const Gdk::Rectangle &box = headerBoxes[timeline_track];
// Get the header box
const optional<Gdk::Rectangle> &optional_box =
timelineWidget->layoutHelper.get_track_header_rect(timeline_track);
REQUIRE(optional_box);
const Gdk::Rectangle box = *optional_box;
// Paint the box, if it will be visible
if(box.get_x() < clip_rect.get_width() &&
@ -498,58 +475,55 @@ TimelineHeaderContainer::draw_header_decoration(
}
}
boost::shared_ptr<timeline::Track>
TimelineHeaderContainer::header_from_point(const Gdk::Point &point)
{
std::pair<shared_ptr<timeline::Track>, 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 pair.first;
}
return shared_ptr<timeline::Track>();
}
shared_ptr<timeline::Track>
TimelineHeaderContainer::expander_button_from_point(
const Gdk::Point &point)
{
std::pair<shared_ptr<timeline::Track>, Gdk::Rectangle> pair;
BOOST_FOREACH( pair, headerBoxes )
{
const TimelineLayoutHelper::TrackTree &layout_tree =
timelineWidget->layoutHelper.get_layout_tree();
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so we skip the sequence root
iterator != layout_tree.end();
iterator++)
{
// Hit test the rectangle
const Gdk::Rectangle rect =
get_expander_button_rectangle(pair.first);
const shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(*iterator);
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 pair.first;
// Hit test the rectangle
const optional<Gdk::Rectangle> rect =
get_expander_button_rectangle(timeline_track);
if(rect)
{
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 timeline_track;
}
}
return shared_ptr<timeline::Track>();
}
const Gdk::Rectangle
const optional<Gdk::Rectangle>
TimelineHeaderContainer::get_expander_button_rectangle(
shared_ptr<Track> track)
{
REQUIRE(timelineWidget != NULL);
REQUIRE(track != NULL);
weak_ptr<timeline::Track> ptr(track);
REQUIRE(contains(headerBoxes, ptr));
const Gdk::Rectangle &box = headerBoxes[track];
return Gdk::Rectangle(
margin + box.get_x(), margin + box.get_y(),
expand_button_size, box.get_height() - margin * 2);
optional<Gdk::Rectangle> box =
timelineWidget->layoutHelper.get_track_header_rect(track);
if(box)
{
return optional<Gdk::Rectangle>(Gdk::Rectangle(
margin + box->get_x(), margin + box->get_y(),
expand_button_size, box->get_height() - margin * 2));
}
return optional<Gdk::Rectangle>();
}
shared_ptr<timeline::Track>
@ -594,7 +568,13 @@ void
TimelineHeaderContainer::read_styles()
{
if(margin <= 0)
get_style_property("heading_margin", margin);
{
get_style_property("heading_margin", margin);
margin = max(margin, 0);
}
else
WARN(gui, "TimelineHeaderContainer::read_styles()"
" should only be called once");
}
} // namespace timeline

View file

@ -149,19 +149,7 @@ private:
* stacking etc.
*/
void layout_headers();
/**
* Recursively lays out all the controls in the header widget.
* @param track The parent track object which will be recursed into.
* @param offset A shared value used to accumulate the y-offset of
* header widgets.
* @param header_width The width of this widget in pixels.
* @param depth The depth within the tree of track.
**/
void layout_headers_recursive(boost::shared_ptr<model::Track> track,
int &offset, const int header_width, const int depth,
bool parent_expanded);
/**
* Recursively sets all the track header widgets to be child widgets
* of this widget.
@ -199,9 +187,6 @@ private:
void draw_header_decoration(
boost::shared_ptr<model::Track> model_track,
const Gdk::Rectangle &clip_rect);
boost::shared_ptr<timeline::Track> header_from_point(
const Gdk::Point &point);
/**
* Given a point, expander_button_from_point finds the track of the
@ -219,7 +204,7 @@ private:
* @param track The track to get the expander button rectangle of.
* @return Returns the rectangle of the expander button of track.
**/
const Gdk::Rectangle get_expander_button_rectangle(
const boost::optional<Gdk::Rectangle> get_expander_button_rectangle(
boost::shared_ptr<timeline::Track> track);
/**
@ -278,16 +263,7 @@ private:
* click is not processed by track headers.
**/
Gtk::Menu contextMenu;
/**
* A map of tracks to the rectangles of their headers.
* @remarks This map is used as a cache, so that the rectangles don't
* need to be perpetually recalculated. This cache is regenerated by
* the layout_headers method.
**/
std::map<boost::weak_ptr<timeline::Track>, Gdk::Rectangle>
headerBoxes;
//----- User Interaction State -----//
boost::shared_ptr<timeline::Track> hoveringExpander;
@ -307,6 +283,7 @@ private:
**/
int expand_button_size;
friend class gui::widgets::TimelineWidget;
friend class timeline::Track;
};

View file

@ -0,0 +1,216 @@
/*
timeline-layout-helper.cpp - Implementation of the timeline
layout helper class
Copyright (C) Lumiera.org
2008, Joel Holdsworth <joel@airwebreathe.org.uk>
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 <boost/foreach.hpp>
#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 {
TimelineLayoutHelper::TimelineLayoutHelper(TimelineWidget &owner) :
timelineWidget(owner),
totalHeight(0)
{
}
void
TimelineLayoutHelper::clone_tree_from_sequence()
{
const shared_ptr<model::Sequence> &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<model::Track> parent)
{
BOOST_FOREACH(shared_ptr<model::Track> child,
parent->get_child_tracks())
{
TrackTree::iterator_base child_iterator =
layoutTree.append_child(parent_iterator, child);
add_branch(child_iterator, child);
}
}
optional<Gdk::Rectangle>
TimelineLayoutHelper::get_track_header_rect(
boost::weak_ptr<timeline::Track> track)
{
if(contains(headerBoxes, track))
{
Gdk::Rectangle rect(headerBoxes[track]);
rect.set_y(rect.get_y() - timelineWidget.get_y_scroll_offset());
return optional<Gdk::Rectangle>(rect);
}
return optional<Gdk::Rectangle>();
}
shared_ptr<timeline::Track>
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<weak_ptr<timeline::Track>, 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<timeline::Track>(pair.first);
}
// No track was found - return an empty pointer
return shared_ptr<timeline::Track>();
}
boost::shared_ptr<timeline::Track>
TimelineLayoutHelper::track_from_y(int y)
{
// Apply the scroll offset
y += timelineWidget.get_y_scroll_offset();
// Search the tracks
std::pair<weak_ptr<timeline::Track>, 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<timeline::Track>(pair.first);
}
// No track was found - return an empty pointer
return shared_ptr<timeline::Track>();
}
int
TimelineLayoutHelper::get_total_height() const
{
ENSURE(totalHeight >= 0);
return totalHeight;
}
void
TimelineLayoutHelper::update_layout()
{
int offset = 0;
// 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, header_width, indent_width, 0, true);
totalHeight = offset;
// Signal that the layout has changed
timelineWidget.on_layout_changed();
}
void
TimelineLayoutHelper::layout_headers_recursive(
TrackTree::iterator_base parent_iterator,
int &offset, 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> &model_track = *iterator;
REQUIRE(model_track);
shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(model_track);
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;
}
layout_headers_recursive(iterator, offset, header_width,
indent_width, depth + 1,
timeline_track->get_expanded() && parent_expanded);
}
}
shared_ptr<timeline::Track>
TimelineLayoutHelper::lookup_timeline_track(
shared_ptr<model::Track> model_track)
{
REQUIRE(model_track != NULL);
shared_ptr<timeline::Track> timeline_track =
timelineWidget.lookup_timeline_track(model_track);
ENSURE(timeline_track);
return timeline_track;
}
} // namespace timeline
} // namespace widgets
} // namespace gui

View file

@ -0,0 +1,222 @@
/*
timeline-layout-helper.hpp - Declaration of the timeline
layout helper class
Copyright (C) Lumiera.org
2008, Joel Holdsworth <joel@airwebreathe.org.uk>
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.
*/
/** @file timeline-layout-helper.cpp
** This file contains the definition of the layout helpeer class
*/
#ifndef TIMELINE_LAYOUT_HELPER_HPP
#define TIMELINE_LAYOUT_HELPER_HPP
#include "../../gtk-lumiera.hpp"
#include "../../../lib/tree.hpp"
namespace gui {
namespace model {
class Track;
}
namespace widgets {
class TimelineWidget;
namespace timeline {
class Track;
/**
* A helper class for the TimelineWidget. TimelineLayoutHelper
* is a class which calculates the layout of tracks in the timeline
* track tree.
* @see gui::widgets::TimelineWidget
*/
class TimelineLayoutHelper : public boost::noncopyable
{
public:
/**
* Definition of the layout track tree type.
**/
typedef lumiera::tree< boost::shared_ptr<model::Track> > TrackTree;
public:
/**
* Constructor.
* @param owner The timeline widget which is the owner of this helper
* class.
**/
TimelineLayoutHelper(TimelineWidget &owner);
/**
* Clones the timelineWidget sequence's track tree to create a layout
* tree which will be identitcal to it.
* @remarks The current layout tree will be deleted and replaced with
* the clone.
* @see add_branch
**/
void clone_tree_from_sequence();
/**
* Gets a reference to the helper's layout tree.
* @return Returns a reference to the helper's layout tree.
**/
TrackTree& get_layout_tree();
/**
* Recalculates the track layout from layoutTree.
* @see layout_headers_recursive
**/
void update_layout();
/**
* Get's the header rectangle of a given timeline track.
* @param[in] track The track which will be looked up.
* @return Returns the rectangle of the header offset by the y-scroll
* offset, or if the track is hidden, or not present in the layout
* tree, an empty optional will be returned.
* @remarks This function is only usable after update_layout() has
* been called on a valid tree of tracks.
* @see update_layout()
**/
boost::optional<Gdk::Rectangle> get_track_header_rect(
boost::weak_ptr<timeline::Track> track);
/**
* Searches for a header which has the specified point inside of it.
* @param[in] point The point to search with.
* @return Returns the header which has been found, or if no header is
* found, an empty shared pointer is returned.
* @remarks The point specified is relative to the scroll offset, so
* y = 0 is the top edge of the scroll view. This function is only
* usable after update_layout() has been called on a valid tree of
* tracks.
* @see update_layout()
**/
boost::shared_ptr<timeline::Track> header_from_point(
Gdk::Point point);
/**
* Searches for a tack which has the specified y-offset inside of it.
* @param[in] y The y-coordinate to search with.
* @return Returns the track which has been found, or if no track is
* found, an empty shared pointer is returned.
* @remarks The point specified is relative to the scroll offset, so
* y = 0 is the top edge of the scroll view. This function is only
* usable after update_layout() has been called on a valid tree of
* tracks.
* @see update_layout()
**/
boost::shared_ptr<timeline::Track> track_from_y(int y);
/**
* Returns the total height in pixels of the layout tree.
* @remarks This function is only on returns a valid value fter
* update_layout() has been called on a valid tree of tracks.
* @see update_layout()
**/
int get_total_height() const;
protected:
/**
* A helper function for clone_tree_from_sequence(). This function
* clones a branch within the model tree into the specified point in
* that layout tree.
* @param[in] parent_iterator The iterator of the node in the tree
* which will become the parent of any tracks added.
* @param[in] parent A pointer to the model track whose children
* will be added to the layout tree branch.
* @see clone_tree_from_sequence()
**/
void add_branch(TrackTree::iterator_base parent_iterator,
boost::shared_ptr<model::Track> parent);
/**
* Recursively calculates the boxes for a given branch in the timeline
* tree.
* @param[in] parent_iterator The iterator of the parent of the branch
* whose boxes will be laid out.
* @param[in,out] offset The accumulating y-offset value in pixels.
* This value should be set to 0 on the first call, and will
* susequently accumulate the offset of each box.
* @param[in] header_width The width of the header container widget in
* pixels
* @param[in] depth The depth within the tree of tracks. depth = 0 for
* root tracks.
* @param[in] parent_expanded This value is set to true if all of the
* ancestors of this track, up to the root are expanded and visible,
* false if any of them are collapsed.
* @see update_layout()
**/
void layout_headers_recursive(
TrackTree::iterator_base parent_iterator,
int &offset, const int header_width, const int indent_width,
const int depth, const bool parent_expanded);
/**
* A helper function which calls lookup_timeline_track within the
* parent timeline widget, but also applies lots of data consistency
* checks in the process.
* @param model_track The model track to look up in the parent widget.
* @return Returns the track found, or returns NULL if no matching
* track was found.
* @remarks If the return value is going to be NULL, an ENSURE will
* fail.
**/
boost::shared_ptr<timeline::Track> lookup_timeline_track(
boost::shared_ptr<model::Track> model_track);
protected:
/**
* The owner timeline widget as provided to the constructor.
**/
TimelineWidget &timelineWidget;
/**
* The layout tree.
**/
TrackTree layoutTree;
/**
* A map of tracks to the rectangles of their headers.
* @remarks This map is used as a cache, so that the rectangles don't
* need to be perpetually recalculated. This cache is regenerated by
* the update_layout method.
* @see update_layout()
**/
std::map<boost::weak_ptr<timeline::Track>, Gdk::Rectangle>
headerBoxes;
/**
* The total height of the track tree layout in pixels. This value
* is only valid after layout_headers has been called.
* @see update_layout()
**/
int totalHeight;
};
} // namespace timeline
} // namespace widgets
} // namespace gui
#endif // TIMELINE_LAYOUT_HELPER_HPP

View file

@ -90,6 +90,12 @@ Track::get_header_widget()
return headerWidget;
}
shared_ptr<model::Track>
Track::get_model_track() const
{
return model_track;
}
int
Track::get_height() const
{

View file

@ -45,6 +45,8 @@ public:
Gtk::Widget& get_header_widget();
boost::shared_ptr<model::Track> get_model_track() const;
int get_height() const;
bool get_expanded() const;

View file

@ -86,6 +86,7 @@ noinst_HEADERS += \
$(liblumiera_la_srcdir)/observable-list.hpp \
$(liblumiera_la_srcdir)/sync.hpp \
$(liblumiera_la_srcdir)/sync-classlock.hpp \
$(liblumiera_la_srcdir)/tree.hpp \
$(liblumiera_la_srcdir)/test/mockinjector.hpp \
$(liblumiera_la_srcdir)/test/suite.hpp \
$(liblumiera_la_srcdir)/test/testoption.hpp \

2709
src/lib/tree.hpp Normal file

File diff suppressed because it is too large Load diff