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