ardour/gtk2_ardour/ardour_button.cc
Julien "_FrnchFrgg_" RIVAUD 3921e33c2a Avoid 1px size changes when sizing_text() is used
When requesting a size for ArdourButton, the width and height of the
button could be increased by one to ensure the center of the text would
lie exactly at the center of the button.

I initially thought that it was a good idea even when the text used for
sizing was frozen, but the main (and only?) use-case for that is to
actually freeze the size of the button regardless of the actual
displayed text, so jittering by one pixel is not welcome.

Only do the centering tweak if there is no sizing text.
2016-08-25 19:23:49 +02:00

1254 lines
32 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/controllable.h"
#include "pbd/error.h"
#include "pbd/stacktrace.h"
#include "gtkmm2ext/utils.h"
#include "gtkmm2ext/rgb_macros.h"
#include "gtkmm2ext/gui_thread.h"
#include "canvas/utils.h"
#include "canvas/colors.h"
#include "ardour_button.h"
#include "tooltips.h"
#include "ui_config.h"
#include "pbd/i18n.h"
#define BASELINESTRETCH (1.25)
#define TRACKHEADERBTNW (3.10)
using namespace Gdk;
using namespace Gtk;
using namespace Glib;
using namespace PBD;
using namespace ARDOUR_UI_UTILS;
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)
: _sizing_text("")
, _elements (e)
, _icon (Gtkmm2ext::ArdourIcon::NoIcon)
, _tweaks (Tweaks (0))
, _char_pixel_width (0)
, _char_pixel_height (0)
, _char_avg_pixel_width (0)
, _text_width (0)
, _text_height (0)
, _diameter (0)
, _corner_radius (2.5)
, _corner_mask (0xf)
, _angle(0)
, _xalign(.5)
, _yalign(.5)
, fill_inactive_color (0)
, fill_active_color (0)
, text_active_color(0)
, text_inactive_color(0)
, led_active_color(0)
, led_inactive_color(0)
, led_custom_color (0)
, use_custom_led_color (false)
, convex_pattern (0)
, concave_pattern (0)
, led_inset_pattern (0)
, _led_rect (0)
, _act_on_release (true)
, _led_left (false)
, _distinct_led_click (false)
, _hovering (false)
, _focused (false)
, _fixed_colors_set (false)
, _fallthrough_to_parent (false)
, _layout_ellipsize_width (-1)
, _ellipsis (Pango::ELLIPSIZE_NONE)
, _update_colors (true)
, _pattern_height (0)
{
UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &ArdourButton::color_handler));
/* This is not provided by gtkmm */
signal_grab_broken_event().connect (sigc::mem_fun (*this, &ArdourButton::on_grab_broken_event));
}
ArdourButton::ArdourButton (const std::string& str, Element e)
: _sizing_text("")
, _elements (e)
, _tweaks (Tweaks (0))
, _char_pixel_width (0)
, _char_pixel_height (0)
, _char_avg_pixel_width (0)
, _text_width (0)
, _text_height (0)
, _diameter (0)
, _corner_radius (2.5)
, _corner_mask (0xf)
, _angle(0)
, _xalign(.5)
, _yalign(.5)
, fill_inactive_color (0)
, fill_active_color (0)
, text_active_color(0)
, text_inactive_color(0)
, led_active_color(0)
, led_inactive_color(0)
, led_custom_color (0)
, use_custom_led_color (false)
, convex_pattern (0)
, concave_pattern (0)
, led_inset_pattern (0)
, _led_rect (0)
, _act_on_release (true)
, _led_left (false)
, _distinct_led_click (false)
, _hovering (false)
, _focused (false)
, _fixed_colors_set (false)
, _fallthrough_to_parent (false)
, _layout_ellipsize_width (-1)
, _ellipsis (Pango::ELLIPSIZE_NONE)
, _update_colors (true)
, _pattern_height (0)
{
set_text (str);
UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &ArdourButton::color_handler));
UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &ArdourButton::on_name_changed));
/* This is not provided by gtkmm */
signal_grab_broken_event().connect (sigc::mem_fun (*this, &ArdourButton::on_grab_broken_event));
}
ArdourButton::~ArdourButton()
{
delete _led_rect;
if (convex_pattern) {
cairo_pattern_destroy (convex_pattern);
}
if (concave_pattern) {
cairo_pattern_destroy (concave_pattern);
}
if (led_inset_pattern) {
cairo_pattern_destroy (led_inset_pattern);
}
}
void
ArdourButton::set_layout_font (const Pango::FontDescription& fd)
{
ensure_layout ();
if (_layout) {
_layout->set_font_description (fd);
queue_resize ();
}
}
void
ArdourButton::set_text (const std::string& str)
{
if (_text == str) {
return;
}
_text = str;
if (!is_realized()) {
return;
}
ensure_layout ();
if (_layout && _layout->get_text() != _text) {
_layout->set_text (_text);
/* on_size_request() will fill in _text_width/height
* so queue it even if _sizing_text != "" */
queue_resize ();
}
}
void
ArdourButton::set_sizing_text (const std::string& str)
{
if (_sizing_text == str) {
return;
}
_sizing_text = str;
if (!is_realized()) {
return;
}
ensure_layout ();
if (_layout) {
queue_resize ();
}
}
void
ArdourButton::set_angle (const double angle)
{
_angle = angle;
}
void
ArdourButton::set_alignment (const float xa, const float ya)
{
_xalign = xa;
_yalign = ya;
}
/* TODO make this a dedicated function elsewhere.
*
* Option 1:
* virtual ArdourButton::render_vector_icon()
* ArdourIconButton::render_vector_icon
*
* Option 2:
* ARDOUR_UI_UTILS::render_vector_icon()
*/
void
ArdourButton::render (cairo_t* cr, cairo_rectangle_t *)
{
uint32_t text_color;
uint32_t led_color;
const float corner_radius = std::max(2.f, _corner_radius * UIConfiguration::instance().get_ui_scale());
if (_update_colors) {
set_colors ();
}
if (get_height() != _pattern_height) {
build_patterns ();
}
if ( active_state() == Gtkmm2ext::ExplicitActive ) {
text_color = text_active_color;
led_color = led_active_color;
} else {
text_color = text_inactive_color;
led_color = led_inactive_color;
}
if (use_custom_led_color) {
led_color = led_custom_color;
}
void (*rounded_function)(cairo_t*, double, double, double, double, double);
switch (_corner_mask) {
case 0x1: /* upper left only */
rounded_function = Gtkmm2ext::rounded_top_left_rectangle;
break;
case 0x2: /* upper right only */
rounded_function = Gtkmm2ext::rounded_top_right_rectangle;
break;
case 0x3: /* upper only */
rounded_function = Gtkmm2ext::rounded_top_rectangle;
break;
/* should really have functions for lower right, lower left,
lower only, but for now, we don't
*/
default:
rounded_function = Gtkmm2ext::rounded_rectangle;
}
// draw edge (filling a rect underneath, rather than stroking a border on top, allows the corners to be lighter-weight.
if ((_elements & (Body|Edge)) == (Body|Edge)) {
rounded_function (cr, 0, 0, get_width(), get_height(), corner_radius + 1.5);
cairo_set_source_rgba (cr, 0, 0, 0, 1);
cairo_fill(cr);
}
// background fill
if ((_elements & Body)==Body) {
rounded_function (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
if (active_state() == Gtkmm2ext::ImplicitActive && !((_elements & Indicator)==Indicator)) {
ArdourCanvas::set_source_rgba (cr, fill_inactive_color);
cairo_fill (cr);
} else if ( (active_state() == Gtkmm2ext::ExplicitActive) && !((_elements & Indicator)==Indicator) ) {
//background color
ArdourCanvas::set_source_rgba (cr, fill_active_color);
cairo_fill (cr);
} else { //inactive, or it has an indicator
//background color
ArdourCanvas::set_source_rgba (cr, fill_inactive_color);
}
cairo_fill (cr);
}
// IMPLICIT ACTIVE: draw a border of the active color
if ((_elements & Body)==Body) {
if (active_state() == Gtkmm2ext::ImplicitActive && !((_elements & Indicator)==Indicator)) {
cairo_set_line_width (cr, 2.0);
rounded_function (cr, 2, 2, get_width() - 4, get_height() - 4, corner_radius-0.5);
ArdourCanvas::set_source_rgba (cr, fill_active_color);
cairo_stroke (cr);
}
}
//show the "convex" or "concave" gradient
if (!_flat_buttons && (_elements & Body)==Body) {
if ( active_state() == Gtkmm2ext::ExplicitActive && ( !((_elements & Indicator)==Indicator) || use_custom_led_color) ) {
//concave
cairo_set_source (cr, concave_pattern);
Gtkmm2ext::rounded_rectangle (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
cairo_fill (cr);
} else {
cairo_set_source (cr, convex_pattern);
Gtkmm2ext::rounded_rectangle (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
cairo_fill (cr);
}
}
//Pixbuf, if any
if (_pixbuf) {
double x = rint((get_width() - _pixbuf->get_width()) * .5);
const double y = rint((get_height() - _pixbuf->get_height()) * .5);
#if 0 // DEBUG style (print on hover)
if (_hovering || (_elements & Inactive)) {
printf("%s: p:%dx%d (%dx%d)\n",
get_name().c_str(),
_pixbuf->get_width(), _pixbuf->get_height(),
get_width(), get_height());
}
#endif
if (_elements & Menu) {
//if this is a DropDown with an icon, then we need to
//move the icon left slightly to accomomodate the arrow
x -= _diameter - 2;
}
cairo_rectangle (cr, x, y, _pixbuf->get_width(), _pixbuf->get_height());
gdk_cairo_set_source_pixbuf (cr, _pixbuf->gobj(), x, y);
cairo_fill (cr);
}
else /* VectorIcons are exclusive to Pixbuf Icons */
if (_elements & VectorIcon) {
Gtkmm2ext::ArdourIcon::render (cr, _icon, get_width(), get_height(), active_state(), text_color);
}
const int text_margin = char_pixel_width();
// Text, if any
if (!_pixbuf && ((_elements & Text)==Text) && !_text.empty()) {
assert(_layout);
#if 0 // DEBUG style (print on hover)
if (_hovering || (_elements & Inactive)) {
bool layout_font = true;
Pango::FontDescription fd = _layout->get_font_description();
if (fd.gobj() == NULL) {
layout_font = false;
fd = get_pango_context()->get_font_description();
}
printf("%s: f:%dx%d aw:%.3f bh:%.0f t:%dx%d (%dx%d) %s\"%s\"\n",
get_name().c_str(),
char_pixel_width(), char_pixel_height(), char_avg_pixel_width(),
ceil(char_pixel_height() * BASELINESTRETCH),
_text_width, _text_height,
get_width(), get_height(),
layout_font ? "L:" : "W:",
fd.to_string().c_str());
}
#endif
cairo_save (cr);
cairo_rectangle (cr, 2, 1, get_width() - 4, get_height() - 2);
cairo_clip(cr);
cairo_new_path (cr);
ArdourCanvas::set_source_rgba (cr, text_color);
const double text_ypos = (get_height() - _text_height) * .5;
if (_elements & Menu) {
// always left align (dropdown)
cairo_move_to (cr, text_margin, text_ypos);
pango_cairo_show_layout (cr, _layout->gobj());
} else if ( (_elements & Indicator) == Indicator) {
// left/right align depending on LED position
if (_led_left) {
cairo_move_to (cr, text_margin + _diameter + .5 * char_pixel_width(), text_ypos);
} else {
cairo_move_to (cr, text_margin, text_ypos);
}
pango_cairo_show_layout (cr, _layout->gobj());
} else {
/* centered text otherwise */
double ww, wh;
double xa, ya;
ww = get_width();
wh = get_height();
cairo_save (cr);
cairo_rotate(cr, _angle * M_PI / 180.0);
cairo_device_to_user(cr, &ww, &wh);
xa = (ww - _text_width) * _xalign;
ya = (wh - _text_height) * _yalign;
/* quick hack for left/bottom alignment at -90deg
* TODO this should be generalized incl rotation.
* currently only 'user' of this API is meter_strip.cc
*/
if (_xalign < 0) xa = ceil(.5 + (ww * fabs(_xalign) + text_margin));
cairo_move_to (cr, xa, ya);
pango_cairo_update_layout(cr, _layout->gobj());
pango_cairo_show_layout (cr, _layout->gobj());
cairo_restore (cr);
}
cairo_restore (cr);
}
//Menu "triangle"
if (_elements & Menu) {
const float trih = ceil(_diameter * .5);
const float triw2 = ceil(.577 * _diameter * .5); // 1/sqrt(3) Equilateral triangle
//menu arrow
cairo_set_source_rgba (cr, 1, 1, 1, 0.4);
cairo_move_to(cr, get_width() - triw2 - 3. , rint((get_height() + trih) * .5));
cairo_rel_line_to(cr, -triw2, -trih);
cairo_rel_line_to(cr, 2. * triw2, 0);
cairo_close_path(cr);
cairo_set_source_rgba (cr, 1, 1, 1, 0.4);
cairo_fill(cr);
cairo_move_to(cr, get_width() - triw2 - 3 , rint((get_height() + trih) * .5));
cairo_rel_line_to(cr, .5 - triw2, .5 - trih);
cairo_rel_line_to(cr, 2. * triw2 - 1, 0);
cairo_close_path(cr);
cairo_set_source_rgba (cr, 0, 0, 0, 0.8);
cairo_set_line_width(cr, 1);
cairo_stroke(cr);
}
//Indicator LED
if (_elements & Indicator) {
cairo_save (cr);
/* move to the center of the indicator/led */
if (_elements & Text) {
int led_xoff = ceil(char_pixel_width() + _diameter * .5);
if (_led_left) {
cairo_translate (cr, led_xoff, get_height() * .5);
} else {
cairo_translate (cr, get_width() - led_xoff, get_height() * .5);
}
} else {
cairo_translate (cr, get_width() * .5, get_height() * .5);
}
//inset
if (!_flat_buttons) {
cairo_arc (cr, 0, 0, _diameter * .5, 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 * .5 - 1 * UIConfiguration::instance().get_ui_scale(), 0, 2 * M_PI);
cairo_fill(cr);
//led color
ArdourCanvas::set_source_rgba (cr, led_color);
cairo_arc (cr, 0, 0, _diameter * .5 - 3 * UIConfiguration::instance().get_ui_scale(), 0, 2 * M_PI);
cairo_fill(cr);
cairo_restore (cr);
}
// a transparent overlay to indicate insensitivity
if ((visual_state() & Gtkmm2ext::Insensitive)) {
rounded_function (cr, 0, 0, get_width(), get_height(), corner_radius);
uint32_t ins_color = UIConfiguration::instance().color ("gtk_background");
ArdourCanvas::set_source_rgb_a (cr, ins_color, 0.6);
cairo_fill (cr);
}
// if requested, show hovering
if (UIConfiguration::instance().get_widget_prelight()
&& !((visual_state() & Gtkmm2ext::Insensitive))) {
if (_hovering) {
rounded_function (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.2);
cairo_fill (cr);
}
}
//user is currently pressing the button. dark outline helps to indicate this
if (_grabbed && !(_elements & (Inactive|Menu))) {
rounded_function (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
cairo_set_line_width(cr, 2);
cairo_set_source_rgba (cr, 0.1, 0.1, 0.1, .5);
cairo_stroke (cr);
}
//some buttons (like processor boxes) can be selected (so they can be deleted). Draw a selection indicator
if (visual_state() & Gtkmm2ext::Selected) {
cairo_set_line_width(cr, 1);
cairo_set_source_rgba (cr, 1, 0, 0, 0.8);
rounded_function (cr, 0.5, 0.5, get_width() - 1, get_height() - 1, corner_radius);
cairo_stroke (cr);
}
//I guess this means we have keyboard focus. I don't think this works currently
//
//A: yes, it's keyboard focus and it does work when there's no editor window
// (the editor is always the first receiver for KeyDown).
// It's needed for eg. the engine-dialog at startup or after closing a sesion.
if (_focused) {
rounded_function (cr, 1.5, 1.5, get_width() - 3, get_height() - 3, corner_radius);
cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.8);
double dashes = 1;
cairo_set_dash (cr, &dashes, 1, 0);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
cairo_set_line_width (cr, 1.0);
cairo_stroke (cr);
cairo_set_dash (cr, 0, 0, 0);
}
}
void
ArdourButton::set_corner_radius (float r)
{
_corner_radius = r;
CairoWidget::set_dirty ();
}
void
ArdourButton::on_realize()
{
CairoWidget::on_realize ();
ensure_layout ();
if (_layout) {
if (_layout->get_text() != _text) {
_layout->set_text (_text);
}
queue_resize ();
}
}
void
ArdourButton::on_size_request (Gtk::Requisition* req)
{
req->width = req->height = 0;
CairoWidget::on_size_request (req);
if (_diameter == 0) {
const float newdia = rintf (11.f * UIConfiguration::instance().get_ui_scale());
if (_diameter != newdia) {
_pattern_height = 0;
_diameter = newdia;
}
}
if (_elements & Text) {
ensure_layout();
_layout->set_text (_text);
/* render() needs the size of the displayed text */
_layout->get_pixel_size (_text_width, _text_height);
if (_tweaks & OccasionalText) {
/* size should not change based on presence or absence
* of text.
*/
} else { //if (!_text.empty() || !_sizing_text.empty()) {
req->height = std::max(req->height, (int) ceil(char_pixel_height() * BASELINESTRETCH + 1.0));
req->width += rint(1.75 * char_pixel_width()); // padding
if (!_sizing_text.empty()) {
_layout->set_text (_sizing_text); /* use sizing text */
}
int sizing_text_width = 0, sizing_text_height = 0;
_layout->get_pixel_size (sizing_text_width, sizing_text_height);
req->width += sizing_text_width;
if (!_sizing_text.empty()) {
_layout->set_text (_text); /* restore display text */
}
}
/* XXX hack (surprise). Deal with two common rotation angles */
if (_angle == 90 || _angle == 270) {
/* do not swap text width or height because we rely on
these being the un-rotated values in ::render()
*/
swap (req->width, req->height);
}
} else {
_text_width = 0;
_text_height = 0;
}
if (_pixbuf) {
req->width += _pixbuf->get_width() + char_pixel_width();
req->height = std::max(req->height, _pixbuf->get_height() + 4);
}
if (_elements & Indicator) {
req->width += lrint (_diameter) + char_pixel_width();
req->height = std::max (req->height, (int) lrint (_diameter) + 4);
}
if ((_elements & Menu)) {
req->width += _diameter + 4;
}
if (_elements & VectorIcon) {
assert(!(_elements & Text));
const int wh = std::max (6., std::max (rint (TRACKHEADERBTNW * char_avg_pixel_width()), ceil (char_pixel_height() * BASELINESTRETCH + 1.)));
req->width += wh;
req->height = std::max(req->height, wh);
}
/* Tweaks to mess the nice stuff above up again. */
if (_tweaks & TrackHeader) {
// forget everything above and just use a fixed square [em] size
// "TrackHeader Buttons" are single letter (usually uppercase)
// a SizeGroup is much less efficient (lots of gtk work under the hood for each track)
const int wh = std::max (rint (TRACKHEADERBTNW * char_avg_pixel_width()), ceil (char_pixel_height() * BASELINESTRETCH + 1.));
req->width = wh;
req->height = wh;
}
else if (_tweaks & Square) {
// currerntly unused (again)
if (req->width < req->height)
req->width = req->height;
if (req->height < req->width)
req->height = req->width;
} else if (_sizing_text.empty() && _text_width > 0 && !(_elements & (Menu | Indicator))) {
// properly centered text for those elements that are centered
// (no sub-pixel offset)
if ((req->width - _text_width) & 1) { ++req->width; }
if ((req->height - _text_height) & 1) { ++req->height; }
}
#if 0
printf("REQ: %s: %dx%d\n", get_name().c_str(), req->width, req->height);
#endif
}
/**
* This sets the colors used for rendering based on the name of the button, and
* thus uses information from the GUI config data.
*/
void
ArdourButton::set_colors ()
{
_update_colors = false;
if (_fixed_colors_set == 0x3) {
return;
}
std::string name = get_name();
bool failed = false;
if (!(_fixed_colors_set & 0x1)) {
fill_active_color = UIConfiguration::instance().color (string_compose ("%1: fill active", name), &failed);
if (failed) {
fill_active_color = UIConfiguration::instance().color ("generic button: fill active");
}
}
if (!(_fixed_colors_set & 0x2)) {
fill_inactive_color = UIConfiguration::instance().color (string_compose ("%1: fill", name), &failed);
if (failed) {
fill_inactive_color = UIConfiguration::instance().color ("generic button: fill");
}
}
text_active_color = ArdourCanvas::contrasting_text_color (fill_active_color);
text_inactive_color = ArdourCanvas::contrasting_text_color (fill_inactive_color);
led_active_color = UIConfiguration::instance().color (string_compose ("%1: led active", name), &failed);
if (failed) {
led_active_color = UIConfiguration::instance().color ("generic button: led active");
}
/* The inactive color for the LED is just a fairly dark version of the
* active color.
*/
ArdourCanvas::HSV inactive (led_active_color);
inactive.v = 0.35;
led_inactive_color = inactive.color ();
}
/**
* This sets the colors used for rendering based on two fixed values, rather
* than basing them on the button name, and thus information in the GUI config
* data.
*/
void ArdourButton::set_fixed_colors (const uint32_t color_active, const uint32_t color_inactive)
{
set_active_color (color_active);
set_inactive_color (color_inactive);
}
void ArdourButton::set_active_color (const uint32_t color)
{
_fixed_colors_set |= 0x1;
fill_active_color = color;
unsigned char r, g, b, a;
UINT_TO_RGBA(color, &r, &g, &b, &a);
double white_contrast = (max (double(r), 255.) - min (double(r), 255.)) +
(max (double(g), 255.) - min (double(g), 255.)) +
(max (double(b), 255.) - min (double(b), 255.));
double black_contrast = (max (double(r), 0.) - min (double(r), 0.)) +
(max (double(g), 0.) - min (double(g), 0.)) +
(max (double(b), 0.) - min (double(b), 0.));
text_active_color = (white_contrast > black_contrast) ?
RGBA_TO_UINT(255, 255, 255, 255) : /* use white */
RGBA_TO_UINT( 0, 0, 0, 255); /* use black */
/* XXX what about led colors ? */
CairoWidget::set_dirty ();
}
void ArdourButton::set_inactive_color (const uint32_t color)
{
_fixed_colors_set |= 0x2;
fill_inactive_color = color;
unsigned char r, g, b, a;
UINT_TO_RGBA(color, &r, &g, &b, &a);
double white_contrast = (max (double(r), 255.) - min (double(r), 255.)) +
(max (double(g), 255.) - min (double(g), 255.)) +
(max (double(b), 255.) - min (double(b), 255.));
double black_contrast = (max (double(r), 0.) - min (double(r), 0.)) +
(max (double(g), 0.) - min (double(g), 0.)) +
(max (double(b), 0.) - min (double(b), 0.));
text_inactive_color = (white_contrast > black_contrast) ?
RGBA_TO_UINT(255, 255, 255, 255) : /* use white */
RGBA_TO_UINT( 0, 0, 0, 255); /* use black */
/* XXX what about led colors ? */
CairoWidget::set_dirty ();
}
void
ArdourButton::build_patterns ()
{
if (convex_pattern) {
cairo_pattern_destroy (convex_pattern);
convex_pattern = 0;
}
if (concave_pattern) {
cairo_pattern_destroy (concave_pattern);
concave_pattern = 0;
}
if (led_inset_pattern) {
cairo_pattern_destroy (led_inset_pattern);
led_inset_pattern = 0;
}
//convex gradient
convex_pattern = cairo_pattern_create_linear (0.0, 0, 0.0, get_height());
cairo_pattern_add_color_stop_rgba (convex_pattern, 0.0, 0,0,0, 0.0);
cairo_pattern_add_color_stop_rgba (convex_pattern, 1.0, 0,0,0, 0.35);
//concave gradient
concave_pattern = cairo_pattern_create_linear (0.0, 0, 0.0, get_height());
cairo_pattern_add_color_stop_rgba (concave_pattern, 0.0, 0,0,0, 0.5);
cairo_pattern_add_color_stop_rgba (concave_pattern, 0.7, 0,0,0, 0.0);
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);
_pattern_height = get_height() ;
}
void
ArdourButton::set_led_left (bool yn)
{
_led_left = yn;
}
bool
ArdourButton::on_button_press_event (GdkEventButton *ev)
{
focus_handler (this);
if (ev->button == 1 && (_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 (binding_proxy.button_press_handler (ev)) {
return true;
}
_grabbed = true;
CairoWidget::set_dirty ();
if (ev->button == 1 && !_act_on_release) {
if (_action) {
_action->activate ();
return true;
}
}
if (_fallthrough_to_parent)
return false;
return true;
}
bool
ArdourButton::on_button_release_event (GdkEventButton *ev)
{
if (ev->button == 1 && _hovering && (_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(ev); /* EMIT SIGNAL */
return true;
}
}
_grabbed = false;
CairoWidget::set_dirty ();
if (ev->button == 1 && _hovering) {
signal_clicked ();
if (_act_on_release) {
if (_action) {
_action->activate ();
return true;
}
}
}
if (_fallthrough_to_parent)
return false;
return true;
}
void
ArdourButton::set_distinct_led_click (bool yn)
{
_distinct_led_click = yn;
setup_led_rect ();
}
void
ArdourButton::color_handler ()
{
_update_colors = true;
CairoWidget::set_dirty ();
}
void
ArdourButton::on_size_allocate (Allocation& alloc)
{
CairoWidget::on_size_allocate (alloc);
setup_led_rect ();
}
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::ExplicitActive);
} else {
unset_active_state ();
}
CairoWidget::set_dirty ();
}
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) {
action_toggled ();
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::ExplicitActive);
} else {
unset_active_state ();
}
}
}
void
ArdourButton::on_style_changed (const RefPtr<Gtk::Style>&)
{
_update_colors = true;
CairoWidget::set_dirty ();
_char_pixel_width = 0;
_char_pixel_height = 0;
if (is_realized()) {
queue_resize ();
}
}
void
ArdourButton::on_name_changed ()
{
_char_pixel_width = 0;
_char_pixel_height = 0;
_diameter = 0;
_update_colors = true;
if (is_realized()) {
queue_resize ();
}
}
void
ArdourButton::setup_led_rect ()
{
if (!(_elements & Indicator)) {
delete _led_rect;
_led_rect = 0;
return;
}
if (!_led_rect) {
_led_rect = new cairo_rectangle_t;
}
if (_elements & Text) {
if (_led_left) {
_led_rect->x = char_pixel_width();
} else {
_led_rect->x = get_width() - char_pixel_width() + _diameter;
}
} else {
/* centered */
_led_rect->x = .5 * get_width() - _diameter;
}
_led_rect->y = .5 * (get_height() - _diameter);
_led_rect->width = _diameter;
_led_rect->height = _diameter;
}
void
ArdourButton::set_image (const RefPtr<Gdk::Pixbuf>& img)
{
_pixbuf = img;
if (is_realized()) {
queue_resize ();
}
}
void
ArdourButton::set_active_state (Gtkmm2ext::ActiveState s)
{
bool changed = (_active_state != s);
CairoWidget::set_active_state (s);
if (changed) {
_update_colors = true;
CairoWidget::set_dirty ();
}
}
void
ArdourButton::set_visual_state (Gtkmm2ext::VisualState s)
{
bool changed = (_visual_state != s);
CairoWidget::set_visual_state (s);
if (changed) {
_update_colors = true;
CairoWidget::set_dirty ();
}
}
bool
ArdourButton::on_focus_in_event (GdkEventFocus* ev)
{
_focused = true;
CairoWidget::set_dirty ();
return CairoWidget::on_focus_in_event (ev);
}
bool
ArdourButton::on_focus_out_event (GdkEventFocus* ev)
{
_focused = false;
CairoWidget::set_dirty ();
return CairoWidget::on_focus_out_event (ev);
}
bool
ArdourButton::on_key_release_event (GdkEventKey *ev) {
if (_focused &&
(ev->keyval == GDK_space || ev->keyval == GDK_Return))
{
signal_clicked();
if (_action) {
_action->activate ();
}
return true;
}
return CairoWidget::on_key_release_event (ev);
}
bool
ArdourButton::on_enter_notify_event (GdkEventCrossing* ev)
{
_hovering = (_elements & Inactive) ? false : true;
if (UIConfiguration::instance().get_widget_prelight()) {
CairoWidget::set_dirty ();
}
return CairoWidget::on_enter_notify_event (ev);
}
bool
ArdourButton::on_leave_notify_event (GdkEventCrossing* ev)
{
_hovering = false;
if (UIConfiguration::instance().get_widget_prelight()) {
CairoWidget::set_dirty ();
}
return CairoWidget::on_leave_notify_event (ev);
}
bool
ArdourButton::on_grab_broken_event(GdkEventGrabBroken* grab_broken_event) {
/* Our implicit grab due to a button_press was broken by another grab:
* the button will not get any button_release event if the mouse leaves
* while the grab is taken, so unpress ourselves */
_grabbed = false;
CairoWidget::set_dirty ();
return true;
}
void
ArdourButton::set_tweaks (Tweaks t)
{
if (_tweaks != t) {
_tweaks = t;
if (is_realized()) {
queue_resize ();
}
}
}
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::set_layout_ellipsize_width (int w)
{
if (_layout_ellipsize_width == w) {
return;
}
_layout_ellipsize_width = w;
if (!_layout) {
return;
}
if (_layout_ellipsize_width > 3 * PANGO_SCALE) {
_layout->set_width (_layout_ellipsize_width - 3 * PANGO_SCALE);
}
if (is_realized ()) {
queue_resize ();
}
}
void
ArdourButton::set_text_ellipsize (Pango::EllipsizeMode e)
{
if (_ellipsis == e) {
return;
}
_ellipsis = e;
if (!_layout) {
return;
}
_layout->set_ellipsize(_ellipsis);
if (_layout_ellipsize_width > 3 * PANGO_SCALE) {
_layout->set_width (_layout_ellipsize_width - 3 * PANGO_SCALE);
}
if (is_realized ()) {
queue_resize ();
}
}
void
ArdourButton::ensure_layout ()
{
if (!_layout) {
ensure_style ();
_layout = Pango::Layout::create (get_pango_context());
_layout->set_ellipsize(_ellipsis);
if (_layout_ellipsize_width > 3 * PANGO_SCALE) {
_layout->set_width (_layout_ellipsize_width - 3* PANGO_SCALE);
}
}
}
void
ArdourButton::recalc_char_pixel_geometry ()
{
if (_char_pixel_height > 0 && _char_pixel_width > 0) {
return;
}
ensure_layout();
// NB. this is not static, since the geometry is different
// depending on the font used.
int w, h;
std::string x = _("ABCDEFGHIJLKMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
_layout->set_text (x);
_layout->get_pixel_size (w, h);
_char_pixel_height = std::max(4, h);
// number of actual chars in the string (not bytes)
// Glib to the rescue.
Glib::ustring gx(x);
_char_avg_pixel_width = w / (float)gx.size();
_char_pixel_width = std::max(4, (int) ceil (_char_avg_pixel_width));
_layout->set_text (_text);
}
void
ArdourButton::action_visibility_changed ()
{
if (_action->property_visible ()) {
show ();
} else {
hide ();
}
}
void
ArdourButton::action_tooltip_changed ()
{
string str = _action->property_tooltip().get_value();
set_tooltip (*this, str);
}
void
ArdourButton::set_elements (Element e)
{
_elements = e;
CairoWidget::set_dirty ();
}
void
ArdourButton::add_elements (Element e)
{
_elements = (ArdourButton::Element) (_elements | e);
CairoWidget::set_dirty ();
}
void
ArdourButton::set_icon (Gtkmm2ext::ArdourIcon::Icon i)
{
_icon = i;
_elements = (ArdourButton::Element) ((_elements | ArdourButton::VectorIcon) & ~ArdourButton::Text);
CairoWidget::set_dirty ();
}
void
ArdourButton::set_custom_led_color (uint32_t c, bool useit)
{
if (led_custom_color == c && use_custom_led_color == useit) {
return;
}
led_custom_color = c;
use_custom_led_color = useit;
CairoWidget::set_dirty ();
}