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