non/Timeline/Timeline.C

1201 lines
27 KiB
C
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*******************************************************************************/
/* Copyright (C) 2008 Jonathan Moore Liles */
/* */
/* 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; see the file COPYING. If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/*******************************************************************************/
#include <FL/Fl_Scroll.H>
#include <FL/Fl_Pack.H>
#include <FL/Fl_Scrollbar.H>
#include <FL/Fl_Widget.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Scrollbar.H>
#include <FL/Fl_Menu_Button.H>
#include "Timeline.H"
#include "Tempo_Sequence.H"
#include "Time_Sequence.H"
#include "Audio_Sequence.H"
#include "Control_Sequence.H"
#include "Scalebar.H"
#include "Sequence.H"
#include "Annotation_Sequence.H"
#include "Track.H"
#include "Transport.H"
#include "Engine/Engine.H" // for lock()
bool Timeline::draw_with_measure_lines = true;
Timeline::snap_e Timeline::snap_to = Bars;
bool Timeline::snap_magnetic = true;
bool Timeline::follow_playhead = true;
bool Timeline::center_playhead = true;
const float UPDATE_FREQ = 0.02f;
/** return the combined height of all visible children of (veritcal)
pack, /p/. This is necessary because pack sizes are adjusted only
when the relevant areas are exposes. */
static int
pack_visible_height ( const Fl_Pack *p )
{
int th = 0;
const Fl_Widget* const *w = p->array();
for ( int i = p->children(); i--; ++w )
if ( (*w)->visible() )
th += (*w)->h() + p->spacing();
return th;
}
#define BP fl_begin_polygon()
#define EP fl_end_polygon()
#define vv(x,y) fl_vertex( x, y )
#define BL fl_begin_line()
#define EL fl_end_line()
static void
draw_full_arrow_symbol ( Fl_Color color )
{
/* draw cap */
fl_color( color );
BP;
vv( -1, -1 );
vv( 0, 1 );
vv( 1, -1 );
EP;
/* draw cap outline */
fl_color( FL_BLACK );
BL;
vv( -1, -1 );
vv( 0, 1 );
vv( 1, -1 );
EL;
}
/** recalculate the size of vertical scrolling area and inform scrollbar */
void
Timeline::adjust_vscroll ( void )
{
vscroll->value( _yposition, h() - rulers->h() - hscroll->h(), 0, pack_visible_height( tracks ) );
}
void
Timeline::cb_scroll ( Fl_Widget *w, void *v )
{
((Timeline*)v)->cb_scroll( w );
}
void
Timeline::cb_scroll ( Fl_Widget *w )
{
if ( w == vscroll )
{
tracks->position( tracks->x(), (rulers->y() + rulers->h()) - vscroll->value() );
yposition( vscroll->value() );
adjust_vscroll();
}
else
{
if ( hscroll->zoom_changed() )
{
nframes_t under_mouse = x_to_offset( Fl::event_x() );
_fpp = hscroll->zoom();
const int tw = tracks->w() - Track::width();
// hscroll->value( ts_to_x( xoffset ), tw, 0, ts_to_x( _length ) );
hscroll->value( max( 0, ts_to_x( under_mouse ) - ( Fl::event_x() - tracks->x() - Track::width() ) ),
tw, 0, ts_to_x( _length ) );
redraw();
}
xposition( hscroll->value() );
}
}
#include <FL/Fl_Menu.H>
void
Timeline::menu_cb ( Fl_Widget *w, void *v )
{
((Timeline*)v)->menu_cb( w );
}
void
Timeline::menu_cb ( Fl_Widget *w )
{
Fl_Menu_ *m = static_cast<Fl_Menu_*>(w);
const char *picked = m->mvalue()->label();
/* m->item_pathname( picked, sizeof( picked ) ); */
DMESSAGE( "%s", picked );
if ( ! strcmp( picked, "Add Audio Track" ) )
{
/* FIXME: prompt for I/O config? */
Loggable::block_start();
/* add audio track */
char *name = get_unique_track_name( "Audio" );
Track *t = new Track( name );
Audio_Sequence *o = new Audio_Sequence( t );
add_track( t );
t->sequence( o );
Loggable::block_end();
}
else if ( ! strcmp( picked, "Tempo from range" ) )
{
if ( p1 != p2 )
{
if ( p1 > p2 )
{
nframes_t t = p2;
p2 = p1;
p1 = t;
}
beats_per_minute( p1, sample_rate() * 60 / (float)( p2 - p1 ) );
p2 = p1;
}
}
else if ( ! strcmp( picked, "Playhead to mouse" ) )
{
int X = Fl::event_x() - Track::width();
if ( X > 0 )
{
transport->locate( xoffset + x_to_ts( X ) );
}
}
else if ( ! strcmp( picked, "P1 to mouse" ) )
{
int X = Fl::event_x() - Track::width();
if ( X > 0 )
{
p1 = xoffset + x_to_ts( X );
}
/* FIXME: only needs to damage the location of the old cursor! */
redraw();
}
else if ( ! strcmp( picked, "P2 to mouse" ) )
{
int X = Fl::event_x() - Track::width();
if ( X > 0 )
{
p2 = xoffset + x_to_ts( X );
}
/* FIXME: only needs to damage the location of the old cursor! */
redraw();
}
else
WARNING( "programming error: Unknown menu item" );
}
Timeline::Timeline ( int X, int Y, int W, int H, const char* L ) : Fl_Overlay_Window( X, Y, W, H, L )
{
_sample_rate = 0;
box( FL_FLAT_BOX );
xoffset = 0;
_yposition = 0;
_old_yposition = 0;
_old_xposition = 0;
X = Y = 0;
p1 = p2 = 0;
menu = new Fl_Menu_Button( 0, 0, 0, 0, "Timeline" );
/* menu->add( "Add Track", 0, 0, 0 ); */
menu->add( "Add Audio Track", 'a', &Timeline::menu_cb, this );
menu->add( "Tempo from range", 't', &Timeline::menu_cb, this );
menu->add( "Playhead to mouse", 'p', &Timeline::menu_cb, this );
menu->add( "P1 to mouse", '[', &Timeline::menu_cb, this );
menu->add( "P2 to mouse", ']', &Timeline::menu_cb, this );
{
Scalebar *o = new Scalebar( X, Y + H - 18, W - 18, 18 );
o->range( 0, 48000 * 300 );
// o->zoom_range( 1, 16384 );
// o->zoom_range( 1, 65536 << 4 );
o->zoom_range( 1, 20 );
o->zoom( 8 );
o->type( FL_HORIZONTAL );
o->callback( cb_scroll, this );
hscroll = o;
}
{
Fl_Scrollbar *o = new Fl_Scrollbar( X + W - 18, Y, 18, H - 18 );
o->type( FL_VERTICAL );
o->callback( cb_scroll, this );
vscroll = o;
}
{
Fl_Pack *o = new Fl_Pack( X + Track::width(), Y, (W - Track::width()) - vscroll->w(), H - hscroll->h(), "rulers" );
o->type( Fl_Pack::VERTICAL );
{
Tempo_Sequence *o = new Tempo_Sequence( 0, 0, 800, 24 );
o->color( fl_gray_ramp( 18 ) );
o->label( "Tempo" );
o->align( FL_ALIGN_LEFT );
tempo_track = o;
}
{
Time_Sequence *o = new Time_Sequence( 0, 24, 800, 24 );
o->color( fl_gray_ramp( 16 ) );
o->label( "Time" );
o->align( FL_ALIGN_LEFT );
time_track = o;
}
/* { */
/* Annotation_Sequence *o = new Annotation_Sequence( 0, 24, 800, 24 ); */
/* o->color( fl_gray_ramp( 'F' ) ); */
/* o->label( "Ruler" ); */
/* o->align( FL_ALIGN_LEFT ); */
/* ruler_track = o; */
/* } */
o->size( o->w(), o->child( 0 )->h() * o->children() );
rulers = o;
o->end();
}
{
// sample_rate() = engine->sample_rate();
_fpp = 8;
// _length = sample_rate() * 60 * 2;
/* FIXME: hack */
_length = -1;
{
Fl_Pack *o = new Fl_Pack( X, rulers->y() + rulers->h(), W - vscroll->w(), 1 );
o->type( Fl_Pack::VERTICAL );
o->spacing( 1 );
tracks = o;
o->end();
resizable( o );
}
}
/* rulers go above tracks... */
add( rulers );
/* make sure scrollbars are on top */
add( vscroll );
add( hscroll );
vscroll->range( 0, tracks->h() );
redraw();
end();
Fl::add_timeout( UPDATE_FREQ, update_cb, this );
}
/* float */
/* Timeline::beats_per_minute ( nframes_t when ) const */
/* { */
/* /\* return tempo_track->beats_per_minute( when ); *\/ */
/* } */
/* int */
/* Timeline::beats_per_bar ( nframes_t when ) const */
/* { */
/* time_sig t = time_track->time( when ); */
/* return t.beats_per_bar; */
/* } */
void
Timeline::beats_per_minute ( nframes_t when, float bpm )
{
tempo_track->add( new Tempo_Point( when, bpm ) );
}
void
Timeline::time ( nframes_t when, int bpb, int note_type )
{
time_track->add( new Time_Point( when, bpb, note_type ) );
}
/************/
/* Snapping */
/************/
struct nearest_line_arg
{
nframes_t original;
nframes_t closest;
};
const int snap_pixel = 10;
static nframes_t
abs_diff ( nframes_t n1, nframes_t n2 )
{
return n1 > n2 ? n1 - n2 : n2 - n1;
}
void
nearest_line_cb ( nframes_t frame, const BBT &bbt, void *arg )
{
nearest_line_arg *n = (nearest_line_arg *)arg;
if ( Timeline::snap_to == Timeline::Bars && bbt.beat )
return;
if ( Timeline::snap_magnetic &&
abs_diff( frame, n->original ) > timeline->x_to_ts( snap_pixel ) )
return;
if ( abs_diff( frame, n->original ) < abs_diff( n->original, n->closest ) )
n->closest = frame;
}
/** Set the value pointed to by /frame/ to the frame number of the of
the nearest measure line to /when/. Returns true if the new value of
*frame is valid, false otherwise. */
bool
Timeline::nearest_line ( nframes_t when, nframes_t *frame ) const
{
if ( snap_to == None )
return false;
nearest_line_arg n = { when, -1 };
render_tempomap( when - x_to_ts( w() >> 1 ), x_to_ts( w() ), nearest_line_cb, &n );
*frame = n.closest;
return *frame != (nframes_t)-1;
}
nframes_t
Timeline::x_to_offset ( int x ) const
{
return x_to_ts( max( 0, x - Track::width() ) ) + xoffset;
}
/** draws a single measure line */
static void
draw_measure_cb ( nframes_t frame, const BBT &bbt, void *arg )
{
Fl_Color *color = (Fl_Color*)arg;
fl_color( FL_BLACK );
fl_line_style( FL_DASH, 0 );
if ( bbt.beat )
++color;
fl_color( *color );
const int x = timeline->ts_to_x( frame - timeline->xoffset ) + Track::width();
fl_line( x, 0, x, 5000 );
fl_line_style( FL_SOLID, 0 );
}
/* FIXME: wrong place for this */
const float ticks_per_beat = 1920.0;
void
Timeline::update_tempomap ( void )
{
/* FIXME: we need some type of locking! */
_tempomap.clear();
for ( list <Sequence_Widget *>::const_iterator i = time_track->_widgets.begin();
i != time_track->_widgets.end(); ++i )
_tempomap.push_back( *i );
for ( list <Sequence_Widget *>::const_iterator i = tempo_track->_widgets.begin();
i != tempo_track->_widgets.end(); ++i )
_tempomap.push_back( *i );
_tempomap.sort( Sequence_Widget::sort_func );
}
position_info
Timeline::solve_tempomap ( nframes_t frame ) const
{
return render_tempomap( frame, 0, 0, 0 );
}
/* THREAD: UI and RT */
/** draw appropriate measure lines inside the given bounding box */
position_info
Timeline::render_tempomap( nframes_t start, nframes_t length, measure_line_callback * cb, void *arg ) const
{
const nframes_t end = start + length;
position_info pos;
memset( &pos, 0, sizeof( pos ) );
BBT &bbt = pos.bbt;
const nframes_t samples_per_minute = sample_rate() * 60;
float bpm = 120.0f;
time_sig sig;
sig.beats_per_bar = 4;
sig.beat_type = 4;
nframes_t f = 0;
nframes_t next = 0;
nframes_t frames_per_beat = samples_per_minute / bpm;
if ( ! _tempomap.size() )
return pos;
for ( list <const Sequence_Widget *>::const_iterator i = _tempomap.begin();
i != _tempomap.end(); ++i )
{
if ( ! strcmp( (*i)->class_name(), "Tempo_Point" ) )
{
const Tempo_Point *p = (Tempo_Point*)(*i);
bpm = p->tempo();
frames_per_beat = samples_per_minute / bpm;
}
else
{
const Time_Point *p = (Time_Point*)(*i);
sig = p->time();
/* Time point resets beat */
bbt.beat = 0;
}
{
list <const Sequence_Widget *>::const_iterator n = i;
++n;
if ( n == _tempomap.end() )
next = end;
else
// next = min( (*n)->start(), end );
/* points may not always be aligned with beat boundaries, so we must align here */
next = (*n)->start() - ( ( (*n)->start() - (*i)->start() ) % frames_per_beat );
}
for ( ; f < next; ++bbt.beat, f += frames_per_beat )
{
if ( bbt.beat == sig.beats_per_bar )
{
bbt.beat = 0;
++bbt.bar;
}
if ( f >= start )
{
/* in the zone */
if ( cb )
cb( f, bbt, arg );
}
/* ugliness to avoid failing out at -1 */
if ( end >= frames_per_beat )
{
if ( f >= end - frames_per_beat )
goto done;
}
else if ( f + frames_per_beat >= end )
goto done;
}
}
done:
pos.frame = f;
pos.tempo = bpm;
pos.beats_per_bar = sig.beats_per_bar;
pos.beat_type = sig.beat_type;
assert( f <= end );
assert( end - f <= frames_per_beat );
/* FIXME: this this right? */
const double frames_per_tick = frames_per_beat / ticks_per_beat;
bbt.tick = ( end - f ) / frames_per_tick;
return pos;
}
void
Timeline::draw_measure_lines ( int X, int Y, int W, int H, Fl_Color color )
{
if ( ! draw_with_measure_lines )
return;
Fl_Color colors[2];
colors[1] = fl_color_average( FL_BLACK, color, 0.65f );
colors[0] = fl_color_average( FL_RED, colors[1], 0.65f );
const nframes_t start = x_to_offset( X );
const nframes_t length = x_to_ts( W );
fl_push_clip( X, Y, W, H );
render_tempomap( start, length, draw_measure_cb, &colors );
fl_pop_clip();
}
/* /\** just like draw mesure lines except that it also draws the BBT values. *\/ */
/* void */
/* Timeline::draw_measure_BBT ( int X, int Y, int W, int H, Fl_Color color ) */
/* { */
/* // render_tempomap( X, Y, W, H, color, true ); */
/* } */
void
Timeline::xposition ( int X )
{
// _old_xposition = xoffset;
/* FIXME: shouldn't have to do this... */
X = min( X, ts_to_x( _length ) - tracks->w() - Track::width() );
xoffset = x_to_ts( X );
damage( FL_DAMAGE_SCROLL );
}
void
Timeline::yposition ( int Y )
{
// _old_yposition = _yposition;
_yposition = Y;
damage( FL_DAMAGE_SCROLL );
}
void
Timeline::draw_clip ( void * v, int X, int Y, int W, int H )
{
Timeline *tl = (Timeline *)v;
fl_push_clip( X, Y, W, H );
/* fl_color( rand() ); */
/* fl_rectf( X, Y, X + W, Y + H ); */
tl->draw_box();
tl->draw_child( *tl->rulers );
fl_push_clip( tl->tracks->x(), tl->rulers->y() + tl->rulers->h(), tl->tracks->w(), tl->h() - tl->rulers->h() - tl->hscroll->h() );
tl->draw_child( *tl->tracks );
fl_pop_clip();
fl_pop_clip();
}
void
Timeline::resize ( int X, int Y, int W, int H )
{
Fl_Overlay_Window::resize( X, Y, W, H );
/* why is this necessary? */
rulers->resize( Track::width(), 0, W - Track::width() - vscroll->w(), rulers->h() );
/* why is THIS necessary? */
hscroll->resize( 0, H - 18, hscroll->w(), 18 );
vscroll->size( vscroll->w(), H - 18 );
}
void
Timeline::draw ( void )
{
int X, Y, W, H;
int bdx = 0;
int bdw = 0;
X = tracks->x() + bdx + 1;
Y = tracks->y();
W = tracks->w() - bdw - 1;
H = tracks->h();
adjust_vscroll();
if ( ( damage() & FL_DAMAGE_ALL ) || ( damage() & FL_DAMAGE_EXPOSE ) )
{
DMESSAGE( "complete redraw" );
draw_box( box(), 0, 0, w(), h(), color() );
fl_push_clip( 0, rulers->y(), w(), rulers->h() );
draw_child( *rulers );
fl_pop_clip();
fl_push_clip( tracks->x(), rulers->y() + rulers->h(), tracks->w(), hscroll->y() - (rulers->y() + rulers->h()) );
draw_child( *tracks );
fl_pop_clip();
draw_child( *hscroll );
draw_child( *vscroll );
if ( p1 != p2 )
{
draw_cursor( p1, FL_BLUE, draw_full_arrow_symbol );
draw_cursor( p2, FL_GREEN, draw_full_arrow_symbol );
}
redraw_overlay();
goto done;
}
if ( damage() & FL_DAMAGE_SCROLL )
{
int dx = ts_to_x( _old_xposition ) - ts_to_x( xoffset );
int dy = _old_yposition - _yposition;
/* draw_child( *rulers ); */
if ( ! dy )
fl_scroll( rulers->x(), rulers->y(), rulers->w(), rulers->h(), dx, 0, draw_clip, this );
Y = rulers->y() + rulers->h();
H = h() - rulers->h() - hscroll->h();
if ( dy == 0 )
fl_scroll( X + Track::width(), Y, W - Track::width(), H, dx, dy, draw_clip, this );
else
fl_scroll( X, Y, W, H, dx, dy, draw_clip, this );
}
if ( damage() & FL_DAMAGE_CHILD )
{
fl_push_clip( rulers->x(), rulers->y(), rulers->w(), rulers->h() );
update_child( *rulers );
fl_pop_clip();
fl_push_clip( tracks->x(), rulers->y() + rulers->h(), tracks->w(), h() - rulers->h() - hscroll->h() );
update_child( *tracks );
fl_pop_clip();
update_child( *hscroll );
update_child( *vscroll );
if ( p1 != p2 )
{
draw_cursor( p1, FL_BLUE, draw_full_arrow_symbol );
draw_cursor( p2, FL_GREEN, draw_full_arrow_symbol );
}
}
done:
_old_xposition = xoffset;
_old_yposition = _yposition;
}
void
Timeline::draw_cursor ( nframes_t frame, Fl_Color color, void (*symbol)(Fl_Color) )
{
// int x = ( ts_to_x( frame ) - ts_to_x( xoffset ) ) + tracks->x() + Track::width();
if ( frame < xoffset )
return;
const int x = ts_to_x( frame - xoffset ) + tracks->x() + Track::width();
if ( x > tracks->x() + tracks->w() )
return;
fl_color( color );
const int y = rulers->y() + rulers->h();
const int h = this->h() - hscroll->h() - 1;
fl_push_clip( tracks->x() + Track::width(), y, tracks->w(), h );
fl_line( x, y, x, h );
fl_color( fl_darker( color ) );
fl_line( x - 1, y, x - 1, h );
fl_color( FL_BLACK );
fl_line( x + 1, y, x + 1, h );
fl_push_matrix();
fl_translate( x, y );
fl_scale( 16, 8 );
symbol( color );
fl_pop_matrix();
fl_pop_clip();
}
void
Timeline::draw_playhead ( void )
{
draw_cursor( transport->frame, FL_RED, draw_full_arrow_symbol );
}
void
Timeline::redraw_playhead ( void )
{
static nframes_t last_playhead = -1;
if ( last_playhead != transport->frame )
{
redraw_overlay();
last_playhead = transport->frame;
if ( follow_playhead )
{
if ( center_playhead && active() )
xposition( max( 0, ts_to_x( transport->frame ) - ( ( tracks->w() - Track::width() ) >> 1 ) ) );
else if ( ts_to_x( transport->frame ) > ts_to_x( xoffset ) + ( tracks->w() - Track::width() ) )
xposition( ts_to_x( transport->frame ) );
}
}
}
/** called so many times a second to redraw the playhead etc. */
void
Timeline::update_cb ( void *arg )
{
Fl::repeat_timeout( UPDATE_FREQ, update_cb, arg );
Timeline *tl = (Timeline *)arg;
tl->redraw_playhead();
}
void
Timeline::draw_overlay ( void )
{
draw_playhead();
if ( ! ( _selection.w && _selection.h ) )
return;
fl_push_clip( tracks->x() + Track::width(), rulers->y() + rulers->h(), tracks->w() - Track::width(), h() - rulers->h() - hscroll->h() );
const Rectangle &r = _selection;
fl_color( FL_BLACK );
fl_line_style( FL_SOLID, 2 );
fl_rect( r.x + 2, r.y + 2, r.w, r.h );
fl_color( FL_MAGENTA );
fl_line_style( FL_DASH, 2 );
fl_rect( r.x, r.y, r.w, r.h );
fl_line( r.x, r.y, r.x + r.w, r.y + r.h );
fl_line( r.x + r.w, r.y, r.x, r.y + r.h );
fl_line_style( FL_SOLID, 0 );
fl_pop_clip();
}
// #include "Sequence_Widget.H"
/** select all widgets in inside rectangle /r/ */
void
Timeline::select ( const Rectangle &r )
{
const int Y = r.y;
for ( int i = tracks->children(); i-- ; )
{
Track *t = (Track*)tracks->child( i );
if ( ! ( t->y() > Y + r.h || t->y() + t->h() < Y ) )
t->select( r.x, r.y, r.w, r.h, true, true );
}
}
void
Timeline::delete_selected ( void )
{
Sequence_Widget::delete_selected();
}
void
Timeline::select_none ( void )
{
Sequence_Widget::select_none();
}
/** An unfortunate necessity for implementing our own DND aside from
* the (bogus) native FLTK system */
Track *
Timeline::track_under ( int Y )
{
for ( int i = tracks->children(); i-- ; )
{
Track *t = (Track*)tracks->child( i );
if ( ! ( t->y() > Y || t->y() + t->h() < Y ) )
return t;
}
return NULL;
}
#include "FL/event_name.H"
#include "FL/test_press.H"
int
Timeline::handle ( int m )
{
static Drag *drag = NULL;
static bool range = false;
/* if ( m != FL_NO_EVENT ) */
/* DMESSAGE( "%s", event_name( m ) ); */
int r = Fl_Overlay_Window::handle( m );
switch ( m )
{
case FL_FOCUS:
case FL_UNFOCUS:
// redraw();
return 1;
case FL_ENTER:
return 1;
case FL_LEAVE:
return 1;
case FL_KEYDOWN:
if ( Fl::event_key() == 'r' )
{
range = true;
return 1;
}
return 0;
case FL_KEYUP:
if ( Fl::event_key() == 'r' )
{
range = false;
return 1;
}
return 0;
// case FL_KEYBOARD:
case FL_SHORTCUT:
{
if ( Fl::event_state() & ( FL_ALT | FL_CTRL | FL_SHIFT ) )
/* we don't want any keys with modifiers... */
return 0;
switch ( Fl::event_key() )
{
case FL_Delete:
case FL_Home:
case FL_End:
/* keep scrollbar from eating these. */
return 0;
default:
return r;
}
return 0;
}
default:
{
/* if ( m == FL_PUSH ) */
/* take_focus(); */
//Fl::focus( this );
/* r = Fl_Overlay_Window::handle( m ); */
if ( m != FL_RELEASE && r )
return r;
const int X = Fl::event_x();
const int Y = Fl::event_y();
switch ( m )
{
case FL_PUSH:
{
if ( test_press( FL_BUTTON1 ) )
{
assert( ! drag );
drag = new Drag( X - x(), Y - y() );
_selection.x = drag->x;
_selection.y = drag->y;
return 1;
}
else if ( test_press( FL_BUTTON3 ) )
{
const Fl_Menu_Item *r = menu->menu()->popup( X, Y, "Timeline" );
if ( r )
{
menu->value( r );
r->do_callback( static_cast<Fl_Widget*>(menu) );
}
return 1;
}
return 0;
}
case FL_DRAG:
{
int ox = X - drag->x;
int oy = Y - drag->y;
if ( ox < 0 )
_selection.x = X;
if ( oy < 0 )
_selection.y = Y;
_selection.w = abs( ox );
_selection.h = abs( oy );
if ( range )
{
p1 = x_to_offset( _selection.x );
p2 = x_to_offset( _selection.x + _selection.w );
redraw();
}
redraw_overlay();
return 1;
break;
}
case FL_RELEASE:
{
delete drag;
drag = NULL;
if ( range )
{
p1 = x_to_offset( _selection.x );
p2 = x_to_offset( _selection.x + _selection.w );
redraw();
}
else
select( _selection );
_selection.w = _selection.h = 0;
redraw_overlay();
return 1;
}
default:
return 0;
break;
}
return 0;
}
}
}
void
Timeline::zoom_in ( void )
{
hscroll->zoom_in();
}
void
Timeline::zoom_out ( void )
{
hscroll->zoom_out();
}
/** zoom the display to show /secs/ seconds per screen */
void
Timeline::zoom ( float secs )
{
const int sw = tracks->w() - Track::width();
int fpp = (int)((secs * sample_rate()) / sw);
int p = 0;
while ( 1 << p < fpp ) p++;
hscroll->zoom( p );
redraw();
}
void
Timeline::zoom_fit ( void )
{
zoom( _length / (float)sample_rate() );
}
Track *
Timeline::track_by_name ( const char *name )
{
for ( int i = tracks->children(); i-- ; )
{
Track *t = (Track*)tracks->child( i );
if ( ! strcmp( name, t->name() ) )
return t;
}
return NULL;
}
char *
Timeline::get_unique_track_name ( const char *name )
{
char pat[256];
strcpy( pat, name );
for ( int i = 1; track_by_name( pat ); ++i )
snprintf( pat, sizeof( pat ), "%s.%d", name, i );
return strdup( pat );
}
void
Timeline::add_track ( Track *track )
{
DMESSAGE( "added new track to the timeline" );
engine->lock();
tracks->add( track );
engine->unlock();
/* FIXME: why is this necessary? doesn't the above add do DAMAGE_CHILD? */
redraw();
}
void
Timeline::remove_track ( Track *track )
{
DMESSAGE( "removed track from the timeline" );
engine->lock();
/* FIXME: what to do about track contents? */
tracks->remove( track );
engine->unlock();
/* FIXME: why is this necessary? doesn't the above add do DAMAGE_CHILD? */
redraw();
}