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