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-group-track.hpp \
$(lumigui_srcdir)/widgets/timeline/timeline-clip.cpp \ $(lumigui_srcdir)/widgets/timeline/timeline-clip.cpp \
$(lumigui_srcdir)/widgets/timeline/timeline-clip.hpp \ $(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.cpp \
$(lumigui_srcdir)/model/project.hpp \ $(lumigui_srcdir)/model/project.hpp \
$(lumigui_srcdir)/model/track.cpp \ $(lumigui_srcdir)/model/track.cpp \

View file

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

View file

@ -36,6 +36,7 @@ namespace widgets {
const int TimelineWidget::TrackPadding = 1; const int TimelineWidget::TrackPadding = 1;
const int TimelineWidget::HeaderWidth = 150; const int TimelineWidget::HeaderWidth = 150;
const int TimelineWidget::HeaderIndentWidth = 10;
const double TimelineWidget::ZoomIncrement = 1.25; const double TimelineWidget::ZoomIncrement = 1.25;
const int64_t TimelineWidget::MaxScale = 30000000; const int64_t TimelineWidget::MaxScale = 30000000;
@ -43,13 +44,13 @@ TimelineWidget::TimelineWidget(
shared_ptr<model::Sequence> source_sequence) : shared_ptr<model::Sequence> source_sequence) :
Table(2, 2), Table(2, 2),
sequence(source_sequence), sequence(source_sequence),
layoutHelper(*this),
viewWindow(this, 0, 1), viewWindow(this, 0, 1),
selectionStart(0), selectionStart(0),
selectionEnd(0), selectionEnd(0),
playbackPeriodStart(0), playbackPeriodStart(0),
playbackPeriodEnd(0), playbackPeriodEnd(0),
playbackPoint(GAVL_TIME_UNDEFINED), playbackPoint(GAVL_TIME_UNDEFINED),
totalHeight(0),
horizontalAdjustment(0, 0, 0), horizontalAdjustment(0, 0, 0),
verticalAdjustment(0, 0, 0), verticalAdjustment(0, 0, 0),
horizontalScroll(horizontalAdjustment), horizontalScroll(horizontalAdjustment),
@ -300,17 +301,9 @@ TimelineWidget::update_tracks()
headerContainer->show_all_children(); headerContainer->show_all_children();
headerContainer->update_headers(); headerContainer->update_headers();
// Update the body // Update the layout helper
body->queue_draw(); layoutHelper.clone_tree_from_sequence();
layoutHelper.update_layout();
// 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);
}
} }
void void
@ -338,9 +331,6 @@ TimelineWidget::create_timeline_tracks_from_branch(
// We will need to create one // We will need to create one
trackMap[model_track] = trackMap[model_track] =
create_timeline_track_from_model_track(model_track); create_timeline_track_from_model_track(model_track);
// Hook up
} }
// Recurse to child tracks // Recurse to child tracks
@ -418,6 +408,9 @@ TimelineWidget::lookup_timeline_track(
shared_ptr<model::Track> model_track) const shared_ptr<model::Track> model_track) const
{ {
REQUIRE(sequence); 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> >:: std::map<shared_ptr<model::Track>, shared_ptr<timeline::Track> >::
const_iterator iterator = trackMap.find(model_track); const_iterator iterator = trackMap.find(model_track);
if(iterator == trackMap.end()) if(iterator == trackMap.end())
@ -433,30 +426,15 @@ TimelineWidget::lookup_timeline_track(
return iterator->second; return iterator->second;
} }
boost::shared_ptr<model::Track> void
TimelineWidget::lookup_model_track( TimelineWidget::on_layout_changed()
const timeline::Track *timeline_track) const
{ {
REQUIRE(sequence); REQUIRE(headerContainer != NULL);
REQUIRE(body != NULL);
std::pair<shared_ptr<model::Track>, shared_ptr<timeline::Track> > headerContainer->layout_headers();
pair; body->queue_draw();
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>();
}
void void
TimelineWidget::update_scroll() TimelineWidget::update_scroll()
@ -478,7 +456,8 @@ TimelineWidget::update_scroll()
// Calculate the vertical length that can be scrolled: // Calculate the vertical length that can be scrolled:
// the total height of all the tracks minus one screenful // 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(y_scroll_length < 0) y_scroll_length = 0;
// If by resizing we're now over-scrolled, scroll back to // 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 int
TimelineWidget::get_y_scroll_offset() const TimelineWidget::get_y_scroll_offset() const
{ {

View file

@ -35,6 +35,7 @@
#include "timeline/timeline-ibeam-tool.hpp" #include "timeline/timeline-ibeam-tool.hpp"
#include "timeline/timeline-group-track.hpp" #include "timeline/timeline-group-track.hpp"
#include "timeline/timeline-clip-track.hpp" #include "timeline/timeline-clip-track.hpp"
#include "timeline/timeline-layout-helper.hpp"
#include "../model/sequence.hpp" #include "../model/sequence.hpp"
@ -216,24 +217,12 @@ private:
**/ **/
boost::shared_ptr<timeline::Track> lookup_timeline_track( boost::shared_ptr<timeline::Track> lookup_timeline_track(
boost::shared_ptr<model::Track> model_track) const; 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 ----- // // ----- Layout Functions ----- //
void update_scroll(); void on_layout_changed();
int measure_branch_height( void update_scroll();
boost::shared_ptr<model::Track> model_track);
int get_y_scroll_offset() const; int get_y_scroll_offset() const;
@ -276,6 +265,9 @@ protected:
std::map<boost::shared_ptr<model::Track>, std::map<boost::shared_ptr<model::Track>,
boost::shared_ptr<timeline::Track> > boost::shared_ptr<timeline::Track> >
trackMap; trackMap;
// Helper Classes
timeline::TimelineLayoutHelper layoutHelper;
// View State // View State
timeline::TimelineViewWindow viewWindow; timeline::TimelineViewWindow viewWindow;
@ -289,8 +281,6 @@ protected:
boost::shared_ptr<timeline::Track> hoveringTrack; boost::shared_ptr<timeline::Track> hoveringTrack;
int totalHeight;
// Child Widgets // Child Widgets
timeline::TimelineHeaderContainer *headerContainer; timeline::TimelineHeaderContainer *headerContainer;
timeline::TimelineBody *body; timeline::TimelineBody *body;
@ -318,11 +308,13 @@ public:
protected: protected:
static const int TrackPadding; static const int TrackPadding;
static const int HeaderWidth; static const int HeaderWidth;
static const int HeaderIndentWidth;
static const double ZoomIncrement; static const double ZoomIncrement;
friend class timeline::TimelineViewWindow; friend class timeline::TimelineViewWindow;
friend class timeline::TimelineBody; friend class timeline::TimelineBody;
friend class timeline::TimelineHeaderContainer; friend class timeline::TimelineHeaderContainer;
friend class timeline::TimelineLayoutHelper;
friend class timeline::TimelineRuler; friend class timeline::TimelineRuler;
friend class timeline::Tool; friend class timeline::Tool;
friend class timeline::ArrowTool; friend class timeline::ArrowTool;

View file

@ -244,6 +244,7 @@ bool
TimelineBody::on_motion_notify_event(GdkEventMotion *event) TimelineBody::on_motion_notify_event(GdkEventMotion *event)
{ {
REQUIRE(event != NULL); REQUIRE(event != NULL);
REQUIRE(timelineWidget != NULL);
// Handle a middle-mouse drag if one is occuring // Handle a middle-mouse drag if one is occuring
switch(dragType) switch(dragType)
@ -270,8 +271,8 @@ TimelineBody::on_motion_notify_event(GdkEventMotion *event)
tool->on_motion_notify_event(event); tool->on_motion_notify_event(event);
// See if the track that we're hovering over has changed // See if the track that we're hovering over has changed
shared_ptr<timeline::Track> new_hovering_track = shared_ptr<timeline::Track> new_hovering_track(
track_from_point(event->y); timelineWidget->layoutHelper.track_from_y(event->y));
if(timelineWidget->get_hovering_track() != new_hovering_track) if(timelineWidget->get_hovering_track() != new_hovering_track)
timelineWidget->set_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); REQUIRE(timelineWidget->sequence);
// Prepare // Prepare
TimelineLayoutHelper &layout_helper = timelineWidget->layoutHelper;
const TimelineLayoutHelper::TrackTree &layout_tree =
layout_helper.get_layout_tree();
const Allocation allocation = get_allocation(); const Allocation allocation = get_allocation();
// Save the view matrix // Save the view matrix
Cairo::Matrix view_matrix; Cairo::Matrix view_matrix;
cr->get_matrix(view_matrix); cr->get_matrix(view_matrix);
// Translate the view by the scroll distance // Iterate drawing each track
cr->translate(0, -get_vertical_offset()); TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so we skip the sequence root
// Interate drawing each track iterator != layout_tree.end();
BOOST_FOREACH( shared_ptr<model::Track> model_track, iterator++)
timelineWidget->sequence->get_child_tracks() ) {
draw_track_recursive(cr, model_track, allocation.get_width()); 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 // Restore the view matrix
cr->set_matrix(view_matrix); cr->set_matrix(view_matrix);
} }
void void
TimelineBody::draw_track_recursive(Cairo::RefPtr<Cairo::Context> cr, TimelineBody::draw_track(Cairo::RefPtr<Cairo::Context> cr,
shared_ptr<model::Track> model_track, const int view_width) const shared_ptr<timeline::Track> timeline_track,
const int view_width) const
{ {
REQUIRE(cr); REQUIRE(cr);
REQUIRE(model_track != NULL); REQUIRE(timeline_track != NULL);
REQUIRE(timelineWidget != NULL); REQUIRE(timelineWidget != NULL);
shared_ptr<timeline::Track> timeline_track = timelineWidget->
lookup_timeline_track(model_track);
const int height = timeline_track->get_height(); const int height = timeline_track->get_height();
REQUIRE(height >= 0); REQUIRE(height >= 0);
@ -329,14 +348,6 @@ TimelineBody::draw_track_recursive(Cairo::RefPtr<Cairo::Context> cr,
cr->save(); cr->save();
timeline_track->draw_track(cr, &timelineWidget->get_view_window()); timeline_track->draw_track(cr, &timelineWidget->get_view_window());
cr->restore(); 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 void
@ -436,60 +447,6 @@ TimelineBody::set_vertical_offset(int offset)
timelineWidget->verticalAdjustment.set_value(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 void
TimelineBody::register_styles() const TimelineBody::register_styles() const
{ {

View file

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

View file

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

View file

@ -149,19 +149,7 @@ private:
* stacking etc. * stacking etc.
*/ */
void layout_headers(); 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 * Recursively sets all the track header widgets to be child widgets
* of this widget. * of this widget.
@ -199,9 +187,6 @@ private:
void draw_header_decoration( void draw_header_decoration(
boost::shared_ptr<model::Track> model_track, boost::shared_ptr<model::Track> model_track,
const Gdk::Rectangle &clip_rect); 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 * 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. * @param track The track to get the expander button rectangle of.
* @return Returns the rectangle of the expander button of track. * @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); boost::shared_ptr<timeline::Track> track);
/** /**
@ -278,16 +263,7 @@ private:
* click is not processed by track headers. * click is not processed by track headers.
**/ **/
Gtk::Menu contextMenu; 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 -----// //----- User Interaction State -----//
boost::shared_ptr<timeline::Track> hoveringExpander; boost::shared_ptr<timeline::Track> hoveringExpander;
@ -307,6 +283,7 @@ private:
**/ **/
int expand_button_size; int expand_button_size;
friend class gui::widgets::TimelineWidget;
friend class timeline::Track; 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; return headerWidget;
} }
shared_ptr<model::Track>
Track::get_model_track() const
{
return model_track;
}
int int
Track::get_height() const Track::get_height() const
{ {

View file

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

View file

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

2709
src/lib/tree.hpp Normal file

File diff suppressed because it is too large Load diff