13
0
livetrax/gtk2_ardour/ardour_button.cc
David Robillard 0a71d52448 Fix key bindings in tooltips for ArdourButton buttons.
Sinced gtkmm2ext needs to get at the actions of widgets, I moved the
action to Gtkmm2ext::Activatable.  Not sure if the wisest thing to
do here would be to use Gtkmm::Activatable, but figured there's
a reason Paul didn't do so (the name set_related_action is from there
so presumably it's known about), so this is the simplest change
that allows access to the action in Gtkmm2ext.  The vfunc calling
stuff should probably move there as well...


git-svn-id: svn://localhost/ardour2/branches/3.0@10818 d708f5d6-7413-0410-9779-e7cbd77b26cf
2011-11-24 05:36:31 +00:00

698 lines
17 KiB
C++

/*
Copyright (C) 2010 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 <iostream>
#include <cmath>
#include <algorithm>
#include <pangomm/layout.h>
#include "pbd/compose.h"
#include "pbd/error.h"
#include "gtkmm2ext/utils.h"
#include "gtkmm2ext/rgb_macros.h"
#include "gtkmm2ext/gui_thread.h"
#include "ardour/rc_configuration.h" // for widget prelight preference
#include "ardour_button.h"
#include "ardour_ui.h"
#include "global_signals.h"
#include "i18n.h"
using namespace Gdk;
using namespace Gtk;
using namespace Glib;
using namespace PBD;
using std::max;
using std::min;
using namespace std;
ArdourButton::Element ArdourButton::default_elements = ArdourButton::Element (ArdourButton::Edge|ArdourButton::Body|ArdourButton::Text);
ArdourButton::Element ArdourButton::led_default_elements = ArdourButton::Element (ArdourButton::default_elements|ArdourButton::Indicator);
ArdourButton::Element ArdourButton::just_led_default_elements = ArdourButton::Element (ArdourButton::Edge|ArdourButton::Body|ArdourButton::Indicator);
ArdourButton::ArdourButton (Element e)
: _elements (e)
, _tweaks (Tweaks (0))
, _text_width (0)
, _text_height (0)
, _diameter (11.0)
, _corner_radius (9.0)
, edge_pattern (0)
, fill_pattern (0)
, led_inset_pattern (0)
, reflection_pattern (0)
, _led_rect (0)
, _act_on_release (true)
, _led_left (false)
, _fixed_diameter (true)
, _distinct_led_click (false)
, _hovering (false)
{
ColorsChanged.connect (sigc::mem_fun (*this, &ArdourButton::color_handler));
}
ArdourButton::ArdourButton (const std::string& str, Element e)
: _elements (e)
, _text_width (0)
, _text_height (0)
, _diameter (11.0)
, _corner_radius (9.0)
, edge_pattern (0)
, fill_pattern (0)
, led_inset_pattern (0)
, reflection_pattern (0)
, _led_rect (0)
, _act_on_release (true)
, _led_left (false)
, _fixed_diameter (true)
, _distinct_led_click (false)
, _hovering (false)
{
set_text (str);
}
ArdourButton::~ArdourButton()
{
delete _led_rect;
}
void
ArdourButton::set_text (const std::string& str)
{
_text = str;
if (!_layout && !_text.empty()) {
_layout = Pango::Layout::create (get_pango_context());
}
if (_layout) {
_layout->set_text (str);
}
queue_resize ();
}
void
ArdourButton::set_markup (const std::string& str)
{
_text = str;
if (!_layout) {
_layout = Pango::Layout::create (get_pango_context());
}
_layout->set_text (str);
queue_resize ();
}
void
ArdourButton::render (cairo_t* cr)
{
if (!_fixed_diameter) {
_diameter = std::min (get_width(), get_height());
}
if (_elements & Edge) {
Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), _corner_radius);
cairo_set_source (cr, edge_pattern);
cairo_fill (cr);
}
if (_elements & Body) {
if (_elements & Edge) {
Gtkmm2ext::rounded_rectangle (cr, 1, 1, get_width()-2, get_height()-2, _corner_radius - 1.0);
} else {
Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), _corner_radius - 1.0);
}
cairo_set_source (cr, fill_pattern);
cairo_fill (cr);
}
if (_pixbuf) {
double x,y;
x = (get_width() - _pixbuf->get_width())/2.0;
y = (get_height() - _pixbuf->get_height())/2.0;
cairo_rectangle (cr, x, y, _pixbuf->get_width(), _pixbuf->get_height());
gdk_cairo_set_source_pixbuf (cr, _pixbuf->gobj(), x, y);
cairo_fill (cr);
}
/* text, if any */
int text_margin;
if (get_width() < 75) {
text_margin = 3;
} else {
text_margin = 10;
}
if ((_elements & Text) && !_text.empty()) {
cairo_set_source_rgba (cr, text_r, text_g, text_b, text_a);
if (_elements & Indicator) {
if (_led_left) {
cairo_move_to (cr, text_margin + _diameter + 4, get_height()/2.0 - _text_height/2.0);
} else {
cairo_move_to (cr, text_margin, get_height()/2.0 - _text_height/2.0);
}
} else {
/* center text */
cairo_move_to (cr, (get_width() - _text_width)/2.0, get_height()/2.0 - _text_height/2.0);
}
pango_cairo_show_layout (cr, _layout->gobj());
}
if (_elements & Indicator) {
/* move to the center of the indicator/led */
cairo_save (cr);
if (_elements & Text) {
if (_led_left) {
cairo_translate (cr, text_margin + (_diameter/2.0), get_height()/2.0);
} else {
cairo_translate (cr, get_width() - ((_diameter/2.0) + 4.0), get_height()/2.0);
}
} else {
cairo_translate (cr, get_width()/2.0, get_height()/2.0);
}
//inset
cairo_arc (cr, 0, 0, _diameter/2, 0, 2 * M_PI);
cairo_set_source (cr, led_inset_pattern);
cairo_fill (cr);
//black ring
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_arc (cr, 0, 0, _diameter/2-2, 0, 2 * M_PI);
cairo_fill(cr);
//led color
cairo_set_source_rgba (cr, led_r, led_g, led_b, led_a);
cairo_arc (cr, 0, 0, _diameter/2-3, 0, 2 * M_PI);
cairo_fill(cr);
//reflection
cairo_scale(cr, 0.7, 0.7);
cairo_arc (cr, 0, 0, _diameter/2-3, 0, 2 * M_PI);
cairo_set_source (cr, reflection_pattern);
cairo_fill (cr);
cairo_restore (cr);
}
/* a partially transparent gray layer to indicate insensitivity */
if ((visual_state() & Gtkmm2ext::Insensitive)) {
Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), _corner_radius);
cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.5);
cairo_fill (cr);
}
/* if requested, show hovering */
if (ARDOUR::Config->get_widget_prelight()) {
if (_hovering) {
Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), _corner_radius);
cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.2);
cairo_fill (cr);
}
}
}
void
ArdourButton::set_diameter (float d)
{
_diameter = (d*2) + 5.0;
if (_diameter != 0.0) {
_fixed_diameter = true;
}
set_colors ();
}
void
ArdourButton::set_corner_radius (float r)
{
_corner_radius = r;
set_dirty ();
}
void
ArdourButton::on_size_request (Gtk::Requisition* req)
{
int xpad = 0;
int ypad = 6;
CairoWidget::on_size_request (req);
if ((_elements & Text) && !_text.empty()) {
_layout->get_pixel_size (_text_width, _text_height);
if (_text_width + _diameter < 75) {
xpad = 7;
} else {
xpad = 20;
}
} else {
_text_width = 0;
_text_height = 0;
}
if (_pixbuf) {
xpad = 6;
}
if ((_elements & Indicator) && _fixed_diameter) {
if (_pixbuf) {
req->width = _pixbuf->get_width() + lrint (_diameter) + xpad;
req->height = max (_pixbuf->get_height(), (int) lrint (_diameter)) + ypad;
} else {
req->width = _text_width + lrint (_diameter) + xpad;
req->height = max (_text_height, (int) lrint (_diameter)) + ypad;
}
} else {
if (_pixbuf) {
req->width = _pixbuf->get_width() + xpad;
req->height = _pixbuf->get_height() + ypad;
} else {
req->width = _text_width + xpad;
req->height = _text_height + ypad;
}
}
}
void
ArdourButton::set_colors ()
{
uint32_t start_color;
uint32_t end_color;
uint32_t r, g, b, a;
uint32_t text_color;
uint32_t led_color;
/* we use the edge of the button to show Selected state, so the
* color/pattern used there will vary depending on that
*/
if (edge_pattern) {
cairo_pattern_destroy (edge_pattern);
}
if (_elements & Edge) {
edge_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, get_height());
if (visual_state() & Gtkmm2ext::Selected) {
start_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: border start selected", get_name()));
end_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: border end selected", get_name()));
} else {
start_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: border start", get_name()));
end_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: border end", get_name()));
}
UINT_TO_RGBA (start_color, &r, &g, &b, &a);
cairo_pattern_add_color_stop_rgba (edge_pattern, 0, r/255.0,g/255.0,b/255.0, 0.7);
UINT_TO_RGBA (end_color, &r, &g, &b, &a);
cairo_pattern_add_color_stop_rgba (edge_pattern, 1, r/255.0,g/255.0,b/255.0, 0.7);
}
/* the fill pattern is used to indicate Normal/Active/Mid state
*/
if (fill_pattern) {
cairo_pattern_destroy (fill_pattern);
}
if (_elements & Body) {
fill_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, get_height());
if (active_state() == Gtkmm2ext::Mid) {
start_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: fill start mid", get_name()));
end_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: fill end mid", get_name()));
} else if (active_state() == Gtkmm2ext::Active) {
start_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: fill start active", get_name()));
end_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: fill end active", get_name()));
} else {
start_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: fill start", get_name()));
end_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: fill end", get_name()));
}
UINT_TO_RGBA (start_color, &r, &g, &b, &a);
cairo_pattern_add_color_stop_rgba (fill_pattern, 0, r/255.0,g/255.0,b/255.0, a/255.0);
UINT_TO_RGBA (end_color, &r, &g, &b, &a);
cairo_pattern_add_color_stop_rgba (fill_pattern, 1, r/255.0,g/255.0,b/255.0, a/255.0);
}
if (led_inset_pattern) {
cairo_pattern_destroy (led_inset_pattern);
}
if (reflection_pattern) {
cairo_pattern_destroy (reflection_pattern);
}
if (_elements & Indicator) {
led_inset_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, _diameter);
cairo_pattern_add_color_stop_rgba (led_inset_pattern, 0, 0,0,0, 0.4);
cairo_pattern_add_color_stop_rgba (led_inset_pattern, 1, 1,1,1, 0.7);
reflection_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, _diameter/2-3);
cairo_pattern_add_color_stop_rgba (reflection_pattern, 0, 1,1,1, active_state() ? 0.4 : 0.2);
cairo_pattern_add_color_stop_rgba (reflection_pattern, 1, 1,1,1, 0.0);
}
/* text and LED colors depend on Active/Normal/Mid */
if (active_state() == Gtkmm2ext::Active) {
text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: text active", get_name()));
led_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: led active", get_name()));
} else if (active_state() == Gtkmm2ext::Mid) {
text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: text mid", get_name()));
led_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: led mid", get_name()));
} else {
text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: text", get_name()));
led_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: led", get_name()));
}
UINT_TO_RGBA (text_color, &r, &g, &b, &a);
text_r = r/255.0;
text_g = g/255.0;
text_b = b/255.0;
text_a = a/255.0;
UINT_TO_RGBA (led_color, &r, &g, &b, &a);
led_r = r/255.0;
led_g = g/255.0;
led_b = b/255.0;
led_a = a/255.0;
set_dirty ();
}
void
ArdourButton::set_led_left (bool yn)
{
_led_left = yn;
}
bool
ArdourButton::on_button_press_event (GdkEventButton *ev)
{
if ((_elements & Indicator) && _led_rect && _distinct_led_click) {
if (ev->x >= _led_rect->x && ev->x < _led_rect->x + _led_rect->width &&
ev->y >= _led_rect->y && ev->y < _led_rect->y + _led_rect->height) {
return true;
}
}
if (_tweaks & ShowClick) {
set_active_state (Gtkmm2ext::Active);
}
if (binding_proxy.button_press_handler (ev)) {
return true;
}
if (!_act_on_release) {
if (_action) {
_action->activate ();
return true;
}
}
return false;
}
bool
ArdourButton::on_button_release_event (GdkEventButton *ev)
{
if ((_elements & Indicator) && _led_rect && _distinct_led_click) {
if (ev->x >= _led_rect->x && ev->x < _led_rect->x + _led_rect->width &&
ev->y >= _led_rect->y && ev->y < _led_rect->y + _led_rect->height) {
signal_led_clicked(); /* EMIT SIGNAL */
return true;
}
}
if (_tweaks & ShowClick) {
unset_active_state ();
}
signal_clicked ();
if (_act_on_release) {
if (_action) {
Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (_action);
_action->activate ();
return true;
}
}
return false;
}
void
ArdourButton::set_distinct_led_click (bool yn)
{
_distinct_led_click = yn;
setup_led_rect ();
}
void
ArdourButton::color_handler ()
{
set_colors ();
set_dirty ();
}
void
ArdourButton::on_size_allocate (Allocation& alloc)
{
CairoWidget::on_size_allocate (alloc);
setup_led_rect ();
set_colors ();
}
void
ArdourButton::set_controllable (boost::shared_ptr<Controllable> c)
{
watch_connection.disconnect ();
binding_proxy.set_controllable (c);
}
void
ArdourButton::watch ()
{
boost::shared_ptr<Controllable> c (binding_proxy.get_controllable ());
if (!c) {
warning << _("button cannot watch state of non-existing Controllable\n") << endmsg;
return;
}
c->Changed.connect (watch_connection, invalidator(*this), boost::bind (&ArdourButton::controllable_changed, this), gui_context());
}
void
ArdourButton::controllable_changed ()
{
float val = binding_proxy.get_controllable()->get_value();
if (fabs (val) >= 0.5f) {
set_active_state (Gtkmm2ext::Active);
} else {
unset_active_state ();
}
}
void
ArdourButton::set_related_action (RefPtr<Action> act)
{
Gtkmm2ext::Activatable::set_related_action (act);
if (_action) {
action_tooltip_changed ();
Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (_action);
if (tact) {
tact->signal_toggled().connect (sigc::mem_fun (*this, &ArdourButton::action_toggled));
}
_action->connect_property_changed ("sensitive", sigc::mem_fun (*this, &ArdourButton::action_sensitivity_changed));
_action->connect_property_changed ("visible", sigc::mem_fun (*this, &ArdourButton::action_visibility_changed));
_action->connect_property_changed ("tooltip", sigc::mem_fun (*this, &ArdourButton::action_tooltip_changed));
}
}
void
ArdourButton::action_toggled ()
{
Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (_action);
if (tact) {
if (tact->get_active()) {
set_active_state (Gtkmm2ext::Active);
} else {
unset_active_state ();
}
}
}
void
ArdourButton::on_style_changed (const RefPtr<Gtk::Style>&)
{
set_colors ();
}
void
ArdourButton::setup_led_rect ()
{
int text_margin;
if (get_width() < 75) {
text_margin = 3;
} else {
text_margin = 10;
}
if (_elements & Indicator) {
_led_rect = new cairo_rectangle_t;
if (_elements & Text) {
if (_led_left) {
_led_rect->x = text_margin;
} else {
_led_rect->x = get_width() - text_margin - _diameter/2.0;
}
} else {
/* centered */
_led_rect->x = get_width()/2.0 - _diameter/2.0;
}
_led_rect->y = get_height()/2.0 - _diameter/2.0;
_led_rect->width = _diameter;
_led_rect->height = _diameter;
} else {
delete _led_rect;
_led_rect = 0;
}
}
void
ArdourButton::set_image (const RefPtr<Gdk::Pixbuf>& img)
{
_pixbuf = img;
queue_draw ();
}
void
ArdourButton::set_active_state (Gtkmm2ext::ActiveState s)
{
bool changed = (_active_state != s);
CairoWidget::set_active_state (s);
if (changed) {
set_colors ();
}
}
void
ArdourButton::set_visual_state (Gtkmm2ext::VisualState s)
{
bool changed = (_visual_state != s);
CairoWidget::set_visual_state (s);
if (changed) {
set_colors ();
}
}
bool
ArdourButton::on_enter_notify_event (GdkEventCrossing* ev)
{
_hovering = true;
if (ARDOUR::Config->get_widget_prelight()) {
queue_draw ();
}
return CairoWidget::on_enter_notify_event (ev);
}
bool
ArdourButton::on_leave_notify_event (GdkEventCrossing* ev)
{
_hovering = false;
if (ARDOUR::Config->get_widget_prelight()) {
queue_draw ();
}
return CairoWidget::on_leave_notify_event (ev);
}
void
ArdourButton::set_tweaks (Tweaks t)
{
if (_tweaks != t) {
_tweaks = t;
queue_draw ();
}
}
void
ArdourButton::action_sensitivity_changed ()
{
if (_action->property_sensitive ()) {
set_visual_state (Gtkmm2ext::VisualState (visual_state() & ~Gtkmm2ext::Insensitive));
} else {
set_visual_state (Gtkmm2ext::VisualState (visual_state() | Gtkmm2ext::Insensitive));
}
}
void
ArdourButton::action_visibility_changed ()
{
if (_action->property_visible ()) {
show ();
} else {
hide ();
}
}
void
ArdourButton::action_tooltip_changed ()
{
string str = _action->property_tooltip().get_value();
ARDOUR_UI::instance()->set_tip (*this, str);
}