Paul Davis
9a11e3a64d
This functor/closure is responsible for stealing focus from any existing text entry (or whatever else may have focus) when clicking on a CairoWidget or derived class. The old implementation just gave focus back to the editor canvas. The new version walks up the widget packing heirarchy to find a focusable parent (from the CairoWidget for which it is invoked). If no focusable parent is found, it cancels keyboard focus in the toplevel window containing the CairoWidget
1150 lines
30 KiB
C++
1150 lines
30 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 "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)
|
|
: _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));
|
|
}
|
|
|
|
ArdourButton::ArdourButton (const std::string& str, Element e)
|
|
: _elements (e)
|
|
, _tweaks (Tweaks (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));
|
|
}
|
|
|
|
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)
|
|
{
|
|
_text = str;
|
|
if (!is_realized()) {
|
|
return;
|
|
}
|
|
ensure_layout ();
|
|
if (_layout && _layout->get_text() != _text) {
|
|
_layout->set_text (_text);
|
|
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) {
|
|
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 && _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) && !_text.empty()) {
|
|
// if _layout does not exist, char_pixel_height() creates it,
|
|
req->height = std::max(req->height, (int) ceil(char_pixel_height() * BASELINESTRETCH + 1.0));
|
|
_layout->get_pixel_size (_text_width, _text_height);
|
|
req->width += rint(1.75 * char_pixel_width()); // padding
|
|
req->width += _text_width;
|
|
} 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 (_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) {
|
|
return;
|
|
}
|
|
std::string name = get_name();
|
|
bool failed = false;
|
|
|
|
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");
|
|
}
|
|
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)
|
|
{
|
|
_fixed_colors_set = true;
|
|
|
|
fill_active_color = color_active;
|
|
fill_inactive_color = color_inactive;
|
|
|
|
unsigned char r, g, b, a;
|
|
UINT_TO_RGBA(color_active, &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 */
|
|
|
|
|
|
UINT_TO_RGBA(color_inactive, &r, &g, &b, &a);
|
|
|
|
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.));
|
|
|
|
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 ();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 ();
|
|
}
|