Fix Mixbus action fc83d044f8 & 65bda27d4 rebase ordering
[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/compose.h"
28
29 #include "gtkmm2ext/gui_thread.h"
30 #include "gtkmm2ext/gtk_ui.h"
31 #include "gtkmm2ext/keyboard.h"
32 #include "gtkmm2ext/utils.h"
33 #include "gtkmm2ext/persistent_tooltip.h"
34
35 #include "ardour/pannable.h"
36 #include "ardour/panner.h"
37 #include "ardour/panner_shell.h"
38
39 #include "gtkmm2ext/colors.h"
40
41 #include "stereo_panner.h"
42 #include "stereo_panner_editor.h"
43 #include "rgb_macros.h"
44 #include "utils.h"
45 #include "ui_config.h"
46
47 #include "pbd/i18n.h"
48
49 using namespace std;
50 using namespace Gtk;
51 using namespace Gtkmm2ext;
52 using namespace ARDOUR_UI_UTILS;
53
54 using PBD::Controllable;
55
56 StereoPanner::ColorScheme StereoPanner::colors[3];
57 bool StereoPanner::have_colors = false;
58
59 Pango::AttrList StereoPanner::panner_font_attributes;
60 bool            StereoPanner::have_font = false;
61
62 using namespace ARDOUR;
63
64 StereoPanner::StereoPanner (boost::shared_ptr<PannerShell> p)
65         : PannerInterface (p->panner())
66         , _panner_shell (p)
67         , position_control (_panner->pannable()->pan_azimuth_control)
68         , width_control (_panner->pannable()->pan_width_control)
69         , dragging_position (false)
70         , dragging_left (false)
71         , dragging_right (false)
72         , drag_start_x (0)
73         , last_drag_x (0)
74         , accumulated_delta (0)
75         , detented (false)
76         , position_binder (position_control)
77         , width_binder (width_control)
78         , _dragging (false)
79 {
80         if (!have_colors) {
81                 set_colors ();
82                 have_colors = true;
83         }
84         if (!have_font) {
85                 Pango::FontDescription font;
86                 Pango::AttrFontDesc* font_attr;
87                 font = Pango::FontDescription (UIConfiguration::instance().get_SmallBoldMonospaceFont());
88                 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
89                 panner_font_attributes.change(*font_attr);
90                 delete font_attr;
91                 have_font = true;
92         }
93
94         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
95         width_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
96
97         _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&StereoPanner::bypass_handler, this), gui_context());
98         _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&StereoPanner::pannable_handler, this), gui_context());
99
100         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &StereoPanner::color_handler));
101
102         set_tooltip ();
103 }
104
105 StereoPanner::~StereoPanner ()
106 {
107
108 }
109
110 void
111 StereoPanner::set_tooltip ()
112 {
113         if (_panner_shell->bypassed()) {
114                 _tooltip.set_tip (_("bypassed"));
115                 return;
116         }
117         double pos = position_control->get_value(); // 0..1
118
119         /* We show the position of the center of the image relative to the left & right.
120            This is expressed as a pair of percentage values that ranges from (100,0)
121            (hard left) through (50,50) (hard center) to (0,100) (hard right).
122
123            This is pretty wierd, but its the way audio engineers expect it. Just remember that
124            the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
125         */
126
127         char buf[64];
128         snprintf (buf, sizeof (buf), _("L:%3d R:%3d Width:%d%%"), (int) rint (100.0 * (1.0 - pos)),
129                   (int) rint (100.0 * pos),
130                   (int) floor (100.0 * width_control->get_value()));
131         _tooltip.set_tip (buf);
132 }
133
134 bool
135 StereoPanner::on_expose_event (GdkEventExpose*)
136 {
137         Glib::RefPtr<Gdk::Window> win (get_window());
138         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
139         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
140         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
141         layout->set_attributes (panner_font_attributes);
142
143         int tw, th;
144         int width, height;
145         const double pos = position_control->get_value (); /* 0..1 */
146         const double swidth = width_control->get_value (); /* -1..+1 */
147         const double fswidth = fabs (swidth);
148         uint32_t o, f, t, b, r;
149         State state;
150
151         width = get_width();
152         height = get_height ();
153
154         const int step_down = rint(height / 3.5);
155         const double corner_radius = 5.0 * UIConfiguration::instance().get_ui_scale();
156         const int lr_box_size = height - 2 * step_down;
157         const int pos_box_size = (int)(rint(step_down * .8)) | 1;
158         const int top_step = step_down - pos_box_size;
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 = UIConfiguration::instance().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, Controllable::NoGroup);
360                                         position_control->set_value (0, Controllable::NoGroup);
361                                 } else {
362                                         position_control->set_value (min_pos, Controllable::NoGroup);
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, Controllable::NoGroup);
368                                         position_control->set_value (1.0, Controllable::NoGroup);
369                                 } else {
370                                         position_control->set_value (max_pos, Controllable::NoGroup);
371                                 }
372                         } else {
373                                 position_control->set_value (0.5, Controllable::NoGroup);
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, Controllable::NoGroup); // reset width to 100%
386                         } else if (ev->x > 2*width/3) {
387                                 /* right side dbl click */
388                                 width_control->set_value (-max_width, Controllable::NoGroup); // reset width to inverted 100%
389                         } else {
390                                 /* center dbl click */
391                                 width_control->set_value (0, Controllable::NoGroup); // 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                         const int lr_box_size = get_height() - 2 * rint(get_height() / 3.5);
416                         int usable_width = get_width() - lr_box_size;
417                         double center = (lr_box_size/2.0) + (usable_width * pos);
418                         int left = lrint (center - (fswidth * usable_width / 2.0)); // center of leftmost box
419                         int right = lrint (center +  (fswidth * usable_width / 2.0)); // center of rightmost box
420                         const int half_box = lr_box_size/2;
421
422                         if (ev->x >= (left - half_box) && ev->x < (left + half_box)) {
423                                 if (swidth < 0.0) {
424                                         dragging_right = true;
425                                 } else {
426                                         dragging_left = true;
427                                 }
428                         } else if (ev->x >= (right - half_box) && ev->x < (right + half_box)) {
429                                 if (swidth < 0.0) {
430                                         dragging_left = true;
431                                 } else {
432                                         dragging_right = true;
433                                 }
434                         }
435                         StartWidthGesture ();
436                 }
437
438                 _dragging = true;
439                 _tooltip.target_start_drag ();
440         }
441
442         return true;
443 }
444
445 bool
446 StereoPanner::on_button_release_event (GdkEventButton* ev)
447 {
448         if (PannerInterface::on_button_release_event (ev)) {
449                 return true;
450         }
451
452         if (ev->button != 1) {
453                 return false;
454         }
455
456         if (_panner_shell->bypassed()) {
457                 return false;
458         }
459
460         bool const dp = dragging_position;
461
462         _dragging = false;
463         _tooltip.target_stop_drag ();
464         dragging_position = false;
465         dragging_left = false;
466         dragging_right = false;
467         accumulated_delta = 0;
468         detented = false;
469
470         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
471                 _panner->reset ();
472         } else {
473                 if (dp) {
474                         StopPositionGesture ();
475                 } else {
476                         StopWidthGesture ();
477                 }
478         }
479
480         return true;
481 }
482
483 bool
484 StereoPanner::on_scroll_event (GdkEventScroll* ev)
485 {
486         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
487         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
488         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
489         double step;
490
491         if (_panner_shell->bypassed()) {
492                 return false;
493         }
494
495         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
496                 step = one_degree;
497         } else {
498                 step = one_degree * 5.0;
499         }
500
501         switch (ev->direction) {
502         case GDK_SCROLL_LEFT:
503                 wv += step;
504                 width_control->set_value (wv, Controllable::NoGroup);
505                 break;
506         case GDK_SCROLL_UP:
507                 pv -= step;
508                 position_control->set_value (pv, Controllable::NoGroup);
509                 break;
510         case GDK_SCROLL_RIGHT:
511                 wv -= step;
512                 width_control->set_value (wv, Controllable::NoGroup);
513                 break;
514         case GDK_SCROLL_DOWN:
515                 pv += step;
516                 position_control->set_value (pv, Controllable::NoGroup);
517                 break;
518         }
519
520         return true;
521 }
522
523 bool
524 StereoPanner::on_motion_notify_event (GdkEventMotion* ev)
525 {
526         if (_panner_shell->bypassed()) {
527                 _dragging = false;
528         }
529         if (!_dragging) {
530                 return false;
531         }
532
533         const int lr_box_size = get_height() - 2 * rint(get_height() / 3.5);
534         int usable_width = get_width() - lr_box_size;
535         double delta = (ev->x - last_drag_x) / (double) usable_width;
536         double current_width = width_control->get_value ();
537
538         if (dragging_left) {
539                 delta = -delta;
540         }
541
542         if (dragging_left || dragging_right) {
543
544                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
545
546                         /* change width and position in a way that keeps the
547                          * other side in the same place
548                          */
549
550                         _panner->freeze ();
551
552                         double pv = position_control->get_value();
553
554                         if (dragging_left) {
555                                 position_control->set_value (pv - delta, Controllable::NoGroup);
556                         } else {
557                                 position_control->set_value (pv + delta, Controllable::NoGroup);
558                         }
559
560                         if (delta > 0.0) {
561                                 /* delta is positive, so we're about to
562                                    increase the width. But we need to increase it
563                                    by twice the required value so that the
564                                    other side remains in place when we set
565                                    the position as well.
566                                 */
567                                 width_control->set_value (current_width + (delta * 2.0), Controllable::NoGroup);
568                         } else {
569                                 width_control->set_value (current_width + delta, Controllable::NoGroup);
570                         }
571
572                         _panner->thaw ();
573
574                 } else {
575
576                         /* maintain position as invariant as we change the width */
577
578                         /* create a detent close to the center */
579
580                         if (!detented && fabs (current_width) < 0.02) {
581                                 detented = true;
582                                 /* snap to zero */
583                                 width_control->set_value (0, Controllable::NoGroup);
584                         }
585
586                         if (detented) {
587
588                                 accumulated_delta += delta;
589
590                                 /* have we pulled far enough to escape ? */
591
592                                 if (fabs (accumulated_delta) >= 0.025) {
593                                         width_control->set_value (current_width + accumulated_delta, Controllable::NoGroup);
594                                         detented = false;
595                                         accumulated_delta = false;
596                                 }
597
598                         } else {
599                                 /* width needs to change by 2 * delta because both L & R move */
600                                 width_control->set_value (current_width + (delta * 2.0), Controllable::NoGroup);
601                         }
602                 }
603
604         } else if (dragging_position) {
605
606                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
607                 position_control->set_value (pv + delta, Controllable::NoGroup);
608         }
609
610         last_drag_x = ev->x;
611         return true;
612 }
613
614 bool
615 StereoPanner::on_key_press_event (GdkEventKey* ev)
616 {
617         double one_degree = 1.0/180.0;
618         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
619         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
620         double step;
621
622         if (_panner_shell->bypassed()) {
623                 return false;
624         }
625
626         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
627                 step = one_degree;
628         } else {
629                 step = one_degree * 5.0;
630         }
631
632         /* up/down control width because we consider pan position more "important"
633            (and thus having higher "sense" priority) than width.
634         */
635
636         switch (ev->keyval) {
637         case GDK_Up:
638                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
639                         width_control->set_value (1.0, Controllable::NoGroup);
640                 } else {
641                         width_control->set_value (wv + step, Controllable::NoGroup);
642                 }
643                 break;
644         case GDK_Down:
645                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
646                         width_control->set_value (-1.0, Controllable::NoGroup);
647                 } else {
648                         width_control->set_value (wv - step, Controllable::NoGroup);
649                 }
650                 break;
651
652         case GDK_Left:
653                 pv -= step;
654                 position_control->set_value (pv, Controllable::NoGroup);
655                 break;
656         case GDK_Right:
657                 pv += step;
658                 position_control->set_value (pv, Controllable::NoGroup);
659                 break;
660         case GDK_0:
661         case GDK_KP_0:
662                 width_control->set_value (0.0, Controllable::NoGroup);
663                 break;
664
665         default:
666                 return false;
667         }
668
669         return true;
670 }
671
672 void
673 StereoPanner::set_colors ()
674 {
675         colors[Normal].fill = UIConfiguration::instance().color_mod ("stereo panner fill", "panner fill");
676         // colors[Normal].outline = UIConfiguration::instance().color ("stereo panner outline");
677         colors[Normal].outline = Gtkmm2ext::HSV (colors[Normal].fill).outline().color ();
678         colors[Normal].text = UIConfiguration::instance().color ("stereo panner text");
679         colors[Normal].background = UIConfiguration::instance().color ("stereo panner bg");
680         colors[Normal].rule = UIConfiguration::instance().color ("stereo panner rule");
681
682         colors[Mono].fill = UIConfiguration::instance().color ("stereo panner mono fill");
683         colors[Mono].outline = UIConfiguration::instance().color ("stereo panner mono outline");
684         colors[Mono].text = UIConfiguration::instance().color ("stereo panner mono text");
685         colors[Mono].background = UIConfiguration::instance().color ("stereo panner mono bg");
686         colors[Mono].rule = UIConfiguration::instance().color ("stereo panner rule");
687
688         colors[Inverted].fill = UIConfiguration::instance().color_mod ("stereo panner inverted fill", "stereo panner inverted");
689         colors[Inverted].outline = UIConfiguration::instance().color ("stereo panner inverted outline");
690         colors[Inverted].text = UIConfiguration::instance().color ("stereo panner inverted text");
691         colors[Inverted].background = UIConfiguration::instance().color_mod ("stereo panner inverted bg", "stereo panner inverted bg");
692         colors[Inverted].rule = UIConfiguration::instance().color ("stereo panner rule");
693 }
694
695 void
696 StereoPanner::color_handler ()
697 {
698         set_colors ();
699         queue_draw ();
700 }
701
702 void
703 StereoPanner::bypass_handler ()
704 {
705         queue_draw ();
706 }
707
708 void
709 StereoPanner::pannable_handler ()
710 {
711         panvalue_connections.drop_connections();
712         position_control = _panner->pannable()->pan_azimuth_control;
713         width_control = _panner->pannable()->pan_width_control;
714         position_binder.set_controllable(position_control);
715         width_binder.set_controllable(width_control);
716
717         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
718         width_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
719         queue_draw ();
720 }
721
722 PannerEditor*
723 StereoPanner::editor ()
724 {
725         return new StereoPannerEditor (this);
726 }