Unconditionally save instant.xml on session-close
[ardour.git] / gtk2_ardour / stereo_panner.cc
1 /*
2  * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2010-2016 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2012-2017 Robin Gareus <robin@gareus.org>
5  * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <iostream>
23 #include <iomanip>
24 #include <cstring>
25 #include <cmath>
26
27 #include <gtkmm/window.h>
28 #include <pangomm/layout.h>
29
30 #include "pbd/compose.h"
31
32 #include "gtkmm2ext/gui_thread.h"
33 #include "gtkmm2ext/gtk_ui.h"
34 #include "gtkmm2ext/keyboard.h"
35 #include "gtkmm2ext/utils.h"
36 #include "gtkmm2ext/persistent_tooltip.h"
37
38 #include "ardour/pannable.h"
39 #include "ardour/panner.h"
40 #include "ardour/panner_shell.h"
41
42 #include "gtkmm2ext/colors.h"
43
44 #include "stereo_panner.h"
45 #include "stereo_panner_editor.h"
46 #include "rgb_macros.h"
47 #include "utils.h"
48 #include "ui_config.h"
49
50 #include "pbd/i18n.h"
51
52 using namespace std;
53 using namespace Gtk;
54 using namespace Gtkmm2ext;
55 using namespace ARDOUR_UI_UTILS;
56
57 using PBD::Controllable;
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 (UIConfiguration::instance().get_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         UIConfiguration::instance().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         uint32_t o, f, t, b, r;
152         State state;
153
154         width = get_width();
155         height = get_height ();
156
157         const int step_down = rint(height / 3.5);
158         const double corner_radius = 5.0 * UIConfiguration::instance().get_ui_scale();
159         const int lr_box_size = height - 2 * step_down;
160         const int pos_box_size = (int)(rint(step_down * .8)) | 1;
161         const int top_step = step_down - pos_box_size;
162
163         if (swidth == 0.0) {
164                 state = Mono;
165         } else if (swidth < 0.0) {
166                 state = Inverted;
167         } else {
168                 state = Normal;
169         }
170
171         o = colors[state].outline;
172         f = colors[state].fill;
173         t = colors[state].text;
174         b = colors[state].background;
175         r = colors[state].rule;
176
177         if (_panner_shell->bypassed()) {
178                 b  = 0x20202040;
179                 f  = 0x404040ff;
180                 o  = 0x606060ff;
181                 t  = 0x606060ff;
182                 r  = 0x606060ff;
183         }
184
185         if (_send_mode) {
186                 b = UIConfiguration::instance().color ("send bg");
187                 // b = rgba_from_style("SendStripBase",
188                 // UINT_RGBA_R(b), UINT_RGBA_G(b), UINT_RGBA_B(b), 255,
189                 // "fg");
190         }
191         /* background */
192
193         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
194         cairo_rectangle (context->cobj(), 0, 0, width, height);
195         context->fill_preserve ();
196         context->clip();
197
198         /* the usable width is reduced from the real width, because we need space for
199            the two halves of LR boxes that will extend past the actual left/right
200            positions (indicated by the vertical line segment above them).
201         */
202
203         double usable_width = width - lr_box_size;
204
205         /* compute the centers of the L/R boxes based on the current stereo width */
206
207         if (fmod (usable_width,2.0) == 0) {
208                 /* even width, but we need odd, so that there is an exact center.
209                    So, offset cairo by 1, and reduce effective width by 1
210                 */
211                 usable_width -= 1.0;
212                 context->translate (1.0, 0.0);
213         }
214
215         const double half_lr_box = lr_box_size/2.0;
216         const double center = rint(half_lr_box + (usable_width * pos));
217         const double pan_spread = rint((fswidth * (usable_width-1.0))/2.0);
218         const double left  = center - pan_spread;
219         const double right = center + pan_spread;
220
221         /* center line */
222         context->set_line_width (1.0);
223         context->move_to ((usable_width + lr_box_size)/2.0, 0);
224         context->rel_line_to (0, height);
225         context->set_source_rgba (UINT_RGBA_R_FLT(r), UINT_RGBA_G_FLT(r), UINT_RGBA_B_FLT(r), UINT_RGBA_A_FLT(r));
226         context->stroke ();
227
228         /* compute & draw the line through the box */
229         context->set_line_width (2);
230         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
231         context->move_to (left,  top_step + (pos_box_size/2.0) + step_down + 1.0);
232         context->line_to (left,  top_step + (pos_box_size/2.0));
233         context->line_to (right, top_step + (pos_box_size/2.0));
234         context->line_to (right, top_step + (pos_box_size/2.0) + step_down + 1.0);
235         context->stroke ();
236
237         context->set_line_width (1.0);
238
239         /* left box */
240         if (state != Mono) {
241                 rounded_rectangle (context, left - half_lr_box,
242                                 half_lr_box+step_down,
243                                 lr_box_size, lr_box_size, corner_radius);
244                 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
245                 context->fill_preserve();
246                 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
247                 context->stroke();
248
249                 /* add text */
250                 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
251                 if (swidth < 0.0) {
252                         layout->set_text (S_("Panner|R"));
253                 } else {
254                         layout->set_text (S_("Panner|L"));
255                 }
256                 layout->get_pixel_size(tw, th);
257                 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
258                 pango_cairo_show_layout (context->cobj(), layout->gobj());
259         }
260
261         /* right box */
262         rounded_rectangle (context, right - half_lr_box,
263                         half_lr_box+step_down,
264                         lr_box_size, lr_box_size, corner_radius);
265         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
266         context->fill_preserve();
267         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
268         context->stroke();
269
270         /* add text */
271         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
272
273         if (state == Mono) {
274                 layout->set_text (S_("Panner|M"));
275         } else {
276                 if (swidth < 0.0) {
277                         layout->set_text (S_("Panner|L"));
278                 } else {
279                         layout->set_text (S_("Panner|R"));
280                 }
281         }
282         layout->get_pixel_size(tw, th);
283         context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
284         pango_cairo_show_layout (context->cobj(), layout->gobj());
285
286         /* draw the central box */
287         context->set_line_width (2.0);
288         context->move_to (center + (pos_box_size/2.0), top_step); /* top right */
289         context->rel_line_to (0.0, pos_box_size); /* lower right */
290         context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
291         context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
292         context->rel_line_to (0.0, -pos_box_size); /* upper left */
293         context->close_path ();
294
295         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
296         context->stroke_preserve ();
297         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
298         context->fill ();
299
300         return true;
301 }
302
303 bool
304 StereoPanner::on_button_press_event (GdkEventButton* ev)
305 {
306         if (PannerInterface::on_button_press_event (ev)) {
307                 return true;
308         }
309
310         if (_panner_shell->bypassed()) {
311                 return true;
312         }
313
314         drag_start_x = ev->x;
315         last_drag_x = ev->x;
316
317         dragging_position = false;
318         dragging_left = false;
319         dragging_right = false;
320         _dragging = false;
321         _tooltip.target_stop_drag ();
322         accumulated_delta = 0;
323         detented = false;
324
325         /* Let the binding proxies get first crack at the press event
326          */
327
328         if (ev->y < 20) {
329                 if (position_binder.button_press_handler (ev)) {
330                         return true;
331                 }
332         } else {
333                 if (width_binder.button_press_handler (ev)) {
334                         return true;
335                 }
336         }
337
338         if (ev->button != 1) {
339                 return false;
340         }
341
342         if (ev->type == GDK_2BUTTON_PRESS) {
343                 int width = get_width();
344
345                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
346                         /* handled by button release */
347                         return true;
348                 }
349
350                 if (ev->y < 20) {
351
352                         /* upper section: adjusts position, constrained by width */
353
354                         const double w = fabs (width_control->get_value ());
355                         const double max_pos = 1.0 - (w/2.0);
356                         const double min_pos = w/2.0;
357
358                         if (ev->x <= width/3) {
359                                 /* left side dbl click */
360                                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
361                                         /* 2ndary-double click on left, collapse to hard left */
362                                         width_control->set_value (0, Controllable::NoGroup);
363                                         position_control->set_value (0, Controllable::NoGroup);
364                                 } else {
365                                         position_control->set_value (min_pos, Controllable::NoGroup);
366                                 }
367                         } else if (ev->x > 2*width/3) {
368                                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
369                                         /* 2ndary-double click on right, collapse to hard right */
370                                         width_control->set_value (0, Controllable::NoGroup);
371                                         position_control->set_value (1.0, Controllable::NoGroup);
372                                 } else {
373                                         position_control->set_value (max_pos, Controllable::NoGroup);
374                                 }
375                         } else {
376                                 position_control->set_value (0.5, Controllable::NoGroup);
377                         }
378
379                 } else {
380
381                         /* lower section: adjusts width, constrained by position */
382
383                         const double p = position_control->get_value ();
384                         const double max_width = 2.0 * min ((1.0 - p), p);
385
386                         if (ev->x <= width/3) {
387                                 /* left side dbl click */
388                                 width_control->set_value (max_width, Controllable::NoGroup); // reset width to 100%
389                         } else if (ev->x > 2*width/3) {
390                                 /* right side dbl click */
391                                 width_control->set_value (-max_width, Controllable::NoGroup); // reset width to inverted 100%
392                         } else {
393                                 /* center dbl click */
394                                 width_control->set_value (0, Controllable::NoGroup); // collapse width to 0%
395                         }
396                 }
397
398                 _dragging = false;
399                 _tooltip.target_stop_drag ();
400
401         } else if (ev->type == GDK_BUTTON_PRESS) {
402
403                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
404                         /* handled by button release */
405                         return true;
406                 }
407
408                 if (ev->y < 20) {
409                         /* top section of widget is for position drags */
410                         dragging_position = true;
411                         StartPositionGesture ();
412                 } else {
413                         /* lower section is for dragging width */
414
415                         double pos = position_control->get_value (); /* 0..1 */
416                         double swidth = width_control->get_value (); /* -1..+1 */
417                         double fswidth = fabs (swidth);
418                         const int lr_box_size = get_height() - 2 * rint(get_height() / 3.5);
419                         int usable_width = get_width() - lr_box_size;
420                         double center = (lr_box_size/2.0) + (usable_width * pos);
421                         int left = lrint (center - (fswidth * usable_width / 2.0)); // center of leftmost box
422                         int right = lrint (center +  (fswidth * usable_width / 2.0)); // center of rightmost box
423                         const int half_box = lr_box_size/2;
424
425                         if (ev->x >= (left - half_box) && ev->x < (left + half_box)) {
426                                 if (swidth < 0.0) {
427                                         dragging_right = true;
428                                 } else {
429                                         dragging_left = true;
430                                 }
431                         } else if (ev->x >= (right - half_box) && ev->x < (right + half_box)) {
432                                 if (swidth < 0.0) {
433                                         dragging_left = true;
434                                 } else {
435                                         dragging_right = true;
436                                 }
437                         }
438                         StartWidthGesture ();
439                 }
440
441                 _dragging = true;
442                 _tooltip.target_start_drag ();
443         }
444
445         return true;
446 }
447
448 bool
449 StereoPanner::on_button_release_event (GdkEventButton* ev)
450 {
451         if (PannerInterface::on_button_release_event (ev)) {
452                 return true;
453         }
454
455         if (ev->button != 1) {
456                 return false;
457         }
458
459         if (_panner_shell->bypassed()) {
460                 return false;
461         }
462
463         bool const dp = dragging_position;
464
465         _dragging = false;
466         _tooltip.target_stop_drag ();
467         dragging_position = false;
468         dragging_left = false;
469         dragging_right = false;
470         accumulated_delta = 0;
471         detented = false;
472
473         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
474                 _panner->reset ();
475         } else {
476                 if (dp) {
477                         StopPositionGesture ();
478                 } else {
479                         StopWidthGesture ();
480                 }
481         }
482
483         return true;
484 }
485
486 bool
487 StereoPanner::on_scroll_event (GdkEventScroll* ev)
488 {
489         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
490         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
491         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
492         double step;
493
494         if (_panner_shell->bypassed()) {
495                 return false;
496         }
497
498         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
499                 step = one_degree;
500         } else {
501                 step = one_degree * 5.0;
502         }
503
504         switch (ev->direction) {
505         case GDK_SCROLL_LEFT:
506                 wv += step;
507                 width_control->set_value (wv, Controllable::NoGroup);
508                 break;
509         case GDK_SCROLL_UP:
510                 pv -= step;
511                 position_control->set_value (pv, Controllable::NoGroup);
512                 break;
513         case GDK_SCROLL_RIGHT:
514                 wv -= step;
515                 width_control->set_value (wv, Controllable::NoGroup);
516                 break;
517         case GDK_SCROLL_DOWN:
518                 pv += step;
519                 position_control->set_value (pv, Controllable::NoGroup);
520                 break;
521         }
522
523         return true;
524 }
525
526 bool
527 StereoPanner::on_motion_notify_event (GdkEventMotion* ev)
528 {
529         if (_panner_shell->bypassed()) {
530                 _dragging = false;
531         }
532         if (!_dragging) {
533                 return false;
534         }
535
536         const int lr_box_size = get_height() - 2 * rint(get_height() / 3.5);
537         int usable_width = get_width() - lr_box_size;
538         double delta = (ev->x - last_drag_x) / (double) usable_width;
539         double current_width = width_control->get_value ();
540
541         if (dragging_left) {
542                 delta = -delta;
543         }
544
545         if (dragging_left || dragging_right) {
546
547                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
548
549                         /* change width and position in a way that keeps the
550                          * other side in the same place
551                          */
552
553                         _panner->freeze ();
554
555                         double pv = position_control->get_value();
556
557                         if (dragging_left) {
558                                 position_control->set_value (pv - delta, Controllable::NoGroup);
559                         } else {
560                                 position_control->set_value (pv + delta, Controllable::NoGroup);
561                         }
562
563                         if (delta > 0.0) {
564                                 /* delta is positive, so we're about to
565                                    increase the width. But we need to increase it
566                                    by twice the required value so that the
567                                    other side remains in place when we set
568                                    the position as well.
569                                 */
570                                 width_control->set_value (current_width + (delta * 2.0), Controllable::NoGroup);
571                         } else {
572                                 width_control->set_value (current_width + delta, Controllable::NoGroup);
573                         }
574
575                         _panner->thaw ();
576
577                 } else {
578
579                         /* maintain position as invariant as we change the width */
580
581                         /* create a detent close to the center */
582
583                         if (!detented && fabs (current_width) < 0.02) {
584                                 detented = true;
585                                 /* snap to zero */
586                                 width_control->set_value (0, Controllable::NoGroup);
587                         }
588
589                         if (detented) {
590
591                                 accumulated_delta += delta;
592
593                                 /* have we pulled far enough to escape ? */
594
595                                 if (fabs (accumulated_delta) >= 0.025) {
596                                         width_control->set_value (current_width + accumulated_delta, Controllable::NoGroup);
597                                         detented = false;
598                                         accumulated_delta = false;
599                                 }
600
601                         } else {
602                                 /* width needs to change by 2 * delta because both L & R move */
603                                 width_control->set_value (current_width + (delta * 2.0), Controllable::NoGroup);
604                         }
605                 }
606
607         } else if (dragging_position) {
608
609                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
610                 position_control->set_value (pv + delta, Controllable::NoGroup);
611         }
612
613         last_drag_x = ev->x;
614         return true;
615 }
616
617 bool
618 StereoPanner::on_key_press_event (GdkEventKey* ev)
619 {
620         double one_degree = 1.0/180.0;
621         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
622         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
623         double step;
624
625         if (_panner_shell->bypassed()) {
626                 return false;
627         }
628
629         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
630                 step = one_degree;
631         } else {
632                 step = one_degree * 5.0;
633         }
634
635         /* up/down control width because we consider pan position more "important"
636            (and thus having higher "sense" priority) than width.
637         */
638
639         switch (ev->keyval) {
640         case GDK_Up:
641                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
642                         width_control->set_value (1.0, Controllable::NoGroup);
643                 } else {
644                         width_control->set_value (wv + step, Controllable::NoGroup);
645                 }
646                 break;
647         case GDK_Down:
648                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
649                         width_control->set_value (-1.0, Controllable::NoGroup);
650                 } else {
651                         width_control->set_value (wv - step, Controllable::NoGroup);
652                 }
653                 break;
654
655         case GDK_Left:
656                 pv -= step;
657                 position_control->set_value (pv, Controllable::NoGroup);
658                 break;
659         case GDK_Right:
660                 pv += step;
661                 position_control->set_value (pv, Controllable::NoGroup);
662                 break;
663         case GDK_0:
664         case GDK_KP_0:
665                 width_control->set_value (0.0, Controllable::NoGroup);
666                 break;
667
668         default:
669                 return false;
670         }
671
672         return true;
673 }
674
675 void
676 StereoPanner::set_colors ()
677 {
678         colors[Normal].fill = UIConfiguration::instance().color ("stereo panner fill");
679         colors[Normal].outline = UIConfiguration::instance().color ("stereo panner outline");
680         colors[Normal].text = UIConfiguration::instance().color ("stereo panner text");
681         colors[Normal].background = UIConfiguration::instance().color ("stereo panner bg");
682         colors[Normal].rule = UIConfiguration::instance().color ("stereo panner rule");
683
684         colors[Mono].fill = UIConfiguration::instance().color ("stereo panner mono fill");
685         colors[Mono].outline = UIConfiguration::instance().color ("stereo panner mono outline");
686         colors[Mono].text = UIConfiguration::instance().color ("stereo panner mono text");
687         colors[Mono].background = UIConfiguration::instance().color ("stereo panner mono bg");
688         colors[Mono].rule = UIConfiguration::instance().color ("stereo panner rule");
689
690         colors[Inverted].fill = UIConfiguration::instance().color ("stereo panner inverted fill");
691         colors[Inverted].outline = UIConfiguration::instance().color ("stereo panner inverted outline");
692         colors[Inverted].text = UIConfiguration::instance().color ("stereo panner inverted text");
693         colors[Inverted].background = UIConfiguration::instance().color ("stereo panner inverted bg");
694         colors[Inverted].rule = UIConfiguration::instance().color ("stereo panner rule");
695 }
696
697 void
698 StereoPanner::color_handler ()
699 {
700         set_colors ();
701         queue_draw ();
702 }
703
704 void
705 StereoPanner::bypass_handler ()
706 {
707         queue_draw ();
708 }
709
710 void
711 StereoPanner::pannable_handler ()
712 {
713         panvalue_connections.drop_connections();
714         position_control = _panner->pannable()->pan_azimuth_control;
715         width_control = _panner->pannable()->pan_width_control;
716         position_binder.set_controllable(position_control);
717         width_binder.set_controllable(width_control);
718
719         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
720         width_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
721         queue_draw ();
722 }
723
724 PannerEditor*
725 StereoPanner::editor ()
726 {
727         return new StereoPannerEditor (this);
728 }