enough with umpteen "i18n.h" files. Consolidate on pbd/i18n.h
[ardour.git] / gtk2_ardour / stereo_panner.cc
1 /*
2   Copyright (C) 2000-2007 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 #include <iostream>
20 #include <iomanip>
21 #include <cstring>
22 #include <cmath>
23
24 #include <gtkmm/window.h>
25 #include <pangomm/layout.h>
26
27 #include "pbd/controllable.h"
28 #include "pbd/compose.h"
29
30 #include "gtkmm2ext/gui_thread.h"
31 #include "gtkmm2ext/gtk_ui.h"
32 #include "gtkmm2ext/keyboard.h"
33 #include "gtkmm2ext/utils.h"
34 #include "gtkmm2ext/persistent_tooltip.h"
35
36 #include "ardour/pannable.h"
37 #include "ardour/panner.h"
38 #include "ardour/panner_shell.h"
39
40 #include "canvas/colors.h"
41
42 #include "stereo_panner.h"
43 #include "stereo_panner_editor.h"
44 #include "rgb_macros.h"
45 #include "utils.h"
46 #include "ui_config.h"
47
48 #include "pbd/i18n.h"
49
50 using namespace std;
51 using namespace Gtk;
52 using namespace Gtkmm2ext;
53 using namespace ARDOUR_UI_UTILS;
54
55 using PBD::Controllable;
56
57 StereoPanner::ColorScheme StereoPanner::colors[3];
58 bool StereoPanner::have_colors = false;
59
60 Pango::AttrList StereoPanner::panner_font_attributes;
61 bool            StereoPanner::have_font = false;
62
63 using namespace ARDOUR;
64
65 StereoPanner::StereoPanner (boost::shared_ptr<PannerShell> p)
66         : PannerInterface (p->panner())
67         , _panner_shell (p)
68         , position_control (_panner->pannable()->pan_azimuth_control)
69         , width_control (_panner->pannable()->pan_width_control)
70         , dragging_position (false)
71         , dragging_left (false)
72         , dragging_right (false)
73         , drag_start_x (0)
74         , last_drag_x (0)
75         , accumulated_delta (0)
76         , detented (false)
77         , position_binder (position_control)
78         , width_binder (width_control)
79         , _dragging (false)
80 {
81         if (!have_colors) {
82                 set_colors ();
83                 have_colors = true;
84         }
85         if (!have_font) {
86                 Pango::FontDescription font;
87                 Pango::AttrFontDesc* font_attr;
88                 font = Pango::FontDescription (UIConfiguration::instance().get_SmallBoldMonospaceFont());
89                 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
90                 panner_font_attributes.change(*font_attr);
91                 delete font_attr;
92                 have_font = true;
93         }
94
95         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
96         width_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
97
98         _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&StereoPanner::bypass_handler, this), gui_context());
99         _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&StereoPanner::pannable_handler, this), gui_context());
100
101         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &StereoPanner::color_handler));
102
103         set_tooltip ();
104 }
105
106 StereoPanner::~StereoPanner ()
107 {
108
109 }
110
111 void
112 StereoPanner::set_tooltip ()
113 {
114         if (_panner_shell->bypassed()) {
115                 _tooltip.set_tip (_("bypassed"));
116                 return;
117         }
118         double pos = position_control->get_value(); // 0..1
119
120         /* We show the position of the center of the image relative to the left & right.
121            This is expressed as a pair of percentage values that ranges from (100,0)
122            (hard left) through (50,50) (hard center) to (0,100) (hard right).
123
124            This is pretty wierd, but its the way audio engineers expect it. Just remember that
125            the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
126         */
127
128         char buf[64];
129         snprintf (buf, sizeof (buf), _("L:%3d R:%3d Width:%d%%"), (int) rint (100.0 * (1.0 - pos)),
130                   (int) rint (100.0 * pos),
131                   (int) floor (100.0 * width_control->get_value()));
132         _tooltip.set_tip (buf);
133 }
134
135 bool
136 StereoPanner::on_expose_event (GdkEventExpose*)
137 {
138         Glib::RefPtr<Gdk::Window> win (get_window());
139         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
140         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
141         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
142         layout->set_attributes (panner_font_attributes);
143
144         int tw, th;
145         int width, height;
146         const double pos = position_control->get_value (); /* 0..1 */
147         const double swidth = width_control->get_value (); /* -1..+1 */
148         const double fswidth = fabs (swidth);
149         uint32_t o, f, t, b, r;
150         State state;
151
152         width = get_width();
153         height = get_height ();
154
155         const int step_down = rint(height / 3.5);
156         const double corner_radius = 5.0 * UIConfiguration::instance().get_ui_scale();
157         const int lr_box_size = height - 2 * step_down;
158         const int pos_box_size = (int)(rint(step_down * .8)) | 1;
159         const int top_step = step_down - pos_box_size;
160
161         if (swidth == 0.0) {
162                 state = Mono;
163         } else if (swidth < 0.0) {
164                 state = Inverted;
165         } else {
166                 state = Normal;
167         }
168
169         o = colors[state].outline;
170         f = colors[state].fill;
171         t = colors[state].text;
172         b = colors[state].background;
173         r = colors[state].rule;
174
175         if (_panner_shell->bypassed()) {
176                 b  = 0x20202040;
177                 f  = 0x404040ff;
178                 o  = 0x606060ff;
179                 t  = 0x606060ff;
180                 r  = 0x606060ff;
181         }
182
183         if (_send_mode) {
184                 b = UIConfiguration::instance().color ("send bg");
185                 // b = rgba_from_style("SendStripBase",
186                 // UINT_RGBA_R(b), UINT_RGBA_G(b), UINT_RGBA_B(b), 255,
187                 // "fg");
188         }
189         /* background */
190
191         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
192         cairo_rectangle (context->cobj(), 0, 0, width, height);
193         context->fill_preserve ();
194         context->clip();
195
196         /* the usable width is reduced from the real width, because we need space for
197            the two halves of LR boxes that will extend past the actual left/right
198            positions (indicated by the vertical line segment above them).
199         */
200
201         double usable_width = width - lr_box_size;
202
203         /* compute the centers of the L/R boxes based on the current stereo width */
204
205         if (fmod (usable_width,2.0) == 0) {
206                 /* even width, but we need odd, so that there is an exact center.
207                    So, offset cairo by 1, and reduce effective width by 1
208                 */
209                 usable_width -= 1.0;
210                 context->translate (1.0, 0.0);
211         }
212
213         const double half_lr_box = lr_box_size/2.0;
214         const double center = rint(half_lr_box + (usable_width * pos));
215         const double pan_spread = rint((fswidth * (usable_width-1.0))/2.0);
216         const double left  = center - pan_spread;
217         const double right = center + pan_spread;
218
219         /* center line */
220         context->set_line_width (1.0);
221         context->move_to ((usable_width + lr_box_size)/2.0, 0);
222         context->rel_line_to (0, height);
223         context->set_source_rgba (UINT_RGBA_R_FLT(r), UINT_RGBA_G_FLT(r), UINT_RGBA_B_FLT(r), UINT_RGBA_A_FLT(r));
224         context->stroke ();
225
226         /* compute & draw the line through the box */
227         context->set_line_width (2);
228         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
229         context->move_to (left,  top_step + (pos_box_size/2.0) + step_down + 1.0);
230         context->line_to (left,  top_step + (pos_box_size/2.0));
231         context->line_to (right, top_step + (pos_box_size/2.0));
232         context->line_to (right, top_step + (pos_box_size/2.0) + step_down + 1.0);
233         context->stroke ();
234
235         context->set_line_width (1.0);
236
237         /* left box */
238         if (state != Mono) {
239                 rounded_rectangle (context, left - half_lr_box,
240                                 half_lr_box+step_down,
241                                 lr_box_size, lr_box_size, corner_radius);
242                 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
243                 context->fill_preserve();
244                 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
245                 context->stroke();
246
247                 /* add text */
248                 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
249                 if (swidth < 0.0) {
250                         layout->set_text (S_("Panner|R"));
251                 } else {
252                         layout->set_text (S_("Panner|L"));
253                 }
254                 layout->get_pixel_size(tw, th);
255                 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
256                 pango_cairo_show_layout (context->cobj(), layout->gobj());
257         }
258
259         /* right box */
260         rounded_rectangle (context, right - half_lr_box,
261                         half_lr_box+step_down,
262                         lr_box_size, lr_box_size, corner_radius);
263         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
264         context->fill_preserve();
265         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
266         context->stroke();
267
268         /* add text */
269         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
270
271         if (state == Mono) {
272                 layout->set_text (S_("Panner|M"));
273         } else {
274                 if (swidth < 0.0) {
275                         layout->set_text (S_("Panner|L"));
276                 } else {
277                         layout->set_text (S_("Panner|R"));
278                 }
279         }
280         layout->get_pixel_size(tw, th);
281         context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
282         pango_cairo_show_layout (context->cobj(), layout->gobj());
283
284         /* draw the central box */
285         context->set_line_width (2.0);
286         context->move_to (center + (pos_box_size/2.0), top_step); /* top right */
287         context->rel_line_to (0.0, pos_box_size); /* lower right */
288         context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
289         context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
290         context->rel_line_to (0.0, -pos_box_size); /* upper left */
291         context->close_path ();
292
293         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
294         context->stroke_preserve ();
295         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
296         context->fill ();
297
298         return true;
299 }
300
301 bool
302 StereoPanner::on_button_press_event (GdkEventButton* ev)
303 {
304         if (PannerInterface::on_button_press_event (ev)) {
305                 return true;
306         }
307
308         if (_panner_shell->bypassed()) {
309                 return true;
310         }
311
312         drag_start_x = ev->x;
313         last_drag_x = ev->x;
314
315         dragging_position = false;
316         dragging_left = false;
317         dragging_right = false;
318         _dragging = false;
319         _tooltip.target_stop_drag ();
320         accumulated_delta = 0;
321         detented = false;
322
323         /* Let the binding proxies get first crack at the press event
324          */
325
326         if (ev->y < 20) {
327                 if (position_binder.button_press_handler (ev)) {
328                         return true;
329                 }
330         } else {
331                 if (width_binder.button_press_handler (ev)) {
332                         return true;
333                 }
334         }
335
336         if (ev->button != 1) {
337                 return false;
338         }
339
340         if (ev->type == GDK_2BUTTON_PRESS) {
341                 int width = get_width();
342
343                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
344                         /* handled by button release */
345                         return true;
346                 }
347
348                 if (ev->y < 20) {
349
350                         /* upper section: adjusts position, constrained by width */
351
352                         const double w = fabs (width_control->get_value ());
353                         const double max_pos = 1.0 - (w/2.0);
354                         const double min_pos = w/2.0;
355
356                         if (ev->x <= width/3) {
357                                 /* left side dbl click */
358                                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
359                                         /* 2ndary-double click on left, collapse to hard left */
360                                         width_control->set_value (0, Controllable::NoGroup);
361                                         position_control->set_value (0, Controllable::NoGroup);
362                                 } else {
363                                         position_control->set_value (min_pos, Controllable::NoGroup);
364                                 }
365                         } else if (ev->x > 2*width/3) {
366                                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
367                                         /* 2ndary-double click on right, collapse to hard right */
368                                         width_control->set_value (0, Controllable::NoGroup);
369                                         position_control->set_value (1.0, Controllable::NoGroup);
370                                 } else {
371                                         position_control->set_value (max_pos, Controllable::NoGroup);
372                                 }
373                         } else {
374                                 position_control->set_value (0.5, Controllable::NoGroup);
375                         }
376
377                 } else {
378
379                         /* lower section: adjusts width, constrained by position */
380
381                         const double p = position_control->get_value ();
382                         const double max_width = 2.0 * min ((1.0 - p), p);
383
384                         if (ev->x <= width/3) {
385                                 /* left side dbl click */
386                                 width_control->set_value (max_width, Controllable::NoGroup); // reset width to 100%
387                         } else if (ev->x > 2*width/3) {
388                                 /* right side dbl click */
389                                 width_control->set_value (-max_width, Controllable::NoGroup); // reset width to inverted 100%
390                         } else {
391                                 /* center dbl click */
392                                 width_control->set_value (0, Controllable::NoGroup); // collapse width to 0%
393                         }
394                 }
395
396                 _dragging = false;
397                 _tooltip.target_stop_drag ();
398
399         } else if (ev->type == GDK_BUTTON_PRESS) {
400
401                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
402                         /* handled by button release */
403                         return true;
404                 }
405
406                 if (ev->y < 20) {
407                         /* top section of widget is for position drags */
408                         dragging_position = true;
409                         StartPositionGesture ();
410                 } else {
411                         /* lower section is for dragging width */
412
413                         double pos = position_control->get_value (); /* 0..1 */
414                         double swidth = width_control->get_value (); /* -1..+1 */
415                         double fswidth = fabs (swidth);
416                         const int lr_box_size = get_height() - 2 * rint(get_height() / 3.5);
417                         int usable_width = get_width() - lr_box_size;
418                         double center = (lr_box_size/2.0) + (usable_width * pos);
419                         int left = lrint (center - (fswidth * usable_width / 2.0)); // center of leftmost box
420                         int right = lrint (center +  (fswidth * usable_width / 2.0)); // center of rightmost box
421                         const int half_box = lr_box_size/2;
422
423                         if (ev->x >= (left - half_box) && ev->x < (left + half_box)) {
424                                 if (swidth < 0.0) {
425                                         dragging_right = true;
426                                 } else {
427                                         dragging_left = true;
428                                 }
429                         } else if (ev->x >= (right - half_box) && ev->x < (right + half_box)) {
430                                 if (swidth < 0.0) {
431                                         dragging_left = true;
432                                 } else {
433                                         dragging_right = true;
434                                 }
435                         }
436                         StartWidthGesture ();
437                 }
438
439                 _dragging = true;
440                 _tooltip.target_start_drag ();
441         }
442
443         return true;
444 }
445
446 bool
447 StereoPanner::on_button_release_event (GdkEventButton* ev)
448 {
449         if (PannerInterface::on_button_release_event (ev)) {
450                 return true;
451         }
452
453         if (ev->button != 1) {
454                 return false;
455         }
456
457         if (_panner_shell->bypassed()) {
458                 return false;
459         }
460
461         bool const dp = dragging_position;
462
463         _dragging = false;
464         _tooltip.target_stop_drag ();
465         dragging_position = false;
466         dragging_left = false;
467         dragging_right = false;
468         accumulated_delta = 0;
469         detented = false;
470
471         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
472                 _panner->reset ();
473         } else {
474                 if (dp) {
475                         StopPositionGesture ();
476                 } else {
477                         StopWidthGesture ();
478                 }
479         }
480
481         return true;
482 }
483
484 bool
485 StereoPanner::on_scroll_event (GdkEventScroll* ev)
486 {
487         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
488         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
489         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
490         double step;
491
492         if (_panner_shell->bypassed()) {
493                 return false;
494         }
495
496         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
497                 step = one_degree;
498         } else {
499                 step = one_degree * 5.0;
500         }
501
502         switch (ev->direction) {
503         case GDK_SCROLL_LEFT:
504                 wv += step;
505                 width_control->set_value (wv, Controllable::NoGroup);
506                 break;
507         case GDK_SCROLL_UP:
508                 pv -= step;
509                 position_control->set_value (pv, Controllable::NoGroup);
510                 break;
511         case GDK_SCROLL_RIGHT:
512                 wv -= step;
513                 width_control->set_value (wv, Controllable::NoGroup);
514                 break;
515         case GDK_SCROLL_DOWN:
516                 pv += step;
517                 position_control->set_value (pv, Controllable::NoGroup);
518                 break;
519         }
520
521         return true;
522 }
523
524 bool
525 StereoPanner::on_motion_notify_event (GdkEventMotion* ev)
526 {
527         if (_panner_shell->bypassed()) {
528                 _dragging = false;
529         }
530         if (!_dragging) {
531                 return false;
532         }
533
534         const int lr_box_size = get_height() - 2 * rint(get_height() / 3.5);
535         int usable_width = get_width() - lr_box_size;
536         double delta = (ev->x - last_drag_x) / (double) usable_width;
537         double current_width = width_control->get_value ();
538
539         if (dragging_left) {
540                 delta = -delta;
541         }
542
543         if (dragging_left || dragging_right) {
544
545                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
546
547                         /* change width and position in a way that keeps the
548                          * other side in the same place
549                          */
550
551                         _panner->freeze ();
552
553                         double pv = position_control->get_value();
554
555                         if (dragging_left) {
556                                 position_control->set_value (pv - delta, Controllable::NoGroup);
557                         } else {
558                                 position_control->set_value (pv + delta, Controllable::NoGroup);
559                         }
560
561                         if (delta > 0.0) {
562                                 /* delta is positive, so we're about to
563                                    increase the width. But we need to increase it
564                                    by twice the required value so that the
565                                    other side remains in place when we set
566                                    the position as well.
567                                 */
568                                 width_control->set_value (current_width + (delta * 2.0), Controllable::NoGroup);
569                         } else {
570                                 width_control->set_value (current_width + delta, Controllable::NoGroup);
571                         }
572
573                         _panner->thaw ();
574
575                 } else {
576
577                         /* maintain position as invariant as we change the width */
578
579                         /* create a detent close to the center */
580
581                         if (!detented && fabs (current_width) < 0.02) {
582                                 detented = true;
583                                 /* snap to zero */
584                                 width_control->set_value (0, Controllable::NoGroup);
585                         }
586
587                         if (detented) {
588
589                                 accumulated_delta += delta;
590
591                                 /* have we pulled far enough to escape ? */
592
593                                 if (fabs (accumulated_delta) >= 0.025) {
594                                         width_control->set_value (current_width + accumulated_delta, Controllable::NoGroup);
595                                         detented = false;
596                                         accumulated_delta = false;
597                                 }
598
599                         } else {
600                                 /* width needs to change by 2 * delta because both L & R move */
601                                 width_control->set_value (current_width + (delta * 2.0), Controllable::NoGroup);
602                         }
603                 }
604
605         } else if (dragging_position) {
606
607                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
608                 position_control->set_value (pv + delta, Controllable::NoGroup);
609         }
610
611         last_drag_x = ev->x;
612         return true;
613 }
614
615 bool
616 StereoPanner::on_key_press_event (GdkEventKey* ev)
617 {
618         double one_degree = 1.0/180.0;
619         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
620         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
621         double step;
622
623         if (_panner_shell->bypassed()) {
624                 return false;
625         }
626
627         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
628                 step = one_degree;
629         } else {
630                 step = one_degree * 5.0;
631         }
632
633         /* up/down control width because we consider pan position more "important"
634            (and thus having higher "sense" priority) than width.
635         */
636
637         switch (ev->keyval) {
638         case GDK_Up:
639                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
640                         width_control->set_value (1.0, Controllable::NoGroup);
641                 } else {
642                         width_control->set_value (wv + step, Controllable::NoGroup);
643                 }
644                 break;
645         case GDK_Down:
646                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
647                         width_control->set_value (-1.0, Controllable::NoGroup);
648                 } else {
649                         width_control->set_value (wv - step, Controllable::NoGroup);
650                 }
651                 break;
652
653         case GDK_Left:
654                 pv -= step;
655                 position_control->set_value (pv, Controllable::NoGroup);
656                 break;
657         case GDK_Right:
658                 pv += step;
659                 position_control->set_value (pv, Controllable::NoGroup);
660                 break;
661         case GDK_0:
662         case GDK_KP_0:
663                 width_control->set_value (0.0, Controllable::NoGroup);
664                 break;
665
666         default:
667                 return false;
668         }
669
670         return true;
671 }
672
673 void
674 StereoPanner::set_colors ()
675 {
676         colors[Normal].fill = UIConfiguration::instance().color_mod ("stereo panner fill", "panner fill");
677         // colors[Normal].outline = UIConfiguration::instance().color ("stereo panner outline");
678         colors[Normal].outline = ArdourCanvas::HSV (colors[Normal].fill).outline().color ();
679         colors[Normal].text = UIConfiguration::instance().color ("stereo panner text");
680         colors[Normal].background = UIConfiguration::instance().color ("stereo panner bg");
681         colors[Normal].rule = UIConfiguration::instance().color ("stereo panner rule");
682
683         colors[Mono].fill = UIConfiguration::instance().color ("stereo panner mono fill");
684         colors[Mono].outline = UIConfiguration::instance().color ("stereo panner mono outline");
685         colors[Mono].text = UIConfiguration::instance().color ("stereo panner mono text");
686         colors[Mono].background = UIConfiguration::instance().color ("stereo panner mono bg");
687         colors[Mono].rule = UIConfiguration::instance().color ("stereo panner rule");
688
689         colors[Inverted].fill = UIConfiguration::instance().color_mod ("stereo panner inverted fill", "stereo panner inverted");
690         colors[Inverted].outline = UIConfiguration::instance().color ("stereo panner inverted outline");
691         colors[Inverted].text = UIConfiguration::instance().color ("stereo panner inverted text");
692         colors[Inverted].background = UIConfiguration::instance().color_mod ("stereo panner inverted bg", "stereo panner inverted bg");
693         colors[Inverted].rule = UIConfiguration::instance().color ("stereo panner rule");
694 }
695
696 void
697 StereoPanner::color_handler ()
698 {
699         set_colors ();
700         queue_draw ();
701 }
702
703 void
704 StereoPanner::bypass_handler ()
705 {
706         queue_draw ();
707 }
708
709 void
710 StereoPanner::pannable_handler ()
711 {
712         panvalue_connections.drop_connections();
713         position_control = _panner->pannable()->pan_azimuth_control;
714         width_control = _panner->pannable()->pan_width_control;
715         position_binder.set_controllable(position_control);
716         width_binder.set_controllable(width_control);
717
718         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
719         width_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
720         queue_draw ();
721 }
722
723 PannerEditor*
724 StereoPanner::editor ()
725 {
726         return new StereoPannerEditor (this);
727 }