Fix MIDI disk-writer flush
[ardour.git] / gtk2_ardour / mini_timeline.cc
1 /*
2  * Copyright (C) 2016-2017 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2016-2018 Ben Loftis <ben@harrisonconsoles.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19
20 #include "ardour/audioengine.h"
21 #include "ardour/session.h"
22 #include "ardour/tempo.h"
23
24 #include "gtkmm2ext/colors.h"
25 #include "gtkmm2ext/gui_thread.h"
26 #include "gtkmm2ext/keyboard.h"
27
28 #include "widgets/tooltips.h"
29
30 #include "ardour_ui.h"
31 #include "public_editor.h"
32 #include "main_clock.h"
33 #include "mini_timeline.h"
34 #include "timers.h"
35 #include "ui_config.h"
36
37 #include "pbd/i18n.h"
38
39 #define PADDING 3
40 #define BBT_BAR_CHAR "|"
41
42 using namespace ARDOUR;
43
44 MiniTimeline::MiniTimeline ()
45         : _last_update_sample (-1)
46         , _clock_mode (AudioClock::Timecode)
47         , _time_width (0)
48         , _time_height (0)
49         , _n_labels (0)
50         , _px_per_sample (0)
51         , _time_granularity (0)
52         , _time_span_samples (0)
53         , _marker_height (0)
54         , _pointer_x (-1)
55         , _pointer_y (-1)
56         , _minitl_context_menu (0)
57 {
58         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);
59
60         use_intermediate_surface ();
61         _layout = Pango::Layout::create (get_pango_context());
62
63         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &MiniTimeline::set_colors));
64         UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &MiniTimeline::on_name_changed));
65         UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &MiniTimeline::on_name_changed));
66
67         set_name ("minitimeline");
68
69         Location::name_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ());
70         Location::end_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ());
71         Location::start_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ());
72         Location::flags_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ());
73
74         ArdourWidgets::set_tooltip (*this,
75                         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."),
76                                 Gtkmm2ext::Keyboard::primary_modifier_name(),
77                                 Gtkmm2ext::Keyboard::primary_modifier_name (),
78                                 Gtkmm2ext::Keyboard::secondary_modifier_name ()));
79 }
80
81 MiniTimeline::~MiniTimeline ()
82 {
83         delete _minitl_context_menu;
84         _minitl_context_menu = 0;
85 }
86
87 void
88 MiniTimeline::session_going_away ()
89 {
90         super_rapid_connection.disconnect ();
91         session_connection.drop_connections ();
92         SessionHandlePtr::session_going_away ();
93         _jumplist.clear ();
94         delete _minitl_context_menu;
95         _minitl_context_menu = 0;
96 }
97
98 void
99 MiniTimeline::set_session (Session* s)
100 {
101         SessionHandlePtr::set_session (s);
102         if (!s) {
103                 return;
104         }
105
106         assert (!super_rapid_connection.connected ());
107         super_rapid_connection = Timers::super_rapid_connect (
108                         sigc::mem_fun (*this, &MiniTimeline::super_rapid_update)
109                         );
110
111         _session->config.ParameterChanged.connect (session_connection,
112                         invalidator (*this),
113                         boost::bind (&MiniTimeline::parameter_changed, this, _1), gui_context()
114                         );
115         _session->locations()->added.connect (session_connection,
116                         invalidator (*this),
117                         boost::bind (&MiniTimeline::update_minitimeline, this), gui_context()
118                         );
119         _session->locations()->removed.connect (session_connection,
120                         invalidator (*this),
121                         boost::bind (&MiniTimeline::update_minitimeline, this), gui_context()
122                         );
123         _session->locations()->changed.connect (session_connection,
124                         invalidator (*this),
125                         boost::bind (&MiniTimeline::update_minitimeline, this), gui_context()
126                         );
127
128         _jumplist.clear ();
129         calculate_time_spacing ();
130         update_minitimeline ();
131 }
132
133 void
134 MiniTimeline::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
135 {
136         CairoWidget::on_style_changed (old_style);
137         set_colors ();
138         calculate_time_width ();
139 }
140
141 void
142 MiniTimeline::on_name_changed ()
143 {
144         set_colors ();
145         calculate_time_width ();
146
147         if (is_realized()) {
148                 queue_resize ();
149         }
150 }
151
152 void
153 MiniTimeline::set_colors ()
154 {
155         // TODO  UIConfiguration::instance().color & font
156         _phead_color = UIConfiguration::instance().color ("play head");
157 }
158
159 void
160 MiniTimeline::parameter_changed (std::string const& p)
161 {
162         if (p == "minitimeline-span") {
163                 calculate_time_spacing ();
164                 update_minitimeline ();
165         }
166 }
167
168 void
169 MiniTimeline::on_size_request (Gtk::Requisition* req)
170 {
171         req->width = req->height = 0;
172         CairoWidget::on_size_request (req);
173
174         req->width = std::max (req->width, 1);
175         req->height = std::max (req->height, 20);
176 }
177
178 void
179 MiniTimeline::on_size_allocate (Gtk::Allocation& alloc)
180 {
181         CairoWidget::on_size_allocate (alloc);
182         calculate_time_spacing ();
183 }
184
185 void
186 MiniTimeline::set_span (samplecnt_t ts)
187 {
188         assert (_session);
189         if (_session->config.get_minitimeline_span () == ts) {
190                 return;
191         }
192
193         _session->config.set_minitimeline_span (ts);
194         calculate_time_spacing ();
195         update_minitimeline ();
196 }
197
198 void
199 MiniTimeline::super_rapid_update ()
200 {
201         if (!_session || !_session->engine().running() || !is_mapped ()) {
202                 return;
203         }
204         samplepos_t const sample = PublicEditor::instance().playhead_cursor_sample ();
205         AudioClock::Mode m = ARDOUR_UI::instance()->primary_clock->mode();
206
207         bool change = false;
208         if (fabs ((_last_update_sample - sample) * _px_per_sample) >= 1.0) {
209                 change = true;
210         }
211
212         if (m != _clock_mode) {
213                 _clock_mode = m;
214                 calculate_time_width ();
215                 change = true;
216         }
217
218         if (_clock_mode == AudioClock::BBT) {
219                 // TODO check if tempo-map changed
220                 change = true;
221         }
222
223         if (change) {
224                 _last_update_sample = sample;
225                 update_minitimeline ();
226         }
227 }
228
229 void
230 MiniTimeline::update_minitimeline ()
231 {
232         CairoWidget::set_dirty ();
233 }
234
235 void
236 MiniTimeline::calculate_time_width ()
237 {
238         switch (_clock_mode) {
239                 case AudioClock::Timecode:
240                         _layout->set_text (" 88:88:88,888 ");
241                         break;
242                 case AudioClock::BBT:
243                         _layout->set_text ("888|88|8888");
244                         break;
245                 case AudioClock::MinSec:
246                         _layout->set_text ("88:88:88,88");
247                         break;
248                 case AudioClock::Seconds:
249                 case AudioClock::Samples:
250                         _layout->set_text ("8888888888");
251                         break;
252         }
253         _layout->get_pixel_size (_time_width, _time_height);
254 }
255
256 void
257 MiniTimeline::calculate_time_spacing ()
258 {
259         _n_labels = floor (get_width () / (_time_width * 1.15));
260
261         if (_n_labels == 0 || !_session) {
262                 return;
263         }
264
265         const samplecnt_t time_span = _session->config.get_minitimeline_span () / 2;
266         _time_span_samples = time_span * _session->nominal_sample_rate ();
267         _time_granularity = _session->nominal_sample_rate () * ceil (2. * time_span / _n_labels);
268         _px_per_sample = get_width () / (2. * _time_span_samples);
269         //_px_per_sample = 1.0 / round (1.0 / _px_per_sample);
270 }
271
272 void
273 MiniTimeline::format_time (samplepos_t when)
274 {
275         switch (_clock_mode) {
276                 case AudioClock::Timecode:
277                         {
278                                 Timecode::Time TC;
279                                 _session->timecode_time (when, TC);
280                                 // chop of leading space or minus.
281                                 _layout->set_text (Timecode::timecode_format_time (TC).substr(1));
282                         }
283                         break;
284                 case AudioClock::BBT:
285                         {
286                                 char buf[64];
287                                 Timecode::BBT_Time BBT = _session->tempo_map().bbt_at_sample (when);
288                                 snprintf (buf, sizeof (buf), "%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
289                                                 BBT.bars, BBT.beats, BBT.ticks);
290                                 _layout->set_text (buf);
291                         }
292                         break;
293                 case AudioClock::MinSec:
294                         {
295                                 char buf[32];
296                                 AudioClock::print_minsec (when, buf, sizeof (buf), _session->sample_rate());
297                                 _layout->set_text (std::string(buf).substr(1));
298                         }
299                         break;
300                 case AudioClock::Seconds:
301                         {
302                                 char buf[32];
303                                 snprintf (buf, sizeof (buf), "%.1f", when / (float)_session->sample_rate());
304                                 _layout->set_text (buf);
305                         }
306                         break;
307                 case AudioClock::Samples:
308                         {
309                                 char buf[32];
310                                 snprintf (buf, sizeof (buf), "%" PRId64, when);
311                                 _layout->set_text (buf);
312                         }
313                         break;
314         }
315 }
316
317 void
318 MiniTimeline::draw_dots (cairo_t* cr, int left, int right, int y, Gtkmm2ext::Color color)
319 {
320         if (left + 1 >= right) {
321                 return;
322         }
323         cairo_move_to (cr, left + .5, y + .5);
324         cairo_line_to (cr, right - .5, y + .5);
325         Gtkmm2ext::set_source_rgb_a(cr, color, 0.3);
326         const double dashes[] = { 0, 1 };
327         cairo_set_dash (cr, dashes, 2, 1);
328         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
329         cairo_set_line_width (cr, 1.0);
330         cairo_stroke (cr);
331         cairo_set_dash (cr, 0, 0, 0);
332 }
333
334 int
335 MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, const std::string& label, bool& prelight)
336 {
337         int h = _marker_height;
338         /* ArdourMarker shape
339          * MH = 13
340          *
341          * Mark:
342          *
343          *  (0,0)   --  (6,0)
344          *    |           |
345          *    |           |
346          * (0,MH*.4)  (6,MH*.4)
347          *     \         /
348          *        (3,MH)
349          */
350
351         const int y = PADDING;
352         int w2 = (h - 1) / 4;
353         double h0 = h * .4;
354         double h1 = h - h0;
355
356         int lw, lh;
357         _layout->set_text (label);
358         _layout->get_pixel_size (lw, lh);
359         int rw = std::min (x1, x0 + w2 + lw + 2);
360
361         if (_pointer_y >= 0 && _pointer_y <= y + h && _pointer_x >= x0 - w2 && _pointer_x <= rw) {
362                 prelight = true;
363         }
364
365         // TODO cache in set_colors()
366         uint32_t color = UIConfiguration::instance().color (
367                         prelight ? "entered marker" : "location marker");
368
369         double r, g, b, a;
370         Gtkmm2ext::color_to_rgba (color, r, g, b, a);
371
372         if (rw < x0) {
373                 rw = x1;
374         } else {
375                 cairo_save (cr);
376                 cairo_rectangle (cr, x0, y, rw - x0, h);
377                 cairo_set_source_rgba (cr, r, g, b, 0.5); // this should use a shaded color
378                 cairo_fill_preserve (cr);
379                 cairo_clip (cr);
380
381                 // marker label
382                 cairo_move_to (cr, x0 + w2, y + .5 * (h - lh));
383                 cairo_set_source_rgb (cr, 0, 0, 0);
384                 pango_cairo_show_layout (cr, _layout->gobj());
385                 cairo_restore (cr);
386         }
387
388         // draw marker on top
389         cairo_move_to (cr, x0 - .5, y + .5);
390         cairo_rel_line_to (cr, -w2 , 0);
391         cairo_rel_line_to (cr, 0, h0);
392         cairo_rel_line_to (cr, w2, h1);
393         cairo_rel_line_to (cr, w2, -h1);
394         cairo_rel_line_to (cr, 0, -h0);
395         cairo_close_path (cr);
396         cairo_set_source_rgba (cr, r, g, b, 1.0);
397         cairo_set_line_width (cr, 1.0);
398         cairo_stroke_preserve (cr);
399         cairo_fill (cr);
400
401         return rw;
402 }
403
404 int
405 MiniTimeline::draw_edge (cairo_t* cr, int x0, int x1, bool left, const std::string& label, bool& prelight)
406 {
407         int h = _marker_height;
408         int w2 = (h - 1) / 4;
409
410         const int y = PADDING;
411         const double yc = rint (h * .5);
412         const double dy = h * .4;
413
414         bool with_label;
415         int lw, lh, lx;
416         _layout->set_text (label);
417         _layout->get_pixel_size (lw, lh);
418
419         double px, dx;
420         if (left) {
421                 if (x0 + 2 * w2 + lw + 2 < x1) {
422                         x1 = std::min (x1, x0 + 2 * w2 + lw + 2);
423                         with_label = true;
424                 } else {
425                         x1 = std::min (x1, x0 + 2 * w2);
426                         with_label = false;
427                 }
428                 px = x0;
429                 dx = 2 * w2;
430                 lx = x0 + dx;
431         } else {
432                 if (x1 - 2 * w2 - lw - 2 > x0) {
433                         x0 = std::max (x0, x1 - 2 * w2 - lw - 2);
434                         with_label = true;
435                 } else {
436                         x0 = std::max (x0, x1 - 2 * w2);
437                         with_label = false;
438                 }
439                 px = x1;
440                 dx = -2 * w2;
441                 lx = x1 + dx - lw - 2;
442         }
443
444         if (x1 - x0 < 2 * w2) {
445                 return left ? x0 : x1;
446         }
447
448         if (_pointer_y >= 0 && _pointer_y <= y + h && _pointer_x >= x0 && _pointer_x <= x1) {
449                 prelight = true;
450         }
451
452         // TODO cache in set_colors()
453         uint32_t color = UIConfiguration::instance().color (
454                         prelight ? "entered marker" : "location marker");
455
456         double r, g, b, a;
457         Gtkmm2ext::color_to_rgba (color, r, g, b, a);
458
459         if (with_label) {
460                 const int y = PADDING;
461                 cairo_save (cr);
462                 cairo_rectangle (cr, lx, y, lw + 2, h);
463                 cairo_set_source_rgba (cr, r, g, b, 0.5); // this should use a shaded color
464                 cairo_fill_preserve (cr);
465                 cairo_clip (cr);
466
467                 // marker label
468                 cairo_move_to (cr, lx + 1, y + .5 * (h - lh));
469                 cairo_set_source_rgb (cr, 0, 0, 0);
470                 pango_cairo_show_layout (cr, _layout->gobj());
471                 cairo_restore (cr);
472         }
473
474         // draw arrow
475         cairo_move_to (cr, px - .5, PADDING + yc - .5);
476         cairo_rel_line_to (cr, dx , dy);
477         cairo_rel_line_to (cr, 0, -2. * dy);
478         cairo_close_path (cr);
479         cairo_set_source_rgba (cr, r, g, b, 1.0);
480         cairo_set_line_width (cr, 1.0);
481         cairo_stroke_preserve (cr);
482         cairo_fill (cr);
483
484         return left ? x1 : x0;
485 }
486
487
488 struct LocationMarker {
489         LocationMarker (const std::string& l, samplepos_t w)
490                 : label (l), when (w) {}
491         std::string label;
492         samplepos_t  when;
493 };
494
495 struct LocationMarkerSort {
496         bool operator() (const LocationMarker& a, const LocationMarker& b) {
497                 return (a.when < b.when);
498         }
499 };
500
501 void
502 MiniTimeline::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t*)
503 {
504         cairo_t* cr = ctx->cobj();
505         // TODO cache, set_colors()
506         Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
507         Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
508
509         if (_n_labels == 0) {
510                 return;
511         }
512
513         const int width = get_width ();
514         const int height = get_height ();
515
516         Gtkmm2ext::rounded_rectangle (cr, 0, 0, width, height, 4);
517         Gtkmm2ext::set_source_rgba(cr, base);
518         cairo_fill (cr);
519
520         Gtkmm2ext::rounded_rectangle (cr, PADDING, PADDING, width - PADDING - PADDING, height - PADDING - PADDING, 4);
521         cairo_clip (cr);
522
523         if (_session == 0) {
524                 return;
525         }
526
527         /* time */
528         const samplepos_t p = _last_update_sample;
529         const samplepos_t lower = (std::max ((samplepos_t)0, (p - _time_span_samples)) / _time_granularity) * _time_granularity;
530
531         int dot_left = width * .5 + (lower - p) * _px_per_sample;
532         for (int i = 0; i < 2 + _n_labels; ++i) {
533                 samplepos_t when = lower + i * _time_granularity;
534                 double xpos = width * .5 + (when - p) * _px_per_sample;
535
536                 // TODO round to nearest display TC in +/- 1px
537                 // prefer to display BBT |0  or .0
538
539                 int lw, lh;
540                 format_time (when);
541                 _layout->get_pixel_size (lw, lh);
542
543                 int x0 = xpos - lw / 2.0;
544                 int y0 = height - PADDING - _time_height;
545
546                 draw_dots (cr, dot_left, x0, y0 + _time_height * .5, text);
547
548                 cairo_move_to (cr, x0, y0);
549                 Gtkmm2ext::set_source_rgba(cr, text);
550                 pango_cairo_show_layout (cr, _layout->gobj());
551                 dot_left = x0 + lw;
552         }
553         draw_dots (cr, dot_left, width, height - PADDING - _time_height * .5, text);
554
555         /* locations */
556         samplepos_t lmin = std::max ((samplepos_t)0, (p - _time_span_samples));
557         samplepos_t lmax = p + _time_span_samples;
558
559         int tw, th;
560         _layout->set_text (X_("Marker@"));
561         _layout->get_pixel_size (tw, th);
562
563         _marker_height = th + 2;
564         assert (_marker_height > 4);
565         const int mw = (_marker_height - 1) / 4;
566
567         lmin -= mw / _px_per_sample;
568         lmax += mw / _px_per_sample;
569
570         std::vector<LocationMarker> lm;
571
572         const Locations::LocationList& ll (_session->locations ()->list ());
573         for (Locations::LocationList::const_iterator l = ll.begin(); l != ll.end(); ++l) {
574                 if ((*l)->is_session_range ()) {
575                         lm.push_back (LocationMarker(_("start"), (*l)->start ()));
576                         lm.push_back (LocationMarker(_("end"), (*l)->end ()));
577                         continue;
578                 }
579
580                 if (!(*l)->is_mark () || (*l)->name().substr (0, 4) == "xrun") {
581                         continue;
582                 }
583
584                 lm.push_back (LocationMarker((*l)->name(), (*l)->start ()));
585         }
586
587         _jumplist.clear ();
588
589         LocationMarkerSort location_marker_sort;
590         std::sort (lm.begin(), lm.end(), location_marker_sort);
591
592         std::vector<LocationMarker>::const_iterator outside_left = lm.end();
593         std::vector<LocationMarker>::const_iterator outside_right = lm.end();
594         int left_limit = 0;
595         int right_limit = width * .5 + mw;
596         int id = 0;
597
598         for (std::vector<LocationMarker>::const_iterator l = lm.begin(); l != lm.end(); ++id) {
599                 samplepos_t when = (*l).when;
600                 if (when < lmin) {
601                         outside_left = l;
602                         if (++l != lm.end()) {
603                                 left_limit = floor (width * .5 + ((*l).when - p) * _px_per_sample) - 1 - mw;
604                         } else {
605                                 left_limit = width * .5 - mw;
606                         }
607                         continue;
608                 }
609                 if (when > lmax) {
610                         outside_right = l;
611                         break;
612                 }
613                 int x0 = floor (width * .5 + (when - p) * _px_per_sample);
614                 int x1 = width;
615                 const std::string& label = (*l).label;
616                 if (++l != lm.end()) {
617                         x1 = floor (width * .5 + ((*l).when - p) * _px_per_sample) - 1 - mw;
618                 }
619                 bool prelight = false;
620                 x1 = draw_mark (cr, x0, x1, label, prelight);
621                 _jumplist.push_back (JumpRange (x0 - mw, x1, when, prelight));
622                 right_limit = std::max (x1, right_limit);
623         }
624
625         if (outside_left != lm.end ()) {
626                 if (left_limit > 3 * mw + PADDING) {
627                         int x0 = PADDING + 1;
628                         int x1 = left_limit - mw;
629                         bool prelight = false;
630                         x1 = draw_edge (cr, x0, x1, true, (*outside_left).label, prelight);
631                         if (x0 != x1) {
632                                 _jumplist.push_back (JumpRange (x0, x1, (*outside_left).when, prelight));
633                                 right_limit = std::max (x1, right_limit);
634                         }
635                 }
636         }
637
638         if (outside_right != lm.end ()) {
639                 if (right_limit + PADDING < width - 3 * mw) {
640                         int x0 = right_limit;
641                         int x1 = width - PADDING;
642                         bool prelight = false;
643                         x0 = draw_edge (cr, x0, x1, false, (*outside_right).label, prelight);
644                         if (x0 != x1) {
645                                 _jumplist.push_back (JumpRange (x0, x1, (*outside_right).when, prelight));
646                         }
647                 }
648         }
649
650
651         /* playhead on top */
652         int xc = width * 0.5f;
653         cairo_set_line_width (cr, 1.0);
654         double r,g,b,a;  Gtkmm2ext::color_to_rgba(_phead_color, r,g,b,a);
655         cairo_set_source_rgb (cr, r,g,b); // playhead color
656         cairo_move_to (cr, xc - .5, 0);
657         cairo_rel_line_to (cr, 0, height);
658         cairo_stroke (cr);
659         cairo_move_to (cr, xc - .5, height);
660         cairo_rel_line_to (cr, -3,  0);
661         cairo_rel_line_to (cr,  3, -4);
662         cairo_rel_line_to (cr,  3,  4);
663         cairo_close_path (cr);
664         cairo_fill (cr);
665 }
666
667 void
668 MiniTimeline::build_minitl_context_menu ()
669 {
670         using namespace Gtk;
671         using namespace Gtk::Menu_Helpers;
672
673         assert (_session);
674
675         const samplecnt_t time_span = _session->config.get_minitimeline_span ();
676
677         _minitl_context_menu = new Gtk::Menu();
678         MenuList& items = _minitl_context_menu->items();
679
680         // ideally this would have a heading (or rather be a sub-menu to "Visible Time")
681         std::map<samplecnt_t, std::string> spans;
682         spans[30]   = _("30 sec");
683         spans[60]   = _("1 min");
684         spans[120]  = _("2 mins");
685         spans[300]  = _("5 mins");
686         spans[600]  = _("10 mins");
687         spans[1200] = _("20 mins");
688
689         RadioMenuItem::Group span_group;
690         for (std::map<samplecnt_t, std::string>::const_iterator i = spans.begin (); i != spans.end (); ++i) {
691                 items.push_back (RadioMenuElem (span_group, i->second, sigc::bind (sigc::mem_fun (*this, &MiniTimeline::set_span), i->first)));
692                 if (time_span == i->first) {
693                         static_cast<RadioMenuItem*>(&items.back())->set_active ();
694                 }
695         }
696 }
697
698 bool
699 MiniTimeline::on_button_press_event (GdkEventButton *ev)
700 {
701         if (Gtkmm2ext::Keyboard::is_context_menu_event (ev)) {
702                 if (_session) {
703                         if (_minitl_context_menu == 0) {
704                                 build_minitl_context_menu ();
705                         }
706                         _minitl_context_menu->popup (ev->button, ev->time);
707                 }
708                 return true;
709         }
710         return true;
711 }
712
713 bool
714 MiniTimeline::on_button_release_event (GdkEventButton *ev)
715 {
716         if (!_session) { return true; }
717         if (_session->actively_recording ()) { return true; }
718         if (ev->y < 0 || ev->y > get_height () || ev->x < 0 || ev->x > get_width ()) {
719                 return true;
720         }
721
722         if (ev->y <= PADDING + _marker_height) {
723                 for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) {
724                         if (i->left <= ev->x && ev->x <= i->right) {
725                                 _session->request_locate (i->to, _session->transport_rolling ());
726                                 return true;
727                         }
728                 }
729         }
730
731         if (ev->button == 1) {
732                 samplepos_t when = _last_update_sample + (ev->x - get_width() * .5) / _px_per_sample;
733                 _session->request_locate (std::max ((samplepos_t)0, when), _session->transport_rolling ());
734         }
735
736         return true;
737 }
738
739 bool
740 MiniTimeline::on_motion_notify_event (GdkEventMotion *ev)
741 {
742         if (!_session) { return true; }
743         if (_session->actively_recording ()) { return true; }
744
745         _pointer_x = ev->x;
746         _pointer_y = ev->y;
747
748         bool need_expose = false;
749
750         for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) {
751                 if (i->left < ev->x && ev->x < i->right && ev->y <= PADDING + _marker_height) {
752                         if (!(*i).prelight) {
753                                 need_expose = true;
754                                 break;
755                         }
756                 } else {
757                         if ((*i).prelight) {
758                                 need_expose = true;
759                                 break;
760                         }
761                 }
762         }
763         if (need_expose) {
764                 update_minitimeline ();
765         }
766
767         return true;
768 }
769
770 bool
771 MiniTimeline::on_leave_notify_event (GdkEventCrossing *ev)
772 {
773         CairoWidget::on_leave_notify_event (ev);
774         _pointer_x = _pointer_y = -1;
775         for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) {
776                 if ((*i).prelight) {
777                         update_minitimeline ();
778                         break;
779                 }
780         }
781         return true;
782 }
783
784 bool
785 MiniTimeline::on_scroll_event (GdkEventScroll *ev)
786 {
787         if (!_session) { return true; }
788         if (_session->actively_recording ()) { return true; }
789         const samplecnt_t time_span = _session->config.get_minitimeline_span ();
790         samplepos_t when = _session->audible_sample ();
791
792         double scale = time_span / 60.0;
793
794         if (ev->state & Gtkmm2ext::Keyboard::GainFineScaleModifier) {
795                 if (ev->state & Gtkmm2ext::Keyboard::GainExtraFineScaleModifier) {
796                         scale = 0.1;
797                 } else {
798                         scale = 0.5;
799                 }
800         }
801
802         switch (ev->direction) {
803                 case GDK_SCROLL_UP:
804                 case GDK_SCROLL_RIGHT:
805                         when += scale * _session->nominal_sample_rate ();
806                         break;
807                 case GDK_SCROLL_DOWN:
808                 case GDK_SCROLL_LEFT:
809                         when -= scale * _session->nominal_sample_rate ();
810                         break;
811                 default:
812                         return true;
813                         break;
814         }
815         _session->request_locate (std::max ((samplepos_t)0, when), _session->transport_rolling ());
816         return true;
817 }