Merge branch 'master' into windows
[ardour.git] / gtk2_ardour / panner2d.cc
1 /*
2     Copyright (C) 2002 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
20 #include <cmath>
21 #include <climits>
22 #include <string.h>
23
24 #include <cairo.h>
25 #include <gtkmm/menu.h>
26
27 #include "gtkmm2ext/gtk_ui.h"
28
29 #include "pbd/error.h"
30 #include "pbd/cartesian.h"
31 #include "ardour/panner.h"
32 #include "ardour/panner_shell.h"
33 #include "ardour/pannable.h"
34 #include "ardour/speakers.h"
35
36 #include "panner2d.h"
37 #include "keyboard.h"
38 #include "gui_thread.h"
39 #include "utils.h"
40 #include "public_editor.h"
41
42 #include "i18n.h"
43
44 using namespace std;
45 using namespace Gtk;
46 using namespace ARDOUR;
47 using namespace PBD;
48 using Gtkmm2ext::Keyboard;
49
50 static const int large_size_threshold = 100;
51 static const int large_border_width = 25;
52 static const int small_border_width = 8;
53
54 Panner2d::Target::Target (const AngularVector& a, const char *txt)
55         : position (a)
56         , text (txt)
57         , _selected (false)
58 {
59 }
60
61 Panner2d::Target::~Target ()
62 {
63 }
64
65 void
66 Panner2d::Target::set_text (const char* txt)
67 {
68         text = txt;
69 }
70
71 Panner2d::Panner2d (boost::shared_ptr<PannerShell> p, int32_t h)
72         : panner_shell (p)
73         , position (AngularVector (0.0, 0.0), "")
74         , width (0)
75         , height (h)
76         , last_width (0)
77 {
78         panner_shell->Changed.connect (connections, invalidator (*this), boost::bind (&Panner2d::handle_state_change, this), gui_context());
79
80         panner_shell->pannable()->pan_azimuth_control->Changed.connect (connections, invalidator(*this), boost::bind (&Panner2d::handle_position_change, this), gui_context());
81         panner_shell->pannable()->pan_width_control->Changed.connect (connections, invalidator(*this), boost::bind (&Panner2d::handle_position_change, this), gui_context());
82
83         drag_target = 0;
84         set_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
85
86         handle_position_change ();
87 }
88
89 Panner2d::~Panner2d()
90 {
91         for (Targets::iterator i = speakers.begin(); i != speakers.end(); ++i) {
92                 delete *i;
93         }
94 }
95
96 void
97 Panner2d::reset (uint32_t n_inputs)
98 {
99         uint32_t nouts = panner_shell->panner()->out().n_audio();
100
101         /* signals */
102
103         while (signals.size() < n_inputs) {
104                 add_signal ("", AngularVector());
105         }
106
107         if (signals.size() > n_inputs) {
108                 for (uint32_t i = signals.size(); i < n_inputs; ++i) {
109                         delete signals[i];
110                 }
111
112                 signals.resize (n_inputs);
113         }
114
115         label_signals ();
116
117         for (uint32_t i = 0; i < n_inputs; ++i) {
118                 signals[i]->position = panner_shell->panner()->signal_position (i);
119         }
120
121         /* add all outputs */
122
123         while (speakers.size() < nouts) {
124                 add_speaker (AngularVector());
125         }
126
127         if (speakers.size() > nouts) {
128                 for (uint32_t i = nouts; i < speakers.size(); ++i) {
129                         delete speakers[i];
130                 }
131
132                 speakers.resize (nouts);
133         }
134
135         for (Targets::iterator x = speakers.begin(); x != speakers.end(); ++x) {
136                 (*x)->visible = false;
137         }
138
139         vector<Speaker>& the_speakers (panner_shell->panner()->get_speakers()->speakers());
140
141         for (uint32_t n = 0; n < nouts; ++n) {
142                 char buf[16];
143
144                 snprintf (buf, sizeof (buf), "%d", n+1);
145                 speakers[n]->set_text (buf);
146                 speakers[n]->position = the_speakers[n].angles();
147                 speakers[n]->visible = true;
148         }
149
150         queue_draw ();
151 }
152
153 void
154 Panner2d::on_size_allocate (Gtk::Allocation& alloc)
155 {
156         width = alloc.get_width();
157         height = alloc.get_height();
158
159         if (height > large_size_threshold) {
160                 border = large_border_width;
161         } else {
162                 border = small_border_width;
163         }
164
165         radius = min (width, height);
166         radius -= border;
167         radius /= 2;
168
169         hoffset = max ((double) (width - height), border);
170         voffset = max ((double) (height - width), border);
171
172         hoffset /= 2.0;
173         voffset /= 2.0;
174
175         DrawingArea::on_size_allocate (alloc);
176 }
177
178 int
179 Panner2d::add_signal (const char* text, const AngularVector& a)
180 {
181         Target* signal = new Target (a, text);
182         signals.push_back (signal);
183         signal->visible = true;
184
185         return 0;
186 }
187
188 int
189 Panner2d::add_speaker (const AngularVector& a)
190 {
191         Target* speaker = new Target (a, "");
192         speakers.push_back (speaker);
193         speaker->visible = true;
194         queue_draw ();
195
196         return speakers.size() - 1;
197 }
198
199 void
200 Panner2d::handle_state_change ()
201 {
202         queue_draw ();
203 }
204
205 void
206 Panner2d::label_signals ()
207 {
208         double w = panner_shell->pannable()->pan_width_control->get_value();
209         uint32_t sz = signals.size();
210
211         switch (sz) {
212         case 0:
213                 break;
214
215         case 1:
216                 signals[0]->set_text ("");
217                 break;
218
219         case 2:
220                 if (w  >= 0.0) {
221                         signals[0]->set_text ("R");
222                         signals[1]->set_text ("L");
223                 } else {
224                         signals[0]->set_text ("L");
225                         signals[1]->set_text ("R");
226                 }
227                 break;
228
229         default:
230                 for (uint32_t i = 0; i < sz; ++i) {
231                         char buf[64];
232                         if (w >= 0.0) {
233                                 snprintf (buf, sizeof (buf), "%" PRIu32, i + 1);
234                         } else {
235                                 snprintf (buf, sizeof (buf), "%" PRIu32, sz - i);
236                         }
237                         signals[i]->set_text (buf);
238                 }
239                 break;
240         }
241 }
242
243 void
244 Panner2d::handle_position_change ()
245 {
246         uint32_t n;
247         double w = panner_shell->pannable()->pan_width_control->get_value();
248
249         position.position = AngularVector (panner_shell->pannable()->pan_azimuth_control->get_value() * 360.0, 0.0);
250
251         for (uint32_t i = 0; i < signals.size(); ++i) {
252                 signals[i]->position = panner_shell->panner()->signal_position (i);
253         }
254
255         if (w * last_width <= 0) {
256                 /* changed sign */
257                 label_signals ();
258         }
259
260         last_width = w;
261
262         vector<Speaker>& the_speakers (panner_shell->panner()->get_speakers()->speakers());
263
264         for (n = 0; n < speakers.size(); ++n) {
265                 speakers[n]->position = the_speakers[n].angles();
266         }
267
268         queue_draw ();
269 }
270
271 void
272 Panner2d::move_signal (int which, const AngularVector& a)
273 {
274         if (which >= int (speakers.size())) {
275                 return;
276         }
277
278         speakers[which]->position = a;
279         queue_draw ();
280 }
281
282 Panner2d::Target *
283 Panner2d::find_closest_object (gdouble x, gdouble y, bool& is_signal)
284 {
285         Target *closest = 0;
286         Target *candidate;
287         float distance;
288         float best_distance = FLT_MAX;
289         CartesianVector c;
290
291         /* start with the position itself
292          */
293
294         position.position.cartesian (c);
295         cart_to_gtk (c);
296         best_distance = sqrt ((c.x - x) * (c.x - x) +
297                          (c.y - y) * (c.y - y));
298         closest = &position;
299
300         for (Targets::const_iterator i = signals.begin(); i != signals.end(); ++i) {
301                 candidate = *i;
302
303                 candidate->position.cartesian (c);
304                 cart_to_gtk (c);
305
306                 distance = sqrt ((c.x - x) * (c.x - x) +
307                                  (c.y - y) * (c.y - y));
308
309                 if (distance < best_distance) {
310                         closest = candidate;
311                         best_distance = distance;
312                 }
313         }
314
315         is_signal = true;
316
317         if (height > large_size_threshold) {
318                 /* "big" */
319                 if (best_distance > 30) { // arbitrary
320                         closest = 0;
321                 }
322         } else {
323                 /* "small" */
324                 if (best_distance > 10) { // arbitrary
325                         closest = 0;
326                 }
327         }
328
329         /* if we didn't find a signal close by, check the speakers */
330
331         if (!closest) {
332                 for (Targets::const_iterator i = speakers.begin(); i != speakers.end(); ++i) {
333                         candidate = *i;
334
335                         candidate->position.cartesian (c);
336                         cart_to_gtk (c);
337
338                         distance = sqrt ((c.x - x) * (c.x - x) +
339                                          (c.y - y) * (c.y - y));
340
341                         if (distance < best_distance) {
342                                 closest = candidate;
343                                 best_distance = distance;
344                         }
345                 }
346
347                 if (height > large_size_threshold) {
348                         /* "big" */
349                         if (best_distance < 30) { // arbitrary
350                                 is_signal = false;
351                         } else {
352                                 closest = 0;
353                         }
354                 } else {
355                         /* "small" */
356                         if (best_distance < 10) { // arbitrary
357                                 is_signal = false;
358                         } else {
359                                 closest = 0;
360                         }
361                 }
362         }
363
364         return closest;
365 }
366
367 bool
368 Panner2d::on_motion_notify_event (GdkEventMotion *ev)
369 {
370         gint x, y;
371         GdkModifierType state;
372
373         if (ev->is_hint) {
374                 gdk_window_get_pointer (ev->window, &x, &y, &state);
375         } else {
376                 x = (int) floor (ev->x);
377                 y = (int) floor (ev->y);
378                 state = (GdkModifierType) ev->state;
379         }
380
381         if (ev->state & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK)) {
382                 did_move = true;
383         }
384
385         return handle_motion (x, y, state);
386 }
387
388 bool
389 Panner2d::on_expose_event (GdkEventExpose *event)
390 {
391         CartesianVector c;
392         cairo_t* cr;
393         bool small_size = (height <= large_size_threshold);
394         const double diameter = radius*2.0;
395
396         cr = gdk_cairo_create (get_window()->gobj());
397
398         /* background */
399
400         cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height);
401         if (!panner_shell->bypassed()) {
402                 cairo_set_source_rgba (cr, 0.1, 0.1, 0.1, 1.0);
403         } else {
404                 cairo_set_source_rgba (cr, 0.1, 0.1, 0.1, 0.2);
405         }
406         cairo_fill_preserve (cr);
407         cairo_clip (cr);
408
409         /* offset to give us some border */
410
411         cairo_translate (cr, hoffset, voffset);
412
413         cairo_set_line_width (cr, 1.0);
414
415         /* horizontal line of "crosshairs" */
416
417         cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 1.0);
418         cairo_move_to (cr, 0.0, radius);
419         cairo_line_to (cr, diameter, radius);
420         cairo_stroke (cr);
421
422         /* vertical line of "crosshairs" */
423
424         cairo_move_to (cr, radius, 0);
425         cairo_line_to (cr, radius, diameter);
426         cairo_stroke (cr);
427
428         /* the circle on which signals live */
429
430         cairo_set_line_width (cr, 2.0);
431         cairo_set_source_rgba (cr, 0.517, 0.772, 0.882, 1.0);
432         cairo_arc (cr, radius, radius, radius, 0.0, 2.0 * M_PI);
433         cairo_stroke (cr);
434
435         /* 3 other circles of smaller diameter circle on which signals live */
436
437         cairo_set_line_width (cr, 1.0);
438         cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 1.0);
439         cairo_arc (cr, radius, radius, radius * 0.75, 0, 2.0 * M_PI);
440         cairo_stroke (cr);
441         cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 0.85);
442         cairo_arc (cr, radius, radius, radius * 0.50, 0, 2.0 * M_PI);
443         cairo_stroke (cr);
444         cairo_arc (cr, radius, radius, radius * 0.25, 0, 2.0 * M_PI);
445         cairo_stroke (cr);
446
447         if (signals.size() > 1) {
448                 /* arc to show "diffusion" */
449
450                 double width_angle = fabs (panner_shell->pannable()->pan_width_control->get_value()) * 2 * M_PI;
451                 double position_angle = (2 * M_PI) - panner_shell->pannable()->pan_azimuth_control->get_value() * 2 * M_PI;
452
453                 cairo_save (cr);
454                 cairo_translate (cr, radius, radius);
455                 cairo_rotate (cr, position_angle - (width_angle/2.0));
456                 cairo_move_to (cr, 0, 0);
457                 cairo_arc_negative (cr, 0, 0, radius, width_angle, 0.0);
458                 cairo_close_path (cr);
459                 if (panner_shell->pannable()->pan_width_control->get_value() >= 0.0) {
460                         /* normal width */
461                         cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 0.45);
462                 } else {
463                         /* inverse width */
464                         cairo_set_source_rgba (cr, 1.0, 0.419, 0.419, 0.45);
465                 }
466                 cairo_fill (cr);
467                 cairo_restore (cr);
468         }
469
470         if (!panner_shell->bypassed()) {
471
472                 double arc_radius;
473
474                 cairo_select_font_face (cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
475
476                 if (small_size) {
477                         arc_radius = 4.0;
478                 } else {
479                         cairo_set_font_size (cr, 10);
480                         arc_radius = 12.0;
481                 }
482
483                 /* signals */
484
485                 if (signals.size() > 1) {
486                         for (Targets::iterator i = signals.begin(); i != signals.end(); ++i) {
487                                 Target* signal = *i;
488
489                                 if (signal->visible) {
490
491                                         signal->position.cartesian (c);
492                                         cart_to_gtk (c);
493
494                                         cairo_new_path (cr);
495                                         cairo_arc (cr, c.x, c.y, arc_radius, 0, 2.0 * M_PI);
496                                         cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 0.85);
497                                         cairo_fill_preserve (cr);
498                                         cairo_set_source_rgba (cr, 0.517, 0.772, 0.882, 1.0);
499                                         cairo_stroke (cr);
500
501                                         if (!small_size && !signal->text.empty()) {
502                                                 cairo_set_source_rgb (cr, 0.517, 0.772, 0.882);
503                                                 /* the +/- adjustments are a hack to try to center the text in the circle */
504                                                 if (small_size) {
505                                                         cairo_move_to (cr, c.x - 1, c.y + 1);
506                                                 } else {
507                                                         cairo_move_to (cr, c.x - 4, c.y + 4);
508                                                 }
509                                                 cairo_show_text (cr, signal->text.c_str());
510                                         }
511                                 }
512                         }
513                 }
514
515                 /* speakers */
516
517                 int n = 0;
518
519                 for (Targets::iterator i = speakers.begin(); i != speakers.end(); ++i) {
520                         Target *speaker = *i;
521                         char buf[256];
522                         ++n;
523
524                         if (speaker->visible) {
525
526                                 CartesianVector c;
527
528                                 speaker->position.cartesian (c);
529                                 cart_to_gtk (c);
530
531                                 snprintf (buf, sizeof (buf), "%d", n);
532
533                                 /* stroke out a speaker shape */
534
535                                 cairo_move_to (cr, c.x, c.y);
536                                 cairo_save (cr);
537                                 cairo_rotate (cr, -(speaker->position.azi/360.0) * (2.0 * M_PI));
538                                 if (small_size) {
539                                         cairo_scale (cr, 0.8, 0.8);
540                                 } else {
541                                         cairo_scale (cr, 1.2, 1.2);
542                                 }
543                                 cairo_rel_line_to (cr, 4, -2);
544                                 cairo_rel_line_to (cr, 0, -7);
545                                 cairo_rel_line_to (cr, 5, +5);
546                                 cairo_rel_line_to (cr, 5, 0);
547                                 cairo_rel_line_to (cr, 0, 5);
548                                 cairo_rel_line_to (cr, -5, 0);
549                                 cairo_rel_line_to (cr, -5, +5);
550                                 cairo_rel_line_to (cr, 0, -7);
551                                 cairo_close_path (cr);
552                                 cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 1.0);
553                                 cairo_fill (cr);
554                                 cairo_restore (cr);
555
556                                 if (!small_size) {
557                                         cairo_set_font_size (cr, 16);
558
559                                         /* move the text in just a bit */
560
561                                         AngularVector textpos (speaker->position.azi, speaker->position.ele, 0.85);
562                                         textpos.cartesian (c);
563                                         cart_to_gtk (c);
564                                         cairo_move_to (cr, c.x, c.y);
565                                         cairo_show_text (cr, buf);
566                                 }
567
568                         }
569                 }
570
571                 /* draw position */
572
573                 position.position.cartesian (c);
574                 cart_to_gtk (c);
575
576                 cairo_new_path (cr);
577                 cairo_arc (cr, c.x, c.y, arc_radius, 0, 2.0 * M_PI);
578                 cairo_set_source_rgba (cr, 1.0, 0.419, 0.419, 0.85);
579                 cairo_fill_preserve (cr);
580                 cairo_set_source_rgba (cr, 1.0, 0.905, 0.905, 0.85);
581                 cairo_stroke (cr);
582         }
583
584         cairo_destroy (cr);
585
586         return true;
587 }
588
589 bool
590 Panner2d::on_button_press_event (GdkEventButton *ev)
591 {
592         GdkModifierType state;
593         int x;
594         int y;
595         bool is_signal;
596
597         if (ev->type == GDK_2BUTTON_PRESS && ev->button == 1) {
598                 return false;
599         }
600
601         did_move = false;
602
603         switch (ev->button) {
604         case 1:
605         case 2:
606                 x = ev->x - border;
607                 y = ev->y - border;
608
609                 if ((drag_target = find_closest_object (x, y, is_signal)) != 0) {
610                         if (!is_signal) {
611                                 panner_shell->panner()->set_position (drag_target->position.azi/360.0);
612                                 drag_target = 0;
613                         } else {
614                                 drag_target->set_selected (true);
615                         }
616                 }
617
618                 drag_x = ev->x;
619                 drag_y = ev->y;
620                 state = (GdkModifierType) ev->state;
621
622                 return handle_motion (drag_x, drag_y, state);
623                 break;
624
625         default:
626                 break;
627         }
628
629         return false;
630 }
631
632 bool
633 Panner2d::on_button_release_event (GdkEventButton *ev)
634 {
635         gint x, y;
636         GdkModifierType state;
637         bool ret = false;
638
639         switch (ev->button) {
640         case 1:
641                 x = (int) floor (ev->x);
642                 y = (int) floor (ev->y);
643                 state = (GdkModifierType) ev->state;
644                 ret = handle_motion (x, y, state);
645                 drag_target = 0;
646                 break;
647
648         case 2:
649                 x = (int) floor (ev->x);
650                 y = (int) floor (ev->y);
651                 state = (GdkModifierType) ev->state;
652
653                 if (Keyboard::modifier_state_contains (state, Keyboard::TertiaryModifier)) {
654                         toggle_bypass ();
655                         ret = true;
656                 } else {
657                         ret = handle_motion (x, y, state);
658                 }
659
660                 drag_target = 0;
661                 break;
662
663         case 3:
664                 break;
665
666         }
667
668         return ret;
669 }
670
671 gint
672 Panner2d::handle_motion (gint evx, gint evy, GdkModifierType state)
673 {
674         if (drag_target == 0) {
675                 return false;
676         }
677
678         if ((state & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK)) == 0) {
679                 return false;
680         }
681
682
683         if (state & GDK_BUTTON1_MASK && !(state & GDK_BUTTON2_MASK)) {
684                 CartesianVector c;
685                 bool need_move = false;
686
687                 drag_target->position.cartesian (c);
688                 cart_to_gtk (c);
689
690                 if ((evx != c.x) || (evy != c.y)) {
691                         need_move = true;
692                 }
693
694                 if (need_move) {
695                         CartesianVector cp (evx, evy, 0.0);
696                         AngularVector av;
697
698                         /* canonicalize position and then clamp to the circle */
699
700                         gtk_to_cart (cp);
701                         clamp_to_circle (cp.x, cp.y);
702
703                         /* generate an angular representation of the current mouse position */
704
705                         cp.angular (av);
706
707                         if (drag_target == &position) {
708                                 double degree_fract = av.azi / 360.0;
709                                 panner_shell->panner()->set_position (degree_fract);
710                         }
711                 }
712         }
713
714         return true;
715 }
716
717 bool
718 Panner2d::on_scroll_event (GdkEventScroll* ev)
719 {
720         switch (ev->direction) {
721         case GDK_SCROLL_UP:
722         case GDK_SCROLL_RIGHT:
723                 panner_shell->panner()->set_position (panner_shell->pannable()->pan_azimuth_control->get_value() - 1.0/360.0);
724                 break;
725
726         case GDK_SCROLL_DOWN:
727         case GDK_SCROLL_LEFT:
728                 panner_shell->panner()->set_position (panner_shell->pannable()->pan_azimuth_control->get_value() + 1.0/360.0);
729                 break;
730         }
731         return true;
732 }
733
734 void
735 Panner2d::cart_to_gtk (CartesianVector& c) const
736 {
737         /* cartesian coordinate space:
738               center = 0.0
739               dimension = 2.0 * 2.0
740               increasing y moves up
741               so max values along each axis are -1..+1
742
743            GTK uses a coordinate space that is:
744               top left = 0.0
745               dimension = (radius*2.0) * (radius*2.0)
746               increasing y moves down
747         */
748         const double diameter = radius*2.0;
749
750         c.x = diameter * ((c.x + 1.0) / 2.0);
751         /* extra subtraction inverts the y-axis to match "increasing y moves down" */
752         c.y = diameter - (diameter * ((c.y + 1.0) / 2.0));
753 }
754
755 void
756 Panner2d::gtk_to_cart (CartesianVector& c) const
757 {
758         const double diameter = radius*2.0;
759         c.x = ((c.x / diameter) * 2.0) - 1.0;
760         c.y = (((diameter - c.y) / diameter) * 2.0) - 1.0;
761 }
762
763 void
764 Panner2d::clamp_to_circle (double& x, double& y)
765 {
766         double azi, ele;
767         double z = 0.0;
768         double l;
769
770         PBD::cartesian_to_spherical (x, y, z, azi, ele, l);
771         PBD::spherical_to_cartesian (azi, ele, 1.0, x, y, z);
772 }
773
774 void
775 Panner2d::toggle_bypass ()
776 {
777         panner_shell->set_bypassed (!panner_shell->bypassed());
778 }
779
780 Panner2dWindow::Panner2dWindow (boost::shared_ptr<PannerShell> p, int32_t h, uint32_t inputs)
781         : ArdourWindow (_("Panner (2D)"))
782         , widget (p, h)
783         , bypass_button (_("Bypass"))
784 {
785         widget.set_name ("MixerPanZone");
786
787         set_title (_("Panner"));
788         widget.set_size_request (h, h);
789
790         bypass_button.signal_toggled().connect (sigc::mem_fun (*this, &Panner2dWindow::bypass_toggled));
791
792         button_box.set_spacing (6);
793         button_box.pack_start (bypass_button, false, false);
794
795         spinner_box.set_spacing (6);
796         left_side.set_spacing (6);
797
798         left_side.pack_start (button_box, false, false);
799         left_side.pack_start (spinner_box, false, false);
800
801         bypass_button.show ();
802         button_box.show ();
803         spinner_box.show ();
804         left_side.show ();
805
806         hpacker.set_spacing (6);
807         hpacker.set_border_width (12);
808         hpacker.pack_start (widget, false, false);
809         hpacker.pack_start (left_side, false, false);
810         hpacker.show ();
811
812         add (hpacker);
813         reset (inputs);
814         widget.show ();
815 }
816
817 void
818 Panner2dWindow::reset (uint32_t n_inputs)
819 {
820         widget.reset (n_inputs);
821
822 #if 0
823         while (spinners.size() < n_inputs) {
824                 // spinners.push_back (new Gtk::SpinButton (widget.azimuth (spinners.size())));
825                 //spinner_box.pack_start (*spinners.back(), false, false);
826                 //spinners.back()->set_digits (4);
827                 spinners.back()->show ();
828         }
829
830         while (spinners.size() > n_inputs) {
831                 spinner_box.remove (*spinners.back());
832                 delete spinners.back();
833                 spinners.erase (--spinners.end());
834         }
835 #endif
836 }
837
838 void
839 Panner2dWindow::bypass_toggled ()
840 {
841         bool view = bypass_button.get_active ();
842         bool model = widget.get_panner_shell()->bypassed ();
843
844         if (model != view) {
845                 widget.get_panner_shell()->set_bypassed (view);
846         }
847 }
848
849 bool
850 Panner2dWindow::on_key_press_event (GdkEventKey* event)
851 {
852         return relay_key_press (event, &PublicEditor::instance());
853 }
854
855 bool
856 Panner2dWindow::on_key_release_event (GdkEventKey*)
857 {
858         return true;
859 }