13
0
livetrax/gtk2_ardour/shuttle_control.cc
David Robillard 297a51b715 Make shuttle colour a configuration option.
Make default shuttle colour match clock text green.

This is a bit odd, since it is a "canvas variable", except not... all the other colours are in this section though.  Maybe they're more appropriately named "colours", but it's too late for that now, or maybe this option belongs elsewhere, but when we need new mechanism for that...


git-svn-id: svn://localhost/ardour2/branches/3.0@9553 d708f5d6-7413-0410-9779-e7cbd77b26cf
2011-05-18 05:32:41 +00:00

653 lines
16 KiB
C++

/*
Copyright (C) 2011 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 <algorithm>
#include <cairo/cairo.h>
#include "ardour/ardour.h"
#include "ardour/audioengine.h"
#include "ardour/rc_configuration.h"
#include "ardour/session.h"
#include "gtkmm2ext/keyboard.h"
#include "gtkmm2ext/gui_thread.h"
#include "ardour_ui.h"
#include "rgb_macros.h"
#include "shuttle_control.h"
#include "i18n.h"
using namespace Gtk;
using namespace Gtkmm2ext;
using namespace ARDOUR;
using std::min;
using std::max;
gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
{
return FALSE;
}
ShuttleControl::ShuttleControl ()
: _controllable (new ShuttleControllable (*this))
, binding_proxy (_controllable)
{
ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
pattern = 0;
last_shuttle_request = 0;
last_speed_displayed = -99999999;
shuttle_grabbed = false;
shuttle_speed_on_grab = 0;
shuttle_fract = 0.0;
shuttle_max_speed = 8.0f;
shuttle_style_menu = 0;
shuttle_unit_menu = 0;
shuttle_context_menu = 0;
set_flags (CAN_FOCUS);
add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::POINTER_MOTION_MASK|Gdk::SCROLL_MASK);
set_size_request (100, 15);
set_name (X_("ShuttleControl"));
Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
/* gtkmm 2.4: the C++ wrapper doesn't work */
g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
// signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
}
ShuttleControl::~ShuttleControl ()
{
cairo_pattern_destroy (pattern);
}
void
ShuttleControl::set_session (Session *s)
{
SessionHandlePtr::set_session (s);
if (_session) {
set_sensitive (true);
_session->add_controllable (_controllable);
} else {
set_sensitive (false);
}
}
void
ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
{
if (pattern) {
cairo_pattern_destroy (pattern);
pattern = 0;
}
pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
/* add 3 color stops */
uint32_t col = ARDOUR_UI::config()->canvasvar_Shuttle.get();
int r,b,g,a;
UINT_TO_RGBA(col, &r, &g, &b, &a);
cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
cairo_pattern_add_color_stop_rgb (pattern, 0.5, r/255.0, g/255.0, b/255.0);
cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
DrawingArea::on_size_allocate (alloc);
}
void
ShuttleControl::map_transport_state ()
{
float speed = _session->transport_speed ();
if (fabs(speed) <= (2*DBL_EPSILON)) {
shuttle_fract = 0;
} else {
if (Config->get_shuttle_units() == Semitones) {
bool reverse;
int semi = speed_as_semitones (speed, reverse);
shuttle_fract = semitones_as_fract (semi, reverse);
} else {
shuttle_fract = speed/shuttle_max_speed;
}
}
queue_draw ();
}
void
ShuttleControl::build_shuttle_context_menu ()
{
using namespace Menu_Helpers;
shuttle_context_menu = new Menu();
MenuList& items = shuttle_context_menu->items();
Menu* speed_menu = manage (new Menu());
MenuList& speed_items = speed_menu->items();
Menu* units_menu = manage (new Menu);
MenuList& units_items = units_menu->items();
RadioMenuItem::Group units_group;
units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
if (Config->get_shuttle_units() == Percentage) {
static_cast<RadioMenuItem*>(&units_items.back())->set_active();
}
units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
if (Config->get_shuttle_units() == Semitones) {
static_cast<RadioMenuItem*>(&units_items.back())->set_active();
}
items.push_back (MenuElem (_("Units"), *units_menu));
Menu* style_menu = manage (new Menu);
MenuList& style_items = style_menu->items();
RadioMenuItem::Group style_group;
style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
if (Config->get_shuttle_behaviour() == Sprung) {
static_cast<RadioMenuItem*>(&style_items.back())->set_active();
}
style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
if (Config->get_shuttle_behaviour() == Wheel) {
static_cast<RadioMenuItem*>(&style_items.back())->set_active();
}
items.push_back (MenuElem (_("Mode"), *style_menu));
RadioMenuItem::Group speed_group;
speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
if (shuttle_max_speed == 8.0) {
static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
}
speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
if (shuttle_max_speed == 6.0) {
static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
}
speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
if (shuttle_max_speed == 4.0) {
static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
}
speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
if (shuttle_max_speed == 3.0) {
static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
}
speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
if (shuttle_max_speed == 2.0) {
static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
}
speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
if (shuttle_max_speed == 1.5) {
static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
}
items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
}
void
ShuttleControl::show_shuttle_context_menu ()
{
if (shuttle_context_menu == 0) {
build_shuttle_context_menu ();
}
shuttle_context_menu->popup (1, gtk_get_current_event_time());
}
void
ShuttleControl::set_shuttle_max_speed (float speed)
{
shuttle_max_speed = speed;
}
bool
ShuttleControl::on_button_press_event (GdkEventButton* ev)
{
if (!_session) {
return true;
}
if (binding_proxy.button_press_handler (ev)) {
return true;
}
if (Keyboard::is_context_menu_event (ev)) {
show_shuttle_context_menu ();
return true;
}
switch (ev->button) {
case 1:
add_modal_grab ();
shuttle_grabbed = true;
shuttle_speed_on_grab = _session->transport_speed ();
mouse_shuttle (ev->x, true);
break;
case 2:
case 3:
return true;
break;
}
return true;
}
bool
ShuttleControl::on_button_release_event (GdkEventButton* ev)
{
if (!_session) {
return true;
}
switch (ev->button) {
case 1:
shuttle_grabbed = false;
remove_modal_grab ();
if (Config->get_shuttle_behaviour() == Sprung) {
_session->request_transport_speed (shuttle_speed_on_grab);
} else {
mouse_shuttle (ev->x, true);
}
return true;
case 2:
if (_session->transport_rolling()) {
_session->request_transport_speed (1.0);
}
return true;
case 3:
default:
return true;
}
return true;
}
bool
ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
{
return false;
}
bool
ShuttleControl::on_scroll_event (GdkEventScroll* ev)
{
if (!_session || Config->get_shuttle_behaviour() != Wheel) {
return true;
}
switch (ev->direction) {
case GDK_SCROLL_UP:
case GDK_SCROLL_RIGHT:
shuttle_fract += 0.005;
break;
case GDK_SCROLL_DOWN:
case GDK_SCROLL_LEFT:
shuttle_fract -= 0.005;
break;
default:
return false;
}
if (Config->get_shuttle_units() == Semitones) {
float lower_side_of_dead_zone = semitones_as_fract (-24, true);
float upper_side_of_dead_zone = semitones_as_fract (-24, false);
/* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
to the far side of it.
*/
if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
switch (ev->direction) {
case GDK_SCROLL_UP:
case GDK_SCROLL_RIGHT:
shuttle_fract = upper_side_of_dead_zone;
break;
case GDK_SCROLL_DOWN:
case GDK_SCROLL_LEFT:
shuttle_fract = lower_side_of_dead_zone;
break;
default:
/* impossible, checked above */
return false;
}
}
}
use_shuttle_fract (true);
return true;
}
bool
ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
{
if (!_session || !shuttle_grabbed) {
return true;
}
return mouse_shuttle (ev->x, false);
}
gint
ShuttleControl::mouse_shuttle (double x, bool force)
{
double const center = get_width() / 2.0;
double distance_from_center = x - center;
if (distance_from_center > 0) {
distance_from_center = min (distance_from_center, center);
} else {
distance_from_center = max (distance_from_center, -center);
}
/* compute shuttle fract as expressing how far between the center
and the edge we are. positive values indicate we are right of
center, negative values indicate left of center
*/
shuttle_fract = distance_from_center / center; // center == half the width
use_shuttle_fract (force);
return true;
}
void
ShuttleControl::set_shuttle_fract (double f)
{
shuttle_fract = f;
use_shuttle_fract (false);
}
int
ShuttleControl::speed_as_semitones (float speed, bool& reverse)
{
assert (speed != 0.0);
if (speed < 0.0) {
reverse = true;
return (int) round (12.0 * fast_log2 (-speed));
} else {
reverse = false;
return (int) round (12.0 * fast_log2 (speed));
}
}
float
ShuttleControl::semitones_as_speed (int semi, bool reverse)
{
if (reverse) {
return -pow (2.0, (semi / 12.0));
} else {
return pow (2.0, (semi / 12.0));
}
}
float
ShuttleControl::semitones_as_fract (int semi, bool reverse)
{
float speed = semitones_as_speed (semi, reverse);
return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
}
int
ShuttleControl::fract_as_semitones (float fract, bool& reverse)
{
assert (fract != 0.0);
return speed_as_semitones (fract * 4.0, reverse);
}
void
ShuttleControl::use_shuttle_fract (bool force)
{
microseconds_t now = get_microseconds();
shuttle_fract = max (-1.0f, shuttle_fract);
shuttle_fract = min (1.0f, shuttle_fract);
/* do not attempt to submit a motion-driven transport speed request
more than once per process cycle.
*/
if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
return;
}
last_shuttle_request = now;
double speed = 0;
if (Config->get_shuttle_units() == Semitones) {
if (shuttle_fract != 0.0) {
bool reverse;
int semi = fract_as_semitones (shuttle_fract, reverse);
speed = semitones_as_speed (semi, reverse);
} else {
speed = 0.0;
}
} else {
speed = shuttle_max_speed * shuttle_fract;
}
_session->request_transport_speed_nonzero (speed);
}
bool
ShuttleControl::on_expose_event (GdkEventExpose* event)
{
cairo_text_extents_t extents;
Glib::RefPtr<Gdk::Window> win (get_window());
Glib::RefPtr<Gtk::Style> style (get_style());
cairo_t* cr = gdk_cairo_create (win->gobj());
cairo_set_source (cr, pattern);
cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
cairo_fill_preserve (cr);
cairo_set_source_rgb (cr, 0, 0, 0.0);
cairo_stroke (cr);
float speed = 0.0;
if (_session) {
speed = _session->transport_speed ();
}
/* Marker */
double visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
double x = (get_width() / 2.0) + (0.5 * (get_width() * visual_fraction));
cairo_move_to (cr, x, 1);
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
cairo_line_to (cr, x, get_height()-1);
cairo_stroke (cr);
/* speed text */
char buf[32];
if (speed != 0) {
if (Config->get_shuttle_units() == Percentage) {
if (speed == 1.0) {
snprintf (buf, sizeof (buf), _("Playing"));
} else {
if (speed < 0.0) {
snprintf (buf, sizeof (buf), "<<< %d%%", (int) round (-speed * 100));
} else {
snprintf (buf, sizeof (buf), ">>> %d%%", (int) round (speed * 100));
}
}
} else {
bool reversed;
int semi = speed_as_semitones (speed, reversed);
if (reversed) {
snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
} else {
snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
}
}
} else {
snprintf (buf, sizeof (buf), _("Stopped"));
}
last_speed_displayed = speed;
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
cairo_text_extents (cr, buf, &extents);
cairo_move_to (cr, 10, extents.height + 2);
cairo_show_text (cr, buf);
/* style text */
switch (Config->get_shuttle_behaviour()) {
case Sprung:
snprintf (buf, sizeof (buf), _("Sprung"));
break;
case Wheel:
snprintf (buf, sizeof (buf), _("Wheel"));
break;
}
cairo_text_extents (cr, buf, &extents);
cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
cairo_show_text (cr, buf);
cairo_destroy (cr);
return true;
}
void
ShuttleControl::shuttle_unit_clicked ()
{
if (shuttle_unit_menu == 0) {
shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
}
shuttle_unit_menu->popup (1, gtk_get_current_event_time());
}
void
ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
{
Config->set_shuttle_behaviour (s);
}
void
ShuttleControl::set_shuttle_units (ShuttleUnits s)
{
Config->set_shuttle_units (s);
}
void
ShuttleControl::update_speed_display ()
{
if (_session->transport_speed() != last_speed_displayed) {
queue_draw ();
}
}
ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
: PBD::Controllable (X_("Shuttle"))
, sc (s)
{
}
void
ShuttleControl::ShuttleControllable::set_id (const std::string& str)
{
_id = str;
}
void
ShuttleControl::ShuttleControllable::set_value (double val)
{
double fract;
if (val == 0.5) {
fract = 0.0;
} else {
if (val < 0.5) {
fract = -((0.5 - val)/0.5);
} else {
fract = ((val - 0.5)/0.5);
}
}
sc.set_shuttle_fract (fract);
}
double
ShuttleControl::ShuttleControllable::get_value () const
{
return sc.get_shuttle_fract ();
}
void
ShuttleControl::parameter_changed (std::string p)
{
if (p == "shuttle-behaviour") {
switch (Config->get_shuttle_behaviour ()) {
case Sprung:
/* back to Sprung - reset to speed = 1.0 if playing
*/
if (_session) {
if (_session->transport_rolling()) {
if (_session->transport_speed() == 1.0) {
queue_draw ();
} else {
_session->request_transport_speed (1.0);
/* redraw when speed changes */
}
} else {
queue_draw ();
}
}
break;
case Wheel:
queue_draw ();
break;
}
} else if (p == "shuttle-units") {
queue_draw ();
}
}