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