Initial import of gtk2_ardour.
[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     $Id$
19 */
20
21 #include <cmath>
22 #include <climits>
23 #include <string.h>
24
25 #include <pbd/error.h>
26 #include <ardour/panner.h>
27 #include <gtkmmext/gtk_ui.h>
28
29 #include "panner2d.h"
30 #include "keyboard.h"
31 #include "gui_thread.h"
32
33 #include "i18n.h"
34
35 using namespace std;
36 using namespace Gtk;
37 using namespace SigC;
38 using namespace ARDOUR;
39
40 Panner2d::Target::Target (float xa, float ya, const char *txt)
41         : x (xa), y (ya), text (txt ? strdup (txt) : 0)
42 {
43         if (text) {
44                 textlen = strlen (txt);
45         } else {
46                 textlen = 0;
47         }
48 }
49
50 Panner2d::Target::~Target ()
51
52         if (text) {
53                 free (text);
54         }
55 }
56
57 Panner2d::Panner2d (Panner& p, int32_t w, int32_t h)
58         : panner (p), width (w), height (h)
59 {
60         context_menu = 0;
61         bypass_menu_item = 0;
62
63         allow_x = false;
64         allow_y = false;
65         allow_target = false;
66
67         panner.StateChanged.connect (slot (*this, &Panner2d::handle_state_change));
68         
69         drag_target = 0;
70         set_events (GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_POINTER_MOTION_MASK);
71
72 }
73
74 Panner2d::~Panner2d()
75 {
76         for (Targets::iterator i = targets.begin(); i != targets.end(); ++i) {
77                 delete i->second;
78         }
79 }
80
81 void
82 Panner2d::reset (uint32_t n_inputs)
83 {
84         /* add pucks */
85         
86         drop_pucks ();
87         
88         switch (n_inputs) {
89         case 0:
90                 break;
91                 
92         case 1:
93                 add_puck ("", 0.0f, 0.5f);
94                 break;
95                 
96         case 2:
97                 add_puck ("L", 0.5f, 0.25f);
98                 add_puck ("R", 0.25f, 0.5f);
99                 show_puck (0);
100                 show_puck (1);
101                 break;
102                 
103         default:
104                 for (uint32_t i = 0; i < n_inputs; ++i) {
105                         char buf[64];
106                         snprintf (buf, sizeof (buf), "%" PRIu32, i);
107                         add_puck (buf, 0.0f, 0.5f);
108                         show_puck (i);
109                 }
110                 break;
111         }
112         
113         /* add all outputs */
114         
115         drop_targets ();
116         
117         for (uint32_t n = 0; n < panner.nouts(); ++n) {
118                 add_target (panner.output (n).x, panner.output (n).y);
119         }
120         
121         allow_x_motion (true);
122         allow_y_motion (true);
123         allow_target_motion (true);
124 }
125
126 void
127 Panner2d::size_allocate_impl (GtkAllocation *alloc)
128 {
129         width = alloc->width;
130         height = alloc->height;
131
132         DrawingArea::size_allocate_impl (alloc);
133 }
134
135 int
136 Panner2d::add_puck (const char* text, float x, float y)
137 {
138         Target* puck = new Target (x, y, text);
139
140         pair<int,Target *> newpair;
141         newpair.first = pucks.size();
142         newpair.second = puck;
143
144         pucks.insert (newpair);
145         puck->visible = true;
146         
147         return 0;
148 }
149
150 int
151 Panner2d::add_target (float x, float y)
152 {
153         Target *target = new Target (x, y, "");
154
155         pair<int,Target *> newpair;
156         newpair.first = targets.size();
157         newpair.second = target;
158
159         targets.insert (newpair);
160         target->visible = true;
161         queue_draw ();
162
163         return newpair.first;
164 }
165
166 void
167 Panner2d::drop_targets ()
168 {
169         for (Targets::iterator i = targets.begin(); i != targets.end(); ) {
170
171                 Targets::iterator tmp;
172
173                 tmp = i;
174                 ++tmp;
175
176                 delete i->second;
177                 targets.erase (i);
178
179                 i = tmp;
180         }
181
182         queue_draw ();
183 }
184
185 void
186 Panner2d::drop_pucks ()
187 {
188         for (Targets::iterator i = pucks.begin(); i != pucks.end(); ) {
189
190                 Targets::iterator tmp;
191
192                 tmp = i;
193                 ++tmp;
194
195                 delete i->second;
196                 pucks.erase (i);
197
198                 i = tmp;
199         }
200
201         queue_draw ();
202 }
203
204 void
205 Panner2d::remove_target (int which)
206 {
207         Targets::iterator i = targets.find (which);
208
209         if (i != targets.end()) {
210                 delete i->second;
211                 targets.erase (i);
212                 queue_draw ();
213         }
214 }               
215
216 void
217 Panner2d::handle_state_change ()
218 {
219         ENSURE_GUI_THREAD(slot (*this, &Panner2d::handle_state_change));
220
221         queue_draw ();
222 }
223
224 void
225 Panner2d::move_target (int which, float x, float y)
226 {
227         Targets::iterator i = targets.find (which);
228         Target *target;
229
230         if (!allow_target) {
231                 return;
232         }
233
234         if (i != targets.end()) {
235                 target = i->second;
236                 target->x = x;
237                 target->y = y;
238                 
239                 queue_draw ();
240         }
241 }               
242
243 void
244 Panner2d::move_puck (int which, float x, float y)
245 {
246         Targets::iterator i = pucks.find (which);
247         Target *target;
248
249         if (i != pucks.end()) {
250                 target = i->second;
251                 target->x = x;
252                 target->y = y;
253                 
254                 queue_draw ();
255         }
256 }               
257
258 void
259 Panner2d::show_puck (int which)
260 {
261         Targets::iterator i = pucks.find (which);
262
263         if (i != pucks.end()) {
264                 Target* puck = i->second;
265                 if (!puck->visible) {
266                         puck->visible = true;
267                         queue_draw ();
268                 }
269         }
270 }
271
272 void
273 Panner2d::hide_puck (int which)
274 {
275         Targets::iterator i = pucks.find (which);
276
277         if (i != pucks.end()) {
278                 Target* puck = i->second;
279                 if (!puck->visible) {
280                         puck->visible = false;
281                         queue_draw ();
282                 }
283         }
284 }
285
286 void
287 Panner2d::show_target (int which)
288 {
289         Targets::iterator i = targets.find (which);
290         if (i != targets.end()) {
291                 if (!i->second->visible) {
292                         i->second->visible = true;
293                         queue_draw ();
294                 }
295         }
296 }
297
298 void
299 Panner2d::hide_target (int which)
300 {
301         Targets::iterator i = targets.find (which);
302         if (i != targets.end()) {
303                 if (i->second->visible) {
304                         i->second->visible = false;
305                         queue_draw ();
306                 }
307         }
308 }
309
310 Panner2d::Target *
311 Panner2d::find_closest_object (gdouble x, gdouble y, int& which, bool& is_puck) const
312 {
313         gdouble efx, efy;
314         Target *closest = 0;
315         Target *candidate;
316         float distance;
317         float best_distance = FLT_MAX;
318         int pwhich;
319
320         efx = x/width;
321         efy = y/height;
322         which = 0;
323         pwhich = 0;
324         is_puck = false;
325
326         for (Targets::const_iterator i = targets.begin(); i != targets.end(); ++i, ++which) {
327                 candidate = i->second;
328
329                 distance = sqrt ((candidate->x - efx) * (candidate->x - efx) +
330                                  (candidate->y - efy) * (candidate->y - efy));
331
332                 if (distance < best_distance) {
333                         closest = candidate;
334                         best_distance = distance;
335                 }
336         }
337
338         for (Targets::const_iterator i = pucks.begin(); i != pucks.end(); ++i, ++pwhich) {
339                 candidate = i->second;
340
341                 distance = sqrt ((candidate->x - efx) * (candidate->x - efx) +
342                                  (candidate->y - efy) * (candidate->y - efy));
343
344                 if (distance < best_distance) {
345                         closest = candidate;
346                         best_distance = distance;
347                         is_puck = true;
348                         which = pwhich;
349                 }
350         }
351         
352         return closest;
353 }               
354
355 gint
356 Panner2d::motion_notify_event_impl (GdkEventMotion *ev)
357 {
358         gint x, y;
359         GdkModifierType state;
360
361         if (ev->is_hint) {
362                 gdk_window_get_pointer (ev->window, &x, &y, &state);
363         } else {
364                 x = (int) floor (ev->x);
365                 y = (int) floor (ev->y);
366                 state = (GdkModifierType) ev->state;
367         }
368         return handle_motion (x, y, state);
369 }
370 gint
371 Panner2d::handle_motion (gint evx, gint evy, GdkModifierType state)
372 {
373         if (drag_target == 0 || (state & GDK_BUTTON1_MASK) == 0) {
374                 return FALSE;
375         }
376
377         int x, y;
378         bool need_move = false;
379
380         if (!drag_is_puck && !allow_target) {
381                 return TRUE;
382         }
383
384         if (allow_x || !drag_is_puck) {
385                 float new_x;
386                 x = min (evx, width - 1);
387                 x = max (x, 0);
388                 new_x = (float) x / (width - 1);
389                 if (new_x != drag_target->x) {
390                         drag_target->x = new_x;
391                         need_move = true;
392                 }
393         }
394
395         if (allow_y || drag_is_puck) {
396                 float new_y;
397                 y = min (evy, height - 1);
398                 y = max (y, 0);
399                 new_y = (float) y / (height - 1);
400                 if (new_y != drag_target->y) {
401                         drag_target->y = new_y;
402                         need_move = true;
403                 }
404         }
405
406         if (need_move) {
407                 queue_draw ();
408
409                 if (drag_is_puck) {
410                         
411                         panner[drag_index]->set_position (drag_target->x, drag_target->y);
412
413                 } else {
414
415                         TargetMoved (drag_index);
416                 }
417         }
418
419         return TRUE;
420 }
421
422 gint
423 Panner2d::expose_event_impl (GdkEventExpose *event)
424 {
425         gint x, y;
426         float fx, fy;
427
428         /* redraw the background */
429
430         get_window().draw_rectangle (get_style()->get_bg_gc(get_state()),
431                                      true,
432                                      event->area.x, event->area.y,
433                                      event->area.width, event->area.height);
434         
435
436         if (!panner.bypassed()) {
437
438                 for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
439
440                         Target* puck = i->second;
441
442                         if (puck->visible) {
443                                 /* redraw puck */
444                                 
445                                 fx = min (puck->x, 1.0f);
446                                 fx = max (fx, -1.0f);
447                                 x = (gint) floor (width * fx - 4);
448                                 
449                                 fy = min (puck->y, 1.0f);
450                                 fy = max (fy, -1.0f);
451                                 y = (gint) floor (height * fy - 4);
452                                 
453                                 get_window().draw_arc (get_style()->get_fg_gc(GTK_STATE_NORMAL),
454                                                        true,
455                                                        x, y,
456                                                        8, 8,
457                                                        0, 360 * 64);
458                                 get_window().draw_text (get_style()->get_font(),
459                                                         get_style()->get_fg_gc(GTK_STATE_NORMAL),
460                                                         x + 6, y + 6,
461                                                         puck->text,
462                                                         puck->textlen);
463                         }
464                 }
465
466                 /* redraw any visible targets */
467
468                 for (Targets::iterator i = targets.begin(); i != targets.end(); ++i) {
469                         Target *target = i->second;
470
471                         if (target->visible) {
472                                 
473                                 /* why -8 ??? why is this necessary ? */
474                                 
475                                 fx = min (target->x, 1.0f);
476                                 fx = max (fx, -1.0f);
477                                 x = (gint) floor ((width - 8) * fx);
478                         
479                                 fy = min (target->y, 1.0f);
480                                 fy = max (fy, -1.0f);
481                                 y = (gint) floor ((height - 8) * fy);
482
483                                 get_window().draw_rectangle (get_style()->get_fg_gc(GTK_STATE_ACTIVE),
484                                                              true,
485                                                              x, y,
486                                                              4, 4);
487                         }
488                 }
489         }
490
491         return TRUE;
492 }
493
494 gint
495 Panner2d::button_press_event_impl (GdkEventButton *ev)
496 {
497         switch (ev->button) {
498         case 1:
499                 gint x, y;
500                 GdkModifierType state;
501
502                 drag_target = find_closest_object (ev->x, ev->y, drag_index, drag_is_puck);
503                 
504                 x = (int) floor (ev->x);
505                 y = (int) floor (ev->y);
506                 state = (GdkModifierType) ev->state;
507
508                 return handle_motion (x, y, state);
509                 break;
510         default:
511                 break;
512         }
513         
514         return FALSE;
515 }
516
517 gint
518 Panner2d::button_release_event_impl (GdkEventButton *ev)
519 {
520         switch (ev->button) {
521         case 1:
522                 gint x, y;
523                 int ret;
524                 GdkModifierType state;
525
526                 x = (int) floor (ev->x);
527                 y = (int) floor (ev->y);
528                 state = (GdkModifierType) ev->state;
529
530                 if (drag_is_puck && (Keyboard::modifier_state_contains (state, Keyboard::Shift))) {
531                         
532                         for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
533                                 Target* puck = i->second;
534                                 puck->x = 0.5;
535                                 puck->y = 0.5;
536                         }
537
538                         queue_draw ();
539                         PuckMoved (-1);
540                         ret = TRUE;
541
542                 } else {
543                         ret = handle_motion (x, y, state);
544                 }
545                 
546                 drag_target = 0;
547
548                 return ret;
549                 break;
550         case 2:
551                 toggle_bypass ();
552                 return TRUE;
553
554         case 3:
555                 show_context_menu ();
556                 break;
557
558         }
559
560         return FALSE;
561 }
562
563 void
564 Panner2d::toggle_bypass ()
565 {
566         if (bypass_menu_item && (panner.bypassed() != bypass_menu_item->get_active())) {
567                 panner.set_bypassed (!panner.bypassed());
568         }
569 }
570
571 void
572 Panner2d::show_context_menu ()
573 {
574         using namespace Menu_Helpers;
575
576         if (context_menu == 0) {
577                 context_menu = manage (new Menu);
578                 context_menu->set_name ("ArdourContextMenu");
579                 MenuList& items = context_menu->items();
580
581                 items.push_back (CheckMenuElem (_("Bypass")));
582                 bypass_menu_item = static_cast<CheckMenuItem*> (items.back());
583                 bypass_menu_item->toggled.connect (slot (*this, &Panner2d::toggle_bypass));
584
585         } 
586
587         bypass_menu_item->set_active (panner.bypassed());
588         context_menu->popup (1, 0);
589 }
590
591 void
592 Panner2d::allow_x_motion (bool yn)
593 {
594         allow_x = yn;
595 }
596
597 void
598 Panner2d::allow_target_motion (bool yn)
599 {
600         allow_target = yn;
601 }
602
603 void
604 Panner2d::allow_y_motion (bool yn)
605 {
606         allow_y = yn;
607 }
608
609 int
610 Panner2d::puck_position (int which, float& x, float& y)
611 {
612         Targets::iterator i;
613
614         if ((i = pucks.find (which)) != pucks.end()) {
615                 x = i->second->x;
616                 y = i->second->y;
617                 return 0;
618         }
619
620         return -1;
621 }
622
623 int
624 Panner2d::target_position (int which, float& x, float& y)
625 {
626         Targets::iterator i;
627
628         if ((i = targets.find (which)) != targets.end()) {
629                 x = i->second->x;
630                 y = i->second->y;
631                 return 0;
632         }
633
634         return -1;
635 }
636