1 /*
2  * Copyright (C) 2010 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2017-2018 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include <iostream>
21 #include <cmath>
22 #include <algorithm>
23 
24 #include <pangomm/layout.h>
25 #include <gtkmm/toggleaction.h>
26 
27 #include "pbd/compose.h"
28 #include "pbd/controllable.h"
29 #include "pbd/error.h"
30 
31 #include "gtkmm2ext/colors.h"
32 #include "gtkmm2ext/gui_thread.h"
33 #include "gtkmm2ext/rgb_macros.h"
34 #include "gtkmm2ext/utils.h"
35 
36 #include "widgets/ardour_button.h"
37 #include "widgets/tooltips.h"
38 #include "widgets/ui_config.h"
39 
40 #include "pbd/i18n.h"
41 
42 #define BASELINESTRETCH (1.25)
43 #define TRACKHEADERBTNW (3.10)
44 
45 using namespace Gtk;
46 using namespace Glib;
47 using namespace PBD;
48 using namespace ArdourWidgets;
49 using std::max;
50 using std::min;
51 using namespace std;
52 
53 ArdourButton::Element ArdourButton::default_elements = ArdourButton::Element (ArdourButton::Edge|ArdourButton::Body|ArdourButton::Text);
54 ArdourButton::Element ArdourButton::led_default_elements = ArdourButton::Element (ArdourButton::default_elements|ArdourButton::Indicator);
55 ArdourButton::Element ArdourButton::just_led_default_elements = ArdourButton::Element (ArdourButton::Edge|ArdourButton::Body|ArdourButton::Indicator);
56 
ArdourButton(Element e,bool toggle)57 ArdourButton::ArdourButton (Element e, bool toggle)
58 	: _sizing_text("")
59 	, _markup (false)
60 	, _elements (e)
61 	, _icon (ArdourIcon::NoIcon)
62 	, _icon_render_cb (0)
63 	, _icon_render_cb_data (0)
64 	, _tweaks (Tweaks (0))
65 	, _char_pixel_width (0)
66 	, _char_pixel_height (0)
67 	, _char_avg_pixel_width (0)
68 	, _custom_font_set (false)
69 	, _text_width (0)
70 	, _text_height (0)
71 	, _diameter (0)
72 	, _corner_radius (3.5)
73 	, _corner_mask (0xf)
74 	, _angle(0)
75 	, _xalign(.5)
76 	, _yalign(.5)
77 	, fill_inactive_color (0)
78 	, fill_active_color (0)
79 	, text_active_color(0)
80 	, text_inactive_color(0)
81 	, led_active_color(0)
82 	, led_inactive_color(0)
83 	, led_custom_color (0)
84 	, use_custom_led_color (false)
85 	, convex_pattern (0)
86 	, concave_pattern (0)
87 	, led_inset_pattern (0)
88 	, _led_rect (0)
89 	, _act_on_release (true)
90 	, _auto_toggle (toggle)
91 	, _led_left (false)
92 	, _distinct_led_click (false)
93 	, _hovering (false)
94 	, _focused (false)
95 	, _fixed_colors_set (false)
96 	, _fallthrough_to_parent (false)
97 	, _layout_ellipsize_width (-1)
98 	, _ellipsis (Pango::ELLIPSIZE_NONE)
99 	, _update_colors (true)
100 	, _pattern_height (0)
101 {
102 	UIConfigurationBase::instance().ColorsChanged.connect (sigc::mem_fun (*this, &ArdourButton::color_handler));
103 	/* This is not provided by gtkmm */
104 	signal_grab_broken_event().connect (sigc::mem_fun (*this, &ArdourButton::on_grab_broken_event));
105 }
106 
ArdourButton(const std::string & str,Element e,bool toggle)107 ArdourButton::ArdourButton (const std::string& str, Element e, bool toggle)
108 	: _sizing_text("")
109 	, _markup (false)
110 	, _elements (e)
111 	, _icon (ArdourIcon::NoIcon)
112 	, _tweaks (Tweaks (0))
113 	, _char_pixel_width (0)
114 	, _char_pixel_height (0)
115 	, _char_avg_pixel_width (0)
116 	, _custom_font_set (false)
117 	, _text_width (0)
118 	, _text_height (0)
119 	, _diameter (0)
120 	, _corner_radius (3.5)
121 	, _corner_mask (0xf)
122 	, _angle(0)
123 	, _xalign(.5)
124 	, _yalign(.5)
125 	, fill_inactive_color (0)
126 	, fill_active_color (0)
127 	, text_active_color(0)
128 	, text_inactive_color(0)
129 	, led_active_color(0)
130 	, led_inactive_color(0)
131 	, led_custom_color (0)
132 	, use_custom_led_color (false)
133 	, convex_pattern (0)
134 	, concave_pattern (0)
135 	, led_inset_pattern (0)
136 	, _led_rect (0)
137 	, _act_on_release (true)
138 	, _auto_toggle (toggle)
139 	, _led_left (false)
140 	, _distinct_led_click (false)
141 	, _hovering (false)
142 	, _focused (false)
143 	, _fixed_colors_set (false)
144 	, _fallthrough_to_parent (false)
145 	, _layout_ellipsize_width (-1)
146 	, _ellipsis (Pango::ELLIPSIZE_NONE)
147 	, _update_colors (true)
148 	, _pattern_height (0)
149 {
150 	set_text (str);
151 	UIConfigurationBase::instance().ColorsChanged.connect (sigc::mem_fun (*this, &ArdourButton::color_handler));
152 	UIConfigurationBase::instance().DPIReset.connect (sigc::mem_fun (*this, &ArdourButton::on_name_changed));
153 	/* This is not provided by gtkmm */
154 	signal_grab_broken_event().connect (sigc::mem_fun (*this, &ArdourButton::on_grab_broken_event));
155 }
156 
~ArdourButton()157 ArdourButton::~ArdourButton()
158 {
159 	delete _led_rect;
160 
161 	if (convex_pattern) {
162 		cairo_pattern_destroy (convex_pattern);
163 	}
164 
165 	if (concave_pattern) {
166 		cairo_pattern_destroy (concave_pattern);
167 	}
168 
169 	if (led_inset_pattern) {
170 		cairo_pattern_destroy (led_inset_pattern);
171 	}
172 }
173 
174 void
set_layout_font(const Pango::FontDescription & fd)175 ArdourButton::set_layout_font (const Pango::FontDescription& fd)
176 {
177 	ensure_layout ();
178 	if (_layout) {
179 		_layout->set_font_description (fd);
180 		queue_resize ();
181 		_char_pixel_width = 0;
182 		_char_pixel_height = 0;
183 		_custom_font_set = true;
184 	}
185 }
186 
187 void
set_text_internal()188 ArdourButton::set_text_internal () {
189 	assert (_layout);
190 	if (_markup) {
191 		_layout->set_markup (_text);
192 	} else {
193 		_layout->set_text (_text);
194 	}
195 }
196 
197 void
set_text(const std::string & str,bool markup)198 ArdourButton::set_text (const std::string& str, bool markup)
199 {
200 	if (!(_elements & Text)) {
201 		return;
202 	}
203 	if (_text == str && _markup == markup) {
204 		return;
205 	}
206 
207 	_text = str;
208 	_markup = markup;
209 	if (!is_realized()) {
210 		return;
211 	}
212 	ensure_layout ();
213 	if (_layout && _layout->get_text() != _text) {
214 		set_text_internal ();
215 		/* on_size_request() will fill in _text_width/height
216 		 * so queue it even if _sizing_text != "" */
217 		if (_sizing_text.empty ()) {
218 			queue_resize ();
219 		} else {
220 			_layout->get_pixel_size (_text_width, _text_height);
221 			CairoWidget::set_dirty ();
222 		}
223 	}
224 }
225 
226 void
set_sizing_text(const std::string & str)227 ArdourButton::set_sizing_text (const std::string& str)
228 {
229 	if (_sizing_text == str) {
230 		return;
231 	}
232 	_sizing_text = str;
233 	queue_resize ();
234 }
235 
236 void
set_angle(const double angle)237 ArdourButton::set_angle (const double angle)
238 {
239 	_angle = angle;
240 }
241 
242 void
set_alignment(const float xa,const float ya)243 ArdourButton::set_alignment (const float xa, const float ya)
244 {
245 	_xalign = xa;
246 	_yalign = ya;
247 }
248 
249 
250 /* TODO make this a dedicated function elsewhere.
251  *
252  * Option 1:
253  * virtual ArdourButton::render_vector_icon()
254  * ArdourIconButton::render_vector_icon
255  *
256  * Option 2:
257  * ARDOUR_UI_UTILS::render_vector_icon()
258  */
259 void
render(Cairo::RefPtr<Cairo::Context> const & ctx,cairo_rectangle_t *)260 ArdourButton::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t*)
261 {
262 	cairo_t* cr = ctx->cobj();
263 
264 	uint32_t text_color;
265 	uint32_t led_color;
266 
267 	const bool boxy = (_tweaks & ForceBoxy) | boxy_buttons ();
268 	const bool flat = (_tweaks & ForceFlat) | flat_buttons ();
269 
270 	const float corner_radius = boxy ? 0 : std::max(2.f, _corner_radius * UIConfigurationBase::instance().get_ui_scale());
271 
272 	if (_update_colors) {
273 		set_colors ();
274 	}
275 	if (get_height() != _pattern_height) {
276 		build_patterns ();
277 	}
278 
279 	if ( active_state() == Gtkmm2ext::ExplicitActive ) {
280 		text_color = text_active_color;
281 		led_color = led_active_color;
282 	} else {
283 		text_color = text_inactive_color;
284 		led_color = led_inactive_color;
285 	}
286 
287 	if (use_custom_led_color) {
288 		led_color = led_custom_color;
289 	}
290 
291 	void (*rounded_function)(cairo_t*, double, double, double, double, double);
292 
293 	switch (_corner_mask) {
294 	case 0x1: /* upper left only */
295 		rounded_function = Gtkmm2ext::rounded_top_left_rectangle;
296 		break;
297 	case 0x2: /* upper right only */
298 		rounded_function = Gtkmm2ext::rounded_top_right_rectangle;
299 		break;
300 	case 0x3: /* upper only */
301 		rounded_function = Gtkmm2ext::rounded_top_rectangle;
302 		break;
303 		/* should really have functions for lower right, lower left,
304 		   lower only, but for now, we don't
305 		*/
306 	default:
307 		rounded_function = Gtkmm2ext::rounded_rectangle;
308 	}
309 
310 	// draw edge (filling a rect underneath, rather than stroking a border on top, allows the corners to be lighter-weight.
311 	if ((_elements & (Body|Edge)) == (Body|Edge)) {
312 		rounded_function (cr, 0, 0, get_width(), get_height(), corner_radius + 1.5);
313 		cairo_set_source_rgba (cr, 0, 0, 0, 1);
314 		cairo_fill(cr);
315 	}
316 
317 	// background fill
318 	if ((_elements & Body)==Body) {
319 		rounded_function (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
320 		if (active_state() == Gtkmm2ext::ImplicitActive && !((_elements & Indicator)==Indicator)) {
321 			Gtkmm2ext::set_source_rgba (cr, fill_inactive_color);
322 			cairo_fill (cr);
323 		} else if ( (active_state() == Gtkmm2ext::ExplicitActive) && !((_elements & Indicator)==Indicator) ) {
324 			//background color
325 			Gtkmm2ext::set_source_rgba (cr, fill_active_color);
326 			cairo_fill (cr);
327 		} else {  //inactive, or it has an indicator
328 			//background color
329 			Gtkmm2ext::set_source_rgba (cr, fill_inactive_color);
330 		}
331 		cairo_fill (cr);
332 	}
333 
334 	// IMPLICIT ACTIVE: draw a border of the active color
335 	if ((_elements & Body)==Body) {
336 		if (active_state() == Gtkmm2ext::ImplicitActive && !((_elements & Indicator)==Indicator)) {
337 			cairo_set_line_width (cr, 2.0);
338 			rounded_function (cr, 2, 2, get_width() - 4, get_height() - 4, corner_radius-0.5);
339 			Gtkmm2ext::set_source_rgba (cr, fill_active_color);
340 			cairo_stroke (cr);
341 		}
342 	}
343 
344 	//show the "convex" or "concave" gradient
345 	if (!flat && (_elements & Body)==Body) {
346 		if ( active_state() == Gtkmm2ext::ExplicitActive && ( !((_elements & Indicator)==Indicator) || use_custom_led_color) ) {
347 			//concave
348 			cairo_set_source (cr, concave_pattern);
349 			Gtkmm2ext::rounded_rectangle (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
350 			cairo_fill (cr);
351 		} else {
352 			cairo_set_source (cr, convex_pattern);
353 			Gtkmm2ext::rounded_rectangle (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
354 			cairo_fill (cr);
355 		}
356 	}
357 
358 	const int text_margin = char_pixel_width();
359 
360 	//Pixbuf, if any
361 	if (_pixbuf) {
362 		double x = rint((get_width() - _pixbuf->get_width()) * .5);
363 		const double y = rint((get_height() - _pixbuf->get_height()) * .5);
364 #if 0 // DEBUG style (print on hover)
365 		if (_hovering || (_elements & Inactive)) {
366 			printf("%s: p:%dx%d (%dx%d)\n",
367 					get_name().c_str(),
368 					_pixbuf->get_width(), _pixbuf->get_height(),
369 					get_width(), get_height());
370 		}
371 #endif
372 		if (_elements & Menu) {
373 			//if this is a DropDown with an icon, then we need to
374 			//move the icon left slightly to accomomodate the arrow
375 			x -= _diameter - 2;
376 		}
377 		cairo_rectangle (cr, x, y, _pixbuf->get_width(), _pixbuf->get_height());
378 		gdk_cairo_set_source_pixbuf (cr, _pixbuf->gobj(), x, y);
379 		cairo_fill (cr);
380 	}
381 	else /* VectorIcon, IconRenderCallback are exclusive to Pixbuf Icons */
382 	if (_elements & (VectorIcon | IconRenderCallback)) {
383 		int vw = get_width();
384 		int vh = get_height();
385 		cairo_save (cr);
386 
387 		if (_elements & Menu) {
388 			vw -= _diameter + 4;
389 		}
390 		if (_elements & Indicator) {
391 			vw -= _diameter + .5 * text_margin;
392 			if (_led_left) {
393 				cairo_translate (cr, _diameter + text_margin, 0);
394 			}
395 		}
396 		if (_elements & Text) {
397 			vw -= _text_width + text_margin;
398 		}
399 		if (_elements & VectorIcon) {
400 			ArdourIcon::render (cr, _icon, vw, vh, active_state(), text_color);
401 		} else {
402 			rounded_function (cr, 0, 0, get_width(), get_height(), corner_radius + 1.5);
403 			cairo_clip (cr);
404 			_icon_render_cb (cr, vw, vh, text_color, _icon_render_cb_data);
405 		}
406 		cairo_restore (cr);
407 	}
408 
409 	// Text, if any
410 	if (!_pixbuf && ((_elements & Text)==Text) && !_text.empty()) {
411 		assert(_layout);
412 #if 0 // DEBUG style (print on hover)
413 		if (_hovering || (_elements & Inactive)) {
414 			bool layout_font = true;
415 			Pango::FontDescription fd = _layout->get_font_description();
416 			if (fd.gobj() == NULL) {
417 				layout_font = false;
418 				fd = get_pango_context()->get_font_description();
419 			}
420 			printf("%s: f:%dx%d aw:%.3f bh:%.0f t:%dx%d (%dx%d) %s\"%s\"\n",
421 					get_name().c_str(),
422 					char_pixel_width(), char_pixel_height(), char_avg_pixel_width(),
423 					ceil(char_pixel_height() * BASELINESTRETCH),
424 					_text_width, _text_height,
425 					get_width(), get_height(),
426 					layout_font ? "L:" : "W:",
427 					fd.to_string().c_str());
428 		}
429 #endif
430 
431 		cairo_save (cr);
432 		cairo_rectangle (cr, 2, 1, get_width() - 4, get_height() - 2);
433 		cairo_clip(cr);
434 
435 		cairo_new_path (cr);
436 		Gtkmm2ext::set_source_rgba (cr, text_color);
437 		const double text_ypos = round ((get_height() - _text_height) * .5);
438 
439 		if (_elements & Menu) {
440 			// always left align (dropdown)
441 			cairo_move_to (cr, text_margin, text_ypos);
442 			pango_cairo_show_layout (cr, _layout->gobj());
443 		} else if ( (_elements & Indicator)  == Indicator) {
444 			// left/right align depending on LED position
445 			if (_led_left) {
446 				cairo_move_to (cr, round (text_margin + _diameter + .5 * char_pixel_width()), text_ypos);
447 			} else {
448 				cairo_move_to (cr, text_margin, text_ypos);
449 			}
450 			pango_cairo_show_layout (cr, _layout->gobj());
451 		} else if (VectorIcon == (_elements & VectorIcon)) {
452 			cairo_move_to (cr, get_width () - text_margin - _text_width, text_ypos);
453 			pango_cairo_show_layout (cr, _layout->gobj());
454 		} else {
455 			/* centered text otherwise */
456 			double ww, wh;
457 			double xa, ya;
458 			ww = get_width();
459 			wh = get_height();
460 
461 			cairo_matrix_t m1;
462 			cairo_get_matrix (cr, &m1);
463 			cairo_matrix_t m2 = m1;
464 			m2.x0 = 0;
465 			m2.y0 = 0;
466 			cairo_set_matrix (cr, &m2);
467 
468 			if (_angle) {
469 				cairo_rotate(cr, _angle * M_PI / 180.0);
470 			}
471 
472 			cairo_device_to_user(cr, &ww, &wh);
473 			xa = text_margin + (ww - _text_width - 2 * text_margin) * _xalign;
474 			ya = (wh - _text_height) * _yalign;
475 
476 			/* quick hack for left/bottom alignment at -90deg
477 			 * TODO this should be generalized incl rotation.
478 			 * currently only 'user' of this API is meter_strip.cc
479 			 */
480 			if (_xalign < 0) xa = ceil(.5 + (ww * fabs(_xalign) + text_margin));
481 
482 			cairo_move_to (cr, round (xa + m1.x0), round (ya + m1.y0));
483 			pango_cairo_update_layout(cr, _layout->gobj());
484 			pango_cairo_show_layout (cr, _layout->gobj());
485 		}
486 		cairo_restore (cr);
487 	}
488 
489 	//Menu "triangle"
490 	if (_elements & Menu) {
491 		const float trih = ceil(_diameter * .5);
492 		const float triw2 = ceil(.577 * _diameter * .5); // 1/sqrt(3) Equilateral triangle
493 		//menu arrow
494 		cairo_set_source_rgba (cr, 1, 1, 1, 0.4);
495 		cairo_move_to(cr, get_width() - triw2 - 3. , rint((get_height() + trih) * .5));
496 		cairo_rel_line_to(cr, -triw2, -trih);
497 		cairo_rel_line_to(cr, 2. * triw2, 0);
498 		cairo_close_path(cr);
499 
500 		cairo_set_source_rgba (cr, 1, 1, 1, 0.4);
501 		cairo_fill(cr);
502 
503 		cairo_move_to(cr, get_width() - triw2 - 3 , rint((get_height() + trih) * .5));
504 		cairo_rel_line_to(cr, .5 - triw2, .5 - trih);
505 		cairo_rel_line_to(cr, 2. * triw2 - 1, 0);
506 		cairo_close_path(cr);
507 		cairo_set_source_rgba (cr, 0, 0, 0, 0.8);
508 		cairo_set_line_width(cr, 1);
509 		cairo_stroke(cr);
510 	}
511 
512 	//Indicator LED
513 	if (_elements & Indicator) {
514 		cairo_save (cr);
515 
516 		/* move to the center of the indicator/led */
517 		if (_elements & (Text | VectorIcon | IconRenderCallback)) {
518 			int led_xoff = ceil((char_pixel_width() + _diameter) * .5);
519 			if (_led_left) {
520 				cairo_translate (cr, led_xoff, get_height() * .5);
521 			} else {
522 				cairo_translate (cr, get_width() - led_xoff, get_height() * .5);
523 			}
524 		} else {
525 			cairo_translate (cr, get_width() * .5, get_height() * .5);
526 		}
527 
528 		//inset
529 		if (!flat) {
530 			cairo_arc (cr, 0, 0, _diameter * .5, 0, 2 * M_PI);
531 			cairo_set_source (cr, led_inset_pattern);
532 			cairo_fill (cr);
533 		}
534 
535 		//black ring
536 		cairo_set_source_rgb (cr, 0, 0, 0);
537 		cairo_arc (cr, 0, 0, _diameter * .5 - 1 * UIConfigurationBase::instance().get_ui_scale(), 0, 2 * M_PI);
538 		cairo_fill(cr);
539 
540 		//led color
541 		Gtkmm2ext::set_source_rgba (cr, led_color);
542 		cairo_arc (cr, 0, 0, _diameter * .5 - 3 * UIConfigurationBase::instance().get_ui_scale(), 0, 2 * M_PI);
543 		cairo_fill(cr);
544 
545 		cairo_restore (cr);
546 	}
547 
548 	// a transparent overlay to indicate insensitivity
549 	if ((visual_state() & Gtkmm2ext::Insensitive)) {
550 		rounded_function (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
551 		uint32_t ins_color = UIConfigurationBase::instance().color ("gtk_background");
552 		Gtkmm2ext::set_source_rgb_a (cr, ins_color, 0.6);
553 		cairo_fill (cr);
554 	}
555 
556 	// if requested, show hovering
557 	if (UIConfigurationBase::instance().get_widget_prelight()
558 			&& !((visual_state() & Gtkmm2ext::Insensitive))) {
559 		if (_hovering) {
560 			rounded_function (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
561 			cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.2);
562 			cairo_fill (cr);
563 		}
564 	}
565 
566 	//user is currently pressing the button. dark outline helps to indicate this
567 	if (_grabbed && !(_elements & (Inactive|Menu))) {
568 		rounded_function (cr, 1, 1, get_width() - 2, get_height() - 2, corner_radius);
569 		cairo_set_line_width(cr, 2);
570 		cairo_set_source_rgba (cr, 0.1, 0.1, 0.1, .5);
571 		cairo_stroke (cr);
572 	}
573 
574 	//some buttons (like processor boxes) can be selected  (so they can be deleted).  Draw a selection indicator
575 	if (visual_state() & Gtkmm2ext::Selected) {
576 		cairo_set_line_width(cr, 1);
577 		cairo_set_source_rgba (cr, 1, 0, 0, 0.8);
578 		rounded_function (cr, 0.5, 0.5, get_width() - 1, get_height() - 1, corner_radius);
579 		cairo_stroke (cr);
580 	}
581 
582 	//I guess this means we have keyboard focus.  I don't think this works currently
583 	//
584 	//A: yes, it's keyboard focus and it does work when there's no editor window
585 	//   (the editor is always the first receiver for KeyDown).
586 	//   It's needed for eg. the engine-dialog at startup or after closing a sesion.
587 	if (_focused) {
588 		rounded_function (cr, 1.5, 1.5, get_width() - 3, get_height() - 3, corner_radius);
589 		cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.8);
590 		double dashes = 1;
591 		cairo_set_dash (cr, &dashes, 1, 0);
592 		cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
593 		cairo_set_line_width (cr, 1.0);
594 		cairo_stroke (cr);
595 		cairo_set_dash (cr, 0, 0, 0);
596 	}
597 }
598 
599 void
set_corner_radius(float r)600 ArdourButton::set_corner_radius (float r)
601 {
602 	_corner_radius = r;
603 	CairoWidget::set_dirty ();
604 }
605 
606 void
on_realize()607 ArdourButton::on_realize()
608 {
609 	CairoWidget::on_realize ();
610 	ensure_layout ();
611 	if (_layout) {
612 		if (_layout->get_text() != _text) {
613 			set_text_internal ();
614 			queue_resize ();
615 		}
616 	}
617 }
618 
619 void
on_size_request(Gtk::Requisition * req)620 ArdourButton::on_size_request (Gtk::Requisition* req)
621 {
622 	req->width = req->height = 0;
623 	CairoWidget::on_size_request (req);
624 
625 	if (_diameter == 0) {
626 		const float newdia = rintf (11.f * UIConfigurationBase::instance().get_ui_scale());
627 		if (_diameter != newdia) {
628 			_pattern_height = 0;
629 			_diameter = newdia;
630 		}
631 	}
632 
633 	if (_elements & Text) {
634 
635 		ensure_layout();
636 		set_text_internal ();
637 
638 		/* render() needs the size of the displayed text */
639 		_layout->get_pixel_size (_text_width, _text_height);
640 
641 		if (_tweaks & OccasionalText) {
642 
643 			/* size should not change based on presence or absence
644 			 * of text.
645 			 */
646 
647 		} else if (_layout_ellipsize_width > 0 && _sizing_text.empty()) {
648 
649 			req->height = std::max(req->height, (int) ceil(char_pixel_height() * BASELINESTRETCH + 1.0));
650 			req->width += _layout_ellipsize_width / PANGO_SCALE;
651 
652 		} else /*if (!_text.empty() || !_sizing_text.empty()) */ {
653 
654 			req->height = std::max(req->height, (int) ceil(char_pixel_height() * BASELINESTRETCH + 1.0));
655 			req->width += rint(1.75 * char_pixel_width()); // padding
656 
657 			if (!_sizing_text.empty()) {
658 				_layout->set_text (_sizing_text); /* use sizing text */
659 			}
660 
661 			int sizing_text_width = 0, sizing_text_height = 0;
662 			_layout->get_pixel_size (sizing_text_width, sizing_text_height);
663 
664 			req->width += sizing_text_width;
665 
666 			if (!_sizing_text.empty()) {
667 				set_text_internal (); /* restore display text */
668 			}
669 		}
670 
671 		/* XXX hack (surprise). Deal with two common rotation angles */
672 
673 		if (_angle == 90 || _angle == 270) {
674 			/* do not swap text width or height because we rely on
675 			   these being the un-rotated values in ::render()
676 			*/
677 			swap (req->width, req->height);
678 		}
679 
680 	} else {
681 		_text_width = 0;
682 		_text_height = 0;
683 	}
684 
685 	if (_pixbuf) {
686 		req->width += _pixbuf->get_width() + char_pixel_width();
687 		req->height = std::max(req->height, _pixbuf->get_height() + 4);
688 	}
689 
690 	if ((_elements & Indicator) || (_tweaks & OccasionalLED)) {
691 		req->width += ceil (_diameter + char_pixel_width());
692 		req->height = std::max (req->height, (int) lrint (_diameter) + 4);
693 	}
694 
695 	if ((_elements & Menu)) {
696 		req->width += _diameter + 4;
697 	}
698 
699 	if (_elements & (VectorIcon | IconRenderCallback)) {
700 		const int wh = std::max (8., std::max (ceil (TRACKHEADERBTNW * char_avg_pixel_width()), ceil (char_pixel_height() * BASELINESTRETCH + 1.)));
701 		req->width += wh;
702 		req->height = std::max(req->height, wh);
703 	}
704 
705 	/* Tweaks to mess the nice stuff above up again. */
706 	if (_tweaks & TrackHeader) {
707 		// forget everything above and just use a fixed square [em] size
708 		// "TrackHeader Buttons" are single letter (usually uppercase)
709 		// a SizeGroup is much less efficient (lots of gtk work under the hood for each track)
710 		const int wh = std::max (rint (TRACKHEADERBTNW * char_avg_pixel_width()), ceil (char_pixel_height() * BASELINESTRETCH + 1.));
711 		req->width  = wh;
712 		req->height = wh;
713 	}
714 	else if (_tweaks & Square) {
715 		// currerntly unused (again)
716 		if (req->width < req->height)
717 			req->width = req->height;
718 		if (req->height < req->width)
719 			req->height = req->width;
720 	} else if (_sizing_text.empty() && _text_width > 0 && !(_elements & Menu)) {
721 		// properly centered text for those elements that are centered
722 		// (no sub-pixel offset)
723 		if ((req->width - _text_width) & 1) { ++req->width; }
724 		if ((req->height - _text_height) & 1) { ++req->height; }
725 	}
726 #if 0
727 		printf("REQ: %s: %dx%d\n", get_name().c_str(), req->width, req->height);
728 #endif
729 }
730 
731 /**
732  * This sets the colors used for rendering based on the name of the button, and
733  * thus uses information from the GUI config data.
734  */
735 void
set_colors()736 ArdourButton::set_colors ()
737 {
738 	_update_colors = false;
739 
740 	if (_fixed_colors_set == 0x3) {
741 		return;
742 	}
743 
744 	std::string name = get_name();
745 	bool failed = false;
746 
747 	if (!(_fixed_colors_set & 0x1)) {
748 		fill_active_color = UIConfigurationBase::instance().color (string_compose ("%1: fill active", name), &failed);
749 		if (failed) {
750 			fill_active_color = UIConfigurationBase::instance().color ("generic button: fill active");
751 		}
752 	}
753 
754 	if (!(_fixed_colors_set & 0x2)) {
755 		fill_inactive_color = UIConfigurationBase::instance().color (string_compose ("%1: fill", name), &failed);
756 		if (failed) {
757 			fill_inactive_color = UIConfigurationBase::instance().color ("generic button: fill");
758 		}
759 	}
760 
761 	text_active_color = Gtkmm2ext::contrasting_text_color (fill_active_color);
762 	text_inactive_color = Gtkmm2ext::contrasting_text_color (fill_inactive_color);
763 
764 	led_active_color = UIConfigurationBase::instance().color (string_compose ("%1: led active", name), &failed);
765 	if (failed) {
766 		led_active_color = UIConfigurationBase::instance().color ("generic button: led active");
767 	}
768 
769 	/* The inactive color for the LED is just a fairly dark version of the
770 	 * active color.
771 	 */
772 
773 	Gtkmm2ext::HSV inactive (led_active_color);
774 	inactive.v = 0.35;
775 
776 	led_inactive_color = inactive.color ();
777 }
778 
779 /**
780  * This sets the colors used for rendering based on two fixed values, rather
781  * than basing them on the button name, and thus information in the GUI config
782  * data.
783  */
set_fixed_colors(const uint32_t color_active,const uint32_t color_inactive)784 void ArdourButton::set_fixed_colors (const uint32_t color_active, const uint32_t color_inactive)
785 {
786 	set_active_color (color_active);
787 	set_inactive_color (color_inactive);
788 }
789 
set_active_color(const uint32_t color)790 void ArdourButton::set_active_color (const uint32_t color)
791 {
792 	_fixed_colors_set |= 0x1;
793 
794 	fill_active_color = color;
795 
796 	unsigned char r, g, b, a;
797 	UINT_TO_RGBA(color, &r, &g, &b, &a);
798 
799 	double white_contrast = (max (double(r), 255.) - min (double(r), 255.)) +
800 		(max (double(g), 255.) - min (double(g), 255.)) +
801 		(max (double(b), 255.) - min (double(b), 255.));
802 
803 	double black_contrast = (max (double(r), 0.) - min (double(r), 0.)) +
804 		(max (double(g), 0.) - min (double(g), 0.)) +
805 		(max (double(b), 0.) - min (double(b), 0.));
806 
807 	text_active_color = (white_contrast > black_contrast) ?
808 		RGBA_TO_UINT(255, 255, 255, 255) : /* use white */
809 		RGBA_TO_UINT(  0,   0,   0,   255);  /* use black */
810 
811 	/* XXX what about led colors ? */
812 	CairoWidget::set_dirty ();
813 }
814 
set_inactive_color(const uint32_t color)815 void ArdourButton::set_inactive_color (const uint32_t color)
816 {
817 	_fixed_colors_set |= 0x2;
818 
819 	fill_inactive_color = color;
820 
821 	unsigned char r, g, b, a;
822 	UINT_TO_RGBA(color, &r, &g, &b, &a);
823 
824 	double white_contrast = (max (double(r), 255.) - min (double(r), 255.)) +
825 		(max (double(g), 255.) - min (double(g), 255.)) +
826 		(max (double(b), 255.) - min (double(b), 255.));
827 
828 	double black_contrast = (max (double(r), 0.) - min (double(r), 0.)) +
829 		(max (double(g), 0.) - min (double(g), 0.)) +
830 		(max (double(b), 0.) - min (double(b), 0.));
831 
832 	text_inactive_color = (white_contrast > black_contrast) ?
833 		RGBA_TO_UINT(255, 255, 255, 255) : /* use white */
834 		RGBA_TO_UINT(  0,   0,   0,   255);  /* use black */
835 
836 	/* XXX what about led colors ? */
837 	CairoWidget::set_dirty ();
838 }
839 
reset_fixed_colors()840 void ArdourButton::reset_fixed_colors ()
841 {
842 	if (_fixed_colors_set == 0) {
843 		return;
844 	}
845 	_fixed_colors_set = 0;
846 	_update_colors = true;
847 	CairoWidget::set_dirty ();
848 }
849 
850 void
build_patterns()851 ArdourButton::build_patterns ()
852 {
853 	if (convex_pattern) {
854 		cairo_pattern_destroy (convex_pattern);
855 		convex_pattern = 0;
856 	}
857 
858 	if (concave_pattern) {
859 		cairo_pattern_destroy (concave_pattern);
860 		concave_pattern = 0;
861 	}
862 
863 	if (led_inset_pattern) {
864 		cairo_pattern_destroy (led_inset_pattern);
865 		led_inset_pattern = 0;
866 	}
867 
868 	//convex gradient
869 	convex_pattern = cairo_pattern_create_linear (0.0, 0, 0.0,  get_height());
870 	cairo_pattern_add_color_stop_rgba (convex_pattern, 0.0, 0,0,0, 0.0);
871 	cairo_pattern_add_color_stop_rgba (convex_pattern, 1.0, 0,0,0, 0.35);
872 
873 	//concave gradient
874 	concave_pattern = cairo_pattern_create_linear (0.0, 0, 0.0,  get_height());
875 	cairo_pattern_add_color_stop_rgba (concave_pattern, 0.0, 0,0,0, 0.5);
876 	cairo_pattern_add_color_stop_rgba (concave_pattern, 0.7, 0,0,0, 0.0);
877 
878 	led_inset_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, _diameter);
879 	cairo_pattern_add_color_stop_rgba (led_inset_pattern, 0, 0,0,0, 0.4);
880 	cairo_pattern_add_color_stop_rgba (led_inset_pattern, 1, 1,1,1, 0.7);
881 
882 	_pattern_height = get_height() ;
883 }
884 
885 void
set_led_left(bool yn)886 ArdourButton::set_led_left (bool yn)
887 {
888 	_led_left = yn;
889 }
890 
891 bool
on_button_press_event(GdkEventButton * ev)892 ArdourButton::on_button_press_event (GdkEventButton *ev)
893 {
894 	focus_handler (this);
895 
896 	if (ev->type == GDK_2BUTTON_PRESS || ev->type == GDK_3BUTTON_PRESS) {
897 		return _fallthrough_to_parent ? false : true;
898 	}
899 
900 	if (ev->button == 1 && (_elements & Indicator) && _led_rect && _distinct_led_click) {
901 		if (ev->x >= _led_rect->x && ev->x < _led_rect->x + _led_rect->width &&
902 		    ev->y >= _led_rect->y && ev->y < _led_rect->y + _led_rect->height) {
903 			return true;
904 		}
905 	}
906 
907 	if (binding_proxy.button_press_handler (ev)) {
908 		return true;
909 	}
910 
911 	_grabbed = true;
912 	CairoWidget::set_dirty ();
913 
914 	if (ev->button == 1 && !_act_on_release) {
915 		if (_action) {
916 			_action->activate ();
917 			return true;
918 		} else if (_auto_toggle) {
919 			set_active (!get_active ());
920 			signal_clicked ();
921 			return true;
922 		}
923 	}
924 
925 	return _fallthrough_to_parent ? false : true;
926 }
927 
928 bool
on_button_release_event(GdkEventButton * ev)929 ArdourButton::on_button_release_event (GdkEventButton *ev)
930 {
931 	if (ev->type == GDK_2BUTTON_PRESS || ev->type == GDK_3BUTTON_PRESS) {
932 		return _fallthrough_to_parent ? false : true;
933 	}
934 	if (ev->button == 1 && _hovering && (_elements & Indicator) && _led_rect && _distinct_led_click) {
935 		if (ev->x >= _led_rect->x && ev->x < _led_rect->x + _led_rect->width &&
936 		    ev->y >= _led_rect->y && ev->y < _led_rect->y + _led_rect->height) {
937 			signal_led_clicked(ev); /* EMIT SIGNAL */
938 			return true;
939 		}
940 	}
941 
942 	_grabbed = false;
943 	CairoWidget::set_dirty ();
944 
945 	if (ev->button == 1 && _hovering) {
946 		if (_act_on_release && _auto_toggle && !_action) {
947 			set_active (!get_active ());
948 		}
949 		signal_clicked ();
950 		if (_act_on_release) {
951 			if (_action) {
952 				_action->activate ();
953 				return true;
954 			}
955 		}
956 	}
957 
958 	return _fallthrough_to_parent ? false : true;
959 }
960 
961 void
set_distinct_led_click(bool yn)962 ArdourButton::set_distinct_led_click (bool yn)
963 {
964 	_distinct_led_click = yn;
965 	setup_led_rect ();
966 }
967 
968 void
color_handler()969 ArdourButton::color_handler ()
970 {
971 	_update_colors = true;
972 	CairoWidget::set_dirty ();
973 }
974 
975 void
on_size_allocate(Allocation & alloc)976 ArdourButton::on_size_allocate (Allocation& alloc)
977 {
978 	CairoWidget::on_size_allocate (alloc);
979 	setup_led_rect ();
980 	if (_layout) {
981 		/* re-center text */
982 		//_layout->get_pixel_size (_text_width, _text_height);
983 	}
984 }
985 
986 void
set_controllable(boost::shared_ptr<Controllable> c)987 ArdourButton::set_controllable (boost::shared_ptr<Controllable> c)
988 {
989 	watch_connection.disconnect ();
990 	binding_proxy.set_controllable (c);
991 }
992 
993 void
watch()994 ArdourButton::watch ()
995 {
996 	boost::shared_ptr<Controllable> c (binding_proxy.get_controllable ());
997 
998 	if (!c) {
999 		warning << _("button cannot watch state of non-existing Controllable\n") << endmsg;
1000 		return;
1001 	}
1002 	c->Changed.connect (watch_connection, invalidator(*this), boost::bind (&ArdourButton::controllable_changed, this), gui_context());
1003 }
1004 
1005 void
controllable_changed()1006 ArdourButton::controllable_changed ()
1007 {
1008 	float val = binding_proxy.get_controllable()->get_value();
1009 
1010 	if (fabs (val) >= 0.5f) {
1011 		set_active_state (Gtkmm2ext::ExplicitActive);
1012 	} else {
1013 		unset_active_state ();
1014 	}
1015 	CairoWidget::set_dirty ();
1016 }
1017 
1018 void
set_related_action(RefPtr<Action> act)1019 ArdourButton::set_related_action (RefPtr<Action> act)
1020 {
1021 	Gtkmm2ext::Activatable::set_related_action (act);
1022 
1023 	if (_action) {
1024 
1025 		action_tooltip_changed ();
1026 		action_sensitivity_changed ();
1027 
1028 		Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (_action);
1029 		if (tact) {
1030 			action_toggled ();
1031 			tact->signal_toggled().connect (sigc::mem_fun (*this, &ArdourButton::action_toggled));
1032 		}
1033 
1034 		_action->connect_property_changed ("sensitive", sigc::mem_fun (*this, &ArdourButton::action_sensitivity_changed));
1035 		_action->connect_property_changed ("visible", sigc::mem_fun (*this, &ArdourButton::action_visibility_changed));
1036 		_action->connect_property_changed ("tooltip", sigc::mem_fun (*this, &ArdourButton::action_tooltip_changed));
1037 	}
1038 }
1039 
1040 void
action_toggled()1041 ArdourButton::action_toggled ()
1042 {
1043 	Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (_action);
1044 
1045 	if (tact) {
1046 		if (tact->get_active()) {
1047 			set_active_state (Gtkmm2ext::ExplicitActive);
1048 		} else {
1049 			unset_active_state ();
1050 		}
1051 	}
1052 }
1053 
1054 void
on_style_changed(const RefPtr<Gtk::Style> & style)1055 ArdourButton::on_style_changed (const RefPtr<Gtk::Style>& style)
1056 {
1057 	CairoWidget::on_style_changed (style);
1058 	Glib::RefPtr<Gtk::Style> const& new_style = get_style();
1059 
1060 	CairoWidget::set_dirty ();
1061 	_update_colors = true;
1062 	_char_pixel_width = 0;
1063 	_char_pixel_height = 0;
1064 
1065 	if (!_custom_font_set && _layout && _layout->get_font_description () != new_style->get_font ()) {
1066 		_layout->set_font_description (new_style->get_font ());
1067 		queue_resize ();
1068 	} else if (is_realized()) {
1069 		queue_resize ();
1070 	}
1071 }
1072 
1073 void
on_name_changed()1074 ArdourButton::on_name_changed ()
1075 {
1076 	_char_pixel_width = 0;
1077 	_char_pixel_height = 0;
1078 	_diameter = 0;
1079 	_update_colors = true;
1080 	if (is_realized()) {
1081 		queue_resize ();
1082 	}
1083 }
1084 
1085 void
setup_led_rect()1086 ArdourButton::setup_led_rect ()
1087 {
1088 	if (!(_elements & Indicator)) {
1089 		delete _led_rect;
1090 		_led_rect = 0;
1091 		return;
1092 	}
1093 
1094 	if (!_led_rect) {
1095 		_led_rect = new cairo_rectangle_t;
1096 	}
1097 
1098 	if (_elements & Text) {
1099 		if (_led_left) {
1100 			_led_rect->x = char_pixel_width();
1101 		} else {
1102 			_led_rect->x = get_width() - char_pixel_width() + _diameter;
1103 		}
1104 	} else {
1105 		/* centered */
1106 		_led_rect->x = .5 * get_width() - _diameter;
1107 	}
1108 
1109 	_led_rect->y = .5 * (get_height() - _diameter);
1110 	_led_rect->width = _diameter;
1111 	_led_rect->height = _diameter;
1112 }
1113 
1114 void
set_image(const RefPtr<Gdk::Pixbuf> & img)1115 ArdourButton::set_image (const RefPtr<Gdk::Pixbuf>& img)
1116 {
1117 	 _elements = (ArdourButton::Element) (_elements & ~ArdourButton::Text);
1118 	_pixbuf = img;
1119 	if (is_realized()) {
1120 		queue_resize ();
1121 	}
1122 }
1123 
1124 void
set_active_state(Gtkmm2ext::ActiveState s)1125 ArdourButton::set_active_state (Gtkmm2ext::ActiveState s)
1126 {
1127 	bool changed = (_active_state != s);
1128 	CairoWidget::set_active_state (s);
1129 	if (changed) {
1130 		_update_colors = true;
1131 		CairoWidget::set_dirty ();
1132 	}
1133 }
1134 
1135 void
set_visual_state(Gtkmm2ext::VisualState s)1136 ArdourButton::set_visual_state (Gtkmm2ext::VisualState s)
1137 {
1138 	bool changed = (_visual_state != s);
1139 	CairoWidget::set_visual_state (s);
1140 	if (changed) {
1141 		_update_colors = true;
1142 		CairoWidget::set_dirty ();
1143 	}
1144 }
1145 
1146 bool
on_focus_in_event(GdkEventFocus * ev)1147 ArdourButton::on_focus_in_event (GdkEventFocus* ev)
1148 {
1149 	_focused = true;
1150 	CairoWidget::set_dirty ();
1151 	return CairoWidget::on_focus_in_event (ev);
1152 }
1153 
1154 bool
on_focus_out_event(GdkEventFocus * ev)1155 ArdourButton::on_focus_out_event (GdkEventFocus* ev)
1156 {
1157 	_focused = false;
1158 	CairoWidget::set_dirty ();
1159 	return CairoWidget::on_focus_out_event (ev);
1160 }
1161 
1162 bool
on_key_release_event(GdkEventKey * ev)1163 ArdourButton::on_key_release_event (GdkEventKey *ev) {
1164 	if (_act_on_release && _focused &&
1165 			(ev->keyval == GDK_space || ev->keyval == GDK_Return))
1166 	{
1167 		if (_auto_toggle && !_action) {
1168 				set_active (!get_active ());
1169 		}
1170 		signal_clicked();
1171 		if (_action) {
1172 			_action->activate ();
1173 		}
1174 		return true;
1175 	}
1176 	return CairoWidget::on_key_release_event (ev);
1177 }
1178 
1179 bool
on_key_press_event(GdkEventKey * ev)1180 ArdourButton::on_key_press_event (GdkEventKey *ev) {
1181 	if (!_act_on_release && _focused &&
1182 			(ev->keyval == GDK_space || ev->keyval == GDK_Return))
1183 	{
1184 		if (_auto_toggle && !_action) {
1185 				set_active (!get_active ());
1186 		}
1187 		signal_clicked();
1188 		if (_action) {
1189 			_action->activate ();
1190 		}
1191 		return true;
1192 	}
1193 	return CairoWidget::on_key_release_event (ev);
1194 }
1195 
1196 bool
on_enter_notify_event(GdkEventCrossing * ev)1197 ArdourButton::on_enter_notify_event (GdkEventCrossing* ev)
1198 {
1199 	_hovering = (_elements & Inactive) ? false : true;
1200 
1201 	if (UIConfigurationBase::instance().get_widget_prelight()) {
1202 		CairoWidget::set_dirty ();
1203 	}
1204 
1205 	boost::shared_ptr<PBD::Controllable> c (binding_proxy.get_controllable ());
1206 	if (c) {
1207 		PBD::Controllable::GUIFocusChanged (boost::weak_ptr<PBD::Controllable> (c));
1208 	}
1209 
1210 	return CairoWidget::on_enter_notify_event (ev);
1211 }
1212 
1213 bool
on_leave_notify_event(GdkEventCrossing * ev)1214 ArdourButton::on_leave_notify_event (GdkEventCrossing* ev)
1215 {
1216 	_hovering = false;
1217 
1218 	if (UIConfigurationBase::instance().get_widget_prelight()) {
1219 		CairoWidget::set_dirty ();
1220 	}
1221 
1222 	if (binding_proxy.get_controllable()) {
1223 		PBD::Controllable::GUIFocusChanged (boost::weak_ptr<PBD::Controllable> ());
1224 	}
1225 
1226 	return CairoWidget::on_leave_notify_event (ev);
1227 }
1228 
1229 bool
on_grab_broken_event(GdkEventGrabBroken * grab_broken_event)1230 ArdourButton::on_grab_broken_event(GdkEventGrabBroken* grab_broken_event) {
1231 	/* Our implicit grab due to a button_press was broken by another grab:
1232 	 * the button will not get any button_release event if the mouse leaves
1233 	 * while the grab is taken, so unpress ourselves */
1234 	_grabbed = false;
1235 	CairoWidget::set_dirty ();
1236 	return true;
1237 }
1238 
1239 void
set_tweaks(Tweaks t)1240 ArdourButton::set_tweaks (Tweaks t)
1241 {
1242 	if (_tweaks != t) {
1243 		_tweaks = t;
1244 		if (is_realized()) {
1245 			queue_resize ();
1246 		}
1247 	}
1248 }
1249 
1250 void
action_sensitivity_changed()1251 ArdourButton::action_sensitivity_changed ()
1252 {
1253 	if (_action->property_sensitive ()) {
1254 		set_visual_state (Gtkmm2ext::VisualState (visual_state() & ~Gtkmm2ext::Insensitive));
1255 	} else {
1256 		set_visual_state (Gtkmm2ext::VisualState (visual_state() | Gtkmm2ext::Insensitive));
1257 	}
1258 }
1259 
1260 void
set_layout_ellipsize_width(int w)1261 ArdourButton::set_layout_ellipsize_width (int w)
1262 {
1263 	if (_layout_ellipsize_width == w) {
1264 		return;
1265 	}
1266 	_layout_ellipsize_width = w;
1267 	if (!_layout) {
1268 		return;
1269 	}
1270 	if (_layout_ellipsize_width > 3 * PANGO_SCALE) {
1271 		_layout->set_width (_layout_ellipsize_width - 3 * PANGO_SCALE);
1272 	}
1273 	if (is_realized ()) {
1274 		queue_resize ();
1275 	}
1276 }
1277 
1278 void
set_text_ellipsize(Pango::EllipsizeMode e)1279 ArdourButton::set_text_ellipsize (Pango::EllipsizeMode e)
1280 {
1281 	if (_ellipsis == e) {
1282 		return;
1283 	}
1284 	_ellipsis = e;
1285 	if (!_layout) {
1286 		return;
1287 	}
1288 	_layout->set_ellipsize(_ellipsis);
1289 	if (_layout_ellipsize_width > 3 * PANGO_SCALE) {
1290 		_layout->set_width (_layout_ellipsize_width - 3 * PANGO_SCALE);
1291 	}
1292 	if (is_realized ()) {
1293 		queue_resize ();
1294 	}
1295 }
1296 
1297 void
ensure_layout()1298 ArdourButton::ensure_layout ()
1299 {
1300 	if (!_layout) {
1301 		ensure_style ();
1302 		_layout = Pango::Layout::create (get_pango_context());
1303 		_layout->set_font_description (get_style()->get_font());
1304 		_layout->set_ellipsize(_ellipsis);
1305 		if (_layout_ellipsize_width > 3 * PANGO_SCALE) {
1306 			_layout->set_width (_layout_ellipsize_width - 3* PANGO_SCALE);
1307 		}
1308 	}
1309 }
1310 
1311 void
recalc_char_pixel_geometry()1312 ArdourButton::recalc_char_pixel_geometry ()
1313 {
1314 	if (_char_pixel_height > 0 && _char_pixel_width > 0) {
1315 		return;
1316 	}
1317 	ensure_layout();
1318 	// NB. this is not static, since the geometry is different
1319 	// depending on the font used.
1320 	int w, h;
1321 	std::string x = _("@ABCDEFGHIJLKMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
1322 	_layout->set_text (x);
1323 	_layout->get_pixel_size (w, h);
1324 	_char_pixel_height = std::max(4, h);
1325 	// number of actual chars in the string (not bytes)
1326 	// Glib to the rescue.
1327 	Glib::ustring gx(x);
1328 	_char_avg_pixel_width = w / (float)gx.size();
1329 	_char_pixel_width = std::max(4, (int) ceil (_char_avg_pixel_width));
1330 	set_text_internal (); /* restore display text */
1331 }
1332 
1333 void
action_visibility_changed()1334 ArdourButton::action_visibility_changed ()
1335 {
1336 	if (_action->property_visible ()) {
1337 		show ();
1338 	} else {
1339 		hide ();
1340 	}
1341 }
1342 
1343 void
action_tooltip_changed()1344 ArdourButton::action_tooltip_changed ()
1345 {
1346 	string str = _action->property_tooltip().get_value();
1347 	set_tooltip (*this, str);
1348 }
1349 
1350 void
set_elements(Element e)1351 ArdourButton::set_elements (Element e)
1352 {
1353 	_elements = e;
1354 	CairoWidget::set_dirty ();
1355 }
1356 
1357 void
add_elements(Element e)1358 ArdourButton::add_elements (Element e)
1359 {
1360 	_elements = (ArdourButton::Element) (_elements | e);
1361 	CairoWidget::set_dirty ();
1362 }
1363 
1364 void
set_icon(ArdourIcon::Icon i)1365 ArdourButton::set_icon (ArdourIcon::Icon i)
1366 {
1367 	_icon = i;
1368 	_icon_render_cb = 0;
1369 	_icon_render_cb_data = 0;
1370 	_elements = (ArdourButton::Element) ((_elements | VectorIcon) & ~(ArdourButton::Text | IconRenderCallback));
1371 	CairoWidget::set_dirty ();
1372 }
1373 
1374 void
set_icon(rendercallback_t cb,void * d)1375 ArdourButton::set_icon (rendercallback_t cb, void* d)
1376 {
1377 	if (!cb) {
1378 		_elements = (ArdourButton::Element) ((_elements | ArdourButton::Text) & ~(IconRenderCallback | VectorIcon));
1379 		_icon_render_cb = 0;
1380 		_icon_render_cb_data = 0;
1381 	} else {
1382 		_elements = (ArdourButton::Element) ((_elements | IconRenderCallback) & ~(ArdourButton::Text | VectorIcon));
1383 		_icon_render_cb = cb;
1384 		_icon_render_cb_data = d;
1385 	}
1386 	CairoWidget::set_dirty ();
1387 }
1388 
1389 void
set_custom_led_color(uint32_t c,bool useit)1390 ArdourButton::set_custom_led_color (uint32_t c, bool useit)
1391 {
1392 	if (led_custom_color == c && use_custom_led_color == useit) {
1393 		return;
1394 	}
1395 
1396 	led_custom_color = c;
1397 	use_custom_led_color = useit;
1398 	CairoWidget::set_dirty ();
1399 }
1400