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