Colin Fletcher
3cb2f25d89
If this parameter is passed as 'true', then edits to the clock will be accepted when the user clicks away from the clock, rather than unconditionally discarded as hitherto.
2314 lines
51 KiB
C++
2314 lines
51 KiB
C++
/*
|
|
Copyright (C) 1999 Paul Davis
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
#include <cstdio> // for sprintf
|
|
#include <cmath>
|
|
|
|
#include "pbd/convert.h"
|
|
#include "pbd/enumwriter.h"
|
|
|
|
#include <gtkmm/style.h>
|
|
#include <sigc++/bind.h>
|
|
|
|
#include "gtkmm2ext/cairocell.h"
|
|
#include "gtkmm2ext/utils.h"
|
|
#include "gtkmm2ext/rgb_macros.h"
|
|
|
|
#include "ardour/profile.h"
|
|
#include "ardour/lmath.h"
|
|
#include "ardour/session.h"
|
|
#include "ardour/slave.h"
|
|
#include "ardour/tempo.h"
|
|
#include "ardour/types.h"
|
|
|
|
#include "ardour_ui.h"
|
|
#include "audio_clock.h"
|
|
#include "global_signals.h"
|
|
#include "utils.h"
|
|
#include "keyboard.h"
|
|
#include "gui_thread.h"
|
|
#include "i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
using namespace ARDOUR_UI_UTILS;
|
|
using namespace PBD;
|
|
using namespace Gtk;
|
|
using namespace std;
|
|
|
|
using Gtkmm2ext::Keyboard;
|
|
|
|
sigc::signal<void> AudioClock::ModeChanged;
|
|
vector<AudioClock*> AudioClock::clocks;
|
|
const double AudioClock::info_font_scale_factor = 0.68;
|
|
const double AudioClock::separator_height = 0.0;
|
|
const double AudioClock::x_leading_padding = 6.0;
|
|
|
|
#define BBT_BAR_CHAR "|"
|
|
#define BBT_SCANF_FORMAT "%" PRIu32 "%*c%" PRIu32 "%*c%" PRIu32
|
|
#define INFO_FONT_SIZE ((int)lrint(font_size * info_font_scale_factor))
|
|
#define TXTSPAN "<span font-family=\"Sans\" foreground=\"white\">"
|
|
|
|
AudioClock::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)
|
|
: ops_menu (0)
|
|
, _name (clock_name)
|
|
, is_transient (transient)
|
|
, is_duration (duration)
|
|
, editable (allow_edit)
|
|
, _follows_playhead (follows_playhead)
|
|
, _accept_on_focus_out (accept_on_focus_out)
|
|
, _off (false)
|
|
, em_width (0)
|
|
, _edit_by_click_field (false)
|
|
, _negative_allowed (false)
|
|
, edit_is_negative (false)
|
|
, editing_attr (0)
|
|
, foreground_attr (0)
|
|
, first_height (0)
|
|
, first_width (0)
|
|
, style_resets_first (true)
|
|
, layout_height (0)
|
|
, layout_width (0)
|
|
, info_height (0)
|
|
, upper_height (0)
|
|
, mode_based_info_ratio (1.0)
|
|
, corner_radius (4)
|
|
, font_size (10240)
|
|
, editing (false)
|
|
, bbt_reference_time (-1)
|
|
, last_when(0)
|
|
, last_pdelta (0)
|
|
, last_sdelta (0)
|
|
, dragging (false)
|
|
, drag_field (Field (0))
|
|
, xscale (1.0)
|
|
, yscale (1.0)
|
|
{
|
|
set_flags (CAN_FOCUS);
|
|
|
|
_layout = Pango::Layout::create (get_pango_context());
|
|
_layout->set_attributes (normal_attributes);
|
|
|
|
if (with_info) {
|
|
_left_layout = Pango::Layout::create (get_pango_context());
|
|
_right_layout = Pango::Layout::create (get_pango_context());
|
|
}
|
|
|
|
set_widget_name (widget_name);
|
|
|
|
_mode = BBT; /* lie to force mode switch */
|
|
set_mode (Timecode);
|
|
set (last_when, true);
|
|
|
|
if (!is_transient) {
|
|
clocks.push_back (this);
|
|
}
|
|
|
|
ColorsChanged.connect (sigc::mem_fun (*this, &AudioClock::set_colors));
|
|
DPIReset.connect (sigc::mem_fun (*this, &AudioClock::dpi_reset));
|
|
}
|
|
|
|
AudioClock::~AudioClock ()
|
|
{
|
|
delete foreground_attr;
|
|
delete editing_attr;
|
|
}
|
|
|
|
void
|
|
AudioClock::set_widget_name (const string& str)
|
|
{
|
|
if (str.empty()) {
|
|
set_name ("clock");
|
|
} else {
|
|
set_name (str + " clock");
|
|
}
|
|
|
|
if (is_realized()) {
|
|
set_colors ();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
AudioClock::on_realize ()
|
|
{
|
|
Gtk::Requisition req;
|
|
|
|
CairoWidget::on_realize ();
|
|
|
|
set_clock_dimensions (req);
|
|
|
|
first_width = req.width;
|
|
first_height = req.height;
|
|
|
|
// XXX FIX ME: define font based on ... ???
|
|
// set_font ();
|
|
set_colors ();
|
|
}
|
|
|
|
void
|
|
AudioClock::set_font (Pango::FontDescription font)
|
|
{
|
|
Glib::RefPtr<Gtk::Style> style = get_style ();
|
|
Pango::AttrFontDesc* font_attr;
|
|
|
|
font_size = font.get_size();
|
|
font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
|
|
|
|
normal_attributes.change (*font_attr);
|
|
editing_attributes.change (*font_attr);
|
|
|
|
/* now a smaller version of the same font */
|
|
|
|
delete font_attr;
|
|
font.set_size (INFO_FONT_SIZE);
|
|
font.set_weight (Pango::WEIGHT_NORMAL);
|
|
font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
|
|
|
|
info_attributes.change (*font_attr);
|
|
|
|
/* and an even smaller one */
|
|
|
|
delete font_attr;
|
|
|
|
/* get the figure width for the font. This doesn't have to super
|
|
* accurate since we only use it to measure the (roughly 1 character)
|
|
* offset from the position Pango tells us for the "cursor"
|
|
*/
|
|
|
|
Glib::RefPtr<Pango::Layout> tmp = Pango::Layout::create (get_pango_context());
|
|
int ignore_height;
|
|
|
|
tmp->set_text ("8");
|
|
tmp->get_pixel_size (em_width, ignore_height);
|
|
|
|
/* force redraw of markup with new font-size */
|
|
set (last_when, true);
|
|
}
|
|
|
|
void
|
|
AudioClock::set_active_state (Gtkmm2ext::ActiveState s)
|
|
{
|
|
CairoWidget::set_active_state (s);
|
|
set_colors ();
|
|
}
|
|
|
|
void
|
|
AudioClock::set_colors ()
|
|
{
|
|
int r, g, b, a;
|
|
|
|
uint32_t bg_color;
|
|
uint32_t text_color;
|
|
uint32_t editing_color;
|
|
uint32_t cursor_color;
|
|
|
|
if (active_state()) {
|
|
bg_color = ARDOUR_UI::config()->color (string_compose ("%1 active: background", get_name()));
|
|
text_color = ARDOUR_UI::config()->color (string_compose ("%1 active: text", get_name()));
|
|
editing_color = ARDOUR_UI::config()->color (string_compose ("%1 active: edited text", get_name()));
|
|
cursor_color = ARDOUR_UI::config()->color (string_compose ("%1 active: cursor", get_name()));
|
|
} else {
|
|
bg_color = ARDOUR_UI::config()->color (string_compose ("%1: background", get_name()));
|
|
text_color = ARDOUR_UI::config()->color (string_compose ("%1: text", get_name()));
|
|
editing_color = ARDOUR_UI::config()->color (string_compose ("%1: edited text", get_name()));
|
|
cursor_color = ARDOUR_UI::config()->color (string_compose ("%1: cursor", get_name()));
|
|
}
|
|
|
|
/* store for bg and cursor in render() */
|
|
|
|
UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
|
|
|
|
bg_r = r/255.0;
|
|
bg_g = g/255.0;
|
|
bg_b = b/255.0;
|
|
bg_a = a/255.0;
|
|
|
|
UINT_TO_RGBA (cursor_color, &r, &g, &b, &a);
|
|
|
|
cursor_r = r/255.0;
|
|
cursor_g = g/255.0;
|
|
cursor_b = b/255.0;
|
|
cursor_a = a/255.0;
|
|
|
|
/* rescale for Pango colors ... sigh */
|
|
|
|
r = lrint (r * 65535.0);
|
|
g = lrint (g * 65535.0);
|
|
b = lrint (b * 65535.0);
|
|
|
|
UINT_TO_RGBA (text_color, &r, &g, &b, &a);
|
|
r = lrint ((r/255.0) * 65535.0);
|
|
g = lrint ((g/255.0) * 65535.0);
|
|
b = lrint ((b/255.0) * 65535.0);
|
|
delete foreground_attr;
|
|
foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
|
|
|
|
UINT_TO_RGBA (editing_color, &r, &g, &b, &a);
|
|
r = lrint ((r/255.0) * 65535.0);
|
|
g = lrint ((g/255.0) * 65535.0);
|
|
b = lrint ((b/255.0) * 65535.0);
|
|
delete editing_attr;
|
|
editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
|
|
|
|
normal_attributes.change (*foreground_attr);
|
|
info_attributes.change (*foreground_attr);
|
|
editing_attributes.change (*foreground_attr);
|
|
editing_attributes.change (*editing_attr);
|
|
|
|
if (!editing) {
|
|
_layout->set_attributes (normal_attributes);
|
|
} else {
|
|
_layout->set_attributes (editing_attributes);
|
|
}
|
|
|
|
queue_draw ();
|
|
}
|
|
|
|
void
|
|
AudioClock::set_scale (double x, double y)
|
|
{
|
|
xscale = x;
|
|
yscale = y;
|
|
|
|
queue_draw ();
|
|
}
|
|
|
|
void
|
|
AudioClock::render (cairo_t* cr, cairo_rectangle_t*)
|
|
{
|
|
/* main layout: rounded rect, plus the text */
|
|
|
|
if (_need_bg) {
|
|
cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
|
|
if (corner_radius) {
|
|
if (_left_layout) {
|
|
Gtkmm2ext::rounded_top_half_rectangle (cr, 0, 0, get_width(), upper_height, corner_radius);
|
|
} else {
|
|
Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), upper_height, corner_radius);
|
|
}
|
|
} else {
|
|
cairo_rectangle (cr, 0, 0, get_width(), upper_height);
|
|
}
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
double lw = layout_width * xscale;
|
|
double lh = layout_height * yscale;
|
|
|
|
cairo_move_to (cr, (get_width() - lw) / 2.0, (upper_height - lh) / 2.0);
|
|
|
|
if (xscale != 1.0 || yscale != 1.0) {
|
|
cairo_save (cr);
|
|
cairo_scale (cr, xscale, yscale);
|
|
}
|
|
|
|
pango_cairo_show_layout (cr, _layout->gobj());
|
|
|
|
if (xscale != 1.0 || yscale != 1.0) {
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
if (_left_layout) {
|
|
|
|
double h = get_height() - upper_height - separator_height;
|
|
|
|
if (_need_bg) {
|
|
cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
|
|
}
|
|
|
|
if (mode_based_info_ratio != 1.0) {
|
|
|
|
double left_rect_width = get_left_rect_width();
|
|
|
|
if (_need_bg) {
|
|
if (corner_radius) {
|
|
Gtkmm2ext::rounded_bottom_half_rectangle (cr, 0, upper_height + separator_height,
|
|
left_rect_width + (separator_height == 0 ? corner_radius : 0),
|
|
h, corner_radius);
|
|
} else {
|
|
cairo_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h);
|
|
}
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
cairo_move_to (cr, x_leading_padding, upper_height + separator_height + ((h - info_height)/2.0));
|
|
pango_cairo_show_layout (cr, _left_layout->gobj());
|
|
|
|
if (_need_bg) {
|
|
if (corner_radius) {
|
|
Gtkmm2ext::rounded_bottom_half_rectangle (cr, left_rect_width + separator_height,
|
|
upper_height + separator_height,
|
|
get_width() - separator_height - left_rect_width,
|
|
h, corner_radius);
|
|
} else {
|
|
cairo_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height,
|
|
get_width() - separator_height - left_rect_width, h);
|
|
}
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
|
|
if (_right_layout->get_alignment() == Pango::ALIGN_RIGHT) {
|
|
/* right-align does not work per se beacuse layout width is unset.
|
|
* Using _right_layout->set_width([value >=0]) would also enable
|
|
* word-wrapping which is not wanted here.
|
|
* The solution is to custom align the layout depending on its size.
|
|
* if it is larger than the available space it will be cropped on the
|
|
* right edge rather than override text on the left side.
|
|
*/
|
|
int x, rw, rh;
|
|
_right_layout->get_pixel_size(rw, rh);
|
|
x = get_width() - rw - separator_height - x_leading_padding;
|
|
if (x < x_leading_padding + left_rect_width + separator_height) {
|
|
/* rather cut off the right end than overlap with the text on the left */
|
|
x = x_leading_padding + left_rect_width + separator_height;
|
|
}
|
|
cairo_move_to (cr, x, upper_height + separator_height + ((h - info_height)/2.0));
|
|
} else {
|
|
cairo_move_to (cr, x_leading_padding + left_rect_width + separator_height, upper_height + separator_height + ((h - info_height)/2.0));
|
|
}
|
|
pango_cairo_show_layout (cr, _right_layout->gobj());
|
|
|
|
} else {
|
|
/* no info to display, or just one */
|
|
|
|
if (_need_bg) {
|
|
if (corner_radius) {
|
|
Gtkmm2ext::rounded_bottom_half_rectangle (cr, 0, upper_height + separator_height, get_width(), h, corner_radius);
|
|
} else {
|
|
cairo_rectangle (cr, 0, upper_height + separator_height, get_width(), h);
|
|
}
|
|
cairo_fill (cr);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (editing) {
|
|
if (!insert_map.empty()) {
|
|
|
|
int xcenter = (get_width() - layout_width) /2;
|
|
|
|
if (input_string.length() < insert_map.size()) {
|
|
Pango::Rectangle cursor;
|
|
|
|
if (input_string.empty()) {
|
|
/* nothing entered yet, put cursor at the end
|
|
of string
|
|
*/
|
|
cursor = _layout->get_cursor_strong_pos (edit_string.length() - 1);
|
|
} else {
|
|
cursor = _layout->get_cursor_strong_pos (insert_map[input_string.length()]);
|
|
}
|
|
|
|
cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
|
|
cairo_rectangle (cr,
|
|
min (get_width() - 2.0,
|
|
(double) xcenter + cursor.get_x()/PANGO_SCALE + em_width),
|
|
(upper_height - layout_height)/2.0,
|
|
2.0, cursor.get_height()/PANGO_SCALE);
|
|
cairo_fill (cr);
|
|
} else {
|
|
/* we've entered all possible digits, no cursor */
|
|
}
|
|
|
|
} else {
|
|
if (input_string.empty()) {
|
|
cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
|
|
cairo_rectangle (cr,
|
|
(get_width()/2.0),
|
|
(upper_height - layout_height)/2.0,
|
|
2.0, upper_height);
|
|
cairo_fill (cr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioClock::on_size_allocate (Gtk::Allocation& alloc)
|
|
{
|
|
CairoWidget::on_size_allocate (alloc);
|
|
|
|
if (_left_layout) {
|
|
upper_height = (get_height()/2.0) - 1.0;
|
|
} else {
|
|
upper_height = get_height();
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioClock::set_clock_dimensions (Gtk::Requisition& req)
|
|
{
|
|
Glib::RefPtr<Pango::Layout> tmp;
|
|
Glib::RefPtr<Gtk::Style> style = get_style ();
|
|
Pango::FontDescription font;
|
|
|
|
tmp = Pango::Layout::create (get_pango_context());
|
|
|
|
if (!is_realized()) {
|
|
font = get_font_for_style (get_name());
|
|
} else {
|
|
font = style->get_font();
|
|
}
|
|
|
|
tmp->set_font_description (font);
|
|
|
|
/* this string is the longest thing we will ever display */
|
|
if (_mode == MinSec)
|
|
tmp->set_text (" 88:88:88,888 ");
|
|
else
|
|
tmp->set_text (" 88:88:88,88 ");
|
|
tmp->get_pixel_size (req.width, req.height);
|
|
|
|
layout_height = req.height;
|
|
layout_width = req.width;
|
|
}
|
|
|
|
void
|
|
AudioClock::on_size_request (Gtk::Requisition* req)
|
|
{
|
|
/* even for non fixed width clocks, the size we *ask* for never changes,
|
|
even though the size we receive might. so once we've computed it,
|
|
just return it.
|
|
*/
|
|
|
|
if (first_width) {
|
|
req->width = first_width;
|
|
req->height = first_height;
|
|
return;
|
|
}
|
|
|
|
set_clock_dimensions (*req);
|
|
|
|
/* now tackle height, for which we need to know the height of the lower
|
|
* layout
|
|
*/
|
|
|
|
if (_left_layout) {
|
|
|
|
Glib::RefPtr<Pango::Layout> tmp;
|
|
Glib::RefPtr<Gtk::Style> style = get_style ();
|
|
Pango::FontDescription font;
|
|
int w;
|
|
|
|
tmp = Pango::Layout::create (get_pango_context());
|
|
|
|
if (!is_realized()) {
|
|
font = get_font_for_style (get_name());
|
|
} else {
|
|
font = style->get_font();
|
|
}
|
|
|
|
tmp->set_font_description (font);
|
|
|
|
font.set_size (INFO_FONT_SIZE);
|
|
font.set_weight (Pango::WEIGHT_NORMAL);
|
|
tmp->set_font_description (font);
|
|
|
|
/* we only care about height, so put as much stuff in here
|
|
as possible that might change the height.
|
|
*/
|
|
tmp->set_text ("qyhH|"); /* one ascender, one descender */
|
|
|
|
tmp->get_pixel_size (w, info_height);
|
|
|
|
/* silly extra padding that seems necessary to correct the info
|
|
* that pango just gave us. I have no idea why.
|
|
*/
|
|
|
|
req->height += info_height;
|
|
req->height += separator_height;
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioClock::show_edit_status (int length)
|
|
{
|
|
editing_attr->set_start_index (edit_string.length() - length);
|
|
editing_attr->set_end_index (edit_string.length());
|
|
|
|
editing_attributes.change (*foreground_attr);
|
|
editing_attributes.change (*editing_attr);
|
|
|
|
_layout->set_attributes (editing_attributes);
|
|
}
|
|
|
|
void
|
|
AudioClock::start_edit (Field f)
|
|
{
|
|
if (!editing) {
|
|
pre_edit_string = _layout->get_text ();
|
|
if (!insert_map.empty()) {
|
|
edit_string = pre_edit_string;
|
|
} else {
|
|
edit_string.clear ();
|
|
_layout->set_text ("");
|
|
}
|
|
|
|
input_string.clear ();
|
|
editing = true;
|
|
edit_is_negative = false;
|
|
|
|
if (f) {
|
|
input_string = get_field (f);
|
|
show_edit_status (merge_input_and_edit_string ());
|
|
_layout->set_text (edit_string);
|
|
}
|
|
|
|
queue_draw ();
|
|
|
|
Keyboard::magic_widget_grab_focus ();
|
|
grab_focus ();
|
|
}
|
|
}
|
|
|
|
string
|
|
AudioClock::get_field (Field f)
|
|
{
|
|
switch (f) {
|
|
case Timecode_Hours:
|
|
return edit_string.substr (1, 2);
|
|
break;
|
|
case Timecode_Minutes:
|
|
return edit_string.substr (4, 2);
|
|
break;
|
|
case Timecode_Seconds:
|
|
return edit_string.substr (7, 2);
|
|
break;
|
|
case Timecode_Frames:
|
|
return edit_string.substr (10, 2);
|
|
break;
|
|
case MS_Hours:
|
|
return edit_string.substr (1, 2);
|
|
break;
|
|
case MS_Minutes:
|
|
return edit_string.substr (4, 2);
|
|
break;
|
|
case MS_Seconds:
|
|
return edit_string.substr (7, 2);
|
|
break;
|
|
case MS_Milliseconds:
|
|
return edit_string.substr (10, 3);
|
|
break;
|
|
case Bars:
|
|
return edit_string.substr (1, 3);
|
|
break;
|
|
case Beats:
|
|
return edit_string.substr (5, 2);
|
|
break;
|
|
case Ticks:
|
|
return edit_string.substr (8, 4);
|
|
break;
|
|
case AudioFrames:
|
|
return edit_string;
|
|
break;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
void
|
|
AudioClock::end_edit (bool modify)
|
|
{
|
|
if (modify) {
|
|
|
|
bool ok = true;
|
|
|
|
switch (_mode) {
|
|
case Timecode:
|
|
ok = timecode_validate_edit (edit_string);
|
|
break;
|
|
|
|
case BBT:
|
|
ok = bbt_validate_edit (edit_string);
|
|
break;
|
|
|
|
case MinSec:
|
|
ok = minsec_validate_edit (edit_string);
|
|
break;
|
|
|
|
case Frames:
|
|
if (edit_string.length() < 1) {
|
|
edit_string = pre_edit_string;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!ok) {
|
|
edit_string = pre_edit_string;
|
|
input_string.clear ();
|
|
_layout->set_text (edit_string);
|
|
show_edit_status (0);
|
|
/* edit attributes remain in use */
|
|
} else {
|
|
|
|
editing = false;
|
|
framepos_t pos = 0; /* stupid gcc */
|
|
|
|
switch (_mode) {
|
|
case Timecode:
|
|
pos = frames_from_timecode_string (edit_string);
|
|
break;
|
|
|
|
case BBT:
|
|
if (is_duration) {
|
|
pos = frame_duration_from_bbt_string (0, edit_string);
|
|
} else {
|
|
pos = frames_from_bbt_string (0, edit_string);
|
|
}
|
|
break;
|
|
|
|
case MinSec:
|
|
pos = frames_from_minsec_string (edit_string);
|
|
break;
|
|
|
|
case Frames:
|
|
pos = frames_from_audioframes_string (edit_string);
|
|
break;
|
|
}
|
|
|
|
set (pos, true);
|
|
_layout->set_attributes (normal_attributes);
|
|
ValueChanged(); /* EMIT_SIGNAL */
|
|
}
|
|
|
|
} else {
|
|
|
|
editing = false;
|
|
edit_is_negative = false;
|
|
_layout->set_attributes (normal_attributes);
|
|
_layout->set_text (pre_edit_string);
|
|
}
|
|
|
|
queue_draw ();
|
|
|
|
if (!editing) {
|
|
drop_focus ();
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioClock::drop_focus ()
|
|
{
|
|
Keyboard::magic_widget_drop_focus ();
|
|
|
|
if (has_focus()) {
|
|
|
|
/* move focus back to the default widget in the top level window */
|
|
|
|
Widget* top = get_toplevel();
|
|
|
|
if (top->is_toplevel ()) {
|
|
Window* win = dynamic_cast<Window*> (top);
|
|
win->grab_focus ();
|
|
}
|
|
}
|
|
}
|
|
|
|
framecnt_t
|
|
AudioClock::parse_as_frames_distance (const std::string& str)
|
|
{
|
|
framecnt_t f;
|
|
|
|
if (sscanf (str.c_str(), "%" PRId64, &f) == 1) {
|
|
return f;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
framecnt_t
|
|
AudioClock::parse_as_minsec_distance (const std::string& str)
|
|
{
|
|
framecnt_t sr = _session->frame_rate();
|
|
int msecs;
|
|
int secs;
|
|
int mins;
|
|
int hrs;
|
|
|
|
switch (str.length()) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
sscanf (str.c_str(), "%" PRId32, &msecs);
|
|
return msecs * (sr / 1000);
|
|
|
|
case 5:
|
|
sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &msecs);
|
|
return (secs * sr) + (msecs * (sr/1000));
|
|
|
|
case 6:
|
|
sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &msecs);
|
|
return (secs * sr) + (msecs * (sr/1000));
|
|
|
|
case 7:
|
|
sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
|
|
return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
|
|
|
|
case 8:
|
|
sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
|
|
return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
|
|
|
|
case 9:
|
|
sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
|
|
return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
|
|
|
|
case 10:
|
|
sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
|
|
return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
framecnt_t
|
|
AudioClock::parse_as_timecode_distance (const std::string& str)
|
|
{
|
|
double fps = _session->timecode_frames_per_second();
|
|
framecnt_t sr = _session->frame_rate();
|
|
int frames;
|
|
int secs;
|
|
int mins;
|
|
int hrs;
|
|
|
|
switch (str.length()) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
case 2:
|
|
sscanf (str.c_str(), "%" PRId32, &frames);
|
|
return llrint ((frames/(float)fps) * sr);
|
|
|
|
case 3:
|
|
sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &frames);
|
|
return (secs * sr) + llrint ((frames/(float)fps) * sr);
|
|
|
|
case 4:
|
|
sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &frames);
|
|
return (secs * sr) + llrint ((frames/(float)fps) * sr);
|
|
|
|
case 5:
|
|
sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames);
|
|
return (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr);
|
|
|
|
case 6:
|
|
sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames);
|
|
return (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr);
|
|
|
|
case 7:
|
|
sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames);
|
|
return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr);
|
|
|
|
case 8:
|
|
sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames);
|
|
return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
framecnt_t
|
|
AudioClock::parse_as_bbt_distance (const std::string&)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
framecnt_t
|
|
AudioClock::parse_as_distance (const std::string& instr)
|
|
{
|
|
switch (_mode) {
|
|
case Timecode:
|
|
return parse_as_timecode_distance (instr);
|
|
break;
|
|
case Frames:
|
|
return parse_as_frames_distance (instr);
|
|
break;
|
|
case BBT:
|
|
return parse_as_bbt_distance (instr);
|
|
break;
|
|
case MinSec:
|
|
return parse_as_minsec_distance (instr);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
AudioClock::end_edit_relative (bool add)
|
|
{
|
|
bool ok = true;
|
|
|
|
switch (_mode) {
|
|
case Timecode:
|
|
ok = timecode_validate_edit (edit_string);
|
|
break;
|
|
|
|
case BBT:
|
|
ok = bbt_validate_edit (edit_string);
|
|
break;
|
|
|
|
case MinSec:
|
|
ok = minsec_validate_edit (edit_string);
|
|
break;
|
|
|
|
case Frames:
|
|
break;
|
|
}
|
|
|
|
if (!ok) {
|
|
edit_string = pre_edit_string;
|
|
input_string.clear ();
|
|
_layout->set_text (edit_string);
|
|
show_edit_status (0);
|
|
/* edit attributes remain in use */
|
|
queue_draw ();
|
|
return;
|
|
}
|
|
|
|
framecnt_t frames = parse_as_distance (input_string);
|
|
|
|
editing = false;
|
|
|
|
editing = false;
|
|
_layout->set_attributes (normal_attributes);
|
|
|
|
if (frames != 0) {
|
|
if (add) {
|
|
set (current_time() + frames, true);
|
|
} else {
|
|
framepos_t c = current_time();
|
|
|
|
if (c > frames || _negative_allowed) {
|
|
set (c - frames, true);
|
|
} else {
|
|
set (0, true);
|
|
}
|
|
}
|
|
ValueChanged (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
input_string.clear ();
|
|
queue_draw ();
|
|
drop_focus ();
|
|
}
|
|
|
|
void
|
|
AudioClock::session_property_changed (const PropertyChange&)
|
|
{
|
|
set (last_when, true);
|
|
}
|
|
|
|
void
|
|
AudioClock::session_configuration_changed (std::string p)
|
|
{
|
|
if (_negative_allowed) {
|
|
/* session option editor clock */
|
|
return;
|
|
}
|
|
|
|
if (p == "sync-source" || p == "external-sync") {
|
|
set (current_time(), true);
|
|
return;
|
|
}
|
|
|
|
if (p != "timecode-offset" && p != "timecode-offset-negative") {
|
|
return;
|
|
}
|
|
|
|
framecnt_t current;
|
|
|
|
switch (_mode) {
|
|
case Timecode:
|
|
if (is_duration) {
|
|
current = current_duration ();
|
|
} else {
|
|
current = current_time ();
|
|
}
|
|
set (current, true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioClock::set (framepos_t when, bool force, framecnt_t offset)
|
|
{
|
|
if ((!force && !is_visible()) || _session == 0) {
|
|
return;
|
|
}
|
|
|
|
if (is_duration) {
|
|
when = when - offset;
|
|
}
|
|
|
|
if (when == last_when && !force) {
|
|
#if 0 // XXX return if no change and no change forced. verify Aug/2014
|
|
if (_mode != Timecode && _mode != MinSec) {
|
|
/* may need to force display of TC source
|
|
* time, so don't return early.
|
|
*/
|
|
/* ^^ Why was that?, delta times?
|
|
* Timecode FPS, pull-up/down, etc changes
|
|
* trigger a 'session_property_changed' which
|
|
* eventually calls set(last_when, true)
|
|
*
|
|
* re-rendering the clock every 40ms or so just
|
|
* because we can is not ideal.
|
|
*/
|
|
return;
|
|
}
|
|
#else
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
if (!editing) {
|
|
if (_right_layout) {
|
|
_right_layout->set_alignment(Pango::ALIGN_LEFT);
|
|
}
|
|
|
|
switch (_mode) {
|
|
case Timecode:
|
|
if (_right_layout) {
|
|
_right_layout->set_alignment(Pango::ALIGN_RIGHT);
|
|
}
|
|
set_timecode (when, force);
|
|
break;
|
|
|
|
case BBT:
|
|
set_bbt (when, force);
|
|
break;
|
|
|
|
case MinSec:
|
|
if (_right_layout) {
|
|
_right_layout->set_alignment(Pango::ALIGN_RIGHT);
|
|
}
|
|
set_minsec (when, force);
|
|
break;
|
|
|
|
case Frames:
|
|
set_frames (when, force);
|
|
break;
|
|
}
|
|
}
|
|
|
|
queue_draw ();
|
|
last_when = when;
|
|
}
|
|
|
|
void
|
|
AudioClock::set_slave_info ()
|
|
{
|
|
if (!_left_layout || !_right_layout) {
|
|
return;
|
|
}
|
|
|
|
SyncSource sync_src = Config->get_sync_source();
|
|
|
|
if (_session->config.get_external_sync()) {
|
|
Slave* slave = _session->slave();
|
|
|
|
switch (sync_src) {
|
|
case Engine:
|
|
_left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
|
|
INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
|
|
_right_layout->set_text ("");
|
|
break;
|
|
case MIDIClock:
|
|
if (slave) {
|
|
_left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
|
|
INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
|
|
_right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
|
|
INFO_FONT_SIZE, slave->approximate_current_delta()));
|
|
} else {
|
|
_left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
|
|
INFO_FONT_SIZE, _("--pending--")));
|
|
_right_layout->set_text ("");
|
|
}
|
|
break;
|
|
case LTC:
|
|
case MTC:
|
|
if (slave) {
|
|
bool matching;
|
|
TimecodeSlave* tcslave;
|
|
if ((tcslave = dynamic_cast<TimecodeSlave*>(_session->slave())) != 0) {
|
|
matching = (tcslave->apparent_timecode_format() == _session->config.get_timecode_format());
|
|
_left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span><span foreground=\"%3\">%4</span></span>",
|
|
INFO_FONT_SIZE, sync_source_to_string(sync_src, true)[0], (matching?"green":"red"),
|
|
dynamic_cast<TimecodeSlave*>(slave)->approximate_current_position()));
|
|
_right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
|
|
INFO_FONT_SIZE, slave->approximate_current_delta()));
|
|
}
|
|
} else {
|
|
_left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
|
|
INFO_FONT_SIZE, _("--pending--")));
|
|
_right_layout->set_text ("");
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
_left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "INT/%2</span></span>",
|
|
INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
|
|
_right_layout->set_text ("");
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioClock::set_frames (framepos_t when, bool /*force*/)
|
|
{
|
|
char buf[32];
|
|
bool negative = false;
|
|
|
|
if (_off) {
|
|
_layout->set_text (" ----------");
|
|
|
|
if (_left_layout) {
|
|
_left_layout->set_text ("");
|
|
_right_layout->set_text ("");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (when < 0) {
|
|
when = -when;
|
|
negative = true;
|
|
}
|
|
|
|
if (negative) {
|
|
snprintf (buf, sizeof (buf), "-%10" PRId64, when);
|
|
} else {
|
|
snprintf (buf, sizeof (buf), " %10" PRId64, when);
|
|
}
|
|
|
|
_layout->set_text (buf);
|
|
|
|
if (_left_layout) {
|
|
framecnt_t rate = _session->frame_rate();
|
|
|
|
if (fmod (rate, 100.0) == 0.0) {
|
|
sprintf (buf, "%.1fkHz", rate/1000.0);
|
|
} else {
|
|
sprintf (buf, "%" PRId64 "Hz", rate);
|
|
}
|
|
|
|
_left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">%3</span></span>",
|
|
INFO_FONT_SIZE, _("SR"), buf));
|
|
|
|
float vid_pullup = _session->config.get_video_pullup();
|
|
|
|
if (vid_pullup == 0.0) {
|
|
_right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">off</span></span>",
|
|
INFO_FONT_SIZE, _("Pull")));
|
|
} else {
|
|
sprintf (buf, _("%+.4f%%"), vid_pullup);
|
|
_right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">%3</span></span>",
|
|
INFO_FONT_SIZE, _("Pull"), buf));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioClock::print_minsec (framepos_t when, char* buf, size_t bufsize, float frame_rate)
|
|
{
|
|
framecnt_t left;
|
|
int hrs;
|
|
int mins;
|
|
int secs;
|
|
int millisecs;
|
|
bool negative;
|
|
|
|
if (when < 0) {
|
|
when = -when;
|
|
negative = true;
|
|
} else {
|
|
negative = false;
|
|
}
|
|
|
|
left = when;
|
|
hrs = (int) floor (left / (frame_rate * 60.0f * 60.0f));
|
|
left -= (framecnt_t) floor (hrs * frame_rate * 60.0f * 60.0f);
|
|
mins = (int) floor (left / (frame_rate * 60.0f));
|
|
left -= (framecnt_t) floor (mins * frame_rate * 60.0f);
|
|
secs = (int) floor (left / (float) frame_rate);
|
|
left -= (framecnt_t) floor ((double)(secs * frame_rate));
|
|
millisecs = floor (left * 1000.0 / (float) frame_rate);
|
|
|
|
if (negative) {
|
|
snprintf (buf, bufsize, "-%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs);
|
|
} else {
|
|
snprintf (buf, bufsize, " %02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs);
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
AudioClock::set_minsec (framepos_t when, bool /*force*/)
|
|
{
|
|
char buf[32];
|
|
|
|
if (_off) {
|
|
_layout->set_text (" --:--:--.---");
|
|
|
|
if (_left_layout) {
|
|
_left_layout->set_text ("");
|
|
_right_layout->set_text ("");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
print_minsec (when, buf, sizeof (buf), _session->frame_rate());
|
|
|
|
_layout->set_text (buf);
|
|
set_slave_info();
|
|
}
|
|
|
|
void
|
|
AudioClock::set_timecode (framepos_t when, bool /*force*/)
|
|
{
|
|
Timecode::Time TC;
|
|
bool negative = false;
|
|
|
|
if (_off) {
|
|
_layout->set_text (" --:--:--:--");
|
|
if (_left_layout) {
|
|
_left_layout->set_text ("");
|
|
_right_layout->set_text ("");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (when < 0) {
|
|
when = -when;
|
|
negative = true;
|
|
}
|
|
|
|
if (is_duration) {
|
|
_session->timecode_duration (when, TC);
|
|
} else {
|
|
_session->timecode_time (when, TC);
|
|
}
|
|
|
|
TC.negative = TC.negative || negative;
|
|
|
|
_layout->set_text (Timecode::timecode_format_time(TC));
|
|
|
|
set_slave_info();
|
|
}
|
|
|
|
void
|
|
AudioClock::set_bbt (framepos_t when, bool /*force*/)
|
|
{
|
|
char buf[16];
|
|
Timecode::BBT_Time BBT;
|
|
bool negative = false;
|
|
|
|
if (_off) {
|
|
_layout->set_text (" ---|--|----");
|
|
if (_left_layout) {
|
|
_left_layout->set_text ("");
|
|
_right_layout->set_text ("");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (when < 0) {
|
|
when = -when;
|
|
negative = true;
|
|
}
|
|
|
|
/* handle a common case */
|
|
if (is_duration) {
|
|
if (when == 0) {
|
|
BBT.bars = 0;
|
|
BBT.beats = 0;
|
|
BBT.ticks = 0;
|
|
} else {
|
|
_session->tempo_map().bbt_time (when, BBT);
|
|
BBT.bars--;
|
|
BBT.beats--;
|
|
}
|
|
} else {
|
|
_session->tempo_map().bbt_time (when, BBT);
|
|
}
|
|
|
|
if (negative) {
|
|
snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
|
|
BBT.bars, BBT.beats, BBT.ticks);
|
|
} else {
|
|
snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
|
|
BBT.bars, BBT.beats, BBT.ticks);
|
|
}
|
|
|
|
_layout->set_text (buf);
|
|
|
|
if (_right_layout) {
|
|
framepos_t pos;
|
|
|
|
if (bbt_reference_time < 0) {
|
|
pos = when;
|
|
} else {
|
|
pos = bbt_reference_time;
|
|
}
|
|
|
|
TempoMetric m (_session->tempo_map().metric_at (pos));
|
|
|
|
sprintf (buf, "%-5.1f", m.tempo().beats_per_minute());
|
|
_left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%3</span> <span foreground=\"green\">%2</span></span>",
|
|
INFO_FONT_SIZE, buf, _("Tempo")));
|
|
|
|
sprintf (buf, "%g/%g", m.meter().divisions_per_bar(), m.meter().note_divisor());
|
|
_right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%3</span> <span foreground=\"green\">%2</span></span>",
|
|
INFO_FONT_SIZE, buf, _("Meter")));
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioClock::set_session (Session *s)
|
|
{
|
|
SessionHandlePtr::set_session (s);
|
|
|
|
if (_session) {
|
|
|
|
_session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context());
|
|
_session->tempo_map().PropertyChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_property_changed, this, _1), gui_context());
|
|
|
|
const XMLProperty* prop;
|
|
XMLNode* node = _session->extra_xml (X_("ClockModes"));
|
|
AudioClock::Mode amode;
|
|
|
|
if (node) {
|
|
for (XMLNodeList::const_iterator i = node->children().begin(); i != node->children().end(); ++i) {
|
|
if ((prop = (*i)->property (X_("name"))) && prop->value() == _name) {
|
|
|
|
if ((prop = (*i)->property (X_("mode"))) != 0) {
|
|
amode = AudioClock::Mode (string_2_enum (prop->value(), amode));
|
|
set_mode (amode);
|
|
}
|
|
if ((prop = (*i)->property (X_("on"))) != 0) {
|
|
set_off (!string_is_affirmative (prop->value()));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
set (last_when, true);
|
|
}
|
|
}
|
|
|
|
bool
|
|
AudioClock::on_key_press_event (GdkEventKey* ev)
|
|
{
|
|
if (!editing) {
|
|
return false;
|
|
}
|
|
|
|
string new_text;
|
|
char new_char = 0;
|
|
int highlight_length;
|
|
framepos_t pos;
|
|
|
|
switch (ev->keyval) {
|
|
case GDK_0:
|
|
case GDK_KP_0:
|
|
new_char = '0';
|
|
break;
|
|
case GDK_1:
|
|
case GDK_KP_1:
|
|
new_char = '1';
|
|
break;
|
|
case GDK_2:
|
|
case GDK_KP_2:
|
|
new_char = '2';
|
|
break;
|
|
case GDK_3:
|
|
case GDK_KP_3:
|
|
new_char = '3';
|
|
break;
|
|
case GDK_4:
|
|
case GDK_KP_4:
|
|
new_char = '4';
|
|
break;
|
|
case GDK_5:
|
|
case GDK_KP_5:
|
|
new_char = '5';
|
|
break;
|
|
case GDK_6:
|
|
case GDK_KP_6:
|
|
new_char = '6';
|
|
break;
|
|
case GDK_7:
|
|
case GDK_KP_7:
|
|
new_char = '7';
|
|
break;
|
|
case GDK_8:
|
|
case GDK_KP_8:
|
|
new_char = '8';
|
|
break;
|
|
case GDK_9:
|
|
case GDK_KP_9:
|
|
new_char = '9';
|
|
break;
|
|
|
|
case GDK_minus:
|
|
case GDK_KP_Subtract:
|
|
if (_negative_allowed && input_string.empty()) {
|
|
edit_is_negative = true;
|
|
edit_string.replace(0,1,"-");
|
|
_layout->set_text (edit_string);
|
|
queue_draw ();
|
|
} else {
|
|
end_edit_relative (false);
|
|
}
|
|
return true;
|
|
break;
|
|
|
|
case GDK_plus:
|
|
end_edit_relative (true);
|
|
return true;
|
|
break;
|
|
|
|
case GDK_Tab:
|
|
case GDK_Return:
|
|
case GDK_KP_Enter:
|
|
end_edit (true);
|
|
return true;
|
|
break;
|
|
|
|
case GDK_Escape:
|
|
end_edit (false);
|
|
ChangeAborted(); /* EMIT SIGNAL */
|
|
return true;
|
|
|
|
case GDK_Delete:
|
|
case GDK_BackSpace:
|
|
if (!input_string.empty()) {
|
|
/* delete the last key entered
|
|
*/
|
|
input_string = input_string.substr (0, input_string.length() - 1);
|
|
}
|
|
goto use_input_string;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (!insert_map.empty() && (input_string.length() >= insert_map.size())) {
|
|
/* too many digits: eat the key event, but do nothing with it */
|
|
return true;
|
|
}
|
|
|
|
input_string.push_back (new_char);
|
|
|
|
use_input_string:
|
|
|
|
switch (_mode) {
|
|
case Frames:
|
|
/* get this one in the right order, and to the right width */
|
|
if (ev->keyval == GDK_Delete || ev->keyval == GDK_BackSpace) {
|
|
edit_string = edit_string.substr (0, edit_string.length() - 1);
|
|
} else {
|
|
edit_string.push_back (new_char);
|
|
}
|
|
if (!edit_string.empty()) {
|
|
char buf[32];
|
|
sscanf (edit_string.c_str(), "%" PRId64, &pos);
|
|
snprintf (buf, sizeof (buf), " %10" PRId64, pos);
|
|
edit_string = buf;
|
|
}
|
|
/* highlight the whole thing */
|
|
highlight_length = edit_string.length();
|
|
break;
|
|
|
|
default:
|
|
highlight_length = merge_input_and_edit_string ();
|
|
}
|
|
|
|
if (edit_is_negative) {
|
|
edit_string.replace(0,1,"-");
|
|
} else {
|
|
if (!pre_edit_string.empty() && (pre_edit_string.at(0) == '-')) {
|
|
edit_string.replace(0,1,"_");
|
|
} else {
|
|
edit_string.replace(0,1," ");
|
|
}
|
|
}
|
|
|
|
show_edit_status (highlight_length);
|
|
_layout->set_text (edit_string);
|
|
queue_draw ();
|
|
|
|
return true;
|
|
}
|
|
|
|
int
|
|
AudioClock::merge_input_and_edit_string ()
|
|
{
|
|
/* merge with pre-edit-string into edit string */
|
|
|
|
edit_string = pre_edit_string;
|
|
|
|
if (input_string.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
string::size_type target;
|
|
for (string::size_type i = 0; i < input_string.length(); ++i) {
|
|
target = insert_map[input_string.length() - 1 - i];
|
|
edit_string[target] = input_string[i];
|
|
}
|
|
/* highlight from end to wherever the last character was added */
|
|
return edit_string.length() - insert_map[input_string.length()-1];
|
|
}
|
|
|
|
|
|
bool
|
|
AudioClock::on_key_release_event (GdkEventKey *ev)
|
|
{
|
|
if (!editing) {
|
|
return false;
|
|
}
|
|
|
|
/* return true for keys that we used on press
|
|
so that they cannot possibly do double-duty
|
|
*/
|
|
switch (ev->keyval) {
|
|
case GDK_0:
|
|
case GDK_KP_0:
|
|
case GDK_1:
|
|
case GDK_KP_1:
|
|
case GDK_2:
|
|
case GDK_KP_2:
|
|
case GDK_3:
|
|
case GDK_KP_3:
|
|
case GDK_4:
|
|
case GDK_KP_4:
|
|
case GDK_5:
|
|
case GDK_KP_5:
|
|
case GDK_6:
|
|
case GDK_KP_6:
|
|
case GDK_7:
|
|
case GDK_KP_7:
|
|
case GDK_8:
|
|
case GDK_KP_8:
|
|
case GDK_9:
|
|
case GDK_KP_9:
|
|
case GDK_period:
|
|
case GDK_comma:
|
|
case GDK_KP_Decimal:
|
|
case GDK_Tab:
|
|
case GDK_Return:
|
|
case GDK_KP_Enter:
|
|
case GDK_Escape:
|
|
case GDK_minus:
|
|
case GDK_plus:
|
|
case GDK_KP_Add:
|
|
case GDK_KP_Subtract:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AudioClock::Field
|
|
AudioClock::index_to_field (int index) const
|
|
{
|
|
switch (_mode) {
|
|
case Timecode:
|
|
if (index < 4) {
|
|
return Timecode_Hours;
|
|
} else if (index < 7) {
|
|
return Timecode_Minutes;
|
|
} else if (index < 10) {
|
|
return Timecode_Seconds;
|
|
} else {
|
|
return Timecode_Frames;
|
|
}
|
|
break;
|
|
case BBT:
|
|
if (index < 5) {
|
|
return Bars;
|
|
} else if (index < 7) {
|
|
return Beats;
|
|
} else {
|
|
return Ticks;
|
|
}
|
|
break;
|
|
case MinSec:
|
|
if (index < 3) {
|
|
return Timecode_Hours;
|
|
} else if (index < 6) {
|
|
return MS_Minutes;
|
|
} else if (index < 9) {
|
|
return MS_Seconds;
|
|
} else {
|
|
return MS_Milliseconds;
|
|
}
|
|
break;
|
|
case Frames:
|
|
return AudioFrames;
|
|
break;
|
|
}
|
|
|
|
return Field (0);
|
|
}
|
|
|
|
bool
|
|
AudioClock::on_button_press_event (GdkEventButton *ev)
|
|
{
|
|
switch (ev->button) {
|
|
case 1:
|
|
if (editable && !_off) {
|
|
int index;
|
|
int trailing;
|
|
int y;
|
|
int x;
|
|
|
|
/* the text has been centered vertically, so adjust
|
|
* x and y.
|
|
*/
|
|
int xcenter = (get_width() - layout_width) /2;
|
|
|
|
y = ev->y - ((upper_height - layout_height)/2);
|
|
x = ev->x - xcenter;
|
|
|
|
if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
|
|
/* pretend it is a character on the far right */
|
|
index = 99;
|
|
}
|
|
drag_field = index_to_field (index);
|
|
dragging = true;
|
|
/* make absolutely sure that the pointer is grabbed */
|
|
gdk_pointer_grab(ev->window,false ,
|
|
GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
|
|
NULL,NULL,ev->time);
|
|
drag_accum = 0;
|
|
drag_start_y = ev->y;
|
|
drag_y = ev->y;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
AudioClock::on_button_release_event (GdkEventButton *ev)
|
|
{
|
|
if (editable && !_off) {
|
|
if (dragging) {
|
|
gdk_pointer_ungrab (GDK_CURRENT_TIME);
|
|
dragging = false;
|
|
if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 || Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)){
|
|
// we actually dragged so return without
|
|
// setting editing focus, or we shift clicked
|
|
return true;
|
|
} else {
|
|
if (ev->button == 1) {
|
|
|
|
if (_edit_by_click_field) {
|
|
|
|
int xcenter = (get_width() - layout_width) /2;
|
|
int index = 0;
|
|
int trailing;
|
|
int y = ev->y - ((upper_height - layout_height)/2);
|
|
int x = ev->x - xcenter;
|
|
Field f;
|
|
|
|
if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
|
|
return true;
|
|
}
|
|
|
|
f = index_to_field (index);
|
|
|
|
switch (f) {
|
|
case Timecode_Frames:
|
|
case MS_Milliseconds:
|
|
case Ticks:
|
|
f = Field (0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
start_edit (f);
|
|
} else {
|
|
start_edit ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Keyboard::is_context_menu_event (ev)) {
|
|
if (ops_menu == 0) {
|
|
build_ops_menu ();
|
|
}
|
|
ops_menu->popup (1, ev->time);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
AudioClock::on_focus_out_event (GdkEventFocus* ev)
|
|
{
|
|
bool ret = CairoWidget::on_focus_out_event (ev);
|
|
|
|
if (editing) {
|
|
end_edit (_accept_on_focus_out);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
AudioClock::on_scroll_event (GdkEventScroll *ev)
|
|
{
|
|
int index;
|
|
int trailing;
|
|
|
|
if (editing || _session == 0 || !editable || _off) {
|
|
return false;
|
|
}
|
|
|
|
int y;
|
|
int x;
|
|
|
|
/* the text has been centered vertically, so adjust
|
|
* x and y.
|
|
*/
|
|
|
|
int xcenter = (get_width() - layout_width) /2;
|
|
y = ev->y - ((upper_height - layout_height)/2);
|
|
x = ev->x - xcenter;
|
|
|
|
if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
|
|
/* not in the main layout */
|
|
return false;
|
|
}
|
|
|
|
Field f = index_to_field (index);
|
|
framepos_t frames = 0;
|
|
|
|
switch (ev->direction) {
|
|
|
|
case GDK_SCROLL_UP:
|
|
frames = get_frame_step (f);
|
|
if (frames != 0) {
|
|
if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
|
|
frames *= 10;
|
|
}
|
|
set (current_time() + frames, true);
|
|
ValueChanged (); /* EMIT_SIGNAL */
|
|
}
|
|
break;
|
|
|
|
case GDK_SCROLL_DOWN:
|
|
frames = get_frame_step (f);
|
|
if (frames != 0) {
|
|
if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
|
|
frames *= 10;
|
|
}
|
|
|
|
if (!_negative_allowed && (double)current_time() - (double)frames < 0.0) {
|
|
set (0, true);
|
|
} else {
|
|
set (current_time() - frames, true);
|
|
}
|
|
|
|
ValueChanged (); /* EMIT_SIGNAL */
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
AudioClock::on_motion_notify_event (GdkEventMotion *ev)
|
|
{
|
|
if (editing || _session == 0 || !dragging) {
|
|
return false;
|
|
}
|
|
|
|
float pixel_frame_scale_factor = 0.2f;
|
|
|
|
if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
|
|
pixel_frame_scale_factor = 0.1f;
|
|
}
|
|
|
|
|
|
if (Keyboard::modifier_state_contains (ev->state,
|
|
Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) {
|
|
|
|
pixel_frame_scale_factor = 0.025f;
|
|
}
|
|
|
|
double y_delta = ev->y - drag_y;
|
|
|
|
drag_accum += y_delta*pixel_frame_scale_factor;
|
|
|
|
drag_y = ev->y;
|
|
|
|
if (floor (drag_accum) != 0) {
|
|
|
|
framepos_t frames;
|
|
framepos_t pos;
|
|
int dir;
|
|
dir = (drag_accum < 0 ? 1:-1);
|
|
pos = current_time();
|
|
frames = get_frame_step (drag_field, pos, dir);
|
|
|
|
if (frames != 0 && frames * drag_accum < current_time()) {
|
|
set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK
|
|
} else {
|
|
set (0 , false);
|
|
}
|
|
|
|
drag_accum= 0;
|
|
ValueChanged(); /* EMIT_SIGNAL */
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
framepos_t
|
|
AudioClock::get_frame_step (Field field, framepos_t pos, int dir)
|
|
{
|
|
framecnt_t f = 0;
|
|
Timecode::BBT_Time BBT;
|
|
switch (field) {
|
|
case Timecode_Hours:
|
|
f = (framecnt_t) floor (3600.0 * _session->frame_rate());
|
|
break;
|
|
case Timecode_Minutes:
|
|
f = (framecnt_t) floor (60.0 * _session->frame_rate());
|
|
break;
|
|
case Timecode_Seconds:
|
|
f = _session->frame_rate();
|
|
break;
|
|
case Timecode_Frames:
|
|
f = (framecnt_t) floor (_session->frame_rate() / _session->timecode_frames_per_second());
|
|
break;
|
|
|
|
case AudioFrames:
|
|
f = 1;
|
|
break;
|
|
|
|
case MS_Hours:
|
|
f = (framecnt_t) floor (3600.0 * _session->frame_rate());
|
|
break;
|
|
case MS_Minutes:
|
|
f = (framecnt_t) floor (60.0 * _session->frame_rate());
|
|
break;
|
|
case MS_Seconds:
|
|
f = (framecnt_t) _session->frame_rate();
|
|
break;
|
|
case MS_Milliseconds:
|
|
f = (framecnt_t) floor (_session->frame_rate() / 1000.0);
|
|
break;
|
|
|
|
case Bars:
|
|
BBT.bars = 1;
|
|
BBT.beats = 0;
|
|
BBT.ticks = 0;
|
|
f = _session->tempo_map().bbt_duration_at (pos,BBT,dir);
|
|
break;
|
|
case Beats:
|
|
BBT.bars = 0;
|
|
BBT.beats = 1;
|
|
BBT.ticks = 0;
|
|
f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
|
|
break;
|
|
case Ticks:
|
|
BBT.bars = 0;
|
|
BBT.beats = 0;
|
|
BBT.ticks = 1;
|
|
f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
|
|
break;
|
|
default:
|
|
error << string_compose (_("programming error: %1"), "attempt to get frames from non-text field!") << endmsg;
|
|
f = 0;
|
|
break;
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
framepos_t
|
|
AudioClock::current_time (framepos_t) const
|
|
{
|
|
return last_when;
|
|
}
|
|
|
|
framepos_t
|
|
AudioClock::current_duration (framepos_t pos) const
|
|
{
|
|
framepos_t ret = 0;
|
|
|
|
switch (_mode) {
|
|
case Timecode:
|
|
ret = last_when;
|
|
break;
|
|
case BBT:
|
|
ret = frame_duration_from_bbt_string (pos, _layout->get_text());
|
|
break;
|
|
|
|
case MinSec:
|
|
ret = last_when;
|
|
break;
|
|
|
|
case Frames:
|
|
ret = last_when;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
AudioClock::bbt_validate_edit (const string& str)
|
|
{
|
|
AnyTime any;
|
|
|
|
if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
|
|
return false;
|
|
}
|
|
|
|
if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_beat) {
|
|
return false;
|
|
}
|
|
|
|
if (!is_duration && any.bbt.bars == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (!is_duration && any.bbt.beats == 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
AudioClock::timecode_validate_edit (const string&)
|
|
{
|
|
Timecode::Time TC;
|
|
int hours;
|
|
char ignored[2];
|
|
|
|
if (sscanf (_layout->get_text().c_str(), "%[- _]%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32,
|
|
ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
|
|
return false;
|
|
}
|
|
|
|
if (hours < 0) {
|
|
TC.hours = hours * -1;
|
|
TC.negative = true;
|
|
} else {
|
|
TC.hours = hours;
|
|
TC.negative = false;
|
|
}
|
|
|
|
if (TC.negative && !_negative_allowed) {
|
|
return false;
|
|
}
|
|
|
|
if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) {
|
|
return false;
|
|
}
|
|
|
|
if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) {
|
|
return false;
|
|
}
|
|
|
|
if (_session->timecode_drop_frames()) {
|
|
if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
AudioClock::minsec_validate_edit (const string& str)
|
|
{
|
|
int hrs, mins, secs, millisecs;
|
|
|
|
if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
|
|
return false;
|
|
}
|
|
|
|
if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
framepos_t
|
|
AudioClock::frames_from_timecode_string (const string& str) const
|
|
{
|
|
if (_session == 0) {
|
|
return 0;
|
|
}
|
|
|
|
Timecode::Time TC;
|
|
framepos_t sample;
|
|
char ignored[2];
|
|
int hours;
|
|
|
|
if (sscanf (str.c_str(), "%[- _]%d:%d:%d%[:;]%d", ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
|
|
error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg;
|
|
return 0;
|
|
}
|
|
TC.hours = abs(hours);
|
|
TC.rate = _session->timecode_frames_per_second();
|
|
TC.drop= _session->timecode_drop_frames();
|
|
|
|
_session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
|
|
|
|
// timecode_tester ();
|
|
if (edit_is_negative) {
|
|
sample = - sample;
|
|
}
|
|
|
|
return sample;
|
|
}
|
|
|
|
framepos_t
|
|
AudioClock::frames_from_minsec_string (const string& str) const
|
|
{
|
|
if (_session == 0) {
|
|
return 0;
|
|
}
|
|
|
|
int hrs, mins, secs, millisecs;
|
|
framecnt_t sr = _session->frame_rate();
|
|
|
|
if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
|
|
error << string_compose (_("programming error: %1 %2"), "badly formatted minsec clock string", str) << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
return (framepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
|
|
}
|
|
|
|
framepos_t
|
|
AudioClock::frames_from_bbt_string (framepos_t pos, const string& str) const
|
|
{
|
|
if (_session == 0) {
|
|
error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
AnyTime any;
|
|
any.type = AnyTime::BBT;
|
|
|
|
if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
|
|
return 0;
|
|
}
|
|
|
|
if (is_duration) {
|
|
any.bbt.bars++;
|
|
any.bbt.beats++;
|
|
return _session->any_duration_to_frames (pos, any);
|
|
} else {
|
|
return _session->convert_to_frames (any);
|
|
}
|
|
}
|
|
|
|
|
|
framepos_t
|
|
AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) const
|
|
{
|
|
if (_session == 0) {
|
|
error << "AudioClock::frame_duration_from_bbt_string() called with BBT mode but without session!" << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
Timecode::BBT_Time bbt;
|
|
|
|
if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) {
|
|
return 0;
|
|
}
|
|
|
|
return _session->tempo_map().bbt_duration_at(pos,bbt,1);
|
|
}
|
|
|
|
framepos_t
|
|
AudioClock::frames_from_audioframes_string (const string& str) const
|
|
{
|
|
framepos_t f;
|
|
sscanf (str.c_str(), "%" PRId64, &f);
|
|
return f;
|
|
}
|
|
|
|
void
|
|
AudioClock::copy_text_to_clipboard () const
|
|
{
|
|
string val;
|
|
if (editing) {
|
|
val = pre_edit_string;
|
|
} else {
|
|
val = _layout->get_text ();
|
|
}
|
|
const size_t trim = val.find_first_not_of(" ");
|
|
if (trim == string::npos) {
|
|
assert(0); // empty clock, can't be right.
|
|
return;
|
|
}
|
|
Glib::RefPtr<Clipboard> cl = Gtk::Clipboard::get();
|
|
cl->set_text (val.substr(trim));
|
|
}
|
|
|
|
void
|
|
AudioClock::build_ops_menu ()
|
|
{
|
|
using namespace Menu_Helpers;
|
|
ops_menu = new Menu;
|
|
MenuList& ops_items = ops_menu->items();
|
|
ops_menu->set_name ("ArdourContextMenu");
|
|
|
|
if (!Profile->get_sae()) {
|
|
ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode)));
|
|
}
|
|
ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT)));
|
|
ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec)));
|
|
ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames)));
|
|
|
|
if (editable && !_off && !is_duration && !_follows_playhead) {
|
|
ops_items.push_back (SeparatorElem());
|
|
ops_items.push_back (MenuElem (_("Set From Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
|
|
ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
|
|
}
|
|
ops_items.push_back (SeparatorElem());
|
|
ops_items.push_back (MenuElem (_("Copy to clipboard"), sigc::mem_fun(*this, &AudioClock::copy_text_to_clipboard)));
|
|
}
|
|
|
|
void
|
|
AudioClock::set_from_playhead ()
|
|
{
|
|
if (!_session) {
|
|
return;
|
|
}
|
|
|
|
set (_session->transport_frame());
|
|
ValueChanged ();
|
|
}
|
|
|
|
void
|
|
AudioClock::locate ()
|
|
{
|
|
if (!_session || is_duration) {
|
|
return;
|
|
}
|
|
|
|
_session->request_locate (current_time(), _session->transport_rolling ());
|
|
}
|
|
|
|
void
|
|
AudioClock::set_mode (Mode m)
|
|
{
|
|
if (_mode == m) {
|
|
return;
|
|
}
|
|
|
|
_mode = m;
|
|
|
|
insert_map.clear();
|
|
|
|
_layout->set_text ("");
|
|
|
|
Gtk::Requisition req;
|
|
set_clock_dimensions (req);
|
|
|
|
if (_left_layout) {
|
|
|
|
_left_layout->set_attributes (info_attributes);
|
|
_right_layout->set_attributes (info_attributes);
|
|
/* adjust info_height according to font size */
|
|
int ignored;
|
|
_left_layout->set_text (" 1234567890");
|
|
_left_layout->get_pixel_size (ignored, info_height);
|
|
|
|
_left_layout->set_text ("");
|
|
_right_layout->set_text ("");
|
|
}
|
|
|
|
switch (_mode) {
|
|
case Timecode:
|
|
mode_based_info_ratio = 0.6;
|
|
insert_map.push_back (11);
|
|
insert_map.push_back (10);
|
|
insert_map.push_back (8);
|
|
insert_map.push_back (7);
|
|
insert_map.push_back (5);
|
|
insert_map.push_back (4);
|
|
insert_map.push_back (2);
|
|
insert_map.push_back (1);
|
|
break;
|
|
|
|
case BBT:
|
|
mode_based_info_ratio = 0.5;
|
|
insert_map.push_back (11);
|
|
insert_map.push_back (10);
|
|
insert_map.push_back (9);
|
|
insert_map.push_back (8);
|
|
insert_map.push_back (6);
|
|
insert_map.push_back (5);
|
|
insert_map.push_back (3);
|
|
insert_map.push_back (2);
|
|
insert_map.push_back (1);
|
|
break;
|
|
|
|
case MinSec:
|
|
mode_based_info_ratio = 0.6;
|
|
insert_map.push_back (12);
|
|
insert_map.push_back (11);
|
|
insert_map.push_back (10);
|
|
insert_map.push_back (8);
|
|
insert_map.push_back (7);
|
|
insert_map.push_back (5);
|
|
insert_map.push_back (4);
|
|
insert_map.push_back (2);
|
|
insert_map.push_back (1);
|
|
break;
|
|
|
|
case Frames:
|
|
mode_based_info_ratio = 0.45;
|
|
break;
|
|
}
|
|
|
|
set (last_when, true);
|
|
|
|
if (!is_transient) {
|
|
ModeChanged (); /* EMIT SIGNAL (the static one)*/
|
|
}
|
|
|
|
mode_changed (); /* EMIT SIGNAL (the member one) */
|
|
}
|
|
|
|
void
|
|
AudioClock::set_bbt_reference (framepos_t pos)
|
|
{
|
|
bbt_reference_time = pos;
|
|
}
|
|
|
|
void
|
|
AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
|
|
{
|
|
CairoWidget::on_style_changed (old_style);
|
|
|
|
Gtk::Requisition req;
|
|
set_clock_dimensions (req);
|
|
|
|
/* XXXX fix me ... we shouldn't be using GTK styles anyway */
|
|
// set_font ();
|
|
set_colors ();
|
|
}
|
|
|
|
void
|
|
AudioClock::set_editable (bool yn)
|
|
{
|
|
editable = yn;
|
|
}
|
|
|
|
void
|
|
AudioClock::set_is_duration (bool yn)
|
|
{
|
|
if (yn == is_duration) {
|
|
return;
|
|
}
|
|
|
|
is_duration = yn;
|
|
set (last_when, true);
|
|
}
|
|
|
|
void
|
|
AudioClock::set_off (bool yn)
|
|
{
|
|
if (_off == yn) {
|
|
return;
|
|
}
|
|
|
|
_off = yn;
|
|
|
|
/* force a redraw. last_when will be preserved, but the clock text will
|
|
* change
|
|
*/
|
|
|
|
set (last_when, true);
|
|
}
|
|
|
|
void
|
|
AudioClock::focus ()
|
|
{
|
|
start_edit (Field (0));
|
|
}
|
|
|
|
void
|
|
AudioClock::set_corner_radius (double r)
|
|
{
|
|
corner_radius = r;
|
|
first_width = 0;
|
|
first_height = 0;
|
|
queue_resize ();
|
|
}
|
|
|
|
void
|
|
AudioClock::dpi_reset ()
|
|
{
|
|
/* force recomputation of size even if we are fixed width
|
|
*/
|
|
first_width = 0;
|
|
first_height = 0;
|
|
queue_resize ();
|
|
}
|
|
|
|
void
|
|
AudioClock::set_negative_allowed (bool yn)
|
|
{
|
|
_negative_allowed = yn;
|
|
}
|