1 /*
2  * Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
3  * Copyright (C) 2005-2018 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2006-2012 David Robillard <d@drobilla.net>
5  * Copyright (C) 2007 Doug McLain <doug@nostar.net>
6  * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
7  * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
8  * Copyright (C) 2013-2017 John Emmas <john@creativepost.co.uk>
9  * Copyright (C) 2015-2016 Tim Mayberry <mojofunk@gmail.com>
10  * Copyright (C) 2015 Ben Loftis <ben@harrisonconsoles.com>
11  * Copyright (C) 2015 Colin Fletcher <colin.m.fletcher@googlemail.com>
12  * Copyright (C) 2016-2017 Nick Mainsbridge <mainsbridge@gmail.com>
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License along
25  * with this program; if not, write to the Free Software Foundation, Inc.,
26  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27  */
28 
29 #include <cstdio> // for sprintf
30 #include <cmath>
31 
32 #include "pbd/convert.h"
33 #include "pbd/enumwriter.h"
34 
35 #include <gtkmm/style.h>
36 #include <sigc++/bind.h>
37 
38 #include "gtkmm2ext/utils.h"
39 #include "gtkmm2ext/rgb_macros.h"
40 
41 #include "widgets/tooltips.h"
42 
43 #include "ardour/profile.h"
44 #include "ardour/lmath.h"
45 #include "ardour/session.h"
46 #include "ardour/transport_master.h"
47 #include "ardour/tempo.h"
48 #include "ardour/transport_master_manager.h"
49 #include "ardour/types.h"
50 
51 #include "ardour_ui.h"
52 #include "audio_clock.h"
53 #include "enums_convert.h"
54 #include "gui_thread.h"
55 #include "keyboard.h"
56 #include "ui_config.h"
57 #include "utils.h"
58 
59 #include "pbd/i18n.h"
60 
61 using namespace ARDOUR;
62 using namespace ARDOUR_UI_UTILS;
63 using namespace ArdourWidgets;
64 using namespace PBD;
65 using namespace Gtk;
66 using namespace std;
67 
68 using Gtkmm2ext::Keyboard;
69 
70 sigc::signal<void> AudioClock::ModeChanged;
71 vector<AudioClock*> AudioClock::clocks;
72 
73 #define BBT_BAR_CHAR "|"
74 #define BBT_SCANF_FORMAT "%" PRIu32 "%*c%" PRIu32 "%*c%" PRIu32
75 
AudioClock(const string & clock_name,bool transient,const string & widget_name,bool allow_edit,bool follows_playhead,bool duration,bool with_info,bool accept_on_focus_out)76 AudioClock::AudioClock (const string& clock_name, bool transient, const string& widget_name,
77 			bool allow_edit, bool follows_playhead, bool duration, bool with_info,
78 			bool accept_on_focus_out)
79 	: ops_menu (0)
80 	, _name (clock_name)
81 	, is_transient (transient)
82 	, is_duration (duration)
83 	, editable (allow_edit)
84 	, _follows_playhead (follows_playhead)
85 	, _accept_on_focus_out (accept_on_focus_out)
86 	, _off (false)
87 	, em_width (0)
88 	, _edit_by_click_field (false)
89 	, _negative_allowed (false)
90 	, edit_is_negative (false)
91 	, _limit_pos (INT64_MAX - 1)
92 	, _with_info (with_info)
93 	, editing_attr (0)
94 	, foreground_attr (0)
95 	, first_height (0)
96 	, first_width (0)
97 	, style_resets_first (true)
98 	, layout_height (0)
99 	, layout_width (0)
100 	, corner_radius (4)
101 	, font_size (10240)
102 	, editing (false)
103 	, bbt_reference_time (-1)
104 	, last_when(0)
105 	, last_pdelta (0)
106 	, last_sdelta (0)
107 	, dragging (false)
108 	, drag_field (Field (0))
109 	, xscale (1.0)
110 	, yscale (1.0)
111 {
112 	if (editable) {
113 		set_flags (CAN_FOCUS);
114 	}
115 
116 	_layout = Pango::Layout::create (get_pango_context());
117 	_layout->set_attributes (normal_attributes);
118 
119 	set_widget_name (widget_name);
120 
121 	_mode = BBT; /* lie to force mode switch */
122 	set_mode (Timecode);
123 	AudioClock::set (last_when, true);
124 
125 	if (!is_transient) {
126 		clocks.push_back (this);
127 	}
128 
129 	_left_btn.set_name ("transport option button");
130 	_right_btn.set_name ("transport option button");
131 
132 	_left_btn.set_sizing_text (_("0000000000000"));
133 	_right_btn.set_sizing_text (_("0000000000000"));
134 
135 	_left_btn.set_layout_font (UIConfiguration::instance().get_SmallMonospaceFont());
136 	_right_btn.set_layout_font (UIConfiguration::instance().get_SmallMonospaceFont());
137 
138 	UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &AudioClock::set_colors));
139 	UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &AudioClock::dpi_reset));
140 }
141 
~AudioClock()142 AudioClock::~AudioClock ()
143 {
144 	delete ops_menu;
145 	delete foreground_attr;
146 	delete editing_attr;
147 }
148 
149 void
set_widget_name(const string & str)150 AudioClock::set_widget_name (const string& str)
151 {
152 	if (str.empty()) {
153 		set_name ("clock");
154 	} else {
155 		set_name (str + " clock");
156 	}
157 
158 	if (is_realized()) {
159 		set_colors ();
160 	}
161 }
162 
163 
164 void
on_realize()165 AudioClock::on_realize ()
166 {
167 	Gtk::Requisition req;
168 
169 	CairoWidget::on_realize ();
170 
171 	set_clock_dimensions (req);
172 
173 	first_width = req.width;
174 	first_height = req.height;
175 
176 	set_colors ();
177 }
178 
179 void
set_active_state(Gtkmm2ext::ActiveState s)180 AudioClock::set_active_state (Gtkmm2ext::ActiveState s)
181 {
182 	CairoWidget::set_active_state (s);
183 	set_colors ();
184 }
185 
186 void
set_colors()187 AudioClock::set_colors ()
188 {
189 	int r, g, b, a;
190 
191 	uint32_t bg_color;
192 	uint32_t text_color;
193 	uint32_t editing_color;
194 	uint32_t cursor_color;
195 
196 	if (active_state()) {
197 		bg_color = UIConfiguration::instance().color (string_compose ("%1 active: background", get_name()));
198 		text_color = UIConfiguration::instance().color (string_compose ("%1 active: text", get_name()));
199 		editing_color = UIConfiguration::instance().color (string_compose ("%1 active: edited text", get_name()));
200 		cursor_color = UIConfiguration::instance().color (string_compose ("%1 active: cursor", get_name()));
201 	} else {
202 		bg_color = UIConfiguration::instance().color (string_compose ("%1: background", get_name()));
203 		text_color = UIConfiguration::instance().color (string_compose ("%1: text", get_name()));
204 		editing_color = UIConfiguration::instance().color (string_compose ("%1: edited text", get_name()));
205 		cursor_color = UIConfiguration::instance().color (string_compose ("%1: cursor", get_name()));
206 	}
207 
208 	/* store for bg and cursor in render() */
209 
210 	UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
211 
212 	bg_r = r/255.0;
213 	bg_g = g/255.0;
214 	bg_b = b/255.0;
215 	bg_a = a/255.0;
216 
217 	UINT_TO_RGBA (cursor_color, &r, &g, &b, &a);
218 
219 	cursor_r = r/255.0;
220 	cursor_g = g/255.0;
221 	cursor_b = b/255.0;
222 	cursor_a = a/255.0;
223 
224 	/* rescale for Pango colors ... sigh */
225 
226 	r = lrint (r * 65535.0);
227 	g = lrint (g * 65535.0);
228 	b = lrint (b * 65535.0);
229 
230 	UINT_TO_RGBA (text_color, &r, &g, &b, &a);
231 	r = lrint ((r/255.0) * 65535.0);
232 	g = lrint ((g/255.0) * 65535.0);
233 	b = lrint ((b/255.0) * 65535.0);
234 	delete foreground_attr;
235 	foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
236 
237 	UINT_TO_RGBA (editing_color, &r, &g, &b, &a);
238 	r = lrint ((r/255.0) * 65535.0);
239 	g = lrint ((g/255.0) * 65535.0);
240 	b = lrint ((b/255.0) * 65535.0);
241 	delete editing_attr;
242 	editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
243 
244 	normal_attributes.change (*foreground_attr);
245 	editing_attributes.change (*foreground_attr);
246 	editing_attributes.change (*editing_attr);
247 
248 	if (!editing) {
249 		_layout->set_attributes (normal_attributes);
250 	} else {
251 		_layout->set_attributes (editing_attributes);
252 	}
253 
254 	queue_draw ();
255 }
256 
257 void
set_scale(double x,double y)258 AudioClock::set_scale (double x, double y)
259 {
260 	xscale = x;
261 	yscale = y;
262 
263 	queue_draw ();
264 }
265 
266 void
render(Cairo::RefPtr<Cairo::Context> const & ctx,cairo_rectangle_t *)267 AudioClock::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t*)
268 {
269 	cairo_t* cr = ctx->cobj();
270 	/* main layout: rounded rect, plus the text */
271 
272 	if (_need_bg) {
273 		cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
274 		if (corner_radius) {
275 			Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), corner_radius);
276 		} else {
277 			cairo_rectangle (cr, 0, 0, get_width(), get_height());
278 		}
279 		cairo_fill (cr);
280 	}
281 
282 	double lw = layout_width * xscale;
283 	double lh = layout_height * yscale;
284 
285 	cairo_move_to (cr, (get_width() - lw) / 2.0, (get_height() - lh) / 2.0);
286 
287 	if (xscale != 1.0 || yscale != 1.0) {
288 		cairo_save (cr);
289 		cairo_scale (cr, xscale, yscale);
290 	}
291 
292 	pango_cairo_show_layout (cr, _layout->gobj());
293 
294 	if (xscale != 1.0 || yscale != 1.0) {
295 		cairo_restore (cr);
296 	}
297 
298 	if (editing) {
299 		if (!insert_map.empty()) {
300 
301 			int xcenter = (get_width() - layout_width) /2;
302 
303 			if (input_string.length() < insert_map.size()) {
304 				Pango::Rectangle cursor;
305 
306 				if (input_string.empty()) {
307 					/* nothing entered yet, put cursor at the end
308 					   of string
309 					*/
310 					cursor = _layout->get_cursor_strong_pos (edit_string.length());
311 				} else {
312 					cursor = _layout->get_cursor_strong_pos (1 + insert_map[input_string.length()]);
313 				}
314 
315 				cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
316 				cairo_rectangle (cr,
317 				                 min (get_width() - 2.0,
318 				                 (double) xcenter + cursor.get_x()/PANGO_SCALE + em_width),
319 				                 (get_height() - layout_height)/2.0,
320 				                 2.0, cursor.get_height()/PANGO_SCALE);
321 				cairo_fill (cr);
322 			} else {
323 				/* we've entered all possible digits, no cursor */
324 			}
325 
326 		} else {
327 			if (input_string.empty()) {
328 				cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
329 				cairo_rectangle (cr,
330 						 (get_width()/2.0),
331 						 (get_height() - layout_height)/2.0,
332 						 2.0, get_height());
333 				cairo_fill (cr);
334 			}
335 		}
336 	}
337 }
338 
339 void
set_clock_dimensions(Gtk::Requisition & req)340 AudioClock::set_clock_dimensions (Gtk::Requisition& req)
341 {
342 	Glib::RefPtr<Pango::Layout> tmp;
343 	Glib::RefPtr<Gtk::Style> style = get_style ();
344 	Pango::FontDescription font;
345 
346 	tmp = Pango::Layout::create (get_pango_context());
347 
348 	if (!is_realized()) {
349 		font = get_font_for_style (get_name());
350 	} else {
351 		font = style->get_font();
352 	}
353 
354 	tmp->set_font_description (font);
355 
356 	/* this string is the longest thing we will ever display */
357 	if (_mode == MinSec)
358 		tmp->set_text (" 88:88:88,888 ");
359 	else
360 		tmp->set_text (" 88:88:88,88 ");
361 	tmp->get_pixel_size (req.width, req.height);
362 
363 	layout_height = req.height;
364 	layout_width = req.width;
365 }
366 
367 void
on_size_request(Gtk::Requisition * req)368 AudioClock::on_size_request (Gtk::Requisition* req)
369 {
370 	/* even for non fixed width clocks, the size we *ask* for never changes,
371 	   even though the size we receive might. so once we've computed it,
372 	   just return it.
373 	*/
374 
375 	if (first_width) {
376 		req->width = first_width;
377 		req->height = first_height;
378 		return;
379 	}
380 
381 	set_clock_dimensions (*req);
382 
383 	/* now tackle height, for which we need to know the height of the lower
384 	 * layout
385 	 */
386 }
387 
388 void
show_edit_status(int length)389 AudioClock::show_edit_status (int length)
390 {
391 	editing_attr->set_start_index (edit_string.length() - length);
392 	editing_attr->set_end_index (edit_string.length());
393 
394 	editing_attributes.change (*foreground_attr);
395 	editing_attributes.change (*editing_attr);
396 
397 	_layout->set_attributes (editing_attributes);
398 }
399 
400 void
start_edit(Field f)401 AudioClock::start_edit (Field f)
402 {
403 	if (!editing) {
404 		pre_edit_string = _layout->get_text ();
405 		if (!insert_map.empty()) {
406 			edit_string = pre_edit_string;
407 		} else {
408 			edit_string.clear ();
409 			_layout->set_text ("");
410 		}
411 
412 		input_string.clear ();
413 		editing = true;
414 		edit_is_negative = false;
415 
416 		if (f) {
417 			input_string = get_field (f);
418 			show_edit_status (merge_input_and_edit_string ());
419 			_layout->set_text (edit_string);
420 		}
421 
422 		queue_draw ();
423 
424 		Keyboard::magic_widget_grab_focus ();
425 		grab_focus ();
426 	}
427 }
428 
429 string
get_field(Field f)430 AudioClock::get_field (Field f)
431 {
432 	switch (f) {
433 	case Timecode_Hours:
434 		return edit_string.substr (1, 2);
435 		break;
436 	case Timecode_Minutes:
437 		return edit_string.substr (4, 2);
438 		break;
439 	case Timecode_Seconds:
440 		return edit_string.substr (7, 2);
441 		break;
442 	case Timecode_frames:
443 		return edit_string.substr (10, 2);
444 		break;
445 	case MS_Hours:
446 		return edit_string.substr (1, 2);
447 		break;
448 	case MS_Minutes:
449 		return edit_string.substr (4, 2);
450 		break;
451 	case MS_Seconds:
452 		return edit_string.substr (7, 2);
453 		break;
454 	case MS_Milliseconds:
455 		return edit_string.substr (10, 3);
456 		break;
457 	case Bars:
458 		return edit_string.substr (1, 3);
459 		break;
460 	case Beats:
461 		return edit_string.substr (5, 2);
462 		break;
463 	case Ticks:
464 		return edit_string.substr (8, 4);
465 		break;
466 	case SS_Seconds:
467 		return edit_string.substr (0, 8);
468 	case SS_Deciseconds:
469 		return edit_string.substr (9, 1);
470 	case S_Samples:
471 		return edit_string;
472 		break;
473 	}
474 	return "";
475 }
476 
477 void
end_edit(bool modify)478 AudioClock::end_edit (bool modify)
479 {
480 	if (modify) {
481 
482 		bool ok = true;
483 
484 		switch (_mode) {
485 		case Timecode:
486 			ok = timecode_validate_edit (edit_string);
487 			break;
488 
489 		case BBT:
490 			ok = bbt_validate_edit (edit_string);
491 			break;
492 
493 		case MinSec:
494 			ok = minsec_validate_edit (edit_string);
495 			break;
496 
497 		case Seconds:
498 			/* fallthrough */
499 		case Samples:
500 			if (edit_string.length() < 1) {
501 				edit_string = pre_edit_string;
502 			}
503 			break;
504 		}
505 
506 		if (!ok) {
507 			edit_string = pre_edit_string;
508 			input_string.clear ();
509 			_layout->set_text (edit_string);
510 			show_edit_status (0);
511 			/* edit attributes remain in use */
512 		} else {
513 
514 			editing = false;
515 			samplepos_t pos = 0; /* stupid gcc */
516 
517 			switch (_mode) {
518 			case Timecode:
519 				pos = samples_from_timecode_string (edit_string);
520 				break;
521 
522 			case BBT:
523 				if (is_duration) {
524 					pos = sample_duration_from_bbt_string (bbt_reference_time, edit_string);
525 				} else {
526 					pos = samples_from_bbt_string (0, edit_string);
527 				}
528 				break;
529 
530 			case MinSec:
531 				pos = samples_from_minsec_string (edit_string);
532 				break;
533 
534 			case Seconds:
535 				pos = samples_from_seconds_string (edit_string);
536 				break;
537 
538 			case Samples:
539 				pos = samples_from_audiosamples_string (edit_string);
540 				break;
541 			}
542 
543 			AudioClock::set (pos, true);
544 			_layout->set_attributes (normal_attributes);
545 			ValueChanged(); /* EMIT_SIGNAL */
546 		}
547 
548 	} else {
549 
550 		editing = false;
551 		edit_is_negative = false;
552 		_layout->set_attributes (normal_attributes);
553 		_layout->set_text (pre_edit_string);
554 	}
555 
556 	queue_draw ();
557 
558 	if (!editing) {
559 		drop_focus ();
560 	}
561 }
562 
563 void
drop_focus()564 AudioClock::drop_focus ()
565 {
566 	Keyboard::magic_widget_drop_focus ();
567 
568 	if (has_focus()) {
569 		/* move focus back to the default widget in the top level window */
570 		ARDOUR_UI::instance()->reset_focus (this);
571 	}
572 }
573 
574 samplecnt_t
parse_as_seconds_distance(const std::string & str)575 AudioClock::parse_as_seconds_distance (const std::string& str)
576 {
577 	float f;
578 
579 	if (sscanf (str.c_str(), "%f", &f) == 1) {
580 		return f * _session->sample_rate();
581 	}
582 
583 	return 0;
584 }
585 
586 samplecnt_t
parse_as_samples_distance(const std::string & str)587 AudioClock::parse_as_samples_distance (const std::string& str)
588 {
589 	samplecnt_t f;
590 
591 	if (sscanf (str.c_str(), "%" PRId64, &f) == 1) {
592 		return f;
593 	}
594 
595 	return 0;
596 }
597 
598 samplecnt_t
parse_as_minsec_distance(const std::string & str)599 AudioClock::parse_as_minsec_distance (const std::string& str)
600 {
601 	samplecnt_t sr = _session->sample_rate();
602 	int msecs;
603 	int secs;
604 	int mins;
605 	int hrs;
606 
607 	switch (str.length()) {
608 	case 0:
609 		return 0;
610 	case 1:
611 	case 2:
612 	case 3:
613 	case 4:
614 		sscanf (str.c_str(), "%" PRId32, &msecs);
615 		return msecs * (sr / 1000);
616 
617 	case 5:
618 		sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &msecs);
619 		return (secs * sr) + (msecs * (sr/1000));
620 
621 	case 6:
622 		sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &msecs);
623 		return (secs * sr) + (msecs * (sr/1000));
624 
625 	case 7:
626 		sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
627 		return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
628 
629 	case 8:
630 		sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
631 		return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
632 
633 	case 9:
634 		sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
635 		return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
636 
637 	case 10:
638 		sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
639 		return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
640 
641 	default:
642 		break;
643 	}
644 
645 	return 0;
646 }
647 
648 samplecnt_t
parse_as_timecode_distance(const std::string & str)649 AudioClock::parse_as_timecode_distance (const std::string& str)
650 {
651 	double fps = _session->timecode_frames_per_second();
652 	samplecnt_t sr = _session->sample_rate();
653 	int samples;
654 	int secs;
655 	int mins;
656 	int hrs;
657 
658 	switch (str.length()) {
659 	case 0:
660 		return 0;
661 	case 1:
662 	case 2:
663 		sscanf (str.c_str(), "%" PRId32, &samples);
664 		return llrint ((samples/(float)fps) * sr);
665 
666 	case 3:
667 		sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &samples);
668 		return (secs * sr) + llrint ((samples/(float)fps) * sr);
669 
670 	case 4:
671 		sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &samples);
672 		return (secs * sr) + llrint ((samples/(float)fps) * sr);
673 
674 	case 5:
675 		sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &samples);
676 		return (mins * 60 * sr) + (secs * sr) + llrint ((samples/(float)fps) * sr);
677 
678 	case 6:
679 		sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &samples);
680 		return (mins * 60 * sr) + (secs * sr) + llrint ((samples/(float)fps) * sr);
681 
682 	case 7:
683 		sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &samples);
684 		return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((samples/(float)fps) * sr);
685 
686 	case 8:
687 		sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &samples);
688 		return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((samples/(float)fps) * sr);
689 
690 	default:
691 		break;
692 	}
693 
694 	return 0;
695 }
696 
697 samplecnt_t
parse_as_bbt_distance(const std::string &)698 AudioClock::parse_as_bbt_distance (const std::string&)
699 {
700 	return 0;
701 }
702 
703 samplecnt_t
parse_as_distance(const std::string & instr)704 AudioClock::parse_as_distance (const std::string& instr)
705 {
706 	switch (_mode) {
707 	case Timecode:
708 		return parse_as_timecode_distance (instr);
709 		break;
710 	case Samples:
711 		return parse_as_samples_distance (instr);
712 		break;
713 	case BBT:
714 		return parse_as_bbt_distance (instr);
715 		break;
716 	case MinSec:
717 		return parse_as_minsec_distance (instr);
718 		break;
719 	case Seconds:
720 		return parse_as_seconds_distance (instr);
721 		break;
722 	}
723 	return 0;
724 }
725 
726 void
end_edit_relative(bool add)727 AudioClock::end_edit_relative (bool add)
728 {
729 	bool ok = true;
730 
731 	switch (_mode) {
732 	case Timecode:
733 		ok = timecode_validate_edit (edit_string);
734 		break;
735 
736 	case BBT:
737 		ok = bbt_validate_edit (edit_string);
738 		break;
739 
740 	case MinSec:
741 		ok = minsec_validate_edit (edit_string);
742 		break;
743 
744 	case Seconds:
745 		break;
746 
747 	case Samples:
748 		break;
749 	}
750 
751 	if (!ok) {
752 		edit_string = pre_edit_string;
753 		input_string.clear ();
754 		_layout->set_text (edit_string);
755 		show_edit_status (0);
756 		/* edit attributes remain in use */
757 		queue_draw ();
758 		return;
759 	}
760 
761 	samplecnt_t samples = parse_as_distance (input_string);
762 
763 	editing = false;
764 
765 	editing = false;
766 	_layout->set_attributes (normal_attributes);
767 
768 	if (samples != 0) {
769 		if (add) {
770 			AudioClock::set (current_time() + samples, true);
771 		} else {
772 			samplepos_t c = current_time();
773 
774 			if (c > samples || _negative_allowed) {
775 				AudioClock::set (c - samples, true);
776 			} else {
777 				AudioClock::set (0, true);
778 			}
779 		}
780 		ValueChanged (); /* EMIT SIGNAL */
781 	}
782 
783 	input_string.clear ();
784 	queue_draw ();
785 	drop_focus ();
786 }
787 
788 void
session_property_changed(const PropertyChange &)789 AudioClock::session_property_changed (const PropertyChange&)
790 {
791 	AudioClock::set (last_when, true);
792 }
793 
794 void
session_configuration_changed(std::string p)795 AudioClock::session_configuration_changed (std::string p)
796 {
797 	if (_negative_allowed) {
798 		/* session option editor clock */
799 		return;
800 	}
801 
802 	if (p == "sync-source" || p == "external-sync") {
803 		AudioClock::set (current_time(), true);
804 		return;
805 	}
806 
807 	if (p != "timecode-offset" && p != "timecode-offset-negative") {
808 		return;
809 	}
810 
811 	samplecnt_t current;
812 
813 	switch (_mode) {
814 	case Timecode:
815 		if (is_duration) {
816 			current = current_duration ();
817 		} else {
818 			current = current_time ();
819 		}
820 		AudioClock::set (current, true);
821 		break;
822 	default:
823 		break;
824 	}
825 }
826 
827 void
set(samplepos_t when,bool force,samplecnt_t offset)828 AudioClock::set (samplepos_t when, bool force, samplecnt_t offset)
829 {
830 	if ((!force && !is_visible()) || _session == 0) {
831 		return;
832 	}
833 
834 	_offset = offset;
835 	if (is_duration) {
836 		when = when - offset;
837 	}
838 
839 	if (when > _limit_pos) {
840 		when = _limit_pos;
841 	} else if (when < -_limit_pos) {
842 		when = -_limit_pos;
843 	}
844 
845 	if (when == last_when && !force) {
846 #if 0 // XXX return if no change and no change forced. verify Aug/2014
847 		if (_mode != Timecode && _mode != MinSec) {
848 			/* may need to force display of TC source
849 			 * time, so don't return early.
850 			 */
851 			/* ^^ Why was that?,  delta times?
852 			 * Timecode FPS, pull-up/down, etc changes
853 			 * trigger a 'session_property_changed' which
854 			 * eventually calls set(last_when, true)
855 			 *
856 			 * re-rendering the clock every 40ms or so just
857 			 * because we can is not ideal.
858 			 */
859 			return;
860 		}
861 #else
862 		return;
863 #endif
864 	}
865 
866 	bool btn_en = false;
867 
868 	if (!editing) {
869 
870 		switch (_mode) {
871 		case Timecode:
872 			set_timecode (when, force);
873 			break;
874 
875 		case BBT:
876 			set_bbt (when, offset, force);
877 			btn_en = true;
878 			break;
879 
880 		case MinSec:
881 			set_minsec (when, force);
882 			break;
883 
884 		case Seconds:
885 			set_seconds (when, force);
886 			break;
887 
888 		case Samples:
889 			set_samples (when, force);
890 			break;
891 		}
892 	}
893 
894 	if (_with_info) {
895 		_left_btn.set_sensitive (btn_en);
896 		_right_btn.set_sensitive (btn_en);
897 		_left_btn.set_visual_state (Gtkmm2ext::NoVisualState);
898 		_right_btn.set_visual_state (Gtkmm2ext::NoVisualState);
899 		if (btn_en) {
900 			_left_btn.set_elements (ArdourButton::Element(ArdourButton::Edge|ArdourButton::Body|ArdourButton::Text));
901 			_right_btn.set_elements (ArdourButton::Element(ArdourButton::Edge|ArdourButton::Body|ArdourButton::Text));
902 			_left_btn.set_alignment (.5, .5);
903 			_right_btn.set_alignment (.5, .5);
904 			set_tooltip (_left_btn, _("Change current tempo"));
905 			set_tooltip (_right_btn, _("Change current time signature"));
906 		} else {
907 			_left_btn.set_elements (ArdourButton::Text);
908 			_right_btn.set_elements (ArdourButton::Text);
909 			_left_btn.set_alignment (0, .5);
910 			_right_btn.set_alignment (1, .5);
911 			set_tooltip (_left_btn, _(""));
912 			set_tooltip (_right_btn, _(""));
913 		}
914 	}
915 
916 	queue_draw ();
917 	last_when = when;
918 }
919 
920 void
set_slave_info()921 AudioClock::set_slave_info ()
922 {
923 	if (!_with_info || !_session) {
924 		return;
925 	}
926 
927 	boost::shared_ptr<TransportMaster> tm = TransportMasterManager::instance().current();
928 
929 	if (_session->transport_master_is_external()) {
930 
931 		switch (tm->type()) {
932 		case Engine:
933 			_left_btn.set_text (tm->name(), true);
934 			_right_btn.set_text ("", true);
935 			break;
936 		case MIDIClock:
937 			if (tm) {
938 				_left_btn.set_text (tm->display_name(), true);
939 				_right_btn.set_text (tm->delta_string (), true);
940 			} else {
941 				_left_btn.set_text (_("--pending--"), true);
942 				_right_btn.set_text ("", true);
943 			}
944 			break;
945 		case LTC:
946 		case MTC:
947 			if (tm) {
948 				boost::shared_ptr<TimecodeTransportMaster> tcmaster;
949 				if ((tcmaster = boost::dynamic_pointer_cast<TimecodeTransportMaster>(tm)) != 0) {
950 					//TODO: _left_btn.set_name () //  tcmaster->apparent_timecode_format() != _session->config.get_timecode_format();
951 					_left_btn.set_text (string_compose ("%1%2", tm->display_name()[0], tcmaster->position_string ()), false);
952 					_right_btn.set_text (tm->delta_string (), false);
953 				}
954 			} else {
955 				_left_btn.set_text (_("--pending--"), false);
956 				_right_btn.set_text ("", false);
957 			}
958 			break;
959 		}
960 	} else {
961 		_left_btn.set_text (string_compose ("%1/%2", _("INT"), tm->display_name()), false);
962 		_right_btn.set_text ("", false);
963 	}
964 }
965 
966 void
set_out_of_bounds(bool negative)967 AudioClock::set_out_of_bounds (bool negative)
968 {
969 	if (is_duration) {
970 		if (negative) {
971 			_layout->set_text (" >>> -- <<< ");
972 		} else {
973 			_layout->set_text (" >>> ++ <<< ");
974 		}
975 	} else {
976 		if (negative) {
977 			_layout->set_text (" <<<<<<<<<< ");
978 		} else {
979 			_layout->set_text (" >>>>>>>>>> ");
980 		}
981 	}
982 }
983 
984 void
set_samples(samplepos_t when,bool)985 AudioClock::set_samples (samplepos_t when, bool /*force*/)
986 {
987 	char buf[32];
988 	bool negative = false;
989 
990 	if (_off) {
991 		_layout->set_text (" ----------");
992 		_left_btn.set_text ("", false);
993 		_right_btn.set_text ("", false);
994 		return;
995 	}
996 
997 	if (when < 0) {
998 		when = -when;
999 		negative = true;
1000 	}
1001 
1002 	if (when >= _limit_pos) {
1003 		set_out_of_bounds (negative);
1004 	} else if (negative) {
1005 		snprintf (buf, sizeof (buf), "-%10" PRId64, when);
1006 		_layout->set_text (buf);
1007 	} else {
1008 		snprintf (buf, sizeof (buf), " %10" PRId64, when);
1009 		_layout->set_text (buf);
1010 	}
1011 
1012 	if (_with_info) {
1013 		samplecnt_t rate = _session->sample_rate();
1014 
1015 		if (fmod (rate, 100.0) == 0.0) {
1016 			sprintf (buf, "%.1fkHz", rate/1000.0);
1017 		} else {
1018 			sprintf (buf, "%" PRId64 "Hz", rate);
1019 		}
1020 
1021 		_left_btn.set_text (string_compose ("%1 %2", _("SR"), buf), false);
1022 
1023 		float vid_pullup = _session->config.get_video_pullup();
1024 
1025 		if (vid_pullup == 0.0) {
1026 			_right_btn.set_text ("", false);
1027 		} else {
1028 			sprintf (buf, _("%+.4f%%"), vid_pullup);
1029 			_right_btn.set_text (string_compose ("%1 %2", _("Pull"), buf), false);
1030 		}
1031 	}
1032 }
1033 
1034 void
set_seconds(samplepos_t when,bool)1035 AudioClock::set_seconds (samplepos_t when, bool /*force*/)
1036 {
1037 	char buf[32];
1038 
1039 	if (_off) {
1040 		_layout->set_text (" ----------");
1041 		_left_btn.set_text ("", false);
1042 		_right_btn.set_text ("", false);
1043 		return;
1044 	}
1045 
1046 	if (when >= _limit_pos || when <= -_limit_pos) {
1047 		set_out_of_bounds (when < 0);
1048 	} else {
1049 		if (when < 0) {
1050 			snprintf (buf, sizeof (buf), "%12.1f", when / (float)_session->sample_rate());
1051 		} else {
1052 			snprintf (buf, sizeof (buf), " %11.1f", when / (float)_session->sample_rate());
1053 		}
1054 		_layout->set_text (buf);
1055 	}
1056 
1057 	set_slave_info();
1058 }
1059 
1060 void
print_minsec(samplepos_t when,char * buf,size_t bufsize,float sample_rate,int decimals)1061 AudioClock::print_minsec (samplepos_t when, char* buf, size_t bufsize, float sample_rate, int decimals)
1062 {
1063 	samplecnt_t left;
1064 	int hrs;
1065 	int mins;
1066 	int secs;
1067 	int millisecs;
1068 	bool negative;
1069 
1070 	if (when < 0) {
1071 		when = -when;
1072 		negative = true;
1073 	} else {
1074 		negative = false;
1075 	}
1076 
1077 	float dmult;
1078 	switch (decimals) {
1079 		case 1:
1080 			dmult = 10.f;
1081 			break;
1082 		case 2:
1083 			dmult = 100.f;
1084 			break;
1085 		default:
1086 		case 3:
1087 			dmult = 1000.f;
1088 			break;
1089 	}
1090 
1091 	left = when;
1092 	hrs = (int) floor (left / (sample_rate * 60.0f * 60.0f));
1093 	left -= (samplecnt_t) floor (hrs * sample_rate * 60.0f * 60.0f);
1094 	mins = (int) floor (left / (sample_rate * 60.0f));
1095 	left -= (samplecnt_t) floor (mins * sample_rate * 60.0f);
1096 	secs = (int) floor (left / (float) sample_rate);
1097 	left -= (samplecnt_t) floor ((double)(secs * sample_rate));
1098 	millisecs = floor (left * dmult / (float) sample_rate);
1099 
1100 	switch (decimals) {
1101 		case 0:
1102 			snprintf (buf, bufsize, "%c%02" PRId32 ":%02" PRId32 ":%02" PRId32, negative ? '-' : ' ', hrs, mins, secs);
1103 			break;
1104 		case 1:
1105 			snprintf (buf, bufsize, "%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%01" PRId32, negative ? '-' : ' ', hrs, mins, secs, millisecs);
1106 			break;
1107 		case 2:
1108 			snprintf (buf, bufsize, "%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%02" PRId32, negative ? '-' : ' ', hrs, mins, secs, millisecs);
1109 			break;
1110 		default:
1111 		case 3:
1112 			snprintf (buf, bufsize, "%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, negative ? '-' : ' ', hrs, mins, secs, millisecs);
1113 			break;
1114 	}
1115 }
1116 
1117 void
set_minsec(samplepos_t when,bool)1118 AudioClock::set_minsec (samplepos_t when, bool /*force*/)
1119 {
1120 	char buf[32];
1121 
1122 	if (_off) {
1123 		_layout->set_text (" --:--:--.---");
1124 		_left_btn.set_text ("", false);
1125 		_right_btn.set_text ("", false);
1126 
1127 		return;
1128 	}
1129 
1130 	if (when >= _limit_pos || when <= -_limit_pos) {
1131 		set_out_of_bounds (when < 0);
1132 	} else {
1133 		print_minsec (when, buf, sizeof (buf), _session->sample_rate());
1134 		_layout->set_text (buf);
1135 	}
1136 
1137 	set_slave_info();
1138 }
1139 
1140 void
set_timecode(samplepos_t when,bool)1141 AudioClock::set_timecode (samplepos_t when, bool /*force*/)
1142 {
1143 	Timecode::Time TC;
1144 	bool negative = false;
1145 
1146 	if (_off) {
1147 		_layout->set_text (" --:--:--:--");
1148 		_left_btn.set_text ("", false);
1149 		_right_btn.set_text ("", false);
1150 		return;
1151 	}
1152 
1153 	if (when < 0) {
1154 		when = -when;
1155 		negative = true;
1156 	}
1157 	if (when >= _limit_pos) {
1158 		set_out_of_bounds (negative);
1159 		set_slave_info();
1160 		return;
1161 	}
1162 
1163 	if (is_duration) {
1164 		_session->timecode_duration (when, TC);
1165 	} else {
1166 		_session->timecode_time (when, TC);
1167 	}
1168 
1169 	TC.negative = TC.negative || negative;
1170 
1171 	_layout->set_text (Timecode::timecode_format_time(TC));
1172 
1173 	set_slave_info();
1174 }
1175 
1176 void
set_bbt(samplepos_t when,samplecnt_t offset,bool)1177 AudioClock::set_bbt (samplepos_t when, samplecnt_t offset, bool /*force*/)
1178 {
1179 	char buf[64];
1180 	Timecode::BBT_Time BBT;
1181 	bool negative = false;
1182 
1183 	if (_off || when >= _limit_pos || when < -_limit_pos) {
1184 		_layout->set_text (" ---|--|----");
1185 		_left_btn.set_text ("", false);
1186 		_right_btn.set_text ("", false);
1187 		return;
1188 	}
1189 
1190 	if (when < 0) {
1191 		when = -when;
1192 		negative = true;
1193 	}
1194 
1195 	/* handle a common case */
1196 	if (is_duration) {
1197 		if (when == 0) {
1198 			BBT.bars = 0;
1199 			BBT.beats = 0;
1200 			BBT.ticks = 0;
1201 		} else {
1202 			TempoMap& tmap (_session->tempo_map());
1203 
1204 			if (offset == 0) {
1205 				offset = bbt_reference_time;
1206 			}
1207 
1208 			const double divisions = tmap.meter_section_at_sample (offset).divisions_per_bar();
1209 			Timecode::BBT_Time sub_bbt;
1210 
1211 			if (negative) {
1212 				BBT = tmap.bbt_at_beat (tmap.beat_at_sample (offset));
1213 				sub_bbt = tmap.bbt_at_sample (offset - when);
1214 			} else {
1215 				BBT = tmap.bbt_at_beat (tmap.beat_at_sample (when + offset));
1216 				sub_bbt = tmap.bbt_at_sample (offset);
1217 			}
1218 
1219 			BBT.bars -= sub_bbt.bars;
1220 
1221 			if (BBT.ticks < sub_bbt.ticks) {
1222 				if (BBT.beats == 1) {
1223 					BBT.bars--;
1224 					BBT.beats = divisions;
1225 				} else {
1226 					BBT.beats--;
1227 				}
1228 				BBT.ticks = Timecode::BBT_Time::ticks_per_beat - (sub_bbt.ticks - BBT.ticks);
1229 			} else {
1230 				BBT.ticks -= sub_bbt.ticks;
1231 			}
1232 
1233 			if (BBT.beats < sub_bbt.beats) {
1234 				BBT.bars--;
1235 				BBT.beats = divisions - (sub_bbt.beats - BBT.beats);
1236 			} else {
1237 				BBT.beats -= sub_bbt.beats;
1238 			}
1239 		}
1240 	} else {
1241 		BBT = _session->tempo_map().bbt_at_sample (when);
1242 	}
1243 
1244 	if (negative) {
1245 		snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
1246 			  BBT.bars, BBT.beats, BBT.ticks);
1247 	} else {
1248 		snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
1249 			  BBT.bars, BBT.beats, BBT.ticks);
1250 	}
1251 
1252 	_layout->set_text (buf);
1253 
1254 	if (_with_info) {
1255 		samplepos_t pos;
1256 
1257 		if (bbt_reference_time < 0) {
1258 			pos = when;
1259 		} else {
1260 			pos = bbt_reference_time;
1261 		}
1262 
1263 		TempoMetric m (_session->tempo_map().metric_at (pos));
1264 
1265 #ifndef PLATFORM_WINDOWS
1266 		/* UTF8 1/4 note and 1/8 note ♩ (\u2669) and ♪ (\u266a) are n/a on Windows */
1267 		if (m.tempo().note_type() == 4) {
1268 			snprintf (buf, sizeof(buf), "\u2669 = %.3f", _session->tempo_map().tempo_at_sample (pos).note_types_per_minute());
1269 			_left_btn.set_text (string_compose ("%1", buf), false);
1270 		} else if (m.tempo().note_type() == 8) {
1271 			snprintf (buf, sizeof(buf), "\u266a = %.3f", _session->tempo_map().tempo_at_sample (pos).note_types_per_minute());
1272 			_left_btn.set_text (string_compose ("%1", buf), false);
1273 		} else
1274 #endif
1275 		{
1276 			snprintf (buf, sizeof(buf), "1/%.0f = %.3f", m.tempo().note_type(), _session->tempo_map().tempo_at_sample (pos).note_types_per_minute());
1277 			_left_btn.set_text (buf, false);
1278 		}
1279 
1280 		snprintf (buf, sizeof(buf), "%g/%g", m.meter().divisions_per_bar(), m.meter().note_divisor());
1281 		_right_btn.set_text (string_compose ("%1: %2", S_("TimeSignature|TS"), buf), false);
1282 	}
1283 }
1284 
1285 void
set_session(Session * s)1286 AudioClock::set_session (Session *s)
1287 {
1288 	SessionHandlePtr::set_session (s);
1289 
1290 	if (_session) {
1291 
1292 		int64_t limit_sec = UIConfiguration::instance().get_clock_display_limit ();
1293 		if (limit_sec > 0) {
1294 			_limit_pos = (samplecnt_t) floor ((double)(limit_sec * _session->sample_rate()));
1295 		}
1296 
1297 		Config->ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context());
1298 		_session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context());
1299 		_session->tempo_map().PropertyChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_property_changed, this, _1), gui_context());
1300 		_session->tempo_map().MetricPositionChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_property_changed, this, _1), gui_context());
1301 
1302 		XMLNode* node = _session->extra_xml (X_("ClockModes"));
1303 
1304 		if (node) {
1305 			for (XMLNodeList::const_iterator i = node->children().begin(); i != node->children().end(); ++i) {
1306 				std::string name;
1307 				if ((*i)->get_property (X_("name"), name) && name == _name) {
1308 
1309 					AudioClock::Mode amode;
1310 					if ((*i)->get_property (X_("mode"), amode)) {
1311 						set_mode (amode, true);
1312 					}
1313 					bool on;
1314 					if ((*i)->get_property (X_("on"), on)) {
1315 						set_off (!on);
1316 					}
1317 					break;
1318 				}
1319 			}
1320 		}
1321 
1322 		AudioClock::set (last_when, true);
1323 	}
1324 }
1325 
1326 bool
on_key_press_event(GdkEventKey * ev)1327 AudioClock::on_key_press_event (GdkEventKey* ev)
1328 {
1329 	if (!editing) {
1330 		return false;
1331 	}
1332 
1333 	string new_text;
1334 	char new_char = 0;
1335 	int highlight_length;
1336 	samplepos_t pos;
1337 
1338 	switch (ev->keyval) {
1339 	case GDK_0:
1340 	case GDK_KP_0:
1341 		new_char = '0';
1342 		break;
1343 	case GDK_1:
1344 	case GDK_KP_1:
1345 		new_char = '1';
1346 		break;
1347 	case GDK_2:
1348 	case GDK_KP_2:
1349 		new_char = '2';
1350 		break;
1351 	case GDK_3:
1352 	case GDK_KP_3:
1353 		new_char = '3';
1354 		break;
1355 	case GDK_4:
1356 	case GDK_KP_4:
1357 		new_char = '4';
1358 		break;
1359 	case GDK_5:
1360 	case GDK_KP_5:
1361 		new_char = '5';
1362 		break;
1363 	case GDK_6:
1364 	case GDK_KP_6:
1365 		new_char = '6';
1366 		break;
1367 	case GDK_7:
1368 	case GDK_KP_7:
1369 		new_char = '7';
1370 		break;
1371 	case GDK_8:
1372 	case GDK_KP_8:
1373 		new_char = '8';
1374 		break;
1375 	case GDK_9:
1376 	case GDK_KP_9:
1377 		new_char = '9';
1378 		break;
1379 
1380 	case GDK_minus:
1381 	case GDK_KP_Subtract:
1382 		if (_negative_allowed && input_string.empty()) {
1383 				edit_is_negative = true;
1384 				edit_string.replace(0,1,"-");
1385 				_layout->set_text (edit_string);
1386 				queue_draw ();
1387 		} else {
1388 			end_edit_relative (false);
1389 		}
1390 		return true;
1391 		break;
1392 
1393 	case GDK_plus:
1394 		end_edit_relative (true);
1395 		return true;
1396 		break;
1397 
1398 	case GDK_Tab:
1399 	case GDK_Return:
1400 	case GDK_KP_Enter:
1401 		end_edit (true);
1402 		return true;
1403 		break;
1404 
1405 	case GDK_Escape:
1406 		end_edit (false);
1407 		ChangeAborted();  /*  EMIT SIGNAL  */
1408 		return true;
1409 
1410 	case GDK_Delete:
1411 	case GDK_BackSpace:
1412 		if (!input_string.empty()) {
1413 			/* delete the last key entered
1414 			*/
1415 			input_string = input_string.substr (0, input_string.length() - 1);
1416 		}
1417 		goto use_input_string;
1418 
1419 	default:
1420 		/* do not allow other keys to passthru to the rest of the GUI
1421 		   when editing.
1422 		*/
1423 		return true;
1424 	}
1425 
1426 	if (!insert_map.empty() && (input_string.length() >= insert_map.size())) {
1427 		/* too many digits: eat the key event, but do nothing with it */
1428 		return true;
1429 	}
1430 
1431 	input_string.push_back (new_char);
1432 
1433   use_input_string:
1434 
1435 	switch (_mode) {
1436 	case Samples:
1437 		/* get this one in the right order, and to the right width */
1438 		if (ev->keyval == GDK_Delete || ev->keyval == GDK_BackSpace) {
1439 			edit_string = edit_string.substr (0, edit_string.length() - 1);
1440 		} else {
1441 			edit_string.push_back (new_char);
1442 		}
1443 		if (!edit_string.empty()) {
1444 			char buf[32];
1445 			sscanf (edit_string.c_str(), "%" PRId64, &pos);
1446 			snprintf (buf, sizeof (buf), " %10" PRId64, pos);
1447 			edit_string = buf;
1448 		}
1449 		/* highlight the whole thing */
1450 		highlight_length = edit_string.length();
1451 		break;
1452 
1453 	default:
1454 		highlight_length = merge_input_and_edit_string ();
1455 	}
1456 
1457 	if (edit_is_negative) {
1458 		edit_string.replace(0,1,"-");
1459 	} else {
1460 		if (!pre_edit_string.empty() && (pre_edit_string.at(0) == '-')) {
1461 			edit_string.replace(0,1,"_");
1462 		} else {
1463 			edit_string.replace(0,1," ");
1464 		}
1465 	}
1466 
1467 	show_edit_status (highlight_length);
1468 	_layout->set_text (edit_string);
1469 	queue_draw ();
1470 
1471 	return true;
1472 }
1473 
1474 int
merge_input_and_edit_string()1475 AudioClock::merge_input_and_edit_string ()
1476 {
1477 	/* merge with pre-edit-string into edit string */
1478 
1479 	edit_string = pre_edit_string;
1480 
1481 	if (input_string.empty()) {
1482 		return 0;
1483 	}
1484 
1485 	string::size_type target;
1486 	for (string::size_type i = 0; i < input_string.length(); ++i) {
1487 		target = insert_map[input_string.length() - 1 - i];
1488 		edit_string[target] = input_string[i];
1489 	}
1490 	/* highlight from end to wherever the last character was added */
1491 	return edit_string.length() - insert_map[input_string.length()-1];
1492 }
1493 
1494 
1495 bool
on_key_release_event(GdkEventKey * ev)1496 AudioClock::on_key_release_event (GdkEventKey *ev)
1497 {
1498 	if (!editing) {
1499 		return false;
1500 	}
1501 
1502 	/* return true for keys that we used on press
1503 	   so that they cannot possibly do double-duty
1504 	*/
1505 	switch (ev->keyval) {
1506 	case GDK_0:
1507 	case GDK_KP_0:
1508 	case GDK_1:
1509 	case GDK_KP_1:
1510 	case GDK_2:
1511 	case GDK_KP_2:
1512 	case GDK_3:
1513 	case GDK_KP_3:
1514 	case GDK_4:
1515 	case GDK_KP_4:
1516 	case GDK_5:
1517 	case GDK_KP_5:
1518 	case GDK_6:
1519 	case GDK_KP_6:
1520 	case GDK_7:
1521 	case GDK_KP_7:
1522 	case GDK_8:
1523 	case GDK_KP_8:
1524 	case GDK_9:
1525 	case GDK_KP_9:
1526 	case GDK_period:
1527 	case GDK_comma:
1528 	case GDK_KP_Decimal:
1529 	case GDK_Tab:
1530 	case GDK_Return:
1531 	case GDK_KP_Enter:
1532 	case GDK_Escape:
1533 	case GDK_minus:
1534 	case GDK_plus:
1535 	case GDK_KP_Add:
1536 	case GDK_KP_Subtract:
1537 		return true;
1538 	default:
1539 		return false;
1540 	}
1541 }
1542 
1543 AudioClock::Field
index_to_field(int index) const1544 AudioClock::index_to_field (int index) const
1545 {
1546 	switch (_mode) {
1547 	case Timecode:
1548 		if (index < 4) {
1549 			return Timecode_Hours;
1550 		} else if (index < 7) {
1551 			return Timecode_Minutes;
1552 		} else if (index < 10) {
1553 			return Timecode_Seconds;
1554 		} else {
1555 			return Timecode_frames;
1556 		}
1557 		break;
1558 	case BBT:
1559 		if (index < 5) {
1560 			return Bars;
1561 		} else if (index < 7) {
1562 			return Beats;
1563 		} else {
1564 			return Ticks;
1565 		}
1566 		break;
1567 	case MinSec:
1568 		if (index < 3) {
1569 			return Timecode_Hours;
1570 		} else if (index < 6) {
1571 			return MS_Minutes;
1572 		} else if (index < 9) {
1573 			return MS_Seconds;
1574 		} else {
1575 			return MS_Milliseconds;
1576 		}
1577 		break;
1578 	case Seconds:
1579 		if (index < 10) {
1580 			return SS_Seconds;
1581 		} else {
1582 			return SS_Deciseconds;
1583 		}
1584 		break;
1585 	case Samples:
1586 		return S_Samples;
1587 		break;
1588 	}
1589 
1590 	return Field (0);
1591 }
1592 
1593 bool
on_button_press_event(GdkEventButton * ev)1594 AudioClock::on_button_press_event (GdkEventButton *ev)
1595 {
1596 	if (!_session || _session->actively_recording()) {
1597 		/* swallow event, do nothing */
1598 		return true;
1599 	}
1600 
1601 	switch (ev->button) {
1602 	case 1:
1603 		if (editable && !_off) {
1604 			int index;
1605 			int trailing;
1606 			int y;
1607 			int x;
1608 
1609 			/* the text has been centered vertically, so adjust
1610 			 * x and y.
1611 			 */
1612 			int xcenter = (get_width() - layout_width) /2;
1613 
1614 			y = ev->y - ((get_height() - layout_height)/2);
1615 			x = ev->x - xcenter;
1616 
1617 			if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1618 				/* pretend it is a character on the far right */
1619 				index = 99;
1620 			}
1621 			drag_field = index_to_field (index);
1622 			dragging = true;
1623 			/* make absolutely sure that the pointer is grabbed */
1624 			gdk_pointer_grab(ev->window,false ,
1625 					 GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
1626 					 NULL,NULL,ev->time);
1627 			drag_accum = 0;
1628 			drag_start_y = ev->y;
1629 			drag_y = ev->y;
1630 		}
1631 		break;
1632 
1633 	default:
1634 		return false;
1635 		break;
1636 	}
1637 
1638 	return true;
1639 }
1640 
1641 bool
on_button_release_event(GdkEventButton * ev)1642 AudioClock::on_button_release_event (GdkEventButton *ev)
1643 {
1644 	if (!_session || _session->actively_recording()) {
1645 		/* swallow event, do nothing */
1646 		return true;
1647 	}
1648 
1649 	if (editable && !_off) {
1650 		if (dragging) {
1651 			gdk_pointer_ungrab (GDK_CURRENT_TIME);
1652 			dragging = false;
1653 			if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 || Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)){
1654 				// we actually dragged so return without
1655 				// setting editing focus, or we shift clicked
1656 				return true;
1657 			} else {
1658 				if (ev->button == 1) {
1659 
1660 					if (_edit_by_click_field) {
1661 
1662 						int xcenter = (get_width() - layout_width) /2;
1663 						int index = 0;
1664 						int trailing;
1665 						int y = ev->y - ((get_height() - layout_height)/2);
1666 						int x = ev->x - xcenter;
1667 						Field f;
1668 
1669 						if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1670 							return true;
1671 						}
1672 
1673 						f = index_to_field (index);
1674 
1675 						switch (f) {
1676 						case Timecode_frames:
1677 						case MS_Milliseconds:
1678 						case Ticks:
1679 						case SS_Deciseconds:
1680 							f = Field (0);
1681 							break;
1682 						default:
1683 							break;
1684 						}
1685 						start_edit (f);
1686 					} else {
1687 						start_edit ();
1688 					}
1689 				}
1690 			}
1691 		}
1692 	}
1693 
1694 	if (Keyboard::is_context_menu_event (ev)) {
1695 		if (ops_menu == 0) {
1696 			build_ops_menu ();
1697 		}
1698 		ops_menu->popup (1, ev->time);
1699 		return true;
1700 	}
1701 
1702 	return false;
1703 }
1704 
1705 bool
on_focus_out_event(GdkEventFocus * ev)1706 AudioClock::on_focus_out_event (GdkEventFocus* ev)
1707 {
1708 	bool ret = CairoWidget::on_focus_out_event (ev);
1709 
1710 	if (editing) {
1711 		end_edit (_accept_on_focus_out);
1712 	}
1713 
1714 	return ret;
1715 }
1716 
1717 bool
on_scroll_event(GdkEventScroll * ev)1718 AudioClock::on_scroll_event (GdkEventScroll *ev)
1719 {
1720 	int index;
1721 	int trailing;
1722 
1723 	if (editing || _session == 0 || !editable || _off || _session->actively_recording())  {
1724 		return false;
1725 	}
1726 
1727 	int y;
1728 	int x;
1729 
1730 	/* the text has been centered vertically, so adjust
1731 	 * x and y.
1732 	 */
1733 
1734 	int xcenter = (get_width() - layout_width) /2;
1735 	y = ev->y - ((get_height() - layout_height)/2);
1736 	x = ev->x - xcenter;
1737 
1738 	if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1739 		/* not in the main layout */
1740 		return false;
1741 	}
1742 
1743 	Field f = index_to_field (index);
1744 	samplepos_t samples = 0;
1745 
1746 	switch (ev->direction) {
1747 
1748 	case GDK_SCROLL_UP:
1749 		samples = get_sample_step (f, current_time(), 1);
1750 		if (samples != 0) {
1751 			if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1752 				samples *= 10;
1753 			}
1754 			AudioClock::set (current_time() + samples, true);
1755 			ValueChanged (); /* EMIT_SIGNAL */
1756 		}
1757 		break;
1758 
1759 	case GDK_SCROLL_DOWN:
1760 		samples = get_sample_step (f, current_time(), -1);
1761 		if (samples != 0) {
1762 			if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1763 				samples *= 10;
1764 			}
1765 
1766 			if (!_negative_allowed && (double)current_time() - (double)samples < 0.0) {
1767 				AudioClock::set (0, true);
1768 			} else {
1769 				AudioClock::set (current_time() - samples, true);
1770 			}
1771 
1772 			ValueChanged (); /* EMIT_SIGNAL */
1773 		}
1774 		break;
1775 
1776 	default:
1777 		return false;
1778 		break;
1779 	}
1780 
1781 	return true;
1782 }
1783 
1784 bool
on_motion_notify_event(GdkEventMotion * ev)1785 AudioClock::on_motion_notify_event (GdkEventMotion *ev)
1786 {
1787 	if (editing || _session == 0 || !dragging || _session->actively_recording()) {
1788 		return false;
1789 	}
1790 
1791 	float pixel_sample_scale_factor = 0.2f;
1792 
1793 	if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier))  {
1794 		pixel_sample_scale_factor = 0.1f;
1795 	}
1796 
1797 
1798 	if (Keyboard::modifier_state_contains (ev->state,
1799 	                                       Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) {
1800 		pixel_sample_scale_factor = 0.025f;
1801 	}
1802 
1803 	double y_delta = ev->y - drag_y;
1804 
1805 	drag_accum +=  y_delta*pixel_sample_scale_factor;
1806 
1807 	drag_y = ev->y;
1808 
1809 	if (floor (drag_accum) != 0) {
1810 
1811 		samplepos_t samples;
1812 		samplepos_t pos;
1813 		int dir;
1814 		dir = (drag_accum < 0 ? 1:-1);
1815 		pos = current_time();
1816 		samples = get_sample_step (drag_field, pos, dir);
1817 
1818 		if (samples  != 0 &&  samples * drag_accum < current_time()) {
1819 			AudioClock::set ((samplepos_t) floor (pos - drag_accum * samples), false); // minus because up is negative in GTK
1820 		} else {
1821 			AudioClock::set (0 , false);
1822 		}
1823 
1824 		drag_accum= 0;
1825 		ValueChanged();	 /* EMIT_SIGNAL */
1826 	}
1827 
1828 	return true;
1829 }
1830 
1831 samplepos_t
get_sample_step(Field field,samplepos_t pos,int dir)1832 AudioClock::get_sample_step (Field field, samplepos_t pos, int dir)
1833 {
1834 	samplecnt_t f = 0;
1835 	Timecode::BBT_Time BBT;
1836 	switch (field) {
1837 	case Timecode_Hours:
1838 		f = (samplecnt_t) floor (3600.0 * _session->sample_rate());
1839 		break;
1840 	case Timecode_Minutes:
1841 		f = (samplecnt_t) floor (60.0 * _session->sample_rate());
1842 		break;
1843 	case Timecode_Seconds:
1844 		f = _session->sample_rate();
1845 		break;
1846 	case Timecode_frames:
1847 		f = (samplecnt_t) floor (_session->sample_rate() / _session->timecode_frames_per_second());
1848 		break;
1849 
1850 	case S_Samples:
1851 		f = 1;
1852 		break;
1853 
1854 	case SS_Seconds:
1855 		f = (samplecnt_t) _session->sample_rate();
1856 		break;
1857 	case SS_Deciseconds:
1858 		f = (samplecnt_t) _session->sample_rate() / 10.f;
1859 		break;
1860 
1861 	case MS_Hours:
1862 		f = (samplecnt_t) floor (3600.0 * _session->sample_rate());
1863 		break;
1864 	case MS_Minutes:
1865 		f = (samplecnt_t) floor (60.0 * _session->sample_rate());
1866 		break;
1867 	case MS_Seconds:
1868 		f = (samplecnt_t) _session->sample_rate();
1869 		break;
1870 	case MS_Milliseconds:
1871 		f = (samplecnt_t) floor (_session->sample_rate() / 1000.0);
1872 		break;
1873 
1874 	case Bars:
1875 		BBT.bars = 1;
1876 		BBT.beats = 0;
1877 		BBT.ticks = 0;
1878 		f = _session->tempo_map().bbt_duration_at (pos,BBT,dir);
1879 		break;
1880 	case Beats:
1881 		BBT.bars = 0;
1882 		BBT.beats = 1;
1883 		BBT.ticks = 0;
1884 		f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1885 		break;
1886 	case Ticks:
1887 		BBT.bars = 0;
1888 		BBT.beats = 0;
1889 		BBT.ticks = 1;
1890 		f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1891 		break;
1892 	default:
1893 		error << string_compose (_("programming error: %1"), "attempt to get samples from non-text field!") << endmsg;
1894 		f = 0;
1895 		break;
1896 	}
1897 
1898 	return f;
1899 }
1900 
1901 samplepos_t
current_time(samplepos_t) const1902 AudioClock::current_time (samplepos_t) const
1903 {
1904 	return last_when;
1905 }
1906 
1907 samplepos_t
current_duration(samplepos_t pos) const1908 AudioClock::current_duration (samplepos_t pos) const
1909 {
1910 	samplepos_t ret = 0;
1911 
1912 	switch (_mode) {
1913 	case BBT:
1914 		ret = sample_duration_from_bbt_string (pos, _layout->get_text());
1915 		break;
1916 
1917 	case Timecode:
1918 	case MinSec:
1919 	case Seconds:
1920 	case Samples:
1921 		ret = last_when;
1922 		break;
1923 	}
1924 
1925 	return ret;
1926 }
1927 
1928 bool
bbt_validate_edit(string & str)1929 AudioClock::bbt_validate_edit (string & str)
1930 {
1931 	AnyTime any;
1932 
1933 	if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1934 		return false;
1935 	}
1936 
1937 	if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_beat) {
1938 		return false;
1939 	}
1940 
1941 	if (!is_duration && any.bbt.bars == 0) {
1942 		return false;
1943 	}
1944 
1945 	if (!is_duration && any.bbt.beats == 0) {
1946 		/* user could not have mean zero beats because for a
1947 		 * non-duration clock that's impossible. Assume that they
1948 		 * mis-entered things and meant Bar|1|ticks
1949 		 */
1950 
1951 		char buf[128];
1952 		snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, any.bbt.bars, 1, any.bbt.ticks);
1953 		str = buf;
1954 	}
1955 
1956 	return true;
1957 }
1958 
1959 bool
timecode_validate_edit(const string &)1960 AudioClock::timecode_validate_edit (const string&)
1961 {
1962 	Timecode::Time TC;
1963 	int hours;
1964 	char ignored[2];
1965 
1966 	if (sscanf (_layout->get_text().c_str(), "%[- _]%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32,
1967 	            ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
1968 		return false;
1969 	}
1970 
1971 	if (hours < 0) {
1972 		TC.hours = hours * -1;
1973 		TC.negative = true;
1974 	} else {
1975 		TC.hours = hours;
1976 		TC.negative = false;
1977 	}
1978 
1979 	if (TC.negative && !_negative_allowed) {
1980 		return false;
1981 	}
1982 
1983 	if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) {
1984 		return false;
1985 	}
1986 
1987 	if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) {
1988 		return false;
1989 	}
1990 
1991 	if (_session->timecode_drop_frames()) {
1992 		if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) {
1993 			return false;
1994 		}
1995 	}
1996 
1997 	return true;
1998 }
1999 
2000 bool
minsec_validate_edit(const string & str)2001 AudioClock::minsec_validate_edit (const string& str)
2002 {
2003 	int hrs, mins, secs, millisecs;
2004 
2005 	if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
2006 		return false;
2007 	}
2008 
2009 	if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) {
2010 		return false;
2011 	}
2012 
2013 	return true;
2014 }
2015 
2016 samplepos_t
samples_from_timecode_string(const string & str) const2017 AudioClock::samples_from_timecode_string (const string& str) const
2018 {
2019 	if (_session == 0) {
2020 		return 0;
2021 	}
2022 
2023 	Timecode::Time TC;
2024 	samplepos_t sample;
2025 	char ignored[2];
2026 	int hours;
2027 
2028 	if (sscanf (str.c_str(), "%[- _]%d:%d:%d%[:;]%d", ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
2029 		error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg;
2030 		return 0;
2031 	}
2032 	TC.hours = abs(hours);
2033 	TC.rate = _session->timecode_frames_per_second();
2034 	TC.drop= _session->timecode_drop_frames();
2035 
2036 	_session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
2037 
2038 	// timecode_tester ();
2039 	if (edit_is_negative) {
2040 		sample = - sample;
2041 	}
2042 
2043 	return sample;
2044 }
2045 
2046 samplepos_t
samples_from_minsec_string(const string & str) const2047 AudioClock::samples_from_minsec_string (const string& str) const
2048 {
2049 	if (_session == 0) {
2050 		return 0;
2051 	}
2052 
2053 	int hrs, mins, secs, millisecs;
2054 	samplecnt_t sr = _session->sample_rate();
2055 
2056 	if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
2057 		error << string_compose (_("programming error: %1 %2"), "badly formatted minsec clock string", str) << endmsg;
2058 		return 0;
2059 	}
2060 
2061 	return (samplepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
2062 }
2063 
2064 samplepos_t
samples_from_bbt_string(samplepos_t pos,const string & str) const2065 AudioClock::samples_from_bbt_string (samplepos_t pos, const string& str) const
2066 {
2067 	if (_session == 0) {
2068 		error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
2069 		return 0;
2070 	}
2071 
2072 	AnyTime any;
2073 	any.type = AnyTime::BBT;
2074 
2075 	if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
2076 		return 0;
2077 	}
2078 
2079 	if (is_duration) {
2080 		any.bbt.bars++;
2081 		any.bbt.beats++;
2082 		return _session->any_duration_to_samples (pos, any);
2083 	} else {
2084 		return _session->convert_to_samples (any);
2085 	}
2086 }
2087 
2088 
2089 samplepos_t
sample_duration_from_bbt_string(samplepos_t pos,const string & str) const2090 AudioClock::sample_duration_from_bbt_string (samplepos_t pos, const string& str) const
2091 {
2092 	if (_session == 0) {
2093 		error << "AudioClock::sample_duration_from_bbt_string() called with BBT mode but without session!" << endmsg;
2094 		return 0;
2095 	}
2096 
2097 	Timecode::BBT_Time bbt;
2098 
2099 	if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) {
2100 		return 0;
2101 	}
2102 
2103 	return _session->tempo_map().bbt_duration_at(pos,bbt,1);
2104 }
2105 
2106 samplepos_t
samples_from_seconds_string(const string & str) const2107 AudioClock::samples_from_seconds_string (const string& str) const
2108 {
2109 	float f;
2110 	sscanf (str.c_str(), "%f", &f);
2111 	return f * _session->sample_rate();
2112 }
2113 
2114 samplepos_t
samples_from_audiosamples_string(const string & str) const2115 AudioClock::samples_from_audiosamples_string (const string& str) const
2116 {
2117 	samplepos_t f;
2118 	sscanf (str.c_str(), "%" PRId64, &f);
2119 	return f;
2120 }
2121 
2122 void
copy_text_to_clipboard() const2123 AudioClock::copy_text_to_clipboard () const
2124 {
2125 	string val;
2126 	if (editing) {
2127 		val = pre_edit_string;
2128 	} else {
2129 		val = _layout->get_text ();
2130 	}
2131 	const size_t trim = val.find_first_not_of(" ");
2132 	if (trim == string::npos) {
2133 		assert(0); // empty clock, can't be right.
2134 		return;
2135 	}
2136 	Glib::RefPtr<Clipboard> cl = Gtk::Clipboard::get();
2137 	cl->set_text (val.substr(trim));
2138 }
2139 
2140 void
build_ops_menu()2141 AudioClock::build_ops_menu ()
2142 {
2143 	using namespace Menu_Helpers;
2144 	ops_menu = new Menu;
2145 	MenuList& ops_items = ops_menu->items();
2146 	ops_menu->set_name ("ArdourContextMenu");
2147 
2148 	ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode, false)));
2149 	ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT, false)));
2150 	ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec, false)));
2151 	ops_items.push_back (MenuElem (_("Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Seconds, false)));
2152 	ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Samples, false)));
2153 
2154 	if (editable && !_off && !is_duration && !_follows_playhead) {
2155 		ops_items.push_back (SeparatorElem());
2156 		ops_items.push_back (MenuElem (_("Set from Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
2157 		ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
2158 	}
2159 	ops_items.push_back (SeparatorElem());
2160 	ops_items.push_back (MenuElem (_("Copy to clipboard"), sigc::mem_fun(*this, &AudioClock::copy_text_to_clipboard)));
2161 }
2162 
2163 void
set_from_playhead()2164 AudioClock::set_from_playhead ()
2165 {
2166 	if (!_session) {
2167 		return;
2168 	}
2169 
2170 	AudioClock::set (_session->transport_sample());
2171 	ValueChanged ();
2172 }
2173 
2174 void
locate()2175 AudioClock::locate ()
2176 {
2177 	if (!_session || is_duration) {
2178 		return;
2179 	}
2180 	if (_session->actively_recording()) {
2181 		return;
2182 	}
2183 
2184 	_session->request_locate (current_time());
2185 }
2186 
2187 void
set_mode(Mode m,bool noemit)2188 AudioClock::set_mode (Mode m, bool noemit)
2189 {
2190 	if (_mode == m) {
2191 		return;
2192 	}
2193 
2194 	_mode = m;
2195 
2196 	insert_map.clear();
2197 
2198 	_layout->set_text ("");
2199 
2200 	Gtk::Requisition req;
2201 	set_clock_dimensions (req);
2202 
2203 	switch (_mode) {
2204 	case Timecode:
2205 		insert_map.push_back (11);
2206 		insert_map.push_back (10);
2207 		insert_map.push_back (8);
2208 		insert_map.push_back (7);
2209 		insert_map.push_back (5);
2210 		insert_map.push_back (4);
2211 		insert_map.push_back (2);
2212 		insert_map.push_back (1);
2213 		break;
2214 
2215 	case BBT:
2216 		insert_map.push_back (11);
2217 		insert_map.push_back (10);
2218 		insert_map.push_back (9);
2219 		insert_map.push_back (8);
2220 		insert_map.push_back (6);
2221 		insert_map.push_back (5);
2222 		insert_map.push_back (3);
2223 		insert_map.push_back (2);
2224 		insert_map.push_back (1);
2225 		break;
2226 
2227 	case MinSec:
2228 		insert_map.push_back (12);
2229 		insert_map.push_back (11);
2230 		insert_map.push_back (10);
2231 		insert_map.push_back (8);
2232 		insert_map.push_back (7);
2233 		insert_map.push_back (5);
2234 		insert_map.push_back (4);
2235 		insert_map.push_back (2);
2236 		insert_map.push_back (1);
2237 		break;
2238 
2239 	case Seconds:
2240 		insert_map.push_back (11);
2241 		insert_map.push_back (9);
2242 		insert_map.push_back (8);
2243 		insert_map.push_back (7);
2244 		insert_map.push_back (6);
2245 		insert_map.push_back (5);
2246 		insert_map.push_back (4);
2247 		insert_map.push_back (3);
2248 		insert_map.push_back (2);
2249 		insert_map.push_back (1);
2250 		break;
2251 
2252 	case Samples:
2253 		break;
2254 	}
2255 
2256 	AudioClock::set (last_when, true);
2257 
2258 	if (!is_transient && !noemit) {
2259 		ModeChanged (); /* EMIT SIGNAL (the static one)*/
2260 	}
2261 
2262 	mode_changed (); /* EMIT SIGNAL (the member one) */
2263 }
2264 
2265 void
set_bbt_reference(samplepos_t pos)2266 AudioClock::set_bbt_reference (samplepos_t pos)
2267 {
2268 	bbt_reference_time = pos;
2269 }
2270 
2271 void
on_style_changed(const Glib::RefPtr<Gtk::Style> & old_style)2272 AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
2273 {
2274 	CairoWidget::on_style_changed (old_style);
2275 
2276 	Glib::RefPtr<Gtk::Style> const& new_style = get_style ();
2277 	if (_layout && (_layout->get_font_description ().gobj () == 0 || _layout->get_font_description () != new_style->get_font ())) {
2278 		_layout->set_font_description (new_style->get_font ());
2279 		queue_resize ();
2280 	} else if (is_realized ()) {
2281 		queue_resize ();
2282 	}
2283 
2284 	Gtk::Requisition req;
2285 	set_clock_dimensions (req);
2286 
2287 	/* set-colors also sets up font-attributes */
2288 	set_colors ();
2289 }
2290 
2291 void
set_editable(bool yn)2292 AudioClock::set_editable (bool yn)
2293 {
2294 	editable = yn;
2295 }
2296 
2297 void
set_is_duration(bool yn)2298 AudioClock::set_is_duration (bool yn)
2299 {
2300 	if (yn == is_duration) {
2301 		return;
2302 	}
2303 
2304 	is_duration = yn;
2305 	AudioClock::set (last_when, true);
2306 }
2307 
2308 void
set_off(bool yn)2309 AudioClock::set_off (bool yn)
2310 {
2311 	if (_off == yn) {
2312 		return;
2313 	}
2314 
2315 	_off = yn;
2316 
2317 	/* force a redraw. last_when will be preserved, but the clock text will
2318 	 * change
2319 	 */
2320 
2321 	AudioClock::set (last_when, true);
2322 }
2323 
2324 void
focus()2325 AudioClock::focus ()
2326 {
2327 	start_edit (Field (0));
2328 }
2329 
2330 void
set_corner_radius(double r)2331 AudioClock::set_corner_radius (double r)
2332 {
2333 	corner_radius = r;
2334 	first_width = 0;
2335 	first_height = 0;
2336 	queue_resize ();
2337 }
2338 
2339 void
dpi_reset()2340 AudioClock::dpi_reset ()
2341 {
2342 	/* force recomputation of size even if we are fixed width
2343 	 */
2344 	first_width = 0;
2345 	first_height = 0;
2346 	queue_resize ();
2347 }
2348 
2349 void
set_negative_allowed(bool yn)2350 AudioClock::set_negative_allowed (bool yn)
2351 {
2352 	_negative_allowed = yn;
2353 }
2354