over time, a specific Lumiera code writing style has emerged. The GUI, as it stood, used somewhat different conventions, which now have been aligned to the common standard. Basically we use GNU style, with some adjustments for OO-programming, we prefer CamelCase, and write TypeNames uppercase, variableNames lowercase
551 lines
14 KiB
C++
551 lines
14 KiB
C++
/*
|
|
TimelineWidget - custom widget for timeline display of the project
|
|
|
|
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 "gui/widget/timeline-widget.hpp"
|
|
|
|
#include <boost/foreach.hpp>
|
|
#include <typeinfo>
|
|
#include <memory>
|
|
|
|
using lib::time::Time;
|
|
using lib::time::TimeValue;
|
|
using std::dynamic_pointer_cast;
|
|
|
|
using namespace Gtk;
|
|
|
|
using namespace util;
|
|
using namespace gui::widget::timeline;
|
|
|
|
namespace gui {
|
|
namespace widget {
|
|
|
|
const int TimelineWidget::TrackPadding = 1;
|
|
const int TimelineWidget::HeaderWidth = 150;
|
|
const int TimelineWidget::HeaderIndentWidth = 10;
|
|
|
|
|
|
|
|
|
|
TimelineWidget::TimelineWidget (shared_ptr<timeline::TimelineState> source_state)
|
|
: Table(2, 2)
|
|
, layoutHelper(*this)
|
|
, headerContainer(NULL)
|
|
, body(NULL)
|
|
, ruler(NULL)
|
|
, horizontalAdjustment(Gtk::Adjustment::create(0, 0, 0))
|
|
, verticalAdjustment(Gtk::Adjustment::create(0, 0, 0))
|
|
, horizontalScroll(horizontalAdjustment)
|
|
, verticalScroll(verticalAdjustment)
|
|
, update_tracks_frozen(false)
|
|
{
|
|
body = manage(new TimelineBody(*this));
|
|
ENSURE(body != NULL);
|
|
headerContainer = manage(new TimelineHeaderContainer(*this));
|
|
ENSURE(headerContainer != NULL);
|
|
ruler = manage(new TimelineRuler(*this));
|
|
ENSURE(ruler != NULL);
|
|
|
|
horizontalAdjustment->signal_value_changed().connect( sigc::mem_fun(
|
|
this, &TimelineWidget::on_scroll) );
|
|
verticalAdjustment->signal_value_changed().connect( sigc::mem_fun(
|
|
this, &TimelineWidget::on_scroll) );
|
|
body->signal_motion_notify_event().connect( sigc::mem_fun(
|
|
this, &TimelineWidget::on_motion_in_body_notify_event) );
|
|
|
|
update_tracks();
|
|
|
|
attach(*body, 1, 2, 1, 2, FILL|EXPAND, FILL|EXPAND);
|
|
attach(*ruler, 1, 2, 0, 1, FILL|EXPAND, SHRINK);
|
|
attach(*headerContainer, 0, 1, 1, 2, SHRINK, FILL|EXPAND);
|
|
attach(horizontalScroll, 1, 2, 2, 3, FILL|EXPAND, SHRINK);
|
|
attach(verticalScroll, 2, 3, 1, 2, SHRINK, FILL|EXPAND);
|
|
|
|
set_state(source_state);
|
|
|
|
set_tool(timeline::Arrow);
|
|
}
|
|
|
|
|
|
TimelineWidget::~TimelineWidget()
|
|
{
|
|
REQUIRE(headerContainer);
|
|
headerContainer->clear_headers();
|
|
|
|
trackMap.clear();
|
|
}
|
|
|
|
|
|
|
|
/* ===== Data Access ===== */
|
|
|
|
/** @deprecated for #955 */
|
|
shared_ptr<timeline::TimelineState>
|
|
TimelineWidget::get_state()
|
|
{
|
|
return state;
|
|
}
|
|
|
|
/** @deprecated for #955 */
|
|
void
|
|
TimelineWidget::set_state (shared_ptr<timeline::TimelineState> new_state)
|
|
{
|
|
|
|
state = new_state;
|
|
|
|
// Clear the track tree
|
|
trackMap.clear();
|
|
|
|
if (state)
|
|
{
|
|
// Hook up event handlers
|
|
state->getViewWindow().changed_signal().connect( sigc::mem_fun(
|
|
this, &TimelineWidget::on_view_window_changed) );
|
|
state->getSequence()->get_child_track_list().signal_changed().
|
|
connect(sigc::mem_fun(
|
|
this, &TimelineWidget::on_track_list_changed ) );
|
|
|
|
state->selectionChangedSignal().connect(mem_fun(*this,
|
|
&TimelineWidget::on_body_changed));
|
|
state->playbackChangedSignal().connect(mem_fun(*this,
|
|
&TimelineWidget::on_body_changed));
|
|
}
|
|
|
|
update_tracks();
|
|
|
|
// Send the state changed signal
|
|
stateChangedSignal.emit (state);
|
|
}
|
|
|
|
void
|
|
TimelineWidget::zoom_view (double timescale_ratio)
|
|
{
|
|
if(state)
|
|
{
|
|
const int view_width = body->get_allocation().get_width();
|
|
state->getViewWindow().zoom_view(view_width / 2, timescale_ratio);
|
|
}
|
|
}
|
|
|
|
ToolType
|
|
TimelineWidget::get_tool() const
|
|
{
|
|
REQUIRE(body != NULL);
|
|
return body->get_tool();
|
|
}
|
|
|
|
void
|
|
TimelineWidget::set_tool(ToolType tool_type)
|
|
{
|
|
REQUIRE(body != NULL);
|
|
body->set_tool(tool_type);
|
|
}
|
|
|
|
shared_ptr<timeline::Track>
|
|
TimelineWidget::get_hovering_track() const
|
|
{
|
|
return hoveringTrack;
|
|
}
|
|
|
|
|
|
/* ===== Signals ===== */
|
|
|
|
sigc::signal<void, Time>
|
|
TimelineWidget::mouse_hover_signal() const
|
|
{
|
|
return mouseHoverSignal;
|
|
}
|
|
|
|
sigc::signal<void>
|
|
TimelineWidget::playback_period_drag_released_signal() const
|
|
{
|
|
return playbackPeriodDragReleasedSignal;
|
|
}
|
|
|
|
sigc::signal<void, shared_ptr<timeline::Track> >
|
|
TimelineWidget::hovering_track_changed_signal() const
|
|
{
|
|
return hoveringTrackChangedSignal;
|
|
}
|
|
|
|
TimelineWidget::TimelineStateChangeSignal
|
|
TimelineWidget::state_changed_signal() const
|
|
{
|
|
return stateChangedSignal;
|
|
}
|
|
|
|
|
|
|
|
/* ===== Events ===== */
|
|
|
|
void
|
|
TimelineWidget::on_scroll()
|
|
{
|
|
if(state)
|
|
{
|
|
TimeValue newStartOffset ((gavl_time_t)horizontalAdjustment->get_value());
|
|
state->getViewWindow().set_time_offset(Time(newStartOffset));
|
|
}
|
|
}
|
|
|
|
void
|
|
TimelineWidget::on_size_allocate(Allocation& allocation)
|
|
{
|
|
Widget::on_size_allocate(allocation);
|
|
|
|
update_scroll();
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::on_view_window_changed()
|
|
{
|
|
REQUIRE(ruler != NULL);
|
|
|
|
if(state)
|
|
{
|
|
timeline::TimelineViewWindow &window = state->getViewWindow();
|
|
|
|
const int view_width = body->get_allocation().get_width();
|
|
|
|
horizontalAdjustment->set_page_size(
|
|
window.get_time_scale() * view_width);
|
|
|
|
horizontalAdjustment->set_value(_raw(window.get_time_offset()));
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::on_body_changed()
|
|
{
|
|
REQUIRE(ruler != NULL);
|
|
REQUIRE(body != NULL);
|
|
ruler->queue_draw();
|
|
body->queue_draw();
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::on_add_track_command()
|
|
{
|
|
// # TEST CODE
|
|
if(sequence())
|
|
sequence()->get_child_track_list().push_back(
|
|
shared_ptr<model::Track>(new model::ClipTrack()));
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ===== Internals ===== */
|
|
|
|
void
|
|
TimelineWidget::update_tracks()
|
|
{
|
|
if(update_tracks_frozen)
|
|
return;
|
|
|
|
if(state)
|
|
{
|
|
// Remove any tracks which are no longer present in the model
|
|
remove_orphaned_tracks();
|
|
|
|
// Create timeline tracks from all the model tracks
|
|
create_timeline_tracks();
|
|
|
|
// Update the layout helper
|
|
layoutHelper.clone_tree_from_sequence();
|
|
layoutHelper.update_layout();
|
|
}
|
|
else
|
|
trackMap.clear();
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::freeze_update_tracks()
|
|
{
|
|
update_tracks_frozen = true;
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::thaw_update_tracks()
|
|
{
|
|
update_tracks_frozen = false;
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::create_timeline_tracks()
|
|
{
|
|
REQUIRE(state);
|
|
|
|
BOOST_FOREACH(shared_ptr<model::Track> child,
|
|
sequence()->get_child_tracks())
|
|
create_timeline_tracks_from_branch(child);
|
|
|
|
// Update the header container
|
|
REQUIRE(headerContainer != NULL);
|
|
headerContainer->update_headers();
|
|
}
|
|
|
|
|
|
/** @deprecated for #955 */
|
|
void
|
|
TimelineWidget::create_timeline_tracks_from_branch(
|
|
shared_ptr<model::Track> modelTrack)
|
|
{
|
|
REQUIRE(modelTrack);
|
|
|
|
// Is a timeline UI track present in the map already?
|
|
if (!contains(trackMap, modelTrack))
|
|
{
|
|
// The timeline UI track is not present
|
|
// We will need to create one
|
|
trackMap[modelTrack] =
|
|
create_timeline_track_from_modelTrack(modelTrack);
|
|
}
|
|
|
|
// Recurse to child tracks
|
|
BOOST_FOREACH(shared_ptr<model::Track> child,
|
|
modelTrack->get_child_tracks())
|
|
create_timeline_tracks_from_branch(child);
|
|
}
|
|
|
|
|
|
/** @deprecated for #955 */
|
|
shared_ptr<timeline::Track>
|
|
TimelineWidget::create_timeline_track_from_modelTrack(
|
|
shared_ptr<model::Track> modelTrack)
|
|
{
|
|
REQUIRE(modelTrack);
|
|
|
|
// Choose a corresponding timeline track class from the model track's
|
|
// class
|
|
if(typeid(*modelTrack) == typeid(model::ClipTrack))
|
|
return shared_ptr<timeline::Track>(new timeline::ClipTrack(
|
|
*this, dynamic_pointer_cast<model::ClipTrack>(modelTrack)));
|
|
else if(typeid(*modelTrack) == typeid(model::GroupTrack))
|
|
return shared_ptr<timeline::Track>(new timeline::GroupTrack(
|
|
*this, dynamic_pointer_cast<model::GroupTrack>(modelTrack)));
|
|
|
|
ASSERT(NULL); // Unknown track type;
|
|
return shared_ptr<timeline::Track>();
|
|
}
|
|
|
|
|
|
/** @deprecated for #955 */
|
|
void
|
|
TimelineWidget::remove_orphaned_tracks()
|
|
{
|
|
std::map<shared_ptr<model::Track>,
|
|
shared_ptr<timeline::Track> >
|
|
orphan_track_map(trackMap);
|
|
|
|
// Remove all tracks which are still present in the sequence
|
|
BOOST_FOREACH(shared_ptr<model::Track> child,
|
|
sequence()->get_child_tracks())
|
|
search_orphaned_tracks_in_branch(child, orphan_track_map);
|
|
|
|
// orphan_track_map now contains all the orphaned tracks
|
|
// Remove them
|
|
std::pair<shared_ptr<model::Track>, shared_ptr<timeline::Track> >
|
|
pair;
|
|
BOOST_FOREACH( pair, orphan_track_map )
|
|
{
|
|
ENSURE(pair.first);
|
|
trackMap.erase(pair.first);
|
|
}
|
|
}
|
|
|
|
|
|
/** @deprecated for #955 */
|
|
void
|
|
TimelineWidget::search_orphaned_tracks_in_branch(
|
|
shared_ptr<model::Track> modelTrack,
|
|
std::map<shared_ptr<model::Track>,
|
|
shared_ptr<timeline::Track> > &orphan_track_map)
|
|
{
|
|
REQUIRE(modelTrack);
|
|
|
|
// Is the timeline UI still present?
|
|
if(contains(orphan_track_map, modelTrack))
|
|
orphan_track_map.erase(modelTrack);
|
|
|
|
// Recurse to child tracks
|
|
BOOST_FOREACH(shared_ptr<model::Track> child,
|
|
modelTrack->get_child_tracks())
|
|
search_orphaned_tracks_in_branch(child, orphan_track_map);
|
|
}
|
|
|
|
|
|
/** @deprecated for #955 */
|
|
shared_ptr<timeline::Track>
|
|
TimelineWidget::lookup_timeline_track(
|
|
shared_ptr<model::Track> modelTrack) const
|
|
{
|
|
REQUIRE(modelTrack);
|
|
REQUIRE(modelTrack != sequence()); // The sequence isn't
|
|
// really a track
|
|
|
|
std::map<shared_ptr<model::Track>, shared_ptr<timeline::Track> >::
|
|
const_iterator iterator = trackMap.find(modelTrack);
|
|
if (iterator == trackMap.end())
|
|
{
|
|
// 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<timeline::Track>();
|
|
}
|
|
ENSURE(iterator->second != NULL);
|
|
return iterator->second;
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::on_layout_changed()
|
|
{
|
|
REQUIRE(headerContainer != NULL);
|
|
REQUIRE(body != NULL);
|
|
|
|
headerContainer->on_layout_changed();
|
|
body->queue_draw();
|
|
update_scroll();
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::update_scroll()
|
|
{
|
|
REQUIRE(body != NULL);
|
|
const Allocation body_allocation = body->get_allocation();
|
|
|
|
if(state)
|
|
{
|
|
///////////////////////////////////////////////TICKET #861 shoudln't that be performed by TimelineViewWindow, instead of manipulating values from the outside?
|
|
timeline::TimelineViewWindow &window = state->getViewWindow();
|
|
|
|
//----- Horizontal Scroll ------//
|
|
|
|
// TEST CODE
|
|
horizontalAdjustment->set_upper( 1000 * GAVL_TIME_SCALE / 200);
|
|
horizontalAdjustment->set_lower(-1000 * GAVL_TIME_SCALE / 200);
|
|
|
|
// Set the page size
|
|
horizontalAdjustment->set_page_size(
|
|
window.get_time_scale() * body_allocation.get_width());
|
|
|
|
//----- Vertical Scroll -----//
|
|
|
|
// Calculate the vertical length that can be scrolled:
|
|
// the total height of all the tracks minus one screenful
|
|
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
|
|
// maximum distance
|
|
if((int)verticalAdjustment->get_value() > y_scroll_length)
|
|
verticalAdjustment->set_value(y_scroll_length);
|
|
|
|
verticalAdjustment->set_upper(y_scroll_length);
|
|
|
|
// Hide the scrollbar if no scrolling is possible
|
|
// Having this code included seems to cause a layout loop as the
|
|
// window is shrunk
|
|
if(y_scroll_length <= 0 && verticalScroll.get_visible())
|
|
verticalScroll.hide();
|
|
else if(y_scroll_length > 0 && !verticalScroll.get_visible())
|
|
verticalScroll.show();
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
TimelineWidget::get_y_scroll_offset() const
|
|
{
|
|
return (int)verticalAdjustment->get_value();
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::set_y_scroll_offset(const int offset)
|
|
{
|
|
verticalAdjustment->set_value(offset);
|
|
}
|
|
|
|
|
|
bool
|
|
TimelineWidget::on_motion_in_body_notify_event(GdkEventMotion *event)
|
|
{
|
|
REQUIRE(event != NULL);
|
|
ruler->set_mouse_chevron_offset(event->x);
|
|
|
|
if(state)
|
|
{
|
|
timeline::TimelineViewWindow &window = state->getViewWindow();
|
|
mouseHoverSignal.emit(window.x_to_time(event->x));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/** @deprecated for #955 */
|
|
shared_ptr<model::Sequence>
|
|
TimelineWidget::sequence() const
|
|
{
|
|
if(!state)
|
|
return shared_ptr<model::Sequence>();
|
|
shared_ptr<model::Sequence> sequence = state->getSequence();
|
|
ENSURE(sequence);
|
|
return sequence;
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::on_track_list_changed()
|
|
{
|
|
update_tracks();
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::on_playback_period_drag_released()
|
|
{
|
|
playbackPeriodDragReleasedSignal.emit();
|
|
}
|
|
|
|
|
|
void
|
|
TimelineWidget::set_hovering_track(shared_ptr<timeline::Track> hovering_track)
|
|
{
|
|
hoveringTrack = hovering_track;
|
|
hoveringTrackChangedSignal.emit(hovering_track);
|
|
}
|
|
|
|
|
|
}}// gui::widget
|