some C++-ification of GnomeCanvasBlah
[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 <gtkmm2ext/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 (mem_fun(*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::on_size_allocate (Gtk::Allocation alloc)
128 {
129         width = alloc.get_width();
130         height = alloc.get_height();
131
132         DrawingArea::on_size_allocate (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(mem_fun(*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 bool
356 Panner2d::on_motion_notify_event (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 bool
423 Panner2d::on_expose_event (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                         Pango::Layout layout = new Pango::Layout(get_window()->create_pango_layout());
442
443                         if (puck->visible) {
444                                 /* redraw puck */
445                                 
446                                 fx = min (puck->x, 1.0f);
447                                 fx = max (fx, -1.0f);
448                                 x = (gint) floor (width * fx - 4);
449                                 
450                                 fy = min (puck->y, 1.0f);
451                                 fy = max (fy, -1.0f);
452                                 y = (gint) floor (height * fy - 4);
453                                 
454                                 get_window()->draw_arc (get_style()->get_fg_gc(Gtk::STATE_NORMAL),
455                                                        true,
456                                                        x, y,
457                                                        8, 8,
458                                                        0, 360 * 64);
459                                 layout.set_text(puck->text);
460                                 //get_window()->draw_text (get_style()->get_font(),
461                                 //                      get_style()->get_fg_gc(Gtk::STATE_NORMAL),
462                                 //                      x + 6, y + 6,
463                                 //                      puck->text,
464                                 //                      puck->textlen);
465                                 // GTK2FIX : needs a pango layout
466                         }
467                 }
468
469                 /* redraw any visible targets */
470
471                 for (Targets::iterator i = targets.begin(); i != targets.end(); ++i) {
472                         Target *target = i->second;
473
474                         if (target->visible) {
475                                 
476                                 /* why -8 ??? why is this necessary ? */
477                                 
478                                 fx = min (target->x, 1.0f);
479                                 fx = max (fx, -1.0f);
480                                 x = (gint) floor ((width - 8) * fx);
481                         
482                                 fy = min (target->y, 1.0f);
483                                 fy = max (fy, -1.0f);
484                                 y = (gint) floor ((height - 8) * fy);
485
486                                 get_window()->draw_rectangle (get_style()->get_fg_gc(Gtk::STATE_ACTIVE),
487                                                              true,
488                                                              x, y,
489                                                              4, 4);
490                         }
491                 }
492         }
493
494         return TRUE;
495 }
496
497 bool
498 Panner2d::on_button_press_event (GdkEventButton *ev)
499 {
500         switch (ev->button) {
501         case 1:
502                 gint x, y;
503                 GdkModifierType state;
504
505                 drag_target = find_closest_object (ev->x, ev->y, drag_index, drag_is_puck);
506                 
507                 x = (int) floor (ev->x);
508                 y = (int) floor (ev->y);
509                 state = (GdkModifierType) ev->state;
510
511                 return handle_motion (x, y, state);
512                 break;
513         default:
514                 break;
515         }
516         
517         return FALSE;
518 }
519
520 bool
521 Panner2d::on_button_release_event (GdkEventButton *ev)
522 {
523         switch (ev->button) {
524         case 1:
525                 gint x, y;
526                 int ret;
527                 GdkModifierType state;
528
529                 x = (int) floor (ev->x);
530                 y = (int) floor (ev->y);
531                 state = (GdkModifierType) ev->state;
532
533                 if (drag_is_puck && (Keyboard::modifier_state_contains (state, Keyboard::Shift))) {
534                         
535                         for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
536                                 Target* puck = i->second;
537                                 puck->x = 0.5;
538                                 puck->y = 0.5;
539                         }
540
541                         queue_draw ();
542                         PuckMoved (-1);
543                         ret = TRUE;
544
545                 } else {
546                         ret = handle_motion (x, y, state);
547                 }
548                 
549                 drag_target = 0;
550
551                 return ret;
552                 break;
553         case 2:
554                 toggle_bypass ();
555                 return TRUE;
556
557         case 3:
558                 show_context_menu ();
559                 break;
560
561         }
562
563         return FALSE;
564 }
565
566 void
567 Panner2d::toggle_bypass ()
568 {
569         if (bypass_menu_item && (panner.bypassed() != bypass_menu_item->get_active())) {
570                 panner.set_bypassed (!panner.bypassed());
571         }
572 }
573
574 void
575 Panner2d::show_context_menu ()
576 {
577         using namespace Menu_Helpers;
578
579         if (context_menu == 0) {
580                 context_menu = manage (new Menu);
581                 context_menu->set_name ("ArdourContextMenu");
582                 MenuList& items = context_menu->items();
583
584                 items.push_back (CheckMenuElem (_("Bypass")));
585                 bypass_menu_item = static_cast<CheckMenuItem*> (&items.back());
586                 bypass_menu_item->signal_toggled().connect (mem_fun(*this, &Panner2d::toggle_bypass));
587
588         } 
589
590         bypass_menu_item->set_active (panner.bypassed());
591         context_menu->popup (1, 0);
592 }
593
594 void
595 Panner2d::allow_x_motion (bool yn)
596 {
597         allow_x = yn;
598 }
599
600 void
601 Panner2d::allow_target_motion (bool yn)
602 {
603         allow_target = yn;
604 }
605
606 void
607 Panner2d::allow_y_motion (bool yn)
608 {
609         allow_y = yn;
610 }
611
612 int
613 Panner2d::puck_position (int which, float& x, float& y)
614 {
615         Targets::iterator i;
616
617         if ((i = pucks.find (which)) != pucks.end()) {
618                 x = i->second->x;
619                 y = i->second->y;
620                 return 0;
621         }
622
623         return -1;
624 }
625
626 int
627 Panner2d::target_position (int which, float& x, float& y)
628 {
629         Targets::iterator i;
630
631         if ((i = targets.find (which)) != targets.end()) {
632                 x = i->second->x;
633                 y = i->second->y;
634                 return 0;
635         }
636
637         return -1;
638 }
639