colinf's patch to make the cursor be the dbl vertical arrow when over the track resiz...
[ardour2.git] / gtk2_ardour / panner2d.cc
blob6fa64be8e60e5d6a71869ad6a72683ba62c1cbdc
1 /*
2 Copyright (C) 2002 Paul Davis
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.
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.
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.
20 #include <cmath>
21 #include <climits>
22 #include <cstring>
24 #include <gtkmm/menu.h>
25 #include <gtkmm/checkmenuitem.h>
27 #include <pbd/error.h>
28 #include <ardour/panner.h>
29 #include <gtkmm2ext/gtk_ui.h>
31 #include "panner2d.h"
32 #include "keyboard.h"
33 #include "gui_thread.h"
35 #include "i18n.h"
37 using namespace std;
38 using namespace Gtk;
39 using namespace sigc;
40 using namespace ARDOUR;
41 using namespace PBD;
43 Panner2d::Target::Target (float xa, float ya, const char *txt)
44 : x (xa), y (ya), text (txt ? strdup (txt) : 0)
46 if (text) {
47 textlen = strlen (txt);
48 } else {
49 textlen = 0;
53 Panner2d::Target::~Target ()
55 if (text) {
56 free (text);
60 Panner2d::Panner2d (Panner& p, int32_t h)
61 : panner (p), width (0), height (h)
63 context_menu = 0;
64 bypass_menu_item = 0;
66 allow_x = false;
67 allow_y = false;
68 allow_target = false;
70 panner.StateChanged.connect (mem_fun(*this, &Panner2d::handle_state_change));
72 drag_target = 0;
73 set_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
77 Panner2d::~Panner2d()
79 for (Targets::iterator i = targets.begin(); i != targets.end(); ++i) {
80 delete i->second;
84 void
85 Panner2d::reset (uint32_t n_inputs)
87 /* add pucks */
89 drop_pucks ();
91 switch (n_inputs) {
92 case 0:
93 break;
95 case 1:
96 add_puck ("", 0.0f, 0.5f);
97 break;
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;
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);
113 break;
116 /* add all outputs */
118 drop_targets ();
120 for (uint32_t n = 0; n < panner.nouts(); ++n) {
121 add_target (panner.output (n).x, panner.output (n).y);
124 allow_x_motion (true);
125 allow_y_motion (true);
126 allow_target_motion (true);
129 void
130 Panner2d::on_size_allocate (Gtk::Allocation& alloc)
132 width = alloc.get_width();
133 height = alloc.get_height();
135 DrawingArea::on_size_allocate (alloc);
139 Panner2d::add_puck (const char* text, float x, float y)
141 Target* puck = new Target (x, y, text);
143 pair<int,Target *> newpair;
144 newpair.first = pucks.size();
145 newpair.second = puck;
147 pucks.insert (newpair);
148 puck->visible = true;
150 return 0;
154 Panner2d::add_target (float x, float y)
156 Target *target = new Target (x, y, "");
158 pair<int,Target *> newpair;
159 newpair.first = targets.size();
160 newpair.second = target;
162 targets.insert (newpair);
163 target->visible = true;
164 queue_draw ();
166 return newpair.first;
169 void
170 Panner2d::drop_targets ()
172 for (Targets::iterator i = targets.begin(); i != targets.end(); ) {
174 Targets::iterator tmp;
176 tmp = i;
177 ++tmp;
179 delete i->second;
180 targets.erase (i);
182 i = tmp;
185 queue_draw ();
188 void
189 Panner2d::drop_pucks ()
191 for (Targets::iterator i = pucks.begin(); i != pucks.end(); ) {
193 Targets::iterator tmp;
195 tmp = i;
196 ++tmp;
198 delete i->second;
199 pucks.erase (i);
201 i = tmp;
204 queue_draw ();
207 void
208 Panner2d::remove_target (int which)
210 Targets::iterator i = targets.find (which);
212 if (i != targets.end()) {
213 delete i->second;
214 targets.erase (i);
215 queue_draw ();
219 void
220 Panner2d::handle_state_change ()
222 ENSURE_GUI_THREAD(mem_fun(*this, &Panner2d::handle_state_change));
224 queue_draw ();
227 void
228 Panner2d::move_target (int which, float x, float y)
230 Targets::iterator i = targets.find (which);
231 Target *target;
233 if (!allow_target) {
234 return;
237 if (i != targets.end()) {
238 target = i->second;
239 target->x = x;
240 target->y = y;
242 queue_draw ();
246 void
247 Panner2d::move_puck (int which, float x, float y)
249 Targets::iterator i = pucks.find (which);
250 Target *target;
252 if (i != pucks.end()) {
253 target = i->second;
254 target->x = x;
255 target->y = y;
257 queue_draw ();
261 void
262 Panner2d::show_puck (int which)
264 Targets::iterator i = pucks.find (which);
266 if (i != pucks.end()) {
267 Target* puck = i->second;
268 if (!puck->visible) {
269 puck->visible = true;
270 queue_draw ();
275 void
276 Panner2d::hide_puck (int which)
278 Targets::iterator i = pucks.find (which);
280 if (i != pucks.end()) {
281 Target* puck = i->second;
282 if (!puck->visible) {
283 puck->visible = false;
284 queue_draw ();
289 void
290 Panner2d::show_target (int which)
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 ();
301 void
302 Panner2d::hide_target (int which)
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 ();
313 Panner2d::Target *
314 Panner2d::find_closest_object (gdouble x, gdouble y, int& which, bool& is_puck) const
316 gdouble efx, efy;
317 Target *closest = 0;
318 Target *candidate;
319 float distance;
320 float best_distance = FLT_MAX;
321 int pwhich;
323 efx = x/width;
324 efy = y/height;
325 which = 0;
326 pwhich = 0;
327 is_puck = false;
329 for (Targets::const_iterator i = targets.begin(); i != targets.end(); ++i, ++which) {
330 candidate = i->second;
332 distance = sqrt ((candidate->x - efx) * (candidate->x - efx) +
333 (candidate->y - efy) * (candidate->y - efy));
335 if (distance < best_distance) {
336 closest = candidate;
337 best_distance = distance;
341 for (Targets::const_iterator i = pucks.begin(); i != pucks.end(); ++i, ++pwhich) {
342 candidate = i->second;
344 distance = sqrt ((candidate->x - efx) * (candidate->x - efx) +
345 (candidate->y - efy) * (candidate->y - efy));
347 if (distance < best_distance) {
348 closest = candidate;
349 best_distance = distance;
350 is_puck = true;
351 which = pwhich;
355 return closest;
358 bool
359 Panner2d::on_motion_notify_event (GdkEventMotion *ev)
361 gint x, y;
362 GdkModifierType state;
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;
371 return handle_motion (x, y, state);
373 gint
374 Panner2d::handle_motion (gint evx, gint evy, GdkModifierType state)
376 if (drag_target == 0 || (state & GDK_BUTTON1_MASK) == 0) {
377 return FALSE;
380 int x, y;
381 bool need_move = false;
383 if (!drag_is_puck && !allow_target) {
384 return TRUE;
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;
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;
409 if (need_move) {
410 queue_draw ();
412 if (drag_is_puck) {
414 panner[drag_index]->set_position (drag_target->x, drag_target->y);
416 } else {
418 TargetMoved (drag_index);
422 return TRUE;
425 bool
426 Panner2d::on_expose_event (GdkEventExpose *event)
428 gint x, y;
429 float fx, fy;
431 if (layout == 0) {
432 layout = create_pango_layout ("");
433 layout->set_font_description (get_style()->get_font());
436 /* redraw the background */
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);
444 if (!panner.bypassed()) {
446 for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
448 Target* puck = i->second;
450 if (puck->visible) {
451 /* redraw puck */
453 fx = min (puck->x, 1.0f);
454 fx = max (fx, -1.0f);
455 x = (gint) floor (width * fx - 4);
457 fy = min (puck->y, 1.0f);
458 fy = max (fy, -1.0f);
459 y = (gint) floor (height * fy - 4);
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);
467 layout->set_text (puck->text);
469 get_window()->draw_layout (get_style()->get_fg_gc (STATE_NORMAL), x+6, y+6, layout);
473 /* redraw any visible targets */
475 for (Targets::iterator i = targets.begin(); i != targets.end(); ++i) {
476 Target *target = i->second;
478 if (target->visible) {
480 /* why -8 ??? why is this necessary ? */
482 fx = min (target->x, 1.0f);
483 fx = max (fx, -1.0f);
484 x = (gint) floor ((width - 8) * fx);
486 fy = min (target->y, 1.0f);
487 fy = max (fy, -1.0f);
488 y = (gint) floor ((height - 8) * fy);
490 get_window()->draw_rectangle (get_style()->get_fg_gc(Gtk::STATE_ACTIVE),
491 true,
492 x, y,
493 4, 4);
498 return TRUE;
501 bool
502 Panner2d::on_button_press_event (GdkEventButton *ev)
504 switch (ev->button) {
505 case 1:
506 gint x, y;
507 GdkModifierType state;
509 drag_target = find_closest_object (ev->x, ev->y, drag_index, drag_is_puck);
511 x = (int) floor (ev->x);
512 y = (int) floor (ev->y);
513 state = (GdkModifierType) ev->state;
515 return handle_motion (x, y, state);
516 break;
517 default:
518 break;
521 return FALSE;
524 bool
525 Panner2d::on_button_release_event (GdkEventButton *ev)
527 switch (ev->button) {
528 case 1:
529 gint x, y;
530 int ret;
531 GdkModifierType state;
533 x = (int) floor (ev->x);
534 y = (int) floor (ev->y);
535 state = (GdkModifierType) ev->state;
537 if (drag_is_puck && (Keyboard::modifier_state_contains (state, Keyboard::TertiaryModifier))) {
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;
545 queue_draw ();
546 PuckMoved (-1);
547 ret = TRUE;
549 } else {
550 ret = handle_motion (x, y, state);
553 drag_target = 0;
555 return ret;
556 break;
557 case 2:
558 toggle_bypass ();
559 return TRUE;
561 case 3:
562 show_context_menu ();
563 break;
567 return FALSE;
570 void
571 Panner2d::toggle_bypass ()
573 if (bypass_menu_item && (panner.bypassed() != bypass_menu_item->get_active())) {
574 panner.set_bypassed (!panner.bypassed());
578 void
579 Panner2d::show_context_menu ()
581 using namespace Menu_Helpers;
583 if (context_menu == 0) {
584 context_menu = manage (new Menu);
585 context_menu->set_name ("ArdourContextMenu");
586 MenuList& items = context_menu->items();
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));
594 bypass_menu_item->set_active (panner.bypassed());
595 context_menu->popup (1, gtk_get_current_event_time());
598 void
599 Panner2d::allow_x_motion (bool yn)
601 allow_x = yn;
604 void
605 Panner2d::allow_target_motion (bool yn)
607 allow_target = yn;
610 void
611 Panner2d::allow_y_motion (bool yn)
613 allow_y = yn;
617 Panner2d::puck_position (int which, float& x, float& y)
619 Targets::iterator i;
621 if ((i = pucks.find (which)) != pucks.end()) {
622 x = i->second->x;
623 y = i->second->y;
624 return 0;
627 return -1;
631 Panner2d::target_position (int which, float& x, float& y)
633 Targets::iterator i;
635 if ((i = targets.find (which)) != targets.end()) {
636 x = i->second->x;
637 y = i->second->y;
638 return 0;
641 return -1;