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