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