13
0
livetrax/libs/gtkmm2ext/motionfeedback.cc
Paul Davis 9e3299f97d change Controllable::set_value() API to include grouped control consideration.
This also removes Route::group_gain_control() and associated machinery.
Not yet tested with Mackie or other surfaces. More work to done to
start using the group capabilities, and also potentially to add
or derive more controls as RouteAutomationControls
2016-01-02 04:58:30 -05:00

694 lines
19 KiB
C++

/*
Copyright (C) 2010-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.
$Id: motionfeedback.cc,v 1.5 2004/03/01 03:44:19 pauld Exp $
*/
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <unistd.h>
#include <stdio.h> /* for snprintf, grrr */
#include "pbd/gstdio_compat.h"
#include <gdk/gdkkeysyms.h>
#include <gtkmm.h>
#include "pbd/controllable.h"
#include "pbd/compose.h"
#include "pbd/error.h"
#include "gtkmm2ext/motionfeedback.h"
#include "gtkmm2ext/keyboard.h"
#include "gtkmm2ext/prolooks-helpers.h"
#include "gtkmm2ext/gui_thread.h"
#include "i18n.h"
using namespace Gtk;
using namespace Gtkmm2ext;
using namespace sigc;
using PBD::error;
using PBD::Controllable;
Gdk::Color* MotionFeedback::base_color;
MotionFeedback::MotionFeedback (Glib::RefPtr<Gdk::Pixbuf> pix,
Type t,
boost::shared_ptr<PBD::Controllable> c,
double default_val,
double step_increment,
double page_increment,
const char *widget_name,
bool with_numeric_display,
int subw,
int subh)
: _controllable (c)
, value (0)
, default_value (default_val)
, step_inc (step_increment)
, page_inc (page_increment)
, type (t)
, value_packer (0)
, pixbuf (pix)
, subwidth (subw)
, subheight (subh)
{
if (!base_color) {
base_color = new Gdk::Color ("#1a5274");
}
char value_name[1024];
print_func = default_printer;
print_arg = 0;
HBox* hpacker = manage (new HBox);
hpacker->pack_start (pixwin, true, true);
hpacker->show ();
pack_start (*hpacker, false, false);
pixwin.show ();
if (with_numeric_display) {
value_packer = new EventBox;
value_packer->set_name ("MotionControllerValue");
value_packer->show ();
value_packer->set_border_width (6);
value = new Label;
value->set_justify (Gtk::JUSTIFY_RIGHT);
value->show ();
value_packer->add (*value);
hpacker = manage (new HBox);
hpacker->pack_start (*value_packer, true, false);
hpacker->show ();
hpacker->set_border_width (6);
pack_start (*hpacker, false, false);
if (widget_name) {
snprintf (value_name, sizeof(value_name), "%sValue", widget_name);
value->set_name (value_name);
}
if (_controllable) {
char buf[32];
print_func (buf, _controllable, print_arg);
value->set_text (buf);
}
}
pixwin.set_events (Gdk::BUTTON_PRESS_MASK|
Gdk::BUTTON_RELEASE_MASK|
Gdk::POINTER_MOTION_MASK|
Gdk::ENTER_NOTIFY_MASK|
Gdk::LEAVE_NOTIFY_MASK|
Gdk::SCROLL_MASK|
Gdk::KEY_PRESS_MASK|
Gdk::KEY_RELEASE_MASK);
pixwin.set_flags (CAN_FOCUS);
/* Proxy all important events on the pixwin to ourselves */
pixwin.signal_button_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_press_event));
pixwin.signal_button_release_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_release_event));
pixwin.signal_motion_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_motion_notify_event));
pixwin.signal_enter_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_enter_notify_event));
pixwin.signal_leave_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_leave_notify_event));
pixwin.signal_key_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_key_press_event));
pixwin.signal_scroll_event().connect(mem_fun (*this,&MotionFeedback::pixwin_scroll_event));
pixwin.signal_expose_event().connect(mem_fun (*this,&MotionFeedback::pixwin_expose_event), true);
pixwin.signal_size_request().connect(mem_fun (*this,&MotionFeedback::pixwin_size_request));
}
MotionFeedback::~MotionFeedback()
{
delete value;
delete value_packer;
}
bool
MotionFeedback::pixwin_button_press_event (GdkEventButton *ev)
{
if (binding_proxy.button_press_handler (ev)) {
return true;
}
switch (ev->button) {
case 1:
grab_is_fine = false;
break;
case 2:
grab_is_fine = true;
break;
case 3:
return false;
}
gtk_grab_add(GTK_WIDGET(pixwin.gobj()));
grabbed_y = ev->y_root;
grabbed_x = ev->x_root;
return false;
}
bool
MotionFeedback::pixwin_button_release_event (GdkEventButton *ev)
{
if (!_controllable) {
return false;
}
switch (ev->button) {
case 1:
if (pixwin.has_grab()) {
if (!grab_is_fine) {
gtk_grab_remove
(GTK_WIDGET(pixwin.gobj()));
}
}
if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
/* shift click back to the default */
_controllable->set_value (default_value, Controllable::NoGroup);
return true;
} else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
/* ctrl click back to the minimum value */
_controllable->set_value (_controllable->lower (), Controllable::NoGroup);
}
break;
case 3:
if (pixwin.has_grab()) {
if (grab_is_fine) {
gtk_grab_remove
(GTK_WIDGET(pixwin.gobj()));
}
}
break;
}
return VBox::on_button_release_event (ev);
}
bool
MotionFeedback::pixwin_motion_notify_event (GdkEventMotion *ev)
{
if (!_controllable) {
return false;
}
gfloat multiplier;
gfloat x_delta;
gfloat y_delta;
if (!pixwin.has_grab()) {
return VBox::on_motion_notify_event (ev);
}
multiplier = ((ev->state & Keyboard::TertiaryModifier) ? 100 : 1) *
((ev->state & Keyboard::PrimaryModifier) ? 10 : 1) *
((ev->state & Keyboard::SecondaryModifier) ? 0.1 : 1);
if (ev->state & Gdk::BUTTON1_MASK) {
/* vertical control */
y_delta = grabbed_y - ev->y_root;
grabbed_y = ev->y_root;
x_delta = ev->x_root - grabbed_x;
if (y_delta == 0) return TRUE;
y_delta *= 1 + (x_delta/100);
y_delta *= multiplier;
y_delta /= 10;
_controllable->set_value (adjust ((grab_is_fine ? step_inc : page_inc) * y_delta), Controllable::NoGroup);
} else if (ev->state & Gdk::BUTTON2_MASK) {
/* rotary control */
double x = ev->x - subwidth/2;
double y = - ev->y + subwidth/2;
double angle = std::atan2 (y, x) / M_PI;
if (angle < -0.5) {
angle += 2.0;
}
angle = -(2.0/3.0) * (angle - 1.25);
angle *= multiplier;
_controllable->set_value (to_control_value (angle), Controllable::NoGroup);
}
return true;
}
bool
MotionFeedback::pixwin_enter_notify_event (GdkEventCrossing*)
{
pixwin.grab_focus();
return false;
}
bool
MotionFeedback::pixwin_leave_notify_event (GdkEventCrossing*)
{
pixwin.unset_flags (HAS_FOCUS);
return false;
}
bool
MotionFeedback::pixwin_key_press_event (GdkEventKey *ev)
{
if (!_controllable) {
return false;
}
bool retval = false;
double multiplier;
multiplier = ((ev->state & Keyboard::TertiaryModifier) ? 100.0 : 1.0) *
((ev->state & Keyboard::SecondaryModifier) ? 10.0 : 1.0) *
((ev->state & Keyboard::PrimaryModifier) ? 2.0 : 1.0);
switch (ev->keyval) {
case GDK_Page_Up:
retval = true;
_controllable->set_value (adjust (multiplier * page_inc), Controllable::NoGroup);
break;
case GDK_Page_Down:
retval = true;
_controllable->set_value (adjust (-multiplier * page_inc), Controllable::NoGroup);
break;
case GDK_Up:
retval = true;
_controllable->set_value (adjust (multiplier * step_inc), Controllable::NoGroup);
break;
case GDK_Down:
retval = true;
_controllable->set_value (adjust (-multiplier * step_inc), Controllable::NoGroup);
break;
case GDK_Home:
retval = true;
_controllable->set_value (_controllable->lower(), Controllable::NoGroup);
break;
case GDK_End:
retval = true;
_controllable->set_value (_controllable->upper(), Controllable::NoGroup);
break;
}
return retval;
}
bool
MotionFeedback::pixwin_expose_event (GdkEventExpose*)
{
if (!_controllable) {
return true;
}
GdkWindow *window = pixwin.get_window()->gobj();
double display_val = to_display_value (_controllable->get_value());
int32_t phase = lrint (display_val * 64.0);
// skip middle phase except for true middle value
if (type == Rotary && phase == 32) {
double pt = (display_val * 2.0) - 1.0;
if (pt < 0)
phase = 31;
if (pt > 0)
phase = 33;
}
// endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
if (type == Endless && !(phase % 16)) {
if (phase == 64) {
phase = 0;
}
double nom = phase / 64.0;
double diff = display_val - nom;
if (diff > 0.0001)
phase = (phase + 1) % 64;
if (diff < -0.0001)
phase = (phase + 63) % 64;
}
phase = std::min (phase, (int32_t) 63);
GtkWidget* widget = GTK_WIDGET(pixwin.gobj());
gdk_draw_pixbuf (GDK_DRAWABLE(window), widget->style->fg_gc[0],
pixbuf->gobj(),
phase * subwidth, type * subheight,
/* center image in allocated area */
(get_width() - subwidth)/2,
0,
subwidth, subheight, GDK_RGB_DITHER_NORMAL, 0, 0);
return true;
}
bool
MotionFeedback::pixwin_scroll_event (GdkEventScroll* ev)
{
double scale;
if (!_controllable) {
return false;
}
if (ev->state & Keyboard::GainFineScaleModifier) {
if (ev->state & Keyboard::GainExtraFineScaleModifier) {
scale = 0.01;
} else {
scale = 0.10;
}
} else {
scale = 0.20;
}
switch (ev->direction) {
case GDK_SCROLL_UP:
case GDK_SCROLL_RIGHT:
_controllable->set_value (adjust (scale * page_inc), Controllable::NoGroup);
break;
case GDK_SCROLL_DOWN:
case GDK_SCROLL_LEFT:
_controllable->set_value (adjust (-scale * page_inc), Controllable::NoGroup);
break;
}
return true;
}
void
MotionFeedback::pixwin_size_request (GtkRequisition* req)
{
req->width = subwidth;
req->height = subheight;
}
void
MotionFeedback::controllable_value_changed ()
{
if (value) {
char buf[32];
print_func (buf, _controllable, print_arg);
value->set_text (buf);
}
pixwin.queue_draw ();
}
void
MotionFeedback::set_controllable (boost::shared_ptr<PBD::Controllable> c)
{
_controllable = c;
binding_proxy.set_controllable (c);
controller_connection.disconnect ();
if (c) {
c->Changed.connect (controller_connection, MISSING_INVALIDATOR, boost::bind (&MotionFeedback::controllable_value_changed, this), gui_context());
char buf[32];
print_func (buf, _controllable, print_arg);
value->set_text (buf);
}
pixwin.queue_draw ();
}
boost::shared_ptr<PBD::Controllable>
MotionFeedback::controllable () const
{
return _controllable;
}
void
MotionFeedback::default_printer (char buf[32], const boost::shared_ptr<PBD::Controllable>& c, void *)
{
if (c) {
sprintf (buf, "%.2f", c->get_value());
} else {
buf[0] = '\0';
}
}
Glib::RefPtr<Gdk::Pixbuf>
MotionFeedback::render_pixbuf (int size)
{
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
char *path;
int fd;
GError *gerror = NULL;
fd = g_file_open_tmp ("mfimgXXXXXX", &path, &gerror);
if (gerror) {
error << string_compose (_("motionfeedback: failed to open a temporary file for writing: %1"), gerror->message) << endmsg;
g_error_free (gerror);
return pixbuf;
} else {
::close (fd);
}
GdkColor col2 = {0,0,0,0};
GdkColor col3 = {0,0,0,0};
GdkColor dark;
GdkColor bright;
ProlooksHSV* hsv;
hsv = prolooks_hsv_new_for_gdk_color (base_color->gobj());
bright = (prolooks_hsv_to_gdk_color (hsv, &col2), col2);
prolooks_hsv_set_saturation (hsv, 0.66);
prolooks_hsv_set_value (hsv, 0.67);
dark = (prolooks_hsv_to_gdk_color (hsv, &col3), col3);
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size * 64, size);
cairo_t* cr = cairo_create (surface);
for (int i = 0; i < 64; ++i) {
cairo_save (cr);
core_draw (cr, i, size, 20, size*i, 0, &bright, &dark);
cairo_restore (cr);
}
if (cairo_surface_write_to_png (surface, path) != CAIRO_STATUS_SUCCESS) {
error << string_compose (_("motionfeedback: could not save image set to %1"), path) << endmsg;
return pixbuf;
}
cairo_destroy (cr);
cairo_surface_destroy (surface);
try {
pixbuf = Gdk::Pixbuf::create_from_file (path);
} catch (const Gdk::PixbufError &e) {
error << string_compose (_("motionfeedback: caught PixbufError: %1"), e.what()) << endmsg;
} catch (...) {
error << _("motionfeedback: unknown exception") << endmsg;
}
g_unlink (path);
g_free (path);
return pixbuf;
}
void
MotionFeedback::core_draw (cairo_t* cr, int phase, double size, double progress_width, double xorigin, double yorigin,
const GdkColor* bright, const GdkColor* dark)
{
double xc;
double yc;
double start_angle;
double end_angle;
double value_angle;
double value;
double value_x;
double value_y;
double start_angle_x;
double start_angle_y;
double end_angle_x;
double end_angle_y;
double progress_radius;
double progress_radius_inner;
double progress_radius_outer;
g_return_if_fail (cr != NULL);
progress_radius = 40.0;
progress_radius_inner = progress_radius - (progress_width / 2.0);
progress_radius_outer = progress_radius + (progress_width / 2.0);
const double pad = 2.0; /* line width for boundary of progress ring */
const double actual_width = ((2.0 * pad) + (2.0 * progress_radius_outer));
const double scale_factor = size / actual_width;
/* knob center is at middle of the area bounded by (xorigin,yorigin) and (xorigin+size, yorigin+size)
but the coordinates will be scaled by the scale factor when cairo uses them so first
adjust them by the reciprocal of the scale factor.
*/
xc = (xorigin + (size / 2.0)) * (1.0/scale_factor);
yc = (yorigin + (size / 2.0)) * (1.0/scale_factor);
value = (phase * 1.0) / (65 - 1);
start_angle = ((180 - 65) * G_PI) / 180;
end_angle = ((360 + 65) * G_PI) / 180;
value_angle = start_angle + (value * (end_angle - start_angle));
value_x = cos (value_angle);
value_y = sin (value_angle);
start_angle_x = cos (start_angle);
start_angle_y = sin (start_angle);
end_angle_x = cos (end_angle);
end_angle_y = sin (end_angle);
cairo_scale (cr, scale_factor, scale_factor);
//dark arc background
cairo_set_source_rgb (cr, 0.3, 0.3, 0.3 );
cairo_set_line_width (cr, progress_width);
cairo_arc (cr, xc, yc, progress_radius, start_angle, end_angle);
cairo_stroke (cr);
float r = (value) * (((float)bright->red)/G_MAXUINT16) + (1.0-value)*(((float)dark->red)/G_MAXUINT16);
float g = (value) * (((float)bright->green)/G_MAXUINT16) + (1.0-value)*(((float)dark->green)/G_MAXUINT16);
float b = (value) * (((float)bright->blue)/G_MAXUINT16) + (1.0-value)*(((float)dark->blue)/G_MAXUINT16);
//colored arc
cairo_set_source_rgb (cr, r,g,b);
cairo_set_line_width (cr, progress_width);
cairo_arc (cr, xc, yc, progress_radius, start_angle, value_angle);
cairo_stroke (cr);
//overall shade
cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, progress_radius_outer);
cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 1,1,1, 0.3);
cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 1,1,1, 0.0);
cairo_set_source (cr, shade_pattern);
cairo_arc (cr, xc, yc, progress_radius_outer-1, 0, 2.0*G_PI);
cairo_fill (cr);
cairo_pattern_destroy (shade_pattern);
//black border
cairo_set_source_rgb (cr, 0, 0, 0 );
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_width (cr, 1.0/scale_factor);
cairo_move_to (cr, xc + (progress_radius_outer * start_angle_x), yc + (progress_radius_outer * start_angle_y));
cairo_line_to (cr, xc + (progress_radius_inner * start_angle_x), yc + (progress_radius_inner * start_angle_y));
cairo_stroke (cr);
cairo_move_to (cr, xc + (progress_radius_outer * end_angle_x), yc + (progress_radius_outer * end_angle_y));
cairo_line_to (cr, xc + (progress_radius_inner * end_angle_x), yc + (progress_radius_inner * end_angle_y));
cairo_stroke (cr);
cairo_arc (cr, xc, yc, progress_radius_outer, start_angle, end_angle);
cairo_stroke (cr);
cairo_arc (cr, xc, yc, progress_radius_inner, start_angle, end_angle);
cairo_stroke (cr);
//knob shadow
cairo_save(cr);
cairo_translate(cr, 6, 6 );
cairo_set_source_rgba (cr, 0,0,0,0.1 );
cairo_arc (cr, xc, yc, progress_radius_inner-1, 0, 2.0*G_PI);
cairo_fill (cr);
cairo_restore(cr);
//inner circle
cairo_set_source_rgba (cr, 0.3, 0.3, 0.3, 1 );
cairo_arc (cr, xc, yc, progress_radius_inner-1, 0, 2.0*G_PI);
cairo_fill (cr);
//knob shade
shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, progress_radius_outer);
cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 1,1,1, 0.5);
cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0,0,0, 0.3);
cairo_set_source (cr, shade_pattern);
cairo_arc (cr, xc, yc, progress_radius_inner-1, 0, 2.0*G_PI);
cairo_fill (cr);
cairo_pattern_destroy (shade_pattern);
//inner circle
cairo_set_source_rgba (cr, 0.3, 0.3, 0.3, 0.5 );
cairo_arc (cr, xc, yc, progress_radius_inner-5, 0, 2.0*G_PI);
cairo_fill (cr);
//line
cairo_save(cr);
cairo_translate(cr, 2, 2 );
cairo_set_source_rgba (cr, 0,0,0,0.5 );
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_width (cr, 4);
cairo_move_to (cr, xc + (progress_radius_inner * value_x), yc + (progress_radius_inner * value_y));
cairo_line_to (cr, xc + ((progress_radius_inner*0.4) * value_x), yc + ((progress_radius_inner*0.4) * value_y));
cairo_stroke (cr);
cairo_restore(cr);
cairo_set_source_rgba (cr, 1,1,1,0.7 );
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_width (cr, 4.0);
cairo_move_to (cr, xc + (progress_radius_inner * value_x), yc + (progress_radius_inner * value_y));
cairo_line_to (cr, xc + ((progress_radius_inner*0.4) * value_x), yc + ((progress_radius_inner*0.4) * value_y));
cairo_stroke (cr);
//highlight if focused (damn, this is a cached image which doesn't (yet) have a "focused" state
// if (pixwin.has_focus()) {
// cairo_set_source_rgba (cr, 1,1,1, 0.5 );
// cairo_arc (cr, xc, yc, progress_radius_inner-1, 0, 2.0*G_PI);
// cairo_fill (cr);
// }
}
void
MotionFeedback::set_lamp_color (const std::string& str)
{
if (base_color) {
*base_color = Gdk::Color (str);
} else {
base_color = new Gdk::Color (str);
}
}