Move DPIReset and ColorsChanged signals into UIConfiguration
[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 "stereo_panner.h"
44 #include "stereo_panner_editor.h"
45 #include "rgb_macros.h"
46 #include "utils.h"
47
48 #include "i18n.h"
49
50 using namespace std;
51 using namespace Gtk;
52 using namespace Gtkmm2ext;
53 using namespace ARDOUR_UI_UTILS;
54
55 StereoPanner::ColorScheme StereoPanner::colors[3];
56 bool StereoPanner::have_colors = false;
57
58 Pango::AttrList StereoPanner::panner_font_attributes;
59 bool            StereoPanner::have_font = false;
60
61 using namespace ARDOUR;
62
63 StereoPanner::StereoPanner (boost::shared_ptr<PannerShell> p)
64         : PannerInterface (p->panner())
65         , _panner_shell (p)
66         , position_control (_panner->pannable()->pan_azimuth_control)
67         , width_control (_panner->pannable()->pan_width_control)
68         , dragging_position (false)
69         , dragging_left (false)
70         , dragging_right (false)
71         , drag_start_x (0)
72         , last_drag_x (0)
73         , accumulated_delta (0)
74         , detented (false)
75         , position_binder (position_control)
76         , width_binder (width_control)
77         , _dragging (false)
78 {
79         if (!have_colors) {
80                 set_colors ();
81                 have_colors = true;
82         }
83         if (!have_font) {
84                 Pango::FontDescription font;
85                 Pango::AttrFontDesc* font_attr;
86                 font = Pango::FontDescription (ARDOUR_UI::config()->get_SmallBoldMonospaceFont());
87                 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
88                 panner_font_attributes.change(*font_attr);
89                 delete font_attr;
90                 have_font = true;
91         }
92
93         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
94         width_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
95
96         _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&StereoPanner::bypass_handler, this), gui_context());
97         _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&StereoPanner::pannable_handler, this), gui_context());
98
99         UIConfiguration::ColorsChanged.connect (sigc::mem_fun (*this, &StereoPanner::color_handler));
100
101         set_tooltip ();
102 }
103
104 StereoPanner::~StereoPanner ()
105 {
106
107 }
108
109 void
110 StereoPanner::set_tooltip ()
111 {
112         if (_panner_shell->bypassed()) {
113                 _tooltip.set_tip (_("bypassed"));
114                 return;
115         }
116         double pos = position_control->get_value(); // 0..1
117
118         /* We show the position of the center of the image relative to the left & right.
119            This is expressed as a pair of percentage values that ranges from (100,0)
120            (hard left) through (50,50) (hard center) to (0,100) (hard right).
121
122            This is pretty wierd, but its the way audio engineers expect it. Just remember that
123            the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
124         */
125
126         char buf[64];
127         snprintf (buf, sizeof (buf), _("L:%3d R:%3d Width:%d%%"), (int) rint (100.0 * (1.0 - pos)),
128                   (int) rint (100.0 * pos),
129                   (int) floor (100.0 * width_control->get_value()));
130         _tooltip.set_tip (buf);
131 }
132
133 bool
134 StereoPanner::on_expose_event (GdkEventExpose*)
135 {
136         Glib::RefPtr<Gdk::Window> win (get_window());
137         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
138         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
139         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
140         layout->set_attributes (panner_font_attributes);
141
142         int tw, th;
143         int width, height;
144         const double pos = position_control->get_value (); /* 0..1 */
145         const double swidth = width_control->get_value (); /* -1..+1 */
146         const double fswidth = fabs (swidth);
147         uint32_t o, f, t, b, r;
148         State state;
149
150         width = get_width();
151         height = get_height ();
152
153         const int step_down = rint(height / 3.5);
154         const double corner_radius = 5.0 * ARDOUR_UI::ui_scale;
155         const int lr_box_size = height - 2 * step_down;
156         const int pos_box_size = (int)(rint(step_down * .8)) | 1;
157         const int top_step = step_down - pos_box_size;
158
159         if (swidth == 0.0) {
160                 state = Mono;
161         } else if (swidth < 0.0) {
162                 state = Inverted;
163         } else {
164                 state = Normal;
165         }
166
167         o = colors[state].outline;
168         f = colors[state].fill;
169         t = colors[state].text;
170         b = colors[state].background;
171         r = colors[state].rule;
172
173         if (_panner_shell->bypassed()) {
174                 b  = 0x20202040;
175                 f  = 0x404040ff;
176                 o  = 0x606060ff;
177                 t  = 0x606060ff;
178                 r  = 0x606060ff;
179         }
180
181         if (_send_mode) {
182                 b = ARDOUR_UI::config()->color ("send bg");
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 (S_("Panner|R"));
249                 } else {
250                         layout->set_text (S_("Panner|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 (S_("Panner|M"));
271         } else {
272                 if (swidth < 0.0) {
273                         layout->set_text (S_("Panner|L"));
274                 } else {
275                         layout->set_text (S_("Panner|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                         const int lr_box_size = get_height() - 2 * rint(get_height() / 3.5);
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         const int lr_box_size = get_height() - 2 * rint(get_height() / 3.5);
533         int usable_width = get_width() - lr_box_size;
534         double delta = (ev->x - last_drag_x) / (double) usable_width;
535         double current_width = width_control->get_value ();
536
537         if (dragging_left) {
538                 delta = -delta;
539         }
540         
541         if (dragging_left || dragging_right) {
542
543                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
544
545                         /* change width and position in a way that keeps the
546                          * other side in the same place
547                          */
548
549                         _panner->freeze ();
550                         
551                         double pv = position_control->get_value();
552
553                         if (dragging_left) {
554                                 position_control->set_value (pv - delta);
555                         } else {
556                                 position_control->set_value (pv + delta);
557                         }
558
559                         if (delta > 0.0) {
560                                 /* delta is positive, so we're about to
561                                    increase the width. But we need to increase it
562                                    by twice the required value so that the
563                                    other side remains in place when we set
564                                    the position as well.
565                                 */
566                                 width_control->set_value (current_width + (delta * 2.0));
567                         } else {
568                                 width_control->set_value (current_width + delta);
569                         }
570
571                         _panner->thaw ();
572
573                 } else {
574
575                         /* maintain position as invariant as we change the width */
576                         
577                         /* create a detent close to the center */
578                         
579                         if (!detented && fabs (current_width) < 0.02) {
580                                 detented = true;
581                                 /* snap to zero */
582                                 width_control->set_value (0);
583                         }
584                         
585                         if (detented) {
586                                 
587                                 accumulated_delta += delta;
588                                 
589                                 /* have we pulled far enough to escape ? */
590                                 
591                                 if (fabs (accumulated_delta) >= 0.025) {
592                                         width_control->set_value (current_width + accumulated_delta);
593                                         detented = false;
594                                         accumulated_delta = false;
595                                 }
596                                 
597                         } else {
598                                 /* width needs to change by 2 * delta because both L & R move */
599                                 width_control->set_value (current_width + (delta * 2.0));
600                         }
601                 }
602
603         } else if (dragging_position) {
604
605                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
606                 position_control->set_value (pv + delta);
607         }
608
609         last_drag_x = ev->x;
610         return true;
611 }
612
613 bool
614 StereoPanner::on_key_press_event (GdkEventKey* ev)
615 {
616         double one_degree = 1.0/180.0;
617         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
618         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
619         double step;
620
621         if (_panner_shell->bypassed()) {
622                 return false;
623         }
624
625         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
626                 step = one_degree;
627         } else {
628                 step = one_degree * 5.0;
629         }
630
631         /* up/down control width because we consider pan position more "important"
632            (and thus having higher "sense" priority) than width.
633         */
634
635         switch (ev->keyval) {
636         case GDK_Up:
637                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
638                         width_control->set_value (1.0);
639                 } else {
640                         width_control->set_value (wv + step);
641                 }
642                 break;
643         case GDK_Down:
644                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
645                         width_control->set_value (-1.0);
646                 } else {
647                         width_control->set_value (wv - step);
648                 }
649                 break;
650
651         case GDK_Left:
652                 pv -= step;
653                 position_control->set_value (pv);
654                 break;
655         case GDK_Right:
656                 pv += step;
657                 position_control->set_value (pv);
658                 break;
659         case GDK_0:
660         case GDK_KP_0:
661                 width_control->set_value (0.0);
662                 break;
663
664         default:
665                 return false;
666         }
667
668         return true;
669 }
670
671 void
672 StereoPanner::set_colors ()
673 {
674         colors[Normal].fill = ARDOUR_UI::config()->color_mod ("stereo panner fill", "panner fill");
675         // colors[Normal].outline = ARDOUR_UI::config()->color ("stereo panner outline");
676         colors[Normal].outline = ArdourCanvas::HSV (colors[Normal].fill).outline().color ();
677         colors[Normal].text = ARDOUR_UI::config()->color ("stereo panner text");
678         colors[Normal].background = ARDOUR_UI::config()->color ("stereo panner bg");
679         colors[Normal].rule = ARDOUR_UI::config()->color ("stereo panner rule");
680
681         colors[Mono].fill = ARDOUR_UI::config()->color ("stereo panner mono fill");
682         colors[Mono].outline = ARDOUR_UI::config()->color ("stereo panner mono outline");
683         colors[Mono].text = ARDOUR_UI::config()->color ("stereo panner mono text");
684         colors[Mono].background = ARDOUR_UI::config()->color ("stereo panner mono bg");
685         colors[Mono].rule = ARDOUR_UI::config()->color ("stereo panner rule");
686
687         colors[Inverted].fill = ARDOUR_UI::config()->color_mod ("stereo panner inverted fill", "stereo panner inverted");
688         colors[Inverted].outline = ARDOUR_UI::config()->color ("stereo panner inverted outline");
689         colors[Inverted].text = ARDOUR_UI::config()->color ("stereo panner inverted text");
690         colors[Inverted].background = ARDOUR_UI::config()->color_mod ("stereo panner inverted bg", "stereo panner inverted bg");
691         colors[Inverted].rule = ARDOUR_UI::config()->color ("stereo panner rule");
692 }
693
694 void
695 StereoPanner::color_handler ()
696 {
697         set_colors ();
698         queue_draw ();
699 }
700
701 void
702 StereoPanner::bypass_handler ()
703 {
704         queue_draw ();
705 }
706
707 void
708 StereoPanner::pannable_handler ()
709 {
710         panvalue_connections.drop_connections();
711         position_control = _panner->pannable()->pan_azimuth_control;
712         width_control = _panner->pannable()->pan_width_control;
713         position_binder.set_controllable(position_control);
714         width_binder.set_controllable(width_control);
715
716         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
717         width_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
718         queue_draw ();
719 }
720
721 PannerEditor*
722 StereoPanner::editor ()
723 {
724         return new StereoPannerEditor (this);
725 }