2008-04-19 21:31:27 +02:00
/*
2015-05-29 04:44:58 +02:00
TimelineWidget - custom widget for timeline display of the project
2010-12-17 23:28:49 +01:00
2008-04-19 21:31:27 +02:00
Copyright ( C ) Lumiera . org
2008 , Joel Holdsworth < joel @ airwebreathe . org . uk >
2010-12-17 23:28:49 +01:00
2008-04-19 21:31:27 +02:00
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License as
2010-12-17 23:28:49 +01:00
published by the Free Software Foundation ; either version 2 of
the License , or ( at your option ) any later version .
2008-04-19 21:31:27 +02:00
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 .
2010-12-17 23:28:49 +01:00
2008-04-19 21:31:27 +02:00
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 0213 9 , USA .
2010-12-17 23:28:49 +01:00
2008-04-19 21:31:27 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2015-05-29 04:44:58 +02:00
# include "gui/widget/timeline-widget.hpp"
2008-04-19 21:31:27 +02:00
2008-06-07 14:53:17 +02:00
# include <boost/foreach.hpp>
2008-11-29 17:13:58 +01:00
# include <typeinfo>
2014-04-03 22:42:48 +02:00
# include <memory>
using lib : : time : : Time ;
using lib : : time : : TimeValue ;
using std : : dynamic_pointer_cast ;
2008-06-07 14:53:17 +02:00
2008-05-22 20:19:04 +02:00
using namespace Gtk ;
2011-10-21 09:56:10 +02:00
2008-12-06 15:52:59 +01:00
using namespace util ;
2015-05-29 04:44:58 +02:00
using namespace gui : : widget : : timeline ;
2011-05-16 08:38:01 +02:00
2008-04-19 21:31:27 +02:00
namespace gui {
2015-05-29 04:44:58 +02:00
namespace widget {
2008-07-30 01:12:37 +02:00
2015-05-29 04:44:58 +02:00
const int TimelineWidget : : TrackPadding = 1 ;
const int TimelineWidget : : HeaderWidth = 150 ;
const int TimelineWidget : : HeaderIndentWidth = 10 ;
2009-03-27 17:56:37 +01:00
2009-04-13 17:42:58 +02:00
2009-03-27 15:26:08 +01:00
2015-05-29 04:44:58 +02:00
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 )
2009-03-27 17:56:37 +01:00
{
2015-05-29 04:44:58 +02:00
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 ( ) ;
2009-03-27 17:56:37 +01:00
2015-05-29 04:44:58 +02:00
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 ) ;
2009-03-27 17:56:37 +01:00
}
2015-05-29 04:44:58 +02:00
TimelineWidget : : ~ TimelineWidget ( )
{
REQUIRE ( headerContainer ) ;
headerContainer - > clear_headers ( ) ;
2009-03-27 17:56:37 +01:00
2015-05-29 04:44:58 +02:00
trackMap . clear ( ) ;
}
2009-03-27 15:26:08 +01:00
2015-05-29 04:44:58 +02:00
/* ===== Data Access ===== */
/** @deprecated for #955 */
shared_ptr < timeline : : TimelineState >
TimelineWidget : : get_state ( )
2011-10-06 19:38:10 +02:00
{
2015-05-29 04:44:58 +02:00
return state ;
2011-10-06 19:38:10 +02:00
}
2008-06-05 21:27:53 +02:00
2015-05-29 04:44:58 +02:00
/** @deprecated for #955 */
void
TimelineWidget : : set_state ( shared_ptr < timeline : : TimelineState > new_state )
{
2008-06-05 21:27:53 +02:00
2015-05-29 04:44:58 +02:00
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 )
{
2009-03-27 17:56:37 +01:00
const int view_width = body - > get_allocation ( ) . get_width ( ) ;
2015-05-29 04:44:58 +02:00
state - > getViewWindow ( ) . zoom_view ( view_width / 2 , timescale_ratio ) ;
2009-03-27 17:56:37 +01:00
}
2015-05-29 04:44:58 +02:00
}
2009-01-22 00:48:56 +01:00
2015-05-29 04:44:58 +02:00
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 ) ;
2009-03-27 17:56:37 +01:00
2015-05-29 04:44:58 +02:00
// 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 ) ;
2009-01-10 11:50:44 +01:00
2015-05-29 04:44:58 +02:00
// 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 ) ;
}
2008-11-29 18:02:27 +01:00
2015-05-29 04:44:58 +02:00
/** @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 ( )
{
2011-10-21 09:56:10 +02:00
std : : map < shared_ptr < model : : Track > ,
2015-05-29 04:44:58 +02:00
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 ) ;
}
}
2009-03-23 23:22:14 +01:00
2008-10-18 00:36:37 +02:00
2015-05-29 04:44:58 +02:00
/** @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