Initial stab at tempo ramps.
[ardour.git] / gtk2_ardour / editor_rulers.cc
1 /*
2     Copyright (C) 2000 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <cstdio> // for sprintf, grrr
25 #include <cmath>
26 #include <inttypes.h>
27
28 #include <string>
29
30 #include <gtk/gtkaction.h>
31
32 #include "canvas/container.h"
33 #include "canvas/canvas.h"
34 #include "canvas/ruler.h"
35 #include "canvas/debug.h"
36 #include "canvas/scroll_group.h"
37
38 #include "ardour/session.h"
39 #include "ardour/tempo.h"
40 #include "ardour/profile.h"
41
42 #include "gtkmm2ext/gtk_ui.h"
43 #include "gtkmm2ext/keyboard.h"
44
45 #include "ardour_ui.h"
46 #include "editor.h"
47 #include "editing.h"
48 #include "actions.h"
49 #include "gui_thread.h"
50 #include "ruler_dialog.h"
51 #include "time_axis_view.h"
52 #include "editor_drag.h"
53 #include "editor_cursors.h"
54 #include "ui_config.h"
55
56 #include "i18n.h"
57
58 using namespace ARDOUR;
59 using namespace PBD;
60 using namespace Gtk;
61 using namespace Editing;
62
63 /* the order here must match the "metric" enums in editor.h */
64
65 class TimecodeMetric : public ArdourCanvas::Ruler::Metric
66 {
67     public:
68         TimecodeMetric (Editor* e) : _editor (e) {}
69
70         void get_marks (std::vector<ArdourCanvas::Ruler::Mark>& marks, double lower, double upper, int maxchars) const {
71                 _editor->metric_get_timecode (marks, lower, upper, maxchars);
72         }
73
74     private:
75         Editor* _editor;
76 };
77
78 class SamplesMetric : public ArdourCanvas::Ruler::Metric
79 {
80     public:
81         SamplesMetric (Editor* e) : _editor (e) {}
82
83         void get_marks (std::vector<ArdourCanvas::Ruler::Mark>& marks, double lower, double upper, int maxchars) const {
84                 _editor->metric_get_samples (marks, lower, upper, maxchars);
85         }
86
87     private:
88         Editor* _editor;
89 };
90
91 class BBTMetric : public ArdourCanvas::Ruler::Metric
92 {
93     public:
94         BBTMetric (Editor* e) : _editor (e) {}
95
96         void get_marks (std::vector<ArdourCanvas::Ruler::Mark>& marks, double lower, double upper, int maxchars) const {
97                 _editor->metric_get_bbt (marks, lower, upper, maxchars);
98         }
99
100     private:
101         Editor* _editor;
102 };
103
104 class MinsecMetric : public ArdourCanvas::Ruler::Metric
105 {
106     public:
107         MinsecMetric (Editor* e) : _editor (e) {}
108
109         void get_marks (std::vector<ArdourCanvas::Ruler::Mark>& marks, double lower, double upper, int maxchars) const {
110                 _editor->metric_get_minsec (marks, lower, upper, maxchars);
111         }
112
113     private:
114         Editor* _editor;
115 };
116
117 static ArdourCanvas::Ruler::Metric* _bbt_metric;
118 static ArdourCanvas::Ruler::Metric* _timecode_metric;
119 static ArdourCanvas::Ruler::Metric* _samples_metric;
120 static ArdourCanvas::Ruler::Metric* _minsec_metric;
121
122 void
123 Editor::initialize_rulers ()
124 {
125         ruler_grabbed_widget = 0;
126
127         Pango::FontDescription font (UIConfiguration::instance().get_SmallerFont());
128
129         _timecode_metric = new TimecodeMetric (this);
130         _bbt_metric = new BBTMetric (this);
131         _minsec_metric = new MinsecMetric (this);
132         _samples_metric = new SamplesMetric (this);
133
134         timecode_ruler = new ArdourCanvas::Ruler (_time_markers_group, *_timecode_metric,
135                                                   ArdourCanvas::Rect (0, 0, ArdourCanvas::COORD_MAX, timebar_height));
136         timecode_ruler->set_font_description (font);
137         CANVAS_DEBUG_NAME (timecode_ruler, "timecode ruler");
138         timecode_nmarks = 0;
139
140         samples_ruler = new ArdourCanvas::Ruler (_time_markers_group, *_samples_metric,
141                                                  ArdourCanvas::Rect (0, 0, ArdourCanvas::COORD_MAX, timebar_height));
142         samples_ruler->set_font_description (font);
143         CANVAS_DEBUG_NAME (samples_ruler, "samples ruler");
144
145         minsec_ruler = new ArdourCanvas::Ruler (_time_markers_group, *_minsec_metric,
146                                                 ArdourCanvas::Rect (0, 0, ArdourCanvas::COORD_MAX, timebar_height));
147         minsec_ruler->set_font_description (font);
148         CANVAS_DEBUG_NAME (minsec_ruler, "minsec ruler");
149         minsec_nmarks = 0;
150
151         bbt_ruler = new ArdourCanvas::Ruler (_time_markers_group, *_bbt_metric,
152                                              ArdourCanvas::Rect (0, 0, ArdourCanvas::COORD_MAX, timebar_height));
153         bbt_ruler->set_font_description (font);
154         CANVAS_DEBUG_NAME (bbt_ruler, "bbt ruler");
155         timecode_nmarks = 0;
156
157         using namespace Box_Helpers;
158         BoxList & lab_children =  time_bars_vbox.children();
159
160         lab_children.push_back (Element(minsec_label, PACK_SHRINK, PACK_START));
161         lab_children.push_back (Element(timecode_label, PACK_SHRINK, PACK_START));
162         lab_children.push_back (Element(samples_label, PACK_SHRINK, PACK_START));
163         lab_children.push_back (Element(bbt_label, PACK_SHRINK, PACK_START));
164         lab_children.push_back (Element(meter_label, PACK_SHRINK, PACK_START));
165         lab_children.push_back (Element(tempo_label, PACK_SHRINK, PACK_START));
166         lab_children.push_back (Element(range_mark_label, PACK_SHRINK, PACK_START));
167         lab_children.push_back (Element(transport_mark_label, PACK_SHRINK, PACK_START));
168         lab_children.push_back (Element(cd_mark_label, PACK_SHRINK, PACK_START));
169         lab_children.push_back (Element(mark_label, PACK_SHRINK, PACK_START));
170         lab_children.push_back (Element(videotl_label, PACK_SHRINK, PACK_START));
171
172         /* 1 event handler to bind them all ... */
173
174         timecode_ruler->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_ruler_event), timecode_ruler, TimecodeRulerItem));
175         minsec_ruler->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_ruler_event), minsec_ruler, MinsecRulerItem));
176         bbt_ruler->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_ruler_event), bbt_ruler, BBTRulerItem));
177         samples_ruler->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_ruler_event), samples_ruler, SamplesRulerItem));
178
179         visible_timebars = 0; /*this will be changed below */
180 }
181
182 bool
183 Editor::ruler_label_button_release (GdkEventButton* ev)
184 {
185         if (Gtkmm2ext::Keyboard::is_context_menu_event (ev)) {
186                 if (!ruler_dialog) {
187                         ruler_dialog = new RulerDialog ();
188                 }
189                 ruler_dialog->present ();
190         }
191
192         return true;
193 }
194
195 void
196 Editor::popup_ruler_menu (framepos_t where, ItemType t)
197 {
198         using namespace Menu_Helpers;
199
200         if (editor_ruler_menu == 0) {
201                 editor_ruler_menu = new Menu;
202                 editor_ruler_menu->set_name ("ArdourContextMenu");
203         }
204
205         // always build from scratch
206         MenuList& ruler_items = editor_ruler_menu->items();
207         editor_ruler_menu->set_name ("ArdourContextMenu");
208         ruler_items.clear();
209
210         switch (t) {
211         case MarkerBarItem:
212                 ruler_items.push_back (MenuElem (_("New location marker"), sigc::bind ( sigc::mem_fun(*this, &Editor::mouse_add_new_marker), where, false)));
213                 ruler_items.push_back (MenuElem (_("Clear all locations"), sigc::mem_fun(*this, &Editor::clear_markers)));
214                 ruler_items.push_back (MenuElem (_("Unhide locations"), sigc::mem_fun(*this, &Editor::unhide_markers)));
215                 break;
216
217         case RangeMarkerBarItem:
218                 ruler_items.push_back (MenuElem (_("New range"), sigc::bind (sigc::mem_fun (*this, &Editor::mouse_add_new_range), where)));
219                 ruler_items.push_back (MenuElem (_("Clear all ranges"), sigc::mem_fun(*this, &Editor::clear_ranges)));
220                 ruler_items.push_back (MenuElem (_("Unhide ranges"), sigc::mem_fun(*this, &Editor::unhide_ranges)));
221                 break;
222
223         case TransportMarkerBarItem:
224                 ruler_items.push_back (MenuElem (_("New Loop range"), sigc::bind (sigc::mem_fun (*this, &Editor::mouse_add_new_loop), where)));
225                 ruler_items.push_back (MenuElem (_("New Punch range"), sigc::bind (sigc::mem_fun (*this, &Editor::mouse_add_new_punch), where)));
226                 break;
227
228         case CdMarkerBarItem:
229                 // TODO
230                 ruler_items.push_back (MenuElem (_("New CD track marker"), sigc::bind ( sigc::mem_fun(*this, &Editor::mouse_add_new_marker), where, true)));
231                 break;
232
233         case TempoBarItem:
234                 ruler_items.push_back (MenuElem (_("New Tempo"), sigc::bind ( sigc::mem_fun(*this, &Editor::mouse_add_new_tempo_event), where)));
235                 break;
236
237         case MeterBarItem:
238                 ruler_items.push_back (MenuElem (_("New Meter"), sigc::bind ( sigc::mem_fun(*this, &Editor::mouse_add_new_meter_event), where)));
239                 break;
240
241         case VideoBarItem:
242                 /* proper headings would be nice
243                  * but AFAICT the only way to get them will be to define a
244                  * special GTK style for insensitive Elements or subclass MenuItem
245                  */
246                 //ruler_items.push_back (MenuElem (_("Timeline height"))); // heading
247                 //static_cast<MenuItem*>(&ruler_items.back())->set_sensitive(false);
248                 ruler_items.push_back (CheckMenuElem (_("Large"),  sigc::bind ( sigc::mem_fun(*this, &Editor::set_video_timeline_height), 6)));
249                 if (videotl_bar_height == 6) { static_cast<Gtk::CheckMenuItem*>(&ruler_items.back())->set_active(true);}
250                 ruler_items.push_back (CheckMenuElem (_("Normal"), sigc::bind ( sigc::mem_fun(*this, &Editor::set_video_timeline_height), 4)));
251                 if (videotl_bar_height == 4) { static_cast<Gtk::CheckMenuItem*>(&ruler_items.back())->set_active(true);}
252                 ruler_items.push_back (CheckMenuElem (_("Small"),  sigc::bind ( sigc::mem_fun(*this, &Editor::set_video_timeline_height), 3)));
253                 if (videotl_bar_height == 3) { static_cast<Gtk::CheckMenuItem*>(&ruler_items.back())->set_active(true);}
254
255                 ruler_items.push_back (SeparatorElem ());
256
257                 //ruler_items.push_back (MenuElem (_("Align Video Track"))); // heading
258                 //static_cast<MenuItem*>(&ruler_items.back())->set_sensitive(false);
259                 ruler_items.push_back (CheckMenuElem (_("Lock")));
260                 {
261                         Gtk::CheckMenuItem* vtl_lock = static_cast<Gtk::CheckMenuItem*>(&ruler_items.back());
262                         vtl_lock->set_active(is_video_timeline_locked());
263                         vtl_lock->signal_activate().connect (sigc::mem_fun(*this, &Editor::toggle_video_timeline_locked));
264                 }
265
266                 ruler_items.push_back (SeparatorElem ());
267
268                 //ruler_items.push_back (MenuElem (_("Video Monitor"))); // heading
269                 //static_cast<MenuItem*>(&ruler_items.back())->set_sensitive(false);
270                 ruler_items.push_back (CheckMenuElem (_("Video Monitor")));
271                 {
272                         Gtk::CheckMenuItem* xjadeo_toggle = static_cast<Gtk::CheckMenuItem*>(&ruler_items.back());
273                         if (!ARDOUR_UI::instance()->video_timeline->found_xjadeo()) {
274                                 xjadeo_toggle->set_sensitive(false);
275                         }
276                         xjadeo_toggle->set_active(xjadeo_proc_action->get_active());
277                         xjadeo_toggle->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &Editor::toggle_xjadeo_proc), -1));
278                 }
279                 break;
280
281         default:
282                 break;
283         }
284
285         if (!ruler_items.empty()) {
286                 editor_ruler_menu->popup (1, gtk_get_current_event_time());
287         }
288
289         no_ruler_shown_update = false;
290 }
291
292 void
293 Editor::store_ruler_visibility ()
294 {
295         XMLNode* node = new XMLNode(X_("RulerVisibility"));
296
297         node->add_property (X_("timecode"), ruler_timecode_action->get_active() ? "yes": "no");
298         node->add_property (X_("bbt"), ruler_bbt_action->get_active() ? "yes": "no");
299         node->add_property (X_("samples"), ruler_samples_action->get_active() ? "yes": "no");
300         node->add_property (X_("minsec"), ruler_minsec_action->get_active() ? "yes": "no");
301         node->add_property (X_("tempo"), ruler_tempo_action->get_active() ? "yes": "no");
302         node->add_property (X_("meter"), ruler_meter_action->get_active() ? "yes": "no");
303         node->add_property (X_("marker"), ruler_marker_action->get_active() ? "yes": "no");
304         node->add_property (X_("rangemarker"), ruler_range_action->get_active() ? "yes": "no");
305         node->add_property (X_("transportmarker"), ruler_loop_punch_action->get_active() ? "yes": "no");
306         node->add_property (X_("cdmarker"), ruler_cd_marker_action->get_active() ? "yes": "no");
307         node->add_property (X_("videotl"), ruler_video_action->get_active() ? "yes": "no");
308
309         _session->add_extra_xml (*node);
310         _session->set_dirty ();
311 }
312
313 void
314 Editor::restore_ruler_visibility ()
315 {
316         XMLProperty const * prop;
317         XMLNode * node = _session->extra_xml (X_("RulerVisibility"));
318
319         no_ruler_shown_update = true;
320
321         if (node) {
322                 if ((prop = node->property ("timecode")) != 0) {
323                         if (string_is_affirmative (prop->value())) {
324                                 ruler_timecode_action->set_active (true);
325                         } else {
326                                 ruler_timecode_action->set_active (false);
327                         }
328                 }
329                 if ((prop = node->property ("bbt")) != 0) {
330                         if (string_is_affirmative (prop->value())) {
331                                 ruler_bbt_action->set_active (true);
332                         } else {
333                                 ruler_bbt_action->set_active (false);
334                         }
335                 }
336                 if ((prop = node->property ("samples")) != 0) {
337                         if (string_is_affirmative (prop->value())) {
338                                 ruler_samples_action->set_active (true);
339                         } else {
340                                 ruler_samples_action->set_active (false);
341                         }
342                 }
343                 if ((prop = node->property ("minsec")) != 0) {
344                         if (string_is_affirmative (prop->value())) {
345                                 ruler_minsec_action->set_active (true);
346                         } else {
347                                 ruler_minsec_action->set_active (false);
348                         }
349                 }
350                 if ((prop = node->property ("tempo")) != 0) {
351                         if (string_is_affirmative (prop->value())) {
352                                 ruler_tempo_action->set_active (true);
353                         } else {
354                                 ruler_tempo_action->set_active (false);
355                         }
356                 }
357                 if ((prop = node->property ("meter")) != 0) {
358                         if (string_is_affirmative (prop->value())) {
359                                 ruler_meter_action->set_active (true);
360                         } else {
361                                 ruler_meter_action->set_active (false);
362                         }
363                 }
364                 if ((prop = node->property ("marker")) != 0) {
365                         if (string_is_affirmative (prop->value())) {
366                                 ruler_marker_action->set_active (true);
367                         } else {
368                                 ruler_marker_action->set_active (false);
369                         }
370                 }
371                 if ((prop = node->property ("rangemarker")) != 0) {
372                         if (string_is_affirmative (prop->value())) {
373                                 ruler_range_action->set_active (true);
374                         } else {
375                                 ruler_range_action->set_active (false);
376                         }
377                 }
378
379                 if ((prop = node->property ("transportmarker")) != 0) {
380                         if (string_is_affirmative (prop->value())) {
381                                 ruler_loop_punch_action->set_active (true);
382                         } else {
383                                 ruler_loop_punch_action->set_active (false);
384                         }
385                 }
386
387                 if ((prop = node->property ("cdmarker")) != 0) {
388                         if (string_is_affirmative (prop->value())) {
389                                 ruler_cd_marker_action->set_active (true);
390                         } else {
391                                 ruler_cd_marker_action->set_active (false);
392                         }
393
394                 } else {
395                         // this _session doesn't yet know about the cdmarker ruler
396                         // as a benefit to the user who doesn't know the feature exists, show the ruler if
397                         // any cd marks exist
398                         ruler_cd_marker_action->set_active (false);
399                         const Locations::LocationList & locs = _session->locations()->list();
400                         for (Locations::LocationList::const_iterator i = locs.begin(); i != locs.end(); ++i) {
401                                 if ((*i)->is_cd_marker()) {
402                                         ruler_cd_marker_action->set_active (true);
403                                         break;
404                                 }
405                         }
406                 }
407
408                 if ((prop = node->property ("videotl")) != 0) {
409                         if (string_is_affirmative (prop->value())) {
410                                 ruler_video_action->set_active (true);
411                         } else {
412                                 ruler_video_action->set_active (false);
413                         }
414                 }
415
416         }
417
418         no_ruler_shown_update = false;
419         update_ruler_visibility ();
420 }
421
422 void
423 Editor::update_ruler_visibility ()
424 {
425         int visible_timebars = 0;
426
427         if (no_ruler_shown_update) {
428                 return;
429         }
430
431         /* the order of the timebars is fixed, so we have to go through each one
432          * and adjust its position depending on what is shown.
433          *
434          * Order: minsec, timecode, samples, bbt, meter, tempo, ranges,
435          * loop/punch, cd markers, location markers
436          */
437
438         double tbpos = 0.0;
439         double tbgpos = 0.0;
440         double old_unit_pos;
441
442 #ifdef __APPLE__
443         /* gtk update probs require this (damn) */
444         meter_label.hide();
445         tempo_label.hide();
446         range_mark_label.hide();
447         transport_mark_label.hide();
448         cd_mark_label.hide();
449         mark_label.hide();
450         videotl_label.hide();
451 #endif
452
453         if (ruler_minsec_action->get_active()) {
454                 old_unit_pos = minsec_ruler->position().y;
455                 if (tbpos != old_unit_pos) {
456                         minsec_ruler->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
457                 }
458                 minsec_ruler->show();
459                 minsec_label.show();
460                 tbpos += timebar_height;
461                 tbgpos += timebar_height;
462                 visible_timebars++;
463         } else {
464                 minsec_ruler->hide();
465                 minsec_label.hide();
466         }
467
468         if (ruler_timecode_action->get_active()) {
469                 old_unit_pos = timecode_ruler->position().y;
470                 if (tbpos != old_unit_pos) {
471                         timecode_ruler->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
472                 }
473                 timecode_ruler->show();
474                 timecode_label.show();
475                 tbpos += timebar_height;
476                 tbgpos += timebar_height;
477                 visible_timebars++;
478         } else {
479                 timecode_ruler->hide();
480                 timecode_label.hide();
481         }
482
483         if (ruler_samples_action->get_active()) {
484                 old_unit_pos = samples_ruler->position().y;
485                 if (tbpos != old_unit_pos) {
486                         samples_ruler->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
487                 }
488                 samples_ruler->show();
489                 samples_label.show();
490                 tbpos += timebar_height;
491                 tbgpos += timebar_height;
492                 visible_timebars++;
493         } else {
494                 samples_ruler->hide();
495                 samples_label.hide();
496         }
497
498         if (ruler_bbt_action->get_active()) {
499                 old_unit_pos = bbt_ruler->position().y;
500                 if (tbpos != old_unit_pos) {
501                         bbt_ruler->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
502                 }
503                 bbt_ruler->show();
504                 bbt_label.show();
505                 tbpos += timebar_height;
506                 tbgpos += timebar_height;
507                 visible_timebars++;
508         } else {
509                 bbt_ruler->hide();
510                 bbt_label.hide();
511         }
512
513         if (ruler_meter_action->get_active()) {
514                 old_unit_pos = meter_group->position().y;
515                 if (tbpos != old_unit_pos) {
516                         meter_group->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
517                 }
518                 meter_group->show();
519                 meter_label.show();
520                 tbpos += timebar_height;
521                 tbgpos += timebar_height;
522                 visible_timebars++;
523         } else {
524                 meter_group->hide();
525                 meter_label.hide();
526         }
527
528         if (ruler_tempo_action->get_active()) {
529                 old_unit_pos = tempo_group->position().y;
530                 if (tbpos != old_unit_pos) {
531                         tempo_group->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
532                 }
533                 tempo_group->show();
534                 tempo_label.show();
535                 tbpos += timebar_height;
536                 tbgpos += timebar_height;
537                 visible_timebars++;
538         } else {
539                 tempo_group->hide();
540                 tempo_label.hide();
541         }
542
543         if (ruler_range_action->get_active()) {
544                 old_unit_pos = range_marker_group->position().y;
545                 if (tbpos != old_unit_pos) {
546                         range_marker_group->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
547                 }
548                 range_marker_group->show();
549                 range_mark_label.show();
550
551                 tbpos += timebar_height;
552                 tbgpos += timebar_height;
553                 visible_timebars++;
554         } else {
555                 range_marker_group->hide();
556                 range_mark_label.hide();
557         }
558
559         if (ruler_loop_punch_action->get_active()) {
560                 old_unit_pos = transport_marker_group->position().y;
561                 if (tbpos != old_unit_pos) {
562                         transport_marker_group->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
563                 }
564                 transport_marker_group->show();
565                 transport_mark_label.show();
566                 tbpos += timebar_height;
567                 tbgpos += timebar_height;
568                 visible_timebars++;
569         } else {
570                 transport_marker_group->hide();
571                 transport_mark_label.hide();
572         }
573
574         if (ruler_cd_marker_action->get_active()) {
575                 old_unit_pos = cd_marker_group->position().y;
576                 if (tbpos != old_unit_pos) {
577                         cd_marker_group->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
578                 }
579                 cd_marker_group->show();
580                 cd_mark_label.show();
581                 tbpos += timebar_height;
582                 tbgpos += timebar_height;
583                 visible_timebars++;
584                 // make sure all cd markers show up in their respective places
585                 update_cd_marker_display();
586         } else {
587                 cd_marker_group->hide();
588                 cd_mark_label.hide();
589                 // make sure all cd markers show up in their respective places
590                 update_cd_marker_display();
591         }
592
593         if (ruler_marker_action->get_active()) {
594                 old_unit_pos = marker_group->position().y;
595                 if (tbpos != old_unit_pos) {
596                         marker_group->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
597                 }
598                 marker_group->show();
599                 mark_label.show();
600                 tbpos += timebar_height;
601                 tbgpos += timebar_height;
602                 visible_timebars++;
603         } else {
604                 marker_group->hide();
605                 mark_label.hide();
606         }
607
608         if (ruler_video_action->get_active()) {
609                 old_unit_pos = videotl_group->position().y;
610                 if (tbpos != old_unit_pos) {
611                         videotl_group->move (ArdourCanvas::Duple (0.0, tbpos - old_unit_pos));
612                 }
613                 videotl_group->show();
614                 videotl_label.show();
615                 tbpos += timebar_height * videotl_bar_height;
616                 tbgpos += timebar_height * videotl_bar_height;
617                 visible_timebars+=videotl_bar_height;
618                 queue_visual_videotimeline_update();
619         } else {
620                 videotl_group->hide();
621                 videotl_label.hide();
622                 update_video_timeline(true);
623         }
624
625         time_bars_vbox.set_size_request (-1, (int)(timebar_height * visible_timebars));
626
627         /* move hv_scroll_group (trackviews) to the end of the timebars
628          */
629
630         hv_scroll_group->set_y_position (timebar_height * visible_timebars);
631
632         compute_fixed_ruler_scale ();
633         update_fixed_rulers();
634         redisplay_tempo (false);
635
636         /* Changing ruler visibility means that any lines on markers might need updating */
637         for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
638                 i->second->setup_lines ();
639         }
640 }
641
642 void
643 Editor::update_just_timecode ()
644 {
645         ENSURE_GUI_THREAD (*this, &Editor::update_just_timecode)
646
647         if (_session == 0) {
648                 return;
649         }
650
651         framepos_t rightmost_frame = leftmost_frame + current_page_samples();
652
653         if (ruler_timecode_action->get_active()) {
654                 timecode_ruler->set_range (leftmost_frame, rightmost_frame);
655         }
656 }
657
658 void
659 Editor::compute_fixed_ruler_scale ()
660 {
661         if (_session == 0) {
662                 return;
663         }
664
665         if (ruler_timecode_action->get_active()) {
666                 set_timecode_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
667         }
668
669         if (ruler_minsec_action->get_active()) {
670                 set_minsec_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
671         }
672
673         if (ruler_samples_action->get_active()) {
674                 set_samples_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
675         }
676 }
677
678 void
679 Editor::update_fixed_rulers ()
680 {
681         framepos_t rightmost_frame;
682
683         if (_session == 0) {
684                 return;
685         }
686
687         compute_fixed_ruler_scale ();
688
689         _timecode_metric->units_per_pixel = samples_per_pixel;
690         _samples_metric->units_per_pixel = samples_per_pixel;
691         _minsec_metric->units_per_pixel = samples_per_pixel;
692
693         rightmost_frame = leftmost_frame + current_page_samples();
694
695         /* these force a redraw, which in turn will force execution of the metric callbacks
696            to compute the relevant ticks to display.
697         */
698
699         if (ruler_timecode_action->get_active()) {
700                 timecode_ruler->set_range (leftmost_frame, rightmost_frame);
701         }
702
703         if (ruler_samples_action->get_active()) {
704                 samples_ruler->set_range (leftmost_frame, rightmost_frame);
705         }
706
707         if (ruler_minsec_action->get_active()) {
708                 minsec_ruler->set_range (leftmost_frame, rightmost_frame);
709         }
710 }
711
712 void
713 Editor::update_tempo_based_rulers (std::vector<TempoMap::BBTPoint>& grid)
714 {
715         if (_session == 0) {
716                 return;
717         }
718
719         compute_bbt_ruler_scale (grid, leftmost_frame, leftmost_frame+current_page_samples());
720
721         _bbt_metric->units_per_pixel = samples_per_pixel;
722
723         if (ruler_bbt_action->get_active()) {
724                 bbt_ruler->set_range (leftmost_frame, leftmost_frame+current_page_samples());
725         }
726 }
727
728
729 void
730 Editor::set_timecode_ruler_scale (framepos_t lower, framepos_t upper)
731 {
732         using namespace std;
733
734         framepos_t spacer;
735         framepos_t fr;
736
737         if (_session == 0) {
738                 return;
739         }
740
741         fr = _session->frame_rate();
742
743         if (lower > (spacer = (framepos_t) (128 * Editor::get_current_zoom ()))) {
744                 lower = lower - spacer;
745         } else {
746                 lower = 0;
747         }
748
749         upper = upper + spacer;
750         framecnt_t const range = upper - lower;
751
752         if (range < (2 * _session->frames_per_timecode_frame())) { /* 0 - 2 frames */
753                 timecode_ruler_scale = timecode_show_bits;
754                 timecode_mark_modulo = 20;
755                 timecode_nmarks = 2 + (2 * _session->config.get_subframes_per_frame());
756         } else if (range <= (fr / 4)) { /* 2 frames - 0.250 second */
757                 timecode_ruler_scale = timecode_show_frames;
758                 timecode_mark_modulo = 1;
759                 timecode_nmarks = 2 + (range / (framepos_t)_session->frames_per_timecode_frame());
760         } else if (range <= (fr / 2)) { /* 0.25-0.5 second */
761                 timecode_ruler_scale = timecode_show_frames;
762                 timecode_mark_modulo = 2;
763                 timecode_nmarks = 2 + (range / (framepos_t)_session->frames_per_timecode_frame());
764         } else if (range <= fr) { /* 0.5-1 second */
765                 timecode_ruler_scale = timecode_show_frames;
766                 timecode_mark_modulo = 5;
767                 timecode_nmarks = 2 + (range / (framepos_t)_session->frames_per_timecode_frame());
768         } else if (range <= 2 * fr) { /* 1-2 seconds */
769                 timecode_ruler_scale = timecode_show_frames;
770                 timecode_mark_modulo = 10;
771                 timecode_nmarks = 2 + (range / (framepos_t)_session->frames_per_timecode_frame());
772         } else if (range <= 8 * fr) { /* 2-8 seconds */
773                 timecode_ruler_scale = timecode_show_seconds;
774                 timecode_mark_modulo = 1;
775                 timecode_nmarks = 2 + (range / fr);
776         } else if (range <= 16 * fr) { /* 8-16 seconds */
777                 timecode_ruler_scale = timecode_show_seconds;
778                 timecode_mark_modulo = 2;
779                 timecode_nmarks = 2 + (range / fr);
780         } else if (range <= 30 * fr) { /* 16-30 seconds */
781                 timecode_ruler_scale = timecode_show_seconds;
782                 timecode_mark_modulo = 5;
783                 timecode_nmarks = 2 + (range / fr);
784         } else if (range <= 60 * fr) { /* 30-60 seconds */
785                 timecode_ruler_scale = timecode_show_seconds;
786                 timecode_mark_modulo = 5;
787                 timecode_nmarks = 2 + (range / fr);
788         } else if (range <= 2 * 60 * fr) { /* 1-2 minutes */
789                 timecode_ruler_scale = timecode_show_seconds;
790                 timecode_mark_modulo = 15;
791                 timecode_nmarks = 2 + (range / fr);
792         } else if (range <= 4 * 60 * fr) { /* 2-4 minutes */
793                 timecode_ruler_scale = timecode_show_seconds;
794                 timecode_mark_modulo = 30;
795                 timecode_nmarks = 2 + (range / fr);
796         } else if (range <= 10 * 60 * fr) { /* 4-10 minutes */
797                 timecode_ruler_scale = timecode_show_minutes;
798                 timecode_mark_modulo = 2;
799                 timecode_nmarks = 2 + 10;
800         } else if (range <= 30 * 60 * fr) { /* 10-30 minutes */
801                 timecode_ruler_scale = timecode_show_minutes;
802                 timecode_mark_modulo = 5;
803                 timecode_nmarks = 2 + 30;
804         } else if (range <= 60 * 60 * fr) { /* 30 minutes - 1hr */
805                 timecode_ruler_scale = timecode_show_minutes;
806                 timecode_mark_modulo = 10;
807                 timecode_nmarks = 2 + 60;
808         } else if (range <= 4 * 60 * 60 * fr) { /* 1 - 4 hrs*/
809                 timecode_ruler_scale = timecode_show_minutes;
810                 timecode_mark_modulo = 30;
811                 timecode_nmarks = 2 + (60 * 4);
812         } else if (range <= 8 * 60 * 60 * fr) { /* 4 - 8 hrs*/
813                 timecode_ruler_scale = timecode_show_hours;
814                 timecode_mark_modulo = 1;
815                 timecode_nmarks = 2 + 8;
816         } else if (range <= 16 * 60 * 60 * fr) { /* 16-24 hrs*/
817                 timecode_ruler_scale = timecode_show_hours;
818                 timecode_mark_modulo = 1;
819                 timecode_nmarks = 2 + 24;
820         } else {
821
822                 const framecnt_t hours_in_range = range / (60 * 60 * fr);
823                 const int text_width_rough_guess = 120; /* pixels, very very approximate guess at how wide the tick mark text is */
824
825                 /* Normally we do not need to know anything about the width of the canvas
826                    to set the ruler scale, because the caller has already determined
827                    the width and set lower + upper arguments to this function to match that.
828
829                    But in this case, where the range defined by lower and uppper can vary
830                    substantially (basically anything from 24hrs+ to several billion years)
831                    trying to decide which tick marks to show does require us to know
832                    about the available width.
833                 */
834
835                 timecode_nmarks = _track_canvas->width() / text_width_rough_guess;
836                 timecode_ruler_scale = timecode_show_many_hours;
837                 timecode_mark_modulo = max ((framecnt_t) 1, 1 + (hours_in_range / timecode_nmarks));
838         }
839 }
840
841 void
842 Editor::metric_get_timecode (std::vector<ArdourCanvas::Ruler::Mark>& marks, gdouble lower, gdouble /*upper*/, gint /*maxchars*/)
843 {
844         framepos_t pos;
845         framecnt_t spacer;
846         Timecode::Time timecode;
847         gchar buf[16];
848         gint n;
849         ArdourCanvas::Ruler::Mark mark;
850
851         if (_session == 0) {
852                 return;
853         }
854
855         if (lower > (spacer = (framecnt_t)(128 * Editor::get_current_zoom ()))) {
856                 lower = lower - spacer;
857         } else {
858                 lower = 0;
859         }
860
861         pos = (framecnt_t) floor (lower);
862
863         switch (timecode_ruler_scale) {
864         case timecode_show_bits:
865                 // Find timecode time of this sample (pos) with subframe accuracy
866                 _session->sample_to_timecode(pos, timecode, true /* use_offset */, true /* use_subframes */ );
867                 for (n = 0; n < timecode_nmarks; n++) {
868                         _session->timecode_to_sample(timecode, pos, true /* use_offset */, true /* use_subframes */ );
869                         if ((timecode.subframes % timecode_mark_modulo) == 0) {
870                                 if (timecode.subframes == 0) {
871                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
872                                         snprintf (buf, sizeof(buf), "%s%02u:%02u:%02u:%02u", timecode.negative ? "-" : "", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
873                                 } else {
874                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
875                                         snprintf (buf, sizeof(buf), ".%02u", timecode.subframes);
876                                 }
877                         } else {
878                                 snprintf (buf, sizeof(buf)," ");
879                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
880                         }
881                         mark.label = buf;
882                         mark.position = pos;
883                         marks.push_back (mark);
884                         // Increment subframes by one
885                         Timecode::increment_subframes( timecode, _session->config.get_subframes_per_frame() );
886                 }
887                 break;
888
889         case timecode_show_frames:
890                 // Find timecode time of this sample (pos)
891                 _session->sample_to_timecode(pos, timecode, true /* use_offset */, false /* use_subframes */ );
892                 // Go to next whole frame down
893                 Timecode::frames_floor( timecode );
894                 for (n = 0; n < timecode_nmarks; n++) {
895                         _session->timecode_to_sample(timecode, pos, true /* use_offset */, false /* use_subframes */ );
896                         if ((timecode.frames % timecode_mark_modulo) == 0) {
897                                 if (timecode.frames == 0) {
898                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
899                                 } else {
900                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
901                                 }
902                                 mark.position = pos;
903                                 snprintf (buf, sizeof(buf), "%s%02u:%02u:%02u:%02u", timecode.negative ? "-" : "", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
904                         } else {
905                                 snprintf (buf, sizeof(buf)," ");
906                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
907                                 mark.position = pos;
908                         }
909                         mark.label = buf;
910                         marks.push_back (mark);
911                         Timecode::increment( timecode, _session->config.get_subframes_per_frame() );
912                 }
913                 break;
914
915         case timecode_show_seconds:
916                 // Find timecode time of this sample (pos)
917                 _session->sample_to_timecode(pos, timecode, true /* use_offset */, false /* use_subframes */ );
918                 // Go to next whole second down
919                 Timecode::seconds_floor( timecode );
920                 for (n = 0; n < timecode_nmarks; n++) {
921                         _session->timecode_to_sample(timecode, pos, true /* use_offset */, false /* use_subframes */ );
922                         if ((timecode.seconds % timecode_mark_modulo) == 0) {
923                                 if (timecode.seconds == 0) {
924                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
925                                         mark.position = pos;
926                                 } else {
927                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
928                                         mark.position = pos;
929                                 }
930                                 snprintf (buf, sizeof(buf), "%s%02u:%02u:%02u:%02u", timecode.negative ? "-" : "", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
931                         } else {
932                                 snprintf (buf, sizeof(buf)," ");
933                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
934                                 mark.position = pos;
935                         }
936                         mark.label = buf;
937                         marks.push_back (mark);
938                         Timecode::increment_seconds( timecode, _session->config.get_subframes_per_frame() );
939                 }
940                 break;
941
942         case timecode_show_minutes:
943                 //Find timecode time of this sample (pos)
944                 _session->sample_to_timecode(pos, timecode, true /* use_offset */, false /* use_subframes */ );
945                 // Go to next whole minute down
946                 Timecode::minutes_floor( timecode );
947                 for (n = 0; n < timecode_nmarks; n++) {
948                         _session->timecode_to_sample(timecode, pos, true /* use_offset */, false /* use_subframes */ );
949                         if ((timecode.minutes % timecode_mark_modulo) == 0) {
950                                 if (timecode.minutes == 0) {
951                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
952                                 } else {
953                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
954                                 }
955                                 snprintf (buf, sizeof(buf), "%s%02u:%02u:%02u:%02u", timecode.negative ? "-" : "", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
956                         } else {
957                                 snprintf (buf, sizeof(buf)," ");
958                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
959                         }
960                         mark.label = buf;
961                         mark.position = pos;
962                         marks.push_back (mark);
963                         Timecode::increment_minutes( timecode, _session->config.get_subframes_per_frame() );
964                 }
965                 break;
966         case timecode_show_hours:
967                 // Find timecode time of this sample (pos)
968                 _session->sample_to_timecode(pos, timecode, true /* use_offset */, false /* use_subframes */ );
969                 // Go to next whole hour down
970                 Timecode::hours_floor( timecode );
971                 for (n = 0; n < timecode_nmarks; n++) {
972                         _session->timecode_to_sample(timecode, pos, true /* use_offset */, false /* use_subframes */ );
973                         if ((timecode.hours % timecode_mark_modulo) == 0) {
974                                 mark.style = ArdourCanvas::Ruler::Mark::Major;
975                                 snprintf (buf, sizeof(buf), "%s%02u:%02u:%02u:%02u", timecode.negative ? "-" : "", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
976                         } else {
977                                 snprintf (buf, sizeof(buf)," ");
978                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
979                         }
980                         mark.label = buf;
981                         mark.position = pos;
982                         marks.push_back (mark);
983                         Timecode::increment_hours( timecode, _session->config.get_subframes_per_frame() );
984                 }
985                 break;
986         case timecode_show_many_hours:
987                 // Find timecode time of this sample (pos)
988                 _session->sample_to_timecode(pos, timecode, true /* use_offset */, false /* use_subframes */ );
989                 // Go to next whole hour down
990                 Timecode::hours_floor (timecode);
991
992                 for (n = 0; n < timecode_nmarks; ) {
993                         _session->timecode_to_sample(timecode, pos, true /* use_offset */, false /* use_subframes */ );
994                         if ((timecode.hours % timecode_mark_modulo) == 0) {
995                                 mark.style = ArdourCanvas::Ruler::Mark::Major;
996                                 snprintf (buf, sizeof(buf), "%s%02u:%02u:%02u:%02u", timecode.negative ? "-" : "", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
997                                 mark.label = buf;
998                                 mark.position = pos;
999                                 marks.push_back (mark);
1000                                 ++n;
1001                         }
1002                         /* can't use Timecode::increment_hours() here because we may be traversing thousands of hours
1003                            and doing it 1 hour at a time is just stupid (and slow).
1004                         */
1005                         timecode.hours += timecode_mark_modulo;
1006                 }
1007                 break;
1008         }
1009 }
1010
1011 void
1012 Editor::compute_bbt_ruler_scale (std::vector<ARDOUR::TempoMap::BBTPoint>& grid, framepos_t lower, framepos_t upper)
1013 {
1014         if (_session == 0) {
1015                 return;
1016         }
1017
1018         std::vector<TempoMap::BBTPoint>::const_iterator i;
1019         Timecode::BBT_Time lower_beat, upper_beat; // the beats at each end of the ruler
1020         framecnt_t beat_before_lower_pos = _session->tempo_map().frame_at_beat (floor(_session->tempo_map().beat_at_frame (lower)));
1021         framecnt_t beat_after_upper_pos = _session->tempo_map().frame_at_beat (floor (_session->tempo_map().beat_at_frame (upper)) + 1.0);
1022
1023         _session->bbt_time (beat_before_lower_pos, lower_beat);
1024         _session->bbt_time (beat_after_upper_pos, upper_beat);
1025         uint32_t beats = 0;
1026
1027         bbt_accent_modulo = 1;
1028         bbt_bar_helper_on = false;
1029         bbt_bars = 0;
1030         bbt_nmarks = 1;
1031
1032         bbt_ruler_scale =  bbt_show_many;
1033
1034         switch (_snap_type) {
1035         case SnapToBeatDiv2:
1036                 bbt_beat_subdivision = 2;
1037                 break;
1038         case SnapToBeatDiv3:
1039                 bbt_beat_subdivision = 3;
1040                 break;
1041         case SnapToBeatDiv4:
1042                 bbt_beat_subdivision = 4;
1043                 break;
1044         case SnapToBeatDiv5:
1045                 bbt_beat_subdivision = 5;
1046                 bbt_accent_modulo = 2; // XXX YIKES
1047                 break;
1048         case SnapToBeatDiv6:
1049                 bbt_beat_subdivision = 6;
1050                 bbt_accent_modulo = 2; // XXX YIKES
1051                 break;
1052         case SnapToBeatDiv7:
1053                 bbt_beat_subdivision = 7;
1054                 bbt_accent_modulo = 2; // XXX YIKES
1055                 break;
1056         case SnapToBeatDiv8:
1057                 bbt_beat_subdivision = 8;
1058                 bbt_accent_modulo = 2;
1059                 break;
1060         case SnapToBeatDiv10:
1061                 bbt_beat_subdivision = 10;
1062                 bbt_accent_modulo = 2; // XXX YIKES
1063                 break;
1064         case SnapToBeatDiv12:
1065                 bbt_beat_subdivision = 12;
1066                 bbt_accent_modulo = 3;
1067                 break;
1068         case SnapToBeatDiv14:
1069                 bbt_beat_subdivision = 14;
1070                 bbt_accent_modulo = 3; // XXX YIKES!
1071                 break;
1072         case SnapToBeatDiv16:
1073                 bbt_beat_subdivision = 16;
1074                 bbt_accent_modulo = 4;
1075                 break;
1076         case SnapToBeatDiv20:
1077                 bbt_beat_subdivision = 20;
1078                 bbt_accent_modulo = 5;
1079                 break;
1080         case SnapToBeatDiv24:
1081                 bbt_beat_subdivision = 24;
1082                 bbt_accent_modulo = 6;
1083                 break;
1084         case SnapToBeatDiv28:
1085                 bbt_beat_subdivision = 28;
1086                 bbt_accent_modulo = 7;
1087                 break;
1088         case SnapToBeatDiv32:
1089                 bbt_beat_subdivision = 32;
1090                 bbt_accent_modulo = 8;
1091                 break;
1092         case SnapToBeatDiv64:
1093                 bbt_beat_subdivision = 64;
1094                 bbt_accent_modulo = 8;
1095                 break;
1096         case SnapToBeatDiv128:
1097                 bbt_beat_subdivision = 128;
1098                 bbt_accent_modulo = 8;
1099                 break;
1100         default:
1101                 bbt_beat_subdivision = 4;
1102                 break;
1103         }
1104         if (distance (grid.begin(), grid.end()) == 0) {
1105                 return;
1106         }
1107
1108         i = grid.end();
1109         i--;
1110
1111         /* XX ?? */
1112         if ((*i).beat >= (*grid.begin()).beat) {
1113                 bbt_bars = (*i).bar - (*grid.begin()).bar;
1114         } else {
1115                 bbt_bars = (*i).bar - (*grid.begin()).bar;
1116         }
1117
1118         beats = distance (grid.begin(), grid.end()) - bbt_bars;
1119
1120         /* Only show the bar helper if there aren't many bars on the screen */
1121         if ((bbt_bars < 2) || (beats < 5)) {
1122                 bbt_bar_helper_on = true;
1123         }
1124
1125         if (bbt_bars > 8192) {
1126                 bbt_ruler_scale =  bbt_show_many;
1127         } else if (bbt_bars > 1024) {
1128                 bbt_ruler_scale = bbt_show_64;
1129         } else if (bbt_bars > 256) {
1130                 bbt_ruler_scale = bbt_show_16;
1131         } else if (bbt_bars > 64) {
1132                 bbt_ruler_scale = bbt_show_4;
1133         } else if (bbt_bars > 10) {
1134                 bbt_ruler_scale =  bbt_show_1;
1135         } else if (bbt_bars > 2) {
1136                 bbt_ruler_scale =  bbt_show_beats;
1137         } else  if (bbt_bars > 0) {
1138                 bbt_ruler_scale =  bbt_show_ticks;
1139         } else {
1140                 bbt_ruler_scale =  bbt_show_ticks_detail;
1141         }
1142
1143         if ((bbt_ruler_scale == bbt_show_ticks_detail) && beats < 3) {
1144                 bbt_ruler_scale =  bbt_show_ticks_super_detail;
1145         }
1146 }
1147
1148 static void
1149 edit_last_mark_label (std::vector<ArdourCanvas::Ruler::Mark>& marks, const std::string& newlabel)
1150 {
1151         ArdourCanvas::Ruler::Mark copy = marks.back();
1152         copy.label = newlabel;
1153         marks.pop_back ();
1154         marks.push_back (copy);
1155 }
1156
1157 void
1158 Editor::metric_get_bbt (std::vector<ArdourCanvas::Ruler::Mark>& marks, gdouble lower, gdouble upper, gint /*maxchars*/)
1159 {
1160         if (_session == 0) {
1161                 return;
1162         }
1163
1164         std::vector<TempoMap::BBTPoint>::const_iterator i;
1165
1166         char buf[64];
1167         gint  n = 0;
1168         framepos_t pos;
1169         Timecode::BBT_Time next_beat;
1170         uint32_t beats = 0;
1171         uint32_t tick = 0;
1172         uint32_t skip;
1173         uint32_t t;
1174         double bbt_position_of_helper;
1175         bool i_am_accented = false;
1176         bool helper_active = false;
1177         ArdourCanvas::Ruler::Mark mark;
1178
1179         std::vector<TempoMap::BBTPoint> grid;
1180
1181         compute_current_bbt_points (grid, lower, upper);
1182
1183         if (distance (grid.begin(), grid.end()) == 0) {
1184                 return;
1185         }
1186
1187         switch (bbt_ruler_scale) {
1188
1189         case bbt_show_beats:
1190
1191                 beats = distance (grid.begin(), grid.end());
1192                 bbt_nmarks = beats + 2;
1193
1194                 mark.label = "";
1195                 mark.position = lower;
1196                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1197                 marks.push_back (mark);
1198
1199                 for (n = 1, i = grid.begin(); n < bbt_nmarks && i != grid.end(); ++i) {
1200
1201                         if ((*i).frame < lower && (bbt_bar_helper_on)) {
1202                                 snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, (*i).bar, (*i).beat);
1203                                 edit_last_mark_label (marks, buf);
1204                                 helper_active = true;
1205                         } else {
1206
1207                                 if ((*i).is_bar()) {
1208                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1209                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);
1210                                 } else if (((*i).beat % 2 == 1)) {
1211                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1212                                         buf[0] = '\0';
1213                                 } else {
1214                                         mark.style = ArdourCanvas::Ruler::Mark::Micro;
1215                                         buf[0] = '\0';
1216                                 }
1217                                 mark.label = buf;
1218                                 mark.position = (*i).frame;
1219                                 marks.push_back (mark);
1220                                 n++;
1221                         }
1222                 }
1223                 break;
1224
1225         case bbt_show_ticks:
1226
1227                 beats = distance (grid.begin(), grid.end());
1228                 bbt_nmarks = (beats + 2) * bbt_beat_subdivision;
1229
1230                 bbt_position_of_helper = lower + (30 * Editor::get_current_zoom ());
1231
1232                 // could do marks.assign() here to preallocate
1233
1234                 mark.label = "";
1235                 mark.position = lower;
1236                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1237                 marks.push_back (mark);
1238
1239                 for (n = 1, i = grid.begin(); n < bbt_nmarks && i != grid.end(); ++i) {
1240
1241                         if ((*i).frame < lower && (bbt_bar_helper_on)) {
1242                                 snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, (*i).bar, (*i).beat);
1243                                 edit_last_mark_label (marks, buf);
1244                                 helper_active = true;
1245                         } else {
1246
1247                                 if ((*i).is_bar()) {
1248                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1249                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);
1250                                 } else {
1251                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1252                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).beat);
1253                                 }
1254                                 if (((*i).frame < bbt_position_of_helper) && helper_active) {
1255                                         buf[0] = '\0';
1256                                 }
1257                                 mark.label =  buf;
1258                                 mark.position = (*i).frame;
1259                                 marks.push_back (mark);
1260                                 n++;
1261                         }
1262
1263                         /* Add the tick marks */
1264                         skip = Timecode::BBT_Time::ticks_per_beat / bbt_beat_subdivision;
1265                         tick = skip; // the first non-beat tick
1266                         t = 0;
1267                         while (tick < Timecode::BBT_Time::ticks_per_beat && (n < bbt_nmarks)) {
1268
1269                                 next_beat.beats = (*i).beat;
1270                                 next_beat.bars = (*i).bar;
1271                                 next_beat.ticks = tick;
1272                                 pos = _session->tempo_map().frame_time (next_beat);
1273
1274                                 if (t % bbt_accent_modulo == (bbt_accent_modulo - 1)) {
1275                                         i_am_accented = true;
1276                                 }
1277                                 mark.label = "";
1278                                 mark.position = pos;
1279
1280                                 if ((bbt_beat_subdivision > 4) && i_am_accented) {
1281                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1282                                 } else {
1283                                         mark.style = ArdourCanvas::Ruler::Mark::Micro;
1284                                 }
1285                                 i_am_accented = false;
1286                                 marks.push_back (mark);
1287
1288                                 tick += skip;
1289                                 ++t;
1290                                 ++n;
1291                         }
1292                 }
1293
1294           break;
1295
1296         case bbt_show_ticks_detail:
1297
1298                 beats = distance (grid.begin(), grid.end());
1299                 bbt_nmarks = (beats + 2) * bbt_beat_subdivision;
1300
1301                 bbt_position_of_helper = lower + (3 * Editor::get_current_zoom ());
1302
1303                 mark.label = "";
1304                 mark.position = lower;
1305                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1306                 marks.push_back (mark);
1307
1308                 for (n = 1,   i = grid.begin(); n < bbt_nmarks && i != grid.end(); ++i) {
1309
1310                         if ((*i).frame < lower && (bbt_bar_helper_on)) {
1311                                 snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, (*i).bar, (*i).beat);
1312                                 edit_last_mark_label (marks, buf);
1313                                 helper_active = true;
1314                         } else {
1315
1316                                 if ((*i).is_bar()) {
1317                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1318                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);
1319                                 } else {
1320                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1321                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).beat);
1322                                 }
1323                                 if (((*i).frame < bbt_position_of_helper) && helper_active) {
1324                                         buf[0] = '\0';
1325                                 }
1326                                 mark.label =  buf;
1327                                 mark.position = (*i).frame;
1328                                 marks.push_back (mark);
1329                                 n++;
1330                         }
1331
1332                         /* Add the tick marks */
1333                         skip = Timecode::BBT_Time::ticks_per_beat / bbt_beat_subdivision;
1334                         tick = skip; // the first non-beat tick
1335
1336                         t = 0;
1337                         while (tick < Timecode::BBT_Time::ticks_per_beat && (n < bbt_nmarks)) {
1338
1339                                 next_beat.beats = (*i).beat;
1340                                 next_beat.bars = (*i).bar;
1341                                 next_beat.ticks = tick;
1342                                 pos = _session->tempo_map().frame_time (next_beat);
1343
1344                                 if (t % bbt_accent_modulo == (bbt_accent_modulo - 1)) {
1345                                         i_am_accented = true;
1346                                 }
1347                                 if (i_am_accented && (pos > bbt_position_of_helper)){
1348                                         snprintf (buf, sizeof(buf), "%" PRIu32, tick);
1349                                 } else {
1350                                         buf[0] = '\0';
1351                                 }
1352
1353                                 mark.label = buf;
1354                                 mark.position = pos;
1355
1356                                 if ((bbt_beat_subdivision > 4) && i_am_accented) {
1357                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1358                                 } else {
1359                                         mark.style = ArdourCanvas::Ruler::Mark::Micro;
1360                                 }
1361                                 i_am_accented = false;
1362                                 marks.push_back (mark);
1363
1364                                 tick += skip;
1365                                 ++t;
1366                                 ++n;
1367                         }
1368                 }
1369
1370           break;
1371
1372         case bbt_show_ticks_super_detail:
1373
1374                 beats = distance (grid.begin(), grid.end());
1375                 bbt_nmarks = (beats + 2) * bbt_beat_subdivision;
1376
1377                 bbt_position_of_helper = lower + (3 * Editor::get_current_zoom ());
1378
1379                 mark.label = "";
1380                 mark.position = lower;
1381                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1382                 marks.push_back (mark);
1383
1384                 for (n = 1,   i = grid.begin(); n < bbt_nmarks && i != grid.end(); ++i) {
1385
1386                         if ((*i).frame < lower && (bbt_bar_helper_on)) {
1387                                   snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, (*i).bar, (*i).beat);
1388                                   edit_last_mark_label (marks, buf);
1389                                   helper_active = true;
1390                         } else {
1391
1392                                   if ((*i).is_bar()) {
1393                                           mark.style = ArdourCanvas::Ruler::Mark::Major;
1394                                           snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);
1395                                   } else {
1396                                           mark.style = ArdourCanvas::Ruler::Mark::Minor;
1397                                           snprintf (buf, sizeof(buf), "%" PRIu32, (*i).beat);
1398                                   }
1399                                   if (((*i).frame < bbt_position_of_helper) && helper_active) {
1400                                           buf[0] = '\0';
1401                                   }
1402                                   mark.label =  buf;
1403                                   mark.position = (*i).frame;
1404                                   marks.push_back (mark);
1405                                   n++;
1406                         }
1407
1408                         /* Add the tick marks */
1409                         skip = Timecode::BBT_Time::ticks_per_beat / bbt_beat_subdivision;
1410
1411                         next_beat.beats = (*i).beat;
1412                         next_beat.bars = (*i).bar;
1413                         tick = skip; // the first non-beat tick
1414                         t = 0;
1415                         while (tick < Timecode::BBT_Time::ticks_per_beat && (n < bbt_nmarks)) {
1416
1417                                 next_beat.ticks = tick;
1418                                 pos = _session->tempo_map().frame_time (next_beat);
1419                                 if (t % bbt_accent_modulo == (bbt_accent_modulo - 1)) {
1420                                         i_am_accented = true;
1421                                 }
1422
1423                                 if (pos > bbt_position_of_helper) {
1424                                         snprintf (buf, sizeof(buf), "%" PRIu32, tick);
1425                                 } else {
1426                                         buf[0] = '\0';
1427                                 }
1428
1429                                 mark.label = buf;
1430                                 mark.position = pos;
1431
1432                                 if ((bbt_beat_subdivision > 4) && i_am_accented) {
1433                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1434                                 } else {
1435                                         mark.style = ArdourCanvas::Ruler::Mark::Micro;
1436                                 }
1437                                 i_am_accented = false;
1438                                 marks.push_back (mark);
1439
1440                                 tick += skip;
1441                                 ++t;
1442                                 ++n;
1443                         }
1444                 }
1445
1446           break;
1447
1448         case bbt_show_many:
1449                 bbt_nmarks = 1;
1450                 snprintf (buf, sizeof(buf), "cannot handle %" PRIu32 " bars", bbt_bars );
1451                 mark.style = ArdourCanvas::Ruler::Mark::Major;
1452                 mark.label = buf;
1453                 mark.position = lower;
1454                 marks.push_back (mark);
1455                 break;
1456
1457         case bbt_show_64:
1458                         bbt_nmarks = (gint) (bbt_bars / 64) + 1;
1459                         for (n = 0,   i = grid.begin(); i != grid.end() && n < bbt_nmarks; i++) {
1460                                 if ((*i).is_bar()) {
1461                                         if ((*i).bar % 64 == 1) {
1462                                                 if ((*i).bar % 256 == 1) {
1463                                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);
1464                                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1465                                                 } else {
1466                                                         buf[0] = '\0';
1467                                                         if ((*i).bar % 256 == 129)  {
1468                                                                 mark.style = ArdourCanvas::Ruler::Mark::Minor;
1469                                                         } else {
1470                                                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1471                                                         }
1472                                                 }
1473                                                 mark.label = buf;
1474                                                 mark.position = (*i).frame;
1475                                                 marks.push_back (mark);
1476                                                 ++n;
1477                                         }
1478                                 }
1479                         }
1480                         break;
1481
1482         case bbt_show_16:
1483                 bbt_nmarks = (bbt_bars / 16) + 1;
1484                 for (n = 0,  i = grid.begin(); i != grid.end() && n < bbt_nmarks; i++) {
1485                         if ((*i).is_bar()) {
1486                           if ((*i).bar % 16 == 1) {
1487                                 if ((*i).bar % 64 == 1) {
1488                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);
1489                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1490                                 } else {
1491                                         buf[0] = '\0';
1492                                         if ((*i).bar % 64 == 33)  {
1493                                                 mark.style = ArdourCanvas::Ruler::Mark::Minor;
1494                                         } else {
1495                                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1496                                         }
1497                                 }
1498                                 mark.label = buf;
1499                                 mark.position = (*i).frame;
1500                                 marks.push_back (mark);
1501                                 ++n;
1502                           }
1503                         }
1504                 }
1505           break;
1506
1507         case bbt_show_4:
1508                 bbt_nmarks = (bbt_bars / 4) + 1;
1509                 for (n = 0,   i = grid.begin(); i != grid.end() && n < bbt_nmarks; ++i) {
1510                         if ((*i).is_bar()) {
1511                           if ((*i).bar % 4 == 1) {
1512                                 if ((*i).bar % 16 == 1) {
1513                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);
1514                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1515                                 } else {
1516                                         buf[0] = '\0';
1517                                         if ((*i).bar % 16 == 9)  {
1518                                                 mark.style = ArdourCanvas::Ruler::Mark::Minor;
1519                                         } else {
1520                                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1521                                         }
1522                                 }
1523                                 mark.label = buf;
1524                                 mark.position = (*i).frame;
1525                                 marks.push_back (mark);
1526                                 ++n;
1527                           }
1528                         }
1529                 }
1530           break;
1531
1532         case bbt_show_1:
1533 //      default:
1534                 bbt_nmarks = bbt_bars + 2;
1535                 for (n = 0,  i = grid.begin(); i != grid.end() && n < bbt_nmarks; ++i) {
1536                         if ((*i).is_bar()) {
1537                           if ((*i).bar % 4 == 1) {
1538                                         snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);
1539                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1540                           } else {
1541                                   buf[0] = '\0';
1542                                   if ((*i).bar % 4 == 3)  {
1543                                           mark.style = ArdourCanvas::Ruler::Mark::Minor;
1544                                   } else {
1545                                           mark.style = ArdourCanvas::Ruler::Mark::Micro;
1546                                   }
1547                           }
1548                           mark.label = buf;
1549                           mark.position = (*i).frame;
1550                           marks.push_back (mark);
1551                           ++n;
1552                         }
1553                 }
1554                 break;
1555
1556         }
1557 }
1558
1559 void
1560 Editor::set_samples_ruler_scale (framepos_t lower, framepos_t upper)
1561 {
1562         _samples_ruler_interval = (upper - lower) / 5;
1563 }
1564
1565 void
1566 Editor::metric_get_samples (std::vector<ArdourCanvas::Ruler::Mark>& marks, gdouble lower, gdouble /*upper*/, gint /*maxchars*/)
1567 {
1568         framepos_t pos;
1569         framepos_t const ilower = (framepos_t) floor (lower);
1570         gchar buf[16];
1571         gint nmarks;
1572         gint n;
1573         ArdourCanvas::Ruler::Mark mark;
1574
1575         if (_session == 0) {
1576                 return;
1577         }
1578
1579         nmarks = 5;
1580         for (n = 0, pos = ilower; n < nmarks; pos += _samples_ruler_interval, ++n) {
1581                 snprintf (buf, sizeof(buf), "%" PRIi64, pos);
1582                 mark.label = buf;
1583                 mark.position = pos;
1584                 mark.style = ArdourCanvas::Ruler::Mark::Major;
1585                 marks.push_back (mark);
1586         }
1587 }
1588
1589 static void
1590 sample_to_clock_parts ( framepos_t sample,
1591                         framepos_t sample_rate,
1592                         long *hrs_p,
1593                         long *mins_p,
1594                         long *secs_p,
1595                         long *millisecs_p)
1596
1597 {
1598         framepos_t left;
1599         long hrs;
1600         long mins;
1601         long secs;
1602         long millisecs;
1603
1604         left = sample;
1605         hrs = left / (sample_rate * 60 * 60 * 1000);
1606         left -= hrs * sample_rate * 60 * 60 * 1000;
1607         mins = left / (sample_rate * 60 * 1000);
1608         left -= mins * sample_rate * 60 * 1000;
1609         secs = left / (sample_rate * 1000);
1610         left -= secs * sample_rate * 1000;
1611         millisecs = left / sample_rate;
1612
1613         *millisecs_p = millisecs;
1614         *secs_p = secs;
1615         *mins_p = mins;
1616         *hrs_p = hrs;
1617
1618         return;
1619 }
1620
1621 void
1622 Editor::set_minsec_ruler_scale (framepos_t lower, framepos_t upper)
1623 {
1624         framepos_t fr = _session->frame_rate() * 1000;
1625         framepos_t spacer;
1626
1627         if (_session == 0) {
1628                 return;
1629         }
1630
1631
1632         /* to prevent 'flashing' */
1633         if (lower > (spacer = (framepos_t)(128 * Editor::get_current_zoom ()))) {
1634                 lower -= spacer;
1635         } else {
1636                 lower = 0;
1637         }
1638         upper += spacer;
1639         framecnt_t const range = (upper - lower) * 1000;
1640
1641         if (range <= (fr / 10)) { /* 0-0.1 second */
1642                 minsec_mark_interval = fr / 1000; /* show 1/1000 seconds */
1643                 minsec_ruler_scale = minsec_show_msecs;
1644                 minsec_mark_modulo = 10;
1645                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1646         } else if (range <= (fr / 2)) { /* 0-0.5 second */
1647                 minsec_mark_interval = fr / 100;  /* show 1/100 seconds */
1648                 minsec_ruler_scale = minsec_show_msecs;
1649                 minsec_mark_modulo = 100;
1650                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1651         } else if (range <= fr) { /* 0-1 second */
1652                 minsec_mark_interval = fr / 10;  /* show 1/10 seconds */
1653                 minsec_ruler_scale = minsec_show_msecs;
1654                 minsec_mark_modulo = 200;
1655                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1656         } else if (range <= 2 * fr) { /* 1-2 seconds */
1657                 minsec_mark_interval = fr / 10; /* show 1/10 seconds */
1658                 minsec_ruler_scale = minsec_show_msecs;
1659                 minsec_mark_modulo = 500;
1660                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1661         } else if (range <= 8 * fr) { /* 2-5 seconds */
1662                 minsec_mark_interval =  fr / 5; /* show 2 seconds */
1663                 minsec_ruler_scale = minsec_show_msecs;
1664                 minsec_mark_modulo = 1000;
1665                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1666         } else if (range <= 16 * fr) { /* 8-16 seconds */
1667                 minsec_mark_interval =  fr; /* show 1 seconds */
1668                 minsec_ruler_scale = minsec_show_seconds;
1669                 minsec_mark_modulo = 2;
1670                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1671         } else if (range <= 30 * fr) { /* 10-30 seconds */
1672                 minsec_mark_interval =  fr; /* show 1 seconds */
1673                 minsec_ruler_scale = minsec_show_seconds;
1674                 minsec_mark_modulo = 5;
1675                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1676         } else if (range <= 60 * fr) { /* 30-60 seconds */
1677                 minsec_mark_interval = fr; /* show 1 seconds */
1678                 minsec_ruler_scale = minsec_show_seconds;
1679                 minsec_mark_modulo = 5;
1680                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1681         } else if (range <= 2 * 60 * fr) { /* 1-2 minutes */
1682                 minsec_mark_interval = 5 * fr; /* show 5 seconds */
1683                 minsec_ruler_scale = minsec_show_seconds;
1684                 minsec_mark_modulo = 3;
1685                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1686         } else if (range <= 4 * 60 * fr) { /* 4 minutes */
1687                 minsec_mark_interval = 5 * fr; /* show 10 seconds */
1688                 minsec_ruler_scale = minsec_show_seconds;
1689                 minsec_mark_modulo = 30;
1690                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1691         } else if (range <= 10 * 60 * fr) { /* 10 minutes */
1692                 minsec_mark_interval = 30 * fr; /* show 30 seconds */
1693                 minsec_ruler_scale = minsec_show_seconds;
1694                 minsec_mark_modulo = 120;
1695                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1696         } else if (range <= 30 * 60 * fr) { /* 10-30 minutes */
1697                 minsec_mark_interval =  60 * fr; /* show 1 minute */
1698                 minsec_ruler_scale = minsec_show_minutes;
1699                 minsec_mark_modulo = 5;
1700                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1701         } else if (range <= 60 * 60 * fr) { /* 30 minutes - 1hr */
1702                 minsec_mark_interval = 2 * 60 * fr; /* show 2 minutes */
1703                 minsec_ruler_scale = minsec_show_minutes;
1704                 minsec_mark_modulo = 10;
1705                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1706         } else if (range <= 4 * 60 * 60 * fr) { /* 1 - 4 hrs*/
1707                 minsec_mark_interval = 5 * 60 * fr; /* show 10 minutes */
1708                 minsec_ruler_scale = minsec_show_minutes;
1709                 minsec_mark_modulo = 30;
1710                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1711         } else if (range <= 8 * 60 * 60 * fr) { /* 4 - 8 hrs*/
1712                 minsec_mark_interval = 20 * 60 * fr; /* show 20 minutes */
1713                 minsec_ruler_scale = minsec_show_minutes;
1714                 minsec_mark_modulo = 60;
1715                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1716         } else if (range <= 16 * 60 * 60 * fr) { /* 16-24 hrs*/
1717                 minsec_mark_interval =  60 * 60 * fr; /* show 60 minutes */
1718                 minsec_ruler_scale = minsec_show_hours;
1719                 minsec_mark_modulo = 2;
1720                 minsec_nmarks = 2 + (range / minsec_mark_interval);
1721         } else {
1722
1723                 const framecnt_t hours_in_range = range / (60 * 60 * fr);
1724                 const int text_width_rough_guess = 70; /* pixels, very very approximate guess at how wide the tick mark text is */
1725
1726                 /* Normally we do not need to know anything about the width of the canvas
1727                    to set the ruler scale, because the caller has already determined
1728                    the width and set lower + upper arguments to this function to match that.
1729
1730                    But in this case, where the range defined by lower and uppper can vary
1731                    substantially (anything from 24hrs+ to several billion years)
1732                    trying to decide which tick marks to show does require us to know
1733                    about the available width.
1734                 */
1735
1736                 minsec_nmarks = _track_canvas->width() / text_width_rough_guess;
1737                 minsec_mark_modulo = max ((framecnt_t) 1, 1 + (hours_in_range / minsec_nmarks));
1738                 minsec_mark_interval = minsec_mark_modulo * (60 * 60 * fr);
1739                 minsec_ruler_scale = minsec_show_many_hours;
1740         }
1741 }
1742
1743 void
1744 Editor::metric_get_minsec (std::vector<ArdourCanvas::Ruler::Mark>& marks, gdouble lower, gdouble upper, gint /*maxchars*/)
1745 {
1746         framepos_t pos;
1747         framepos_t spacer;
1748         long hrs, mins, secs, millisecs;
1749         gchar buf[16];
1750         gint n;
1751         ArdourCanvas::Ruler::Mark mark;
1752
1753         if (_session == 0) {
1754                 return;
1755         }
1756
1757         /* to prevent 'flashing' */
1758         if (lower > (spacer = (framepos_t) (128 * Editor::get_current_zoom ()))) {
1759                 lower = lower - spacer;
1760         } else {
1761                 lower = 0;
1762         }
1763
1764         pos = (((1000 * (framepos_t) floor(lower)) + (minsec_mark_interval/2))/minsec_mark_interval) * minsec_mark_interval;
1765
1766         switch (minsec_ruler_scale) {
1767
1768         case minsec_show_msecs:
1769                 for (n = 0; n < minsec_nmarks && n < upper; pos += minsec_mark_interval, ++n) {
1770                         sample_to_clock_parts (pos, _session->frame_rate(), &hrs, &mins, &secs, &millisecs);
1771                         if (millisecs % minsec_mark_modulo == 0) {
1772                                 if (millisecs == 0) {
1773                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1774                                 } else {
1775                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1776                                 }
1777                                 snprintf (buf, sizeof(buf), "%02ld:%02ld:%02ld.%03ld", hrs, mins, secs, millisecs);
1778                         } else {
1779                                 buf[0] = '\0';
1780                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1781                         }
1782                         mark.label = buf;
1783                         mark.position = pos/1000.0;
1784                         marks.push_back (mark);
1785                 }
1786                 break;
1787
1788         case minsec_show_seconds:
1789                 for (n = 0; n < minsec_nmarks; pos += minsec_mark_interval, ++n) {
1790                         sample_to_clock_parts (pos, _session->frame_rate(), &hrs, &mins, &secs, &millisecs);
1791                         if (secs % minsec_mark_modulo == 0) {
1792                                 if (secs == 0) {
1793                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1794                                 } else {
1795                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1796                                 }
1797                                 snprintf (buf, sizeof(buf), "%02ld:%02ld:%02ld", hrs, mins, secs);
1798                         } else {
1799                                 buf[0] = '\0';
1800                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1801                         }
1802                         mark.label = buf;
1803                         mark.position = pos/1000.0;
1804                         marks.push_back (mark);
1805                 }
1806                 break;
1807
1808         case minsec_show_minutes:
1809                 for (n = 0; n < minsec_nmarks; pos += minsec_mark_interval, ++n) {
1810                         sample_to_clock_parts (pos, _session->frame_rate(), &hrs, &mins, &secs, &millisecs);
1811                         if (mins % minsec_mark_modulo == 0) {
1812                                 if (mins == 0) {
1813                                         mark.style = ArdourCanvas::Ruler::Mark::Major;
1814                                 } else {
1815                                         mark.style = ArdourCanvas::Ruler::Mark::Minor;
1816                                 }
1817                                 snprintf (buf, sizeof(buf), "%02ld:%02ld:%02ld", hrs, mins, secs);
1818                         } else {
1819                                 buf[0] = '\0';
1820                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1821                         }
1822                         mark.label = buf;
1823                         mark.position = pos/1000.0;
1824                         marks.push_back (mark);
1825                 }
1826                 break;
1827
1828         case minsec_show_hours:
1829                  for (n = 0; n < minsec_nmarks; pos += minsec_mark_interval, ++n) {
1830                         sample_to_clock_parts (pos, _session->frame_rate(), &hrs, &mins, &secs, &millisecs);
1831                         if (hrs % minsec_mark_modulo == 0) {
1832                                 mark.style = ArdourCanvas::Ruler::Mark::Major;
1833                                 snprintf (buf, sizeof(buf), "%02ld:%02ld", hrs, mins);
1834                         } else {
1835                                 buf[0] = '\0';
1836                                 mark.style = ArdourCanvas::Ruler::Mark::Micro;
1837                         }
1838                         mark.label = buf;
1839                         mark.position = pos/1000.0;
1840                         marks.push_back (mark);
1841                  }
1842                  break;
1843
1844         case minsec_show_many_hours:
1845                 for (n = 0; n < minsec_nmarks; ) {
1846                         sample_to_clock_parts (pos, _session->frame_rate(), &hrs, &mins, &secs, &millisecs);
1847                         if (hrs % minsec_mark_modulo == 0) {
1848                                 mark.style = ArdourCanvas::Ruler::Mark::Major;
1849                                 snprintf (buf, sizeof(buf), "%02ld:00", hrs);
1850                                 mark.label = buf;
1851                                 mark.position = pos/1000.0;
1852                                 marks.push_back (mark);
1853                                 ++n;
1854                         }
1855                         pos += minsec_mark_interval;
1856                 }
1857                 break;
1858         }
1859 }