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