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