Merge branch 'master' into saveas
[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 = ARDOUR_UI::config()->color ("send bg");
184                 // b = rgba_from_style("SendStripBase",
185                 // UINT_RGBA_R(b), UINT_RGBA_G(b), UINT_RGBA_B(b), 255,
186                 // "fg");
187         }
188         /* background */
189
190         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
191         cairo_rectangle (context->cobj(), 0, 0, width, height);
192         context->fill_preserve ();
193         context->clip();
194
195         /* the usable width is reduced from the real width, because we need space for
196            the two halves of LR boxes that will extend past the actual left/right
197            positions (indicated by the vertical line segment above them).
198         */
199
200         double usable_width = width - lr_box_size;
201
202         /* compute the centers of the L/R boxes based on the current stereo width */
203
204         if (fmod (usable_width,2.0) == 0) {
205                 /* even width, but we need odd, so that there is an exact center.
206                    So, offset cairo by 1, and reduce effective width by 1
207                 */
208                 usable_width -= 1.0;
209                 context->translate (1.0, 0.0);
210         }
211
212         const double half_lr_box = lr_box_size/2.0;
213         const double center = rint(half_lr_box + (usable_width * pos));
214         const double pan_spread = rint((fswidth * (usable_width-1.0))/2.0);
215         const double left  = center - pan_spread;
216         const double right = center + pan_spread;
217
218         /* center line */
219         context->set_line_width (1.0);
220         context->move_to ((usable_width + lr_box_size)/2.0, 0);
221         context->rel_line_to (0, height);
222         context->set_source_rgba (UINT_RGBA_R_FLT(r), UINT_RGBA_G_FLT(r), UINT_RGBA_B_FLT(r), UINT_RGBA_A_FLT(r));
223         context->stroke ();
224
225         /* compute & draw the line through the box */
226         context->set_line_width (2);
227         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
228         context->move_to (left,  top_step + (pos_box_size/2.0) + step_down + 1.0);
229         context->line_to (left,  top_step + (pos_box_size/2.0));
230         context->line_to (right, top_step + (pos_box_size/2.0));
231         context->line_to (right, top_step + (pos_box_size/2.0) + step_down + 1.0);
232         context->stroke ();
233
234         context->set_line_width (1.0);
235
236         /* left box */
237         if (state != Mono) {
238                 rounded_rectangle (context, left - half_lr_box,
239                                 half_lr_box+step_down,
240                                 lr_box_size, lr_box_size, corner_radius);
241                 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
242                 context->fill_preserve();
243                 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
244                 context->stroke();
245
246                 /* add text */
247                 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
248                 if (swidth < 0.0) {
249                         layout->set_text (S_("Panner|R"));
250                 } else {
251                         layout->set_text (S_("Panner|L"));
252                 }
253                 layout->get_pixel_size(tw, th);
254                 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
255                 pango_cairo_show_layout (context->cobj(), layout->gobj());
256         }
257
258         /* right box */
259         rounded_rectangle (context, right - half_lr_box,
260                         half_lr_box+step_down,
261                         lr_box_size, lr_box_size, corner_radius);
262         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
263         context->fill_preserve();
264         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
265         context->stroke();
266
267         /* add text */
268         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
269
270         if (state == Mono) {
271                 layout->set_text (S_("Panner|M"));
272         } else {
273                 if (swidth < 0.0) {
274                         layout->set_text (S_("Panner|L"));
275                 } else {
276                         layout->set_text (S_("Panner|R"));
277                 }
278         }
279         layout->get_pixel_size(tw, th);
280         context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
281         pango_cairo_show_layout (context->cobj(), layout->gobj());
282
283         /* draw the central box */
284         context->set_line_width (2.0);
285         context->move_to (center + (pos_box_size/2.0), top_step); /* top right */
286         context->rel_line_to (0.0, pos_box_size); /* lower right */
287         context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
288         context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
289         context->rel_line_to (0.0, -pos_box_size); /* upper left */
290         context->close_path ();
291
292         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
293         context->stroke_preserve ();
294         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
295         context->fill ();
296
297         return true;
298 }
299
300 bool
301 StereoPanner::on_button_press_event (GdkEventButton* ev)
302 {
303         if (PannerInterface::on_button_press_event (ev)) {
304                 return true;
305         }
306
307         if (_panner_shell->bypassed()) {
308                 return true;
309         }
310         
311         drag_start_x = ev->x;
312         last_drag_x = ev->x;
313
314         dragging_position = false;
315         dragging_left = false;
316         dragging_right = false;
317         _dragging = false;
318         _tooltip.target_stop_drag ();
319         accumulated_delta = 0;
320         detented = false;
321
322         /* Let the binding proxies get first crack at the press event
323          */
324
325         if (ev->y < 20) {
326                 if (position_binder.button_press_handler (ev)) {
327                         return true;
328                 }
329         } else {
330                 if (width_binder.button_press_handler (ev)) {
331                         return true;
332                 }
333         }
334
335         if (ev->button != 1) {
336                 return false;
337         }
338
339         if (ev->type == GDK_2BUTTON_PRESS) {
340                 int width = get_width();
341
342                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
343                         /* handled by button release */
344                         return true;
345                 }
346
347                 if (ev->y < 20) {
348
349                         /* upper section: adjusts position, constrained by width */
350
351                         const double w = fabs (width_control->get_value ());
352                         const double max_pos = 1.0 - (w/2.0);
353                         const double min_pos = w/2.0;
354
355                         if (ev->x <= width/3) {
356                                 /* left side dbl click */
357                                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
358                                         /* 2ndary-double click on left, collapse to hard left */
359                                         width_control->set_value (0);
360                                         position_control->set_value (0);
361                                 } else {
362                                         position_control->set_value (min_pos);
363                                 }
364                         } else if (ev->x > 2*width/3) {
365                                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
366                                         /* 2ndary-double click on right, collapse to hard right */
367                                         width_control->set_value (0);
368                                         position_control->set_value (1.0);
369                                 } else {
370                                         position_control->set_value (max_pos);
371                                 }
372                         } else {
373                                 position_control->set_value (0.5);
374                         }
375
376                 } else {
377
378                         /* lower section: adjusts width, constrained by position */
379
380                         const double p = position_control->get_value ();
381                         const double max_width = 2.0 * min ((1.0 - p), p);
382
383                         if (ev->x <= width/3) {
384                                 /* left side dbl click */
385                                 width_control->set_value (max_width); // reset width to 100%
386                         } else if (ev->x > 2*width/3) {
387                                 /* right side dbl click */
388                                 width_control->set_value (-max_width); // reset width to inverted 100%
389                         } else {
390                                 /* center dbl click */
391                                 width_control->set_value (0); // collapse width to 0%
392                         }
393                 }
394
395                 _dragging = false;
396                 _tooltip.target_stop_drag ();
397
398         } else if (ev->type == GDK_BUTTON_PRESS) {
399
400                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
401                         /* handled by button release */
402                         return true;
403                 }
404
405                 if (ev->y < 20) {
406                         /* top section of widget is for position drags */
407                         dragging_position = true;
408                         StartPositionGesture ();
409                 } else {
410                         /* lower section is for dragging width */
411
412                         double pos = position_control->get_value (); /* 0..1 */
413                         double swidth = width_control->get_value (); /* -1..+1 */
414                         double fswidth = fabs (swidth);
415                         int usable_width = get_width() - lr_box_size;
416                         double center = (lr_box_size/2.0) + (usable_width * pos);
417                         int left = lrint (center - (fswidth * usable_width / 2.0)); // center of leftmost box
418                         int right = lrint (center +  (fswidth * usable_width / 2.0)); // center of rightmost box
419                         const int half_box = lr_box_size/2;
420
421                         if (ev->x >= (left - half_box) && ev->x < (left + half_box)) {
422                                 if (swidth < 0.0) {
423                                         dragging_right = true;
424                                 } else {
425                                         dragging_left = true;
426                                 }
427                         } else if (ev->x >= (right - half_box) && ev->x < (right + half_box)) {
428                                 if (swidth < 0.0) {
429                                         dragging_left = true;
430                                 } else {
431                                         dragging_right = true;
432                                 }
433                         }
434                         StartWidthGesture ();
435                 }
436
437                 _dragging = true;
438                 _tooltip.target_start_drag ();
439         }
440
441         return true;
442 }
443
444 bool
445 StereoPanner::on_button_release_event (GdkEventButton* ev)
446 {
447         if (PannerInterface::on_button_release_event (ev)) {
448                 return true;
449         }
450         
451         if (ev->button != 1) {
452                 return false;
453         }
454
455         if (_panner_shell->bypassed()) {
456                 return false;
457         }
458
459         bool const dp = dragging_position;
460
461         _dragging = false;
462         _tooltip.target_stop_drag ();
463         dragging_position = false;
464         dragging_left = false;
465         dragging_right = false;
466         accumulated_delta = 0;
467         detented = false;
468
469         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
470                 _panner->reset ();
471         } else {
472                 if (dp) {
473                         StopPositionGesture ();
474                 } else {
475                         StopWidthGesture ();
476                 }
477         }
478
479         return true;
480 }
481
482 bool
483 StereoPanner::on_scroll_event (GdkEventScroll* ev)
484 {
485         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
486         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
487         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
488         double step;
489
490         if (_panner_shell->bypassed()) {
491                 return false;
492         }
493
494         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
495                 step = one_degree;
496         } else {
497                 step = one_degree * 5.0;
498         }
499
500         switch (ev->direction) {
501         case GDK_SCROLL_LEFT:
502                 wv += step;
503                 width_control->set_value (wv);
504                 break;
505         case GDK_SCROLL_UP:
506                 pv -= step;
507                 position_control->set_value (pv);
508                 break;
509         case GDK_SCROLL_RIGHT:
510                 wv -= step;
511                 width_control->set_value (wv);
512                 break;
513         case GDK_SCROLL_DOWN:
514                 pv += step;
515                 position_control->set_value (pv);
516                 break;
517         }
518
519         return true;
520 }
521
522 bool
523 StereoPanner::on_motion_notify_event (GdkEventMotion* ev)
524 {
525         if (_panner_shell->bypassed()) {
526                 _dragging = false;
527         }
528         if (!_dragging) {
529                 return false;
530         }
531
532         int usable_width = get_width() - lr_box_size;
533         double delta = (ev->x - last_drag_x) / (double) usable_width;
534         double current_width = width_control->get_value ();
535
536         if (dragging_left) {
537                 delta = -delta;
538         }
539         
540         if (dragging_left || dragging_right) {
541
542                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
543
544                         /* change width and position in a way that keeps the
545                          * other side in the same place
546                          */
547
548                         _panner->freeze ();
549                         
550                         double pv = position_control->get_value();
551
552                         if (dragging_left) {
553                                 position_control->set_value (pv - delta);
554                         } else {
555                                 position_control->set_value (pv + delta);
556                         }
557
558                         if (delta > 0.0) {
559                                 /* delta is positive, so we're about to
560                                    increase the width. But we need to increase it
561                                    by twice the required value so that the
562                                    other side remains in place when we set
563                                    the position as well.
564                                 */
565                                 width_control->set_value (current_width + (delta * 2.0));
566                         } else {
567                                 width_control->set_value (current_width + delta);
568                         }
569
570                         _panner->thaw ();
571
572                 } else {
573
574                         /* maintain position as invariant as we change the width */
575                         
576                         /* create a detent close to the center */
577                         
578                         if (!detented && fabs (current_width) < 0.02) {
579                                 detented = true;
580                                 /* snap to zero */
581                                 width_control->set_value (0);
582                         }
583                         
584                         if (detented) {
585                                 
586                                 accumulated_delta += delta;
587                                 
588                                 /* have we pulled far enough to escape ? */
589                                 
590                                 if (fabs (accumulated_delta) >= 0.025) {
591                                         width_control->set_value (current_width + accumulated_delta);
592                                         detented = false;
593                                         accumulated_delta = false;
594                                 }
595                                 
596                         } else {
597                                 /* width needs to change by 2 * delta because both L & R move */
598                                 width_control->set_value (current_width + (delta * 2.0));
599                         }
600                 }
601
602         } else if (dragging_position) {
603
604                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
605                 position_control->set_value (pv + delta);
606         }
607
608         last_drag_x = ev->x;
609         return true;
610 }
611
612 bool
613 StereoPanner::on_key_press_event (GdkEventKey* ev)
614 {
615         double one_degree = 1.0/180.0;
616         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
617         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
618         double step;
619
620         if (_panner_shell->bypassed()) {
621                 return false;
622         }
623
624         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
625                 step = one_degree;
626         } else {
627                 step = one_degree * 5.0;
628         }
629
630         /* up/down control width because we consider pan position more "important"
631            (and thus having higher "sense" priority) than width.
632         */
633
634         switch (ev->keyval) {
635         case GDK_Up:
636                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
637                         width_control->set_value (1.0);
638                 } else {
639                         width_control->set_value (wv + step);
640                 }
641                 break;
642         case GDK_Down:
643                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
644                         width_control->set_value (-1.0);
645                 } else {
646                         width_control->set_value (wv - step);
647                 }
648                 break;
649
650         case GDK_Left:
651                 pv -= step;
652                 position_control->set_value (pv);
653                 break;
654         case GDK_Right:
655                 pv += step;
656                 position_control->set_value (pv);
657                 break;
658         case GDK_0:
659         case GDK_KP_0:
660                 width_control->set_value (0.0);
661                 break;
662
663         default:
664                 return false;
665         }
666
667         return true;
668 }
669
670 void
671 StereoPanner::set_colors ()
672 {
673         colors[Normal].fill = ARDOUR_UI::config()->color_mod ("stereo panner fill", "panner fill");
674         // colors[Normal].outline = ARDOUR_UI::config()->color ("stereo panner outline");
675         colors[Normal].outline = ArdourCanvas::HSV (colors[Normal].fill).outline().color ();
676         colors[Normal].text = ARDOUR_UI::config()->color ("stereo panner text");
677         colors[Normal].background = ARDOUR_UI::config()->color ("stereo panner bg");
678         colors[Normal].rule = ARDOUR_UI::config()->color ("stereo panner rule");
679
680         colors[Mono].fill = ARDOUR_UI::config()->color ("stereo panner mono fill");
681         colors[Mono].outline = ARDOUR_UI::config()->color ("stereo panner mono outline");
682         colors[Mono].text = ARDOUR_UI::config()->color ("stereo panner mono text");
683         colors[Mono].background = ARDOUR_UI::config()->color ("stereo panner mono bg");
684         colors[Mono].rule = ARDOUR_UI::config()->color ("stereo panner rule");
685
686         colors[Inverted].fill = ARDOUR_UI::config()->color_mod ("stereo panner inverted fill", "stereo panner inverted");
687         colors[Inverted].outline = ARDOUR_UI::config()->color ("stereo panner inverted outline");
688         colors[Inverted].text = ARDOUR_UI::config()->color ("stereo panner inverted text");
689         colors[Inverted].background = ARDOUR_UI::config()->color_mod ("stereo panner inverted bg", "stereo panner inverted bg");
690         colors[Inverted].rule = ARDOUR_UI::config()->color ("stereo panner rule");
691 }
692
693 void
694 StereoPanner::color_handler ()
695 {
696         set_colors ();
697         queue_draw ();
698 }
699
700 void
701 StereoPanner::bypass_handler ()
702 {
703         queue_draw ();
704 }
705
706 void
707 StereoPanner::pannable_handler ()
708 {
709         panvalue_connections.drop_connections();
710         position_control = _panner->pannable()->pan_azimuth_control;
711         width_control = _panner->pannable()->pan_width_control;
712         position_binder.set_controllable(position_control);
713         width_binder.set_controllable(width_control);
714
715         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
716         width_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
717         queue_draw ();
718 }
719
720 PannerEditor*
721 StereoPanner::editor ()
722 {
723         return new StereoPannerEditor (this);
724 }