2016-12-16 19:43:09 -05:00
/*
* Copyright ( C ) 2016 Robin Gareus < robin @ gareus . org >
*
* 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 . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
# include "ardour/audioengine.h"
# include "ardour/session.h"
# include "ardour/tempo.h"
2017-07-17 12:34:35 -04:00
# include "gtkmm2ext/colors.h"
2016-12-16 19:43:09 -05:00
# include "gtkmm2ext/gui_thread.h"
# include "gtkmm2ext/keyboard.h"
2017-07-15 11:38:28 -04:00
# include "widgets/tooltips.h"
2016-12-16 19:43:09 -05:00
# include "ardour_ui.h"
# include "public_editor.h"
# include "main_clock.h"
# include "mini_timeline.h"
# include "timers.h"
# include "ui_config.h"
# include "pbd/i18n.h"
2016-12-19 21:33:43 -05:00
# define PADDING 3
2016-12-16 19:43:09 -05:00
# define BBT_BAR_CHAR "|"
using namespace ARDOUR ;
MiniTimeline : : MiniTimeline ( )
2017-09-18 12:39:17 -04:00
: _last_update_sample ( - 1 )
2016-12-16 19:43:09 -05:00
, _clock_mode ( AudioClock : : Timecode )
, _time_width ( 0 )
, _time_height ( 0 )
2016-12-18 16:21:10 -05:00
, _n_labels ( 0 )
, _px_per_sample ( 0 )
, _time_granularity ( 0 )
, _time_span_samples ( 0 )
2016-12-19 13:12:13 -05:00
, _marker_height ( 0 )
, _pointer_x ( - 1 )
, _pointer_y ( - 1 )
, _minitl_context_menu ( 0 )
2016-12-16 19:43:09 -05:00
{
2016-12-19 13:12:13 -05:00
add_events ( Gdk : : ENTER_NOTIFY_MASK | Gdk : : LEAVE_NOTIFY_MASK | Gdk : : BUTTON_RELEASE_MASK | Gdk : : BUTTON_PRESS_MASK | Gdk : : POINTER_MOTION_MASK | Gdk : : SCROLL_MASK ) ;
2016-12-16 19:43:09 -05:00
_layout = Pango : : Layout : : create ( get_pango_context ( ) ) ;
UIConfiguration : : instance ( ) . ColorsChanged . connect ( sigc : : mem_fun ( * this , & MiniTimeline : : set_colors ) ) ;
UIConfiguration : : instance ( ) . DPIReset . connect ( sigc : : mem_fun ( * this , & MiniTimeline : : on_name_changed ) ) ;
UIConfiguration : : instance ( ) . DPIReset . connect ( sigc : : mem_fun ( * this , & MiniTimeline : : on_name_changed ) ) ;
set_name ( " minitimeline " ) ;
Location : : name_changed . connect ( marker_connection , invalidator ( * this ) , boost : : bind ( & MiniTimeline : : update_minitimeline , this ) , gui_context ( ) ) ;
Location : : end_changed . connect ( marker_connection , invalidator ( * this ) , boost : : bind ( & MiniTimeline : : update_minitimeline , this ) , gui_context ( ) ) ;
Location : : start_changed . connect ( marker_connection , invalidator ( * this ) , boost : : bind ( & MiniTimeline : : update_minitimeline , this ) , gui_context ( ) ) ;
Location : : flags_changed . connect ( marker_connection , invalidator ( * this ) , boost : : bind ( & MiniTimeline : : update_minitimeline , this ) , gui_context ( ) ) ;
2017-07-15 11:38:28 -04:00
ArdourWidgets : : set_tooltip ( * this ,
2017-05-14 09:15:10 -04:00
string_compose ( _ ( " <b>Navigation Timeline</b>. Use left-click to locate to time position or marker; scroll-wheel to jump, hold %1 for fine grained and %2 + %3 for extra-fine grained control. Right-click to set display range. The display unit is defined by the primary clock. " ) ,
2016-12-28 04:20:39 -05:00
Gtkmm2ext : : Keyboard : : primary_modifier_name ( ) ,
Gtkmm2ext : : Keyboard : : primary_modifier_name ( ) ,
2016-12-19 21:58:49 -05:00
Gtkmm2ext : : Keyboard : : secondary_modifier_name ( ) ) ) ;
2016-12-16 19:43:09 -05:00
}
MiniTimeline : : ~ MiniTimeline ( )
{
2016-12-19 13:12:13 -05:00
delete _minitl_context_menu ;
_minitl_context_menu = 0 ;
2016-12-16 19:43:09 -05:00
}
void
MiniTimeline : : session_going_away ( )
{
super_rapid_connection . disconnect ( ) ;
2016-12-18 08:52:26 -05:00
session_connection . drop_connections ( ) ;
2016-12-16 19:43:09 -05:00
SessionHandlePtr : : session_going_away ( ) ;
_jumplist . clear ( ) ;
2016-12-19 13:12:13 -05:00
delete _minitl_context_menu ;
_minitl_context_menu = 0 ;
2016-12-16 19:43:09 -05:00
}
void
MiniTimeline : : set_session ( Session * s )
{
SessionHandlePtr : : set_session ( s ) ;
if ( ! s ) {
return ;
}
assert ( ! super_rapid_connection . connected ( ) ) ;
super_rapid_connection = Timers : : super_rapid_connect (
sigc : : mem_fun ( * this , & MiniTimeline : : super_rapid_update )
) ;
2016-12-18 08:52:26 -05:00
2016-12-17 18:06:39 -05:00
_session - > config . ParameterChanged . connect ( session_connection ,
invalidator ( * this ) ,
boost : : bind ( & MiniTimeline : : parameter_changed , this , _1 ) , gui_context ( )
) ;
2016-12-18 08:52:26 -05:00
_session - > locations ( ) - > added . connect ( session_connection ,
invalidator ( * this ) ,
boost : : bind ( & MiniTimeline : : update_minitimeline , this ) , gui_context ( )
) ;
_session - > locations ( ) - > removed . connect ( session_connection ,
invalidator ( * this ) ,
boost : : bind ( & MiniTimeline : : update_minitimeline , this ) , gui_context ( )
) ;
_session - > locations ( ) - > changed . connect ( session_connection ,
invalidator ( * this ) ,
boost : : bind ( & MiniTimeline : : update_minitimeline , this ) , gui_context ( )
) ;
2016-12-17 18:06:39 -05:00
2016-12-16 19:43:09 -05:00
_jumplist . clear ( ) ;
2016-12-18 16:58:19 -05:00
calculate_time_spacing ( ) ;
update_minitimeline ( ) ;
2016-12-16 19:43:09 -05:00
}
void
MiniTimeline : : on_style_changed ( const Glib : : RefPtr < Gtk : : Style > & old_style )
{
CairoWidget : : on_style_changed ( old_style ) ;
set_colors ( ) ;
calculate_time_width ( ) ;
}
void
MiniTimeline : : on_name_changed ( )
{
set_colors ( ) ;
calculate_time_width ( ) ;
if ( is_realized ( ) ) {
queue_resize ( ) ;
}
}
void
MiniTimeline : : set_colors ( )
{
// TODO UIConfiguration::instance().color & font
2018-02-09 09:21:45 -05:00
_phead_color = UIConfiguration : : instance ( ) . color ( " play head " ) ;
2016-12-16 19:43:09 -05:00
}
2016-12-17 18:06:39 -05:00
void
MiniTimeline : : parameter_changed ( std : : string const & p )
{
if ( p = = " minitimeline-span " ) {
2016-12-18 16:21:10 -05:00
calculate_time_spacing ( ) ;
2016-12-17 18:06:39 -05:00
update_minitimeline ( ) ;
}
}
2016-12-16 19:43:09 -05:00
void
MiniTimeline : : on_size_request ( Gtk : : Requisition * req )
{
req - > width = req - > height = 0 ;
CairoWidget : : on_size_request ( req ) ;
2016-12-18 16:21:10 -05:00
req - > width = std : : max ( req - > width , 1 ) ;
2016-12-16 19:43:09 -05:00
req - > height = std : : max ( req - > height , 20 ) ;
}
2016-12-18 16:21:10 -05:00
void
MiniTimeline : : on_size_allocate ( Gtk : : Allocation & alloc )
{
CairoWidget : : on_size_allocate ( alloc ) ;
calculate_time_spacing ( ) ;
}
2016-12-19 13:12:13 -05:00
void
2017-09-18 12:39:17 -04:00
MiniTimeline : : set_span ( samplecnt_t ts )
2016-12-19 13:12:13 -05:00
{
assert ( _session ) ;
if ( _session - > config . get_minitimeline_span ( ) = = ts ) {
return ;
}
_session - > config . set_minitimeline_span ( ts ) ;
calculate_time_spacing ( ) ;
update_minitimeline ( ) ;
}
2016-12-16 19:43:09 -05:00
void
MiniTimeline : : super_rapid_update ( )
{
2016-12-20 06:28:54 -05:00
if ( ! _session | | ! _session - > engine ( ) . running ( ) | | ! is_mapped ( ) ) {
2016-12-16 19:43:09 -05:00
return ;
}
2017-09-18 12:39:17 -04:00
samplepos_t const sample = PublicEditor : : instance ( ) . playhead_cursor_sample ( ) ;
2016-12-16 19:43:09 -05:00
AudioClock : : Mode m = ARDOUR_UI : : instance ( ) - > primary_clock - > mode ( ) ;
2016-12-18 16:21:10 -05:00
bool change = false ;
2017-09-18 12:39:17 -04:00
if ( fabs ( ( _last_update_sample - sample ) * _px_per_sample ) > = 1.0 ) {
2016-12-18 16:21:10 -05:00
change = true ;
}
2016-12-16 19:43:09 -05:00
if ( m ! = _clock_mode ) {
_clock_mode = m ;
calculate_time_width ( ) ;
change = true ;
}
2016-12-19 13:25:14 -05:00
if ( _clock_mode = = AudioClock : : BBT ) {
// TODO check if tempo-map changed
change = true ;
}
2016-12-16 19:43:09 -05:00
if ( change ) {
2017-09-18 12:39:17 -04:00
_last_update_sample = sample ;
2016-12-16 19:43:09 -05:00
update_minitimeline ( ) ;
}
}
void
MiniTimeline : : update_minitimeline ( )
{
CairoWidget : : set_dirty ( ) ;
}
void
MiniTimeline : : calculate_time_width ( )
{
switch ( _clock_mode ) {
case AudioClock : : Timecode :
_layout - > set_text ( " 88:88:88,888 " ) ;
break ;
case AudioClock : : BBT :
_layout - > set_text ( " 888|88|8888 " ) ;
break ;
case AudioClock : : MinSec :
_layout - > set_text ( " 88:88:88,88 " ) ;
break ;
2017-11-22 14:36:28 -05:00
case AudioClock : : Seconds :
2017-09-18 12:39:17 -04:00
case AudioClock : : Samples :
2016-12-16 19:43:09 -05:00
_layout - > set_text ( " 8888888888 " ) ;
break ;
}
_layout - > get_pixel_size ( _time_width , _time_height ) ;
}
2016-12-18 16:21:10 -05:00
void
MiniTimeline : : calculate_time_spacing ( )
{
_n_labels = floor ( get_width ( ) / ( _time_width * 1.15 ) ) ;
if ( _n_labels = = 0 | | ! _session ) {
return ;
}
2017-09-18 12:39:17 -04:00
const samplecnt_t time_span = _session - > config . get_minitimeline_span ( ) / 2 ;
_time_span_samples = time_span * _session - > nominal_sample_rate ( ) ;
_time_granularity = _session - > nominal_sample_rate ( ) * ceil ( 2. * time_span / _n_labels ) ;
2016-12-18 16:21:10 -05:00
_px_per_sample = get_width ( ) / ( 2. * _time_span_samples ) ;
//_px_per_sample = 1.0 / round (1.0 / _px_per_sample);
}
2016-12-16 19:43:09 -05:00
void
2017-09-18 12:39:17 -04:00
MiniTimeline : : format_time ( samplepos_t when )
2016-12-16 19:43:09 -05:00
{
switch ( _clock_mode ) {
case AudioClock : : Timecode :
{
Timecode : : Time TC ;
_session - > timecode_time ( when , TC ) ;
// chop of leading space or minus.
_layout - > set_text ( Timecode : : timecode_format_time ( TC ) . substr ( 1 ) ) ;
}
break ;
case AudioClock : : BBT :
{
char buf [ 64 ] ;
2017-09-18 12:39:17 -04:00
Timecode : : BBT_Time BBT = _session - > tempo_map ( ) . bbt_at_sample ( when ) ;
2016-12-16 19:43:09 -05:00
snprintf ( buf , sizeof ( buf ) , " %03 " PRIu32 BBT_BAR_CHAR " %02 " PRIu32 BBT_BAR_CHAR " %04 " PRIu32 ,
BBT . bars , BBT . beats , BBT . ticks ) ;
_layout - > set_text ( buf ) ;
}
break ;
case AudioClock : : MinSec :
{
char buf [ 32 ] ;
2017-09-18 12:39:17 -04:00
AudioClock : : print_minsec ( when , buf , sizeof ( buf ) , _session - > sample_rate ( ) ) ;
2016-12-16 19:43:09 -05:00
_layout - > set_text ( std : : string ( buf ) . substr ( 1 ) ) ;
}
break ;
2017-11-22 14:36:28 -05:00
case AudioClock : : Seconds :
{
char buf [ 32 ] ;
snprintf ( buf , sizeof ( buf ) , " %.1f " , when / ( float ) _session - > sample_rate ( ) ) ;
_layout - > set_text ( buf ) ;
}
break ;
2017-09-18 12:39:17 -04:00
case AudioClock : : Samples :
2016-12-16 19:43:09 -05:00
{
char buf [ 32 ] ;
snprintf ( buf , sizeof ( buf ) , " % " PRId64 , when ) ;
_layout - > set_text ( buf ) ;
}
break ;
}
}
void
2017-07-17 12:34:35 -04:00
MiniTimeline : : draw_dots ( cairo_t * cr , int left , int right , int y , Gtkmm2ext : : Color color )
2016-12-16 19:43:09 -05:00
{
if ( left + 1 > = right ) {
return ;
}
cairo_move_to ( cr , left + .5 , y + .5 ) ;
cairo_line_to ( cr , right - .5 , y + .5 ) ;
2017-07-17 12:34:35 -04:00
Gtkmm2ext : : set_source_rgb_a ( cr , color , 0.3 ) ;
2016-12-16 19:43:09 -05:00
const double dashes [ ] = { 0 , 1 } ;
cairo_set_dash ( cr , dashes , 2 , 1 ) ;
cairo_set_line_cap ( cr , CAIRO_LINE_CAP_ROUND ) ;
cairo_set_line_width ( cr , 1.0 ) ;
cairo_stroke ( cr ) ;
cairo_set_dash ( cr , 0 , 0 , 0 ) ;
}
int
2016-12-19 13:12:13 -05:00
MiniTimeline : : draw_mark ( cairo_t * cr , int x0 , int x1 , const std : : string & label , bool & prelight )
2016-12-16 19:43:09 -05:00
{
2016-12-19 13:12:13 -05:00
int h = _marker_height ;
2016-12-16 19:43:09 -05:00
/* ArdourMarker shape
* MH = 13
*
* Mark :
*
* ( 0 , 0 ) - - ( 6 , 0 )
* | |
* | |
* ( 0 , MH * .4 ) ( 6 , MH * .4 )
* \ /
* ( 3 , MH )
*/
2016-12-19 21:33:43 -05:00
const int y = PADDING ;
2016-12-16 19:43:09 -05:00
int w2 = ( h - 1 ) / 4 ;
double h0 = h * .4 ;
double h1 = h - h0 ;
int lw , lh ;
_layout - > set_text ( label ) ;
_layout - > get_pixel_size ( lw , lh ) ;
2016-12-19 13:12:13 -05:00
int rw = std : : min ( x1 , x0 + w2 + lw + 2 ) ;
2017-04-02 16:20:12 -04:00
if ( _pointer_y > = 0 & & _pointer_y < = y + h & & _pointer_x > = x0 - w2 & & _pointer_x < = rw ) {
2016-12-19 13:12:13 -05:00
prelight = true ;
}
// TODO cache in set_colors()
uint32_t color = UIConfiguration : : instance ( ) . color (
prelight ? " entered marker " : " location marker " ) ;
2016-12-16 19:43:09 -05:00
double r , g , b , a ;
2017-07-17 12:34:35 -04:00
Gtkmm2ext : : color_to_rgba ( color , r , g , b , a ) ;
2016-12-16 19:43:09 -05:00
if ( rw < x0 ) {
rw = x1 ;
} else {
cairo_save ( cr ) ;
cairo_rectangle ( cr , x0 , y , rw - x0 , h ) ;
2016-12-18 12:17:10 -05:00
cairo_set_source_rgba ( cr , r , g , b , 0.5 ) ; // this should use a shaded color
2016-12-16 19:43:09 -05:00
cairo_fill_preserve ( cr ) ;
cairo_clip ( cr ) ;
// marker label
cairo_move_to ( cr , x0 + w2 , y + .5 * ( h - lh ) ) ;
cairo_set_source_rgb ( cr , 0 , 0 , 0 ) ;
pango_cairo_show_layout ( cr , _layout - > gobj ( ) ) ;
cairo_restore ( cr ) ;
}
// draw marker on top
cairo_move_to ( cr , x0 - .5 , y + .5 ) ;
2017-04-02 16:20:12 -04:00
cairo_rel_line_to ( cr , - w2 , 0 ) ;
2016-12-16 19:43:09 -05:00
cairo_rel_line_to ( cr , 0 , h0 ) ;
cairo_rel_line_to ( cr , w2 , h1 ) ;
cairo_rel_line_to ( cr , w2 , - h1 ) ;
cairo_rel_line_to ( cr , 0 , - h0 ) ;
cairo_close_path ( cr ) ;
2016-12-18 12:17:10 -05:00
cairo_set_source_rgba ( cr , r , g , b , 1.0 ) ;
2016-12-16 19:43:09 -05:00
cairo_set_line_width ( cr , 1.0 ) ;
cairo_stroke_preserve ( cr ) ;
cairo_fill ( cr ) ;
return rw ;
}
2017-04-02 16:20:12 -04:00
int
2017-04-03 10:14:01 -04:00
MiniTimeline : : draw_edge ( cairo_t * cr , int x0 , int x1 , bool left , const std : : string & label , bool & prelight )
2017-04-02 16:20:12 -04:00
{
int h = _marker_height ;
int w2 = ( h - 1 ) / 4 ;
const int y = PADDING ;
const double yc = rint ( h * .5 ) ;
const double dy = h * .4 ;
2017-04-03 10:14:01 -04:00
bool with_label ;
int lw , lh , lx ;
_layout - > set_text ( label ) ;
_layout - > get_pixel_size ( lw , lh ) ;
2017-04-02 16:20:12 -04:00
double px , dx ;
if ( left ) {
2017-04-03 10:14:01 -04:00
if ( x0 + 2 * w2 + lw + 2 < x1 ) {
x1 = std : : min ( x1 , x0 + 2 * w2 + lw + 2 ) ;
with_label = true ;
} else {
x1 = std : : min ( x1 , x0 + 2 * w2 ) ;
with_label = false ;
}
2017-04-02 16:20:12 -04:00
px = x0 ;
dx = 2 * w2 ;
2017-04-03 10:14:01 -04:00
lx = x0 + dx ;
2017-04-02 16:20:12 -04:00
} else {
2017-04-03 10:14:01 -04:00
if ( x1 - 2 * w2 - lw - 2 > x0 ) {
x0 = std : : max ( x0 , x1 - 2 * w2 - lw - 2 ) ;
with_label = true ;
} else {
x0 = std : : max ( x0 , x1 - 2 * w2 ) ;
with_label = false ;
}
2017-04-02 16:20:12 -04:00
px = x1 ;
dx = - 2 * w2 ;
2017-04-03 10:14:01 -04:00
lx = x1 + dx - lw - 2 ;
2017-04-02 16:20:12 -04:00
}
2017-04-03 10:14:01 -04:00
2017-04-02 16:20:12 -04:00
if ( x1 - x0 < 2 * w2 ) {
return left ? x0 : x1 ;
}
if ( _pointer_y > = 0 & & _pointer_y < = y + h & & _pointer_x > = x0 & & _pointer_x < = x1 ) {
prelight = true ;
}
// TODO cache in set_colors()
uint32_t color = UIConfiguration : : instance ( ) . color (
prelight ? " entered marker " : " location marker " ) ;
double r , g , b , a ;
2017-07-17 12:34:35 -04:00
Gtkmm2ext : : color_to_rgba ( color , r , g , b , a ) ;
2017-04-02 16:20:12 -04:00
2017-04-03 10:14:01 -04:00
if ( with_label ) {
const int y = PADDING ;
cairo_save ( cr ) ;
cairo_rectangle ( cr , lx , y , lw + 2 , h ) ;
cairo_set_source_rgba ( cr , r , g , b , 0.5 ) ; // this should use a shaded color
cairo_fill_preserve ( cr ) ;
cairo_clip ( cr ) ;
// marker label
cairo_move_to ( cr , lx + 1 , y + .5 * ( h - lh ) ) ;
cairo_set_source_rgb ( cr , 0 , 0 , 0 ) ;
pango_cairo_show_layout ( cr , _layout - > gobj ( ) ) ;
cairo_restore ( cr ) ;
}
2017-04-02 16:20:12 -04:00
// draw arrow
cairo_move_to ( cr , px - .5 , PADDING + yc - .5 ) ;
cairo_rel_line_to ( cr , dx , dy ) ;
cairo_rel_line_to ( cr , 0 , - 2. * dy ) ;
cairo_close_path ( cr ) ;
cairo_set_source_rgba ( cr , r , g , b , 1.0 ) ;
cairo_set_line_width ( cr , 1.0 ) ;
cairo_stroke_preserve ( cr ) ;
cairo_fill ( cr ) ;
return left ? x1 : x0 ;
}
2016-12-17 07:00:33 -05:00
struct LocationMarker {
2017-09-18 12:39:17 -04:00
LocationMarker ( const std : : string & l , samplepos_t w )
2016-12-17 07:00:33 -05:00
: label ( l ) , when ( w ) { }
std : : string label ;
2017-09-18 12:39:17 -04:00
samplepos_t when ;
2016-12-17 07:00:33 -05:00
} ;
struct LocationMarkerSort {
bool operator ( ) ( const LocationMarker & a , const LocationMarker & b ) {
return ( a . when < b . when ) ;
}
} ;
2016-12-16 19:43:09 -05:00
void
2017-03-20 12:11:56 -04:00
MiniTimeline : : render ( Cairo : : RefPtr < Cairo : : Context > const & ctx , cairo_rectangle_t * )
2016-12-16 19:43:09 -05:00
{
2017-03-20 12:11:56 -04:00
cairo_t * cr = ctx - > cobj ( ) ;
2016-12-17 20:16:09 -05:00
// TODO cache, set_colors()
2017-07-17 12:34:35 -04:00
Gtkmm2ext : : Color base = UIConfiguration : : instance ( ) . color ( " ruler base " ) ;
Gtkmm2ext : : Color text = UIConfiguration : : instance ( ) . color ( " ruler text " ) ;
2016-12-17 20:16:09 -05:00
2016-12-18 16:21:10 -05:00
if ( _n_labels = = 0 ) {
2016-12-16 19:43:09 -05:00
return ;
}
2017-04-02 16:20:12 -04:00
const int width = get_width ( ) ;
const int height = get_height ( ) ;
Gtkmm2ext : : rounded_rectangle ( cr , 0 , 0 , width , height , 4 ) ;
2017-07-17 12:34:35 -04:00
Gtkmm2ext : : set_source_rgba ( cr , base ) ;
2016-12-17 20:16:09 -05:00
cairo_fill ( cr ) ;
2017-04-02 16:20:12 -04:00
Gtkmm2ext : : rounded_rectangle ( cr , PADDING , PADDING , width - PADDING - PADDING , height - PADDING - PADDING , 4 ) ;
2016-12-16 19:43:09 -05:00
cairo_clip ( cr ) ;
if ( _session = = 0 ) {
return ;
}
/* time */
2017-09-18 12:39:17 -04:00
const samplepos_t p = _last_update_sample ;
const samplepos_t lower = ( std : : max ( ( samplepos_t ) 0 , ( p - _time_span_samples ) ) / _time_granularity ) * _time_granularity ;
2016-12-16 19:43:09 -05:00
2017-04-02 16:20:12 -04:00
int dot_left = width * .5 + ( lower - p ) * _px_per_sample ;
2016-12-18 16:21:10 -05:00
for ( int i = 0 ; i < 2 + _n_labels ; + + i ) {
2017-09-18 12:39:17 -04:00
samplepos_t when = lower + i * _time_granularity ;
2017-04-02 16:20:12 -04:00
double xpos = width * .5 + ( when - p ) * _px_per_sample ;
2016-12-16 19:43:09 -05:00
// TODO round to nearest display TC in +/- 1px
// prefer to display BBT |0 or .0
int lw , lh ;
format_time ( when ) ;
_layout - > get_pixel_size ( lw , lh ) ;
int x0 = xpos - lw / 2.0 ;
2017-04-02 16:20:12 -04:00
int y0 = height - PADDING - _time_height ;
2016-12-16 19:43:09 -05:00
2016-12-17 20:16:09 -05:00
draw_dots ( cr , dot_left , x0 , y0 + _time_height * .5 , text ) ;
2016-12-16 19:43:09 -05:00
cairo_move_to ( cr , x0 , y0 ) ;
2017-07-17 12:34:35 -04:00
Gtkmm2ext : : set_source_rgba ( cr , text ) ;
2016-12-16 19:43:09 -05:00
pango_cairo_show_layout ( cr , _layout - > gobj ( ) ) ;
dot_left = x0 + lw ;
}
2017-04-02 16:20:12 -04:00
draw_dots ( cr , dot_left , width , height - PADDING - _time_height * .5 , text ) ;
2016-12-16 19:43:09 -05:00
/* locations */
2017-09-18 12:39:17 -04:00
samplepos_t lmin = std : : max ( ( samplepos_t ) 0 , ( p - _time_span_samples ) ) ;
samplepos_t lmax = p + _time_span_samples ;
2016-12-16 19:43:09 -05:00
2016-12-18 10:02:13 -05:00
int tw , th ;
2016-12-18 16:21:10 -05:00
_layout - > set_text ( X_ ( " Marker@ " ) ) ;
2016-12-18 10:02:13 -05:00
_layout - > get_pixel_size ( tw , th ) ;
2016-12-19 13:12:13 -05:00
_marker_height = th + 2 ;
assert ( _marker_height > 4 ) ;
const int mw = ( _marker_height - 1 ) / 4 ;
2016-12-16 19:43:09 -05:00
2016-12-18 16:21:10 -05:00
lmin - = mw / _px_per_sample ;
lmax + = mw / _px_per_sample ;
2016-12-16 19:43:09 -05:00
std : : vector < LocationMarker > lm ;
const Locations : : LocationList & ll ( _session - > locations ( ) - > list ( ) ) ;
for ( Locations : : LocationList : : const_iterator l = ll . begin ( ) ; l ! = ll . end ( ) ; + + l ) {
if ( ( * l ) - > is_session_range ( ) ) {
2017-04-02 16:20:12 -04:00
lm . push_back ( LocationMarker ( _ ( " start " ) , ( * l ) - > start ( ) ) ) ;
lm . push_back ( LocationMarker ( _ ( " end " ) , ( * l ) - > end ( ) ) ) ;
2016-12-16 19:43:09 -05:00
continue ;
}
if ( ! ( * l ) - > is_mark ( ) | | ( * l ) - > name ( ) . substr ( 0 , 4 ) = = " xrun " ) {
continue ;
}
2017-04-02 16:20:12 -04:00
lm . push_back ( LocationMarker ( ( * l ) - > name ( ) , ( * l ) - > start ( ) ) ) ;
2016-12-16 19:43:09 -05:00
}
_jumplist . clear ( ) ;
2016-12-17 07:00:33 -05:00
LocationMarkerSort location_marker_sort ;
2016-12-16 19:43:09 -05:00
std : : sort ( lm . begin ( ) , lm . end ( ) , location_marker_sort ) ;
2017-04-02 16:20:12 -04:00
std : : vector < LocationMarker > : : const_iterator outside_left = lm . end ( ) ;
std : : vector < LocationMarker > : : const_iterator outside_right = lm . end ( ) ;
int left_limit = 0 ;
2017-04-03 10:14:01 -04:00
int right_limit = width * .5 + mw ;
2016-12-19 13:12:13 -05:00
int id = 0 ;
2017-04-02 16:20:12 -04:00
2016-12-19 13:12:13 -05:00
for ( std : : vector < LocationMarker > : : const_iterator l = lm . begin ( ) ; l ! = lm . end ( ) ; + + id ) {
2017-09-18 12:39:17 -04:00
samplepos_t when = ( * l ) . when ;
2017-04-02 16:20:12 -04:00
if ( when < lmin ) {
outside_left = l ;
if ( + + l ! = lm . end ( ) ) {
left_limit = floor ( width * .5 + ( ( * l ) . when - p ) * _px_per_sample ) - 1 - mw ;
} else {
2017-04-03 10:14:01 -04:00
left_limit = width * .5 - mw ;
2017-04-02 16:20:12 -04:00
}
continue ;
}
if ( when > lmax ) {
outside_right = l ;
break ;
}
int x0 = floor ( width * .5 + ( when - p ) * _px_per_sample ) ;
int x1 = width ;
2016-12-16 19:43:09 -05:00
const std : : string & label = ( * l ) . label ;
if ( + + l ! = lm . end ( ) ) {
2017-04-02 16:20:12 -04:00
x1 = floor ( width * .5 + ( ( * l ) . when - p ) * _px_per_sample ) - 1 - mw ;
2016-12-16 19:43:09 -05:00
}
2016-12-19 13:12:13 -05:00
bool prelight = false ;
x1 = draw_mark ( cr , x0 , x1 , label , prelight ) ;
_jumplist . push_back ( JumpRange ( x0 - mw , x1 , when , prelight ) ) ;
2017-04-03 10:14:01 -04:00
right_limit = std : : max ( x1 , right_limit ) ;
2017-04-02 16:20:12 -04:00
}
if ( outside_left ! = lm . end ( ) ) {
if ( left_limit > 3 * mw + PADDING ) {
int x0 = PADDING + 1 ;
2017-04-03 10:14:01 -04:00
int x1 = left_limit - mw ;
2017-04-02 16:20:12 -04:00
bool prelight = false ;
x1 = draw_edge ( cr , x0 , x1 , true , ( * outside_left ) . label , prelight ) ;
if ( x0 ! = x1 ) {
_jumplist . push_back ( JumpRange ( x0 , x1 , ( * outside_left ) . when , prelight ) ) ;
right_limit = std : : max ( x1 , right_limit ) ;
}
}
}
if ( outside_right ! = lm . end ( ) ) {
if ( right_limit + PADDING < width - 3 * mw ) {
int x0 = right_limit ;
int x1 = width - PADDING ;
bool prelight = false ;
x0 = draw_edge ( cr , x0 , x1 , false , ( * outside_right ) . label , prelight ) ;
if ( x0 ! = x1 ) {
_jumplist . push_back ( JumpRange ( x0 , x1 , ( * outside_right ) . when , prelight ) ) ;
}
}
2016-12-16 19:43:09 -05:00
}
2017-04-02 16:20:12 -04:00
2016-12-16 19:43:09 -05:00
/* playhead on top */
2017-04-02 16:20:12 -04:00
int xc = width * 0.5f ;
2016-12-16 19:43:09 -05:00
cairo_set_line_width ( cr , 1.0 ) ;
2018-02-09 09:21:45 -05:00
double r , g , b , a ; Gtkmm2ext : : color_to_rgba ( _phead_color , r , g , b , a ) ;
cairo_set_source_rgb ( cr , r , g , b ) ; // playhead color
2016-12-16 19:43:09 -05:00
cairo_move_to ( cr , xc - .5 , 0 ) ;
2017-04-02 16:20:12 -04:00
cairo_rel_line_to ( cr , 0 , height ) ;
2016-12-16 19:43:09 -05:00
cairo_stroke ( cr ) ;
2017-04-02 16:20:12 -04:00
cairo_move_to ( cr , xc - .5 , height ) ;
2016-12-16 19:43:09 -05:00
cairo_rel_line_to ( cr , - 3 , 0 ) ;
cairo_rel_line_to ( cr , 3 , - 4 ) ;
cairo_rel_line_to ( cr , 3 , 4 ) ;
cairo_close_path ( cr ) ;
cairo_fill ( cr ) ;
}
2016-12-19 13:12:13 -05:00
void
MiniTimeline : : build_minitl_context_menu ( )
{
using namespace Gtk ;
using namespace Gtk : : Menu_Helpers ;
assert ( _session ) ;
2017-09-18 12:39:17 -04:00
const samplecnt_t time_span = _session - > config . get_minitimeline_span ( ) ;
2016-12-19 13:12:13 -05:00
_minitl_context_menu = new Gtk : : Menu ( ) ;
MenuList & items = _minitl_context_menu - > items ( ) ;
// ideally this would have a heading (or rather be a sub-menu to "Visible Time")
2017-09-18 12:39:17 -04:00
std : : map < samplecnt_t , std : : string > spans ;
2016-12-19 13:12:13 -05:00
spans [ 30 ] = _ ( " 30 sec " ) ;
spans [ 60 ] = _ ( " 1 min " ) ;
spans [ 120 ] = _ ( " 2 mins " ) ;
spans [ 300 ] = _ ( " 5 mins " ) ;
spans [ 600 ] = _ ( " 10 mins " ) ;
spans [ 1200 ] = _ ( " 20 mins " ) ;
RadioMenuItem : : Group span_group ;
2017-09-18 12:39:17 -04:00
for ( std : : map < samplecnt_t , std : : string > : : const_iterator i = spans . begin ( ) ; i ! = spans . end ( ) ; + + i ) {
2016-12-19 13:12:13 -05:00
items . push_back ( RadioMenuElem ( span_group , i - > second , sigc : : bind ( sigc : : mem_fun ( * this , & MiniTimeline : : set_span ) , i - > first ) ) ) ;
if ( time_span = = i - > first ) {
static_cast < RadioMenuItem * > ( & items . back ( ) ) - > set_active ( ) ;
}
}
}
bool
MiniTimeline : : on_button_press_event ( GdkEventButton * ev )
{
if ( Gtkmm2ext : : Keyboard : : is_context_menu_event ( ev ) ) {
if ( _session ) {
2017-03-15 21:25:53 -04:00
if ( _minitl_context_menu = = 0 ) {
build_minitl_context_menu ( ) ;
}
_minitl_context_menu - > popup ( ev - > button , ev - > time ) ;
2016-12-19 13:12:13 -05:00
}
return true ;
}
return true ;
}
2016-12-16 19:43:09 -05:00
bool
MiniTimeline : : on_button_release_event ( GdkEventButton * ev )
{
if ( ! _session ) { return true ; }
2017-01-05 11:27:21 -05:00
if ( _session - > actively_recording ( ) ) { return true ; }
2016-12-19 13:12:13 -05:00
if ( ev - > y < 0 | | ev - > y > get_height ( ) | | ev - > x < 0 | | ev - > x > get_width ( ) ) {
return true ;
}
2016-12-17 20:18:16 -05:00
2016-12-19 21:33:43 -05:00
if ( ev - > y < = PADDING + _marker_height ) {
2016-12-19 13:12:13 -05:00
for ( JumpList : : const_iterator i = _jumplist . begin ( ) ; i ! = _jumplist . end ( ) ; + + i ) {
2017-04-02 16:20:12 -04:00
if ( i - > left < = ev - > x & & ev - > x < = i - > right ) {
2016-12-17 20:18:16 -05:00
_session - > request_locate ( i - > to , _session - > transport_rolling ( ) ) ;
2016-12-19 13:12:13 -05:00
return true ;
2016-12-16 19:43:09 -05:00
}
}
2016-12-17 20:18:16 -05:00
}
2016-12-18 16:21:10 -05:00
if ( ev - > button = = 1 ) {
2017-09-18 12:39:17 -04:00
samplepos_t when = _last_update_sample + ( ev - > x - get_width ( ) * .5 ) / _px_per_sample ;
_session - > request_locate ( std : : max ( ( samplepos_t ) 0 , when ) , _session - > transport_rolling ( ) ) ;
2016-12-16 19:43:09 -05:00
}
2016-12-18 16:21:10 -05:00
2016-12-16 19:43:09 -05:00
return true ;
}
2016-12-19 13:12:13 -05:00
bool
MiniTimeline : : on_motion_notify_event ( GdkEventMotion * ev )
{
if ( ! _session ) { return true ; }
2017-01-05 11:27:21 -05:00
if ( _session - > actively_recording ( ) ) { return true ; }
2016-12-19 13:12:13 -05:00
_pointer_x = ev - > x ;
_pointer_y = ev - > y ;
bool need_expose = false ;
for ( JumpList : : const_iterator i = _jumplist . begin ( ) ; i ! = _jumplist . end ( ) ; + + i ) {
2016-12-19 21:33:43 -05:00
if ( i - > left < ev - > x & & ev - > x < i - > right & & ev - > y < = PADDING + _marker_height ) {
2016-12-19 13:12:13 -05:00
if ( ! ( * i ) . prelight ) {
need_expose = true ;
break ;
}
} else {
if ( ( * i ) . prelight ) {
need_expose = true ;
break ;
}
}
}
if ( need_expose ) {
update_minitimeline ( ) ;
}
return true ;
}
bool
MiniTimeline : : on_leave_notify_event ( GdkEventCrossing * ev )
{
CairoWidget : : on_leave_notify_event ( ev ) ;
_pointer_x = _pointer_y = - 1 ;
for ( JumpList : : const_iterator i = _jumplist . begin ( ) ; i ! = _jumplist . end ( ) ; + + i ) {
if ( ( * i ) . prelight ) {
update_minitimeline ( ) ;
break ;
}
}
return true ;
}
2016-12-16 19:43:09 -05:00
bool
MiniTimeline : : on_scroll_event ( GdkEventScroll * ev )
{
if ( ! _session ) { return true ; }
2017-01-05 11:27:21 -05:00
if ( _session - > actively_recording ( ) ) { return true ; }
2017-09-18 12:39:17 -04:00
const samplecnt_t time_span = _session - > config . get_minitimeline_span ( ) ;
samplepos_t when = _session - > audible_sample ( ) ;
2016-12-19 13:12:13 -05:00
double scale = time_span / 60.0 ;
2016-12-16 19:43:09 -05:00
if ( ev - > state & Gtkmm2ext : : Keyboard : : GainFineScaleModifier ) {
if ( ev - > state & Gtkmm2ext : : Keyboard : : GainExtraFineScaleModifier ) {
scale = 0.1 ;
} else {
scale = 0.5 ;
}
}
switch ( ev - > direction ) {
case GDK_SCROLL_UP :
2016-12-20 21:53:33 -05:00
case GDK_SCROLL_RIGHT :
2017-09-18 12:39:17 -04:00
when + = scale * _session - > nominal_sample_rate ( ) ;
2016-12-16 19:43:09 -05:00
break ;
case GDK_SCROLL_DOWN :
2016-12-20 21:53:33 -05:00
case GDK_SCROLL_LEFT :
2017-09-18 12:39:17 -04:00
when - = scale * _session - > nominal_sample_rate ( ) ;
2016-12-16 19:43:09 -05:00
break ;
default :
return true ;
break ;
}
2017-09-18 12:39:17 -04:00
_session - > request_locate ( std : : max ( ( samplepos_t ) 0 , when ) , _session - > transport_rolling ( ) ) ;
2016-12-16 19:43:09 -05:00
return true ;
}