Copy local state in AudioRegionView copy constructor. Fixes #4047.
[ardour2.git] / gtk2_ardour / stereo_panner.cc
blob3826dc1c7a1ac1b7a1d031f6731c92f76e65c10e
1 /*
2 Copyright (C) 2000-2007 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 <iostream>
21 #include <iomanip>
22 #include <cstring>
23 #include <cmath>
25 #include <gtkmm/window.h>
27 #include "pbd/controllable.h"
28 #include "pbd/compose.h"
30 #include "gtkmm2ext/gui_thread.h"
31 #include "gtkmm2ext/gtk_ui.h"
32 #include "gtkmm2ext/keyboard.h"
33 #include "gtkmm2ext/utils.h"
35 #include "ardour/pannable.h"
36 #include "ardour/panner.h"
38 #include "ardour_ui.h"
39 #include "global_signals.h"
40 #include "stereo_panner.h"
41 #include "rgb_macros.h"
42 #include "utils.h"
44 #include "i18n.h"
46 using namespace std;
47 using namespace Gtk;
48 using namespace Gtkmm2ext;
50 static const int pos_box_size = 8;
51 static const int lr_box_size = 15;
52 static const int step_down = 10;
53 static const int top_step = 2;
55 StereoPanner::ColorScheme StereoPanner::colors[3];
56 bool StereoPanner::have_colors = false;
58 using namespace ARDOUR;
60 StereoPanner::StereoPanner (boost::shared_ptr<Panner> panner)
61 : _panner (panner)
62 , position_control (_panner->pannable()->pan_azimuth_control)
63 , width_control (_panner->pannable()->pan_width_control)
64 , dragging (false)
65 , dragging_position (false)
66 , dragging_left (false)
67 , dragging_right (false)
68 , drag_start_x (0)
69 , last_drag_x (0)
70 , accumulated_delta (0)
71 , detented (false)
72 , drag_data_window (0)
73 , drag_data_label (0)
74 , position_binder (position_control)
75 , width_binder (width_control)
77 if (!have_colors) {
78 set_colors ();
79 have_colors = true;
82 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
83 width_control->Changed.connect (connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
85 set_flags (Gtk::CAN_FOCUS);
87 add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|
88 Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|
89 Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|
90 Gdk::SCROLL_MASK|
91 Gdk::POINTER_MOTION_MASK);
93 ColorsChanged.connect (sigc::mem_fun (*this, &StereoPanner::color_handler));
96 StereoPanner::~StereoPanner ()
98 delete drag_data_window;
101 void
102 StereoPanner::set_drag_data ()
104 if (!drag_data_label) {
105 return;
108 double pos = position_control->get_value(); // 0..1
110 /* We show the position of the center of the image relative to the left & right.
111 This is expressed as a pair of percentage values that ranges from (100,0)
112 (hard left) through (50,50) (hard center) to (0,100) (hard right).
114 This is pretty wierd, but its the way audio engineers expect it. Just remember that
115 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
118 char buf[64];
119 snprintf (buf, sizeof (buf), "L:%3d R:%3d Width:%d%%", (int) rint (100.0 * (1.0 - pos)),
120 (int) rint (100.0 * pos),
121 (int) floor (100.0 * width_control->get_value()));
122 drag_data_label->set_markup (buf);
125 void
126 StereoPanner::value_change ()
128 set_drag_data ();
129 queue_draw ();
132 bool
133 StereoPanner::on_expose_event (GdkEventExpose* ev)
135 Glib::RefPtr<Gdk::Window> win (get_window());
136 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
137 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
139 int width, height;
140 double pos = position_control->get_value (); /* 0..1 */
141 double swidth = width_control->get_value (); /* -1..+1 */
142 double fswidth = fabs (swidth);
143 uint32_t o, f, t, b, r;
144 State state;
145 const double corner_radius = 5.0;
147 width = get_width();
148 height = get_height ();
150 if (swidth == 0.0) {
151 state = Mono;
152 } else if (swidth < 0.0) {
153 state = Inverted;
154 } else {
155 state = Normal;
158 o = colors[state].outline;
159 f = colors[state].fill;
160 t = colors[state].text;
161 b = colors[state].background;
162 r = colors[state].rule;
164 /* background */
166 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
167 rounded_rectangle (context, 0, 0, width, height, corner_radius);
168 context->fill ();
170 /* the usable width is reduced from the real width, because we need space for
171 the two halves of LR boxes that will extend past the actual left/right
172 positions (indicated by the vertical line segment above them).
175 double usable_width = width - lr_box_size;
177 /* compute the centers of the L/R boxes based on the current stereo width */
179 if (fmod (usable_width,2.0) == 0) {
180 /* even width, but we need odd, so that there is an exact center.
181 So, offset cairo by 1, and reduce effective width by 1
183 usable_width -= 1.0;
184 context->translate (1.0, 0.0);
187 double center = (lr_box_size/2.0) + (usable_width * pos);
188 const double pan_spread = (fswidth * usable_width)/2.0;
189 const double half_lr_box = lr_box_size/2.0;
190 int left;
191 int right;
193 left = center - pan_spread; // center of left box
194 right = center + pan_spread; // center of right box
196 /* center line */
198 context->set_line_width (1.0);
199 context->move_to ((usable_width + lr_box_size)/2.0, 0);
200 context->rel_line_to (0, height);
201 context->set_source_rgba (UINT_RGBA_R_FLT(r), UINT_RGBA_G_FLT(r), UINT_RGBA_B_FLT(r), UINT_RGBA_A_FLT(r));
202 context->stroke ();
204 /* compute & draw the line through the box */
206 context->set_line_width (2);
207 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
208 context->move_to (left, top_step+(pos_box_size/2.0)+step_down);
209 context->line_to (left, top_step+(pos_box_size/2.0));
210 context->line_to (right, top_step+(pos_box_size/2.0));
211 context->line_to (right, top_step+(pos_box_size/2.0) + step_down);
212 context->stroke ();
214 /* left box */
216 rounded_rectangle (context, left - half_lr_box,
217 half_lr_box+step_down,
218 lr_box_size, lr_box_size, corner_radius);
219 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
220 context->stroke_preserve ();
221 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
222 context->fill ();
224 /* add text */
226 context->move_to (left - half_lr_box + 3,
227 (lr_box_size/2) + step_down + 13);
228 context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
230 if (state != Mono) {
231 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
232 if (swidth < 0.0) {
233 context->show_text (_("R"));
234 } else {
235 context->show_text (_("L"));
239 /* right box */
241 rounded_rectangle (context, right - half_lr_box,
242 half_lr_box+step_down,
243 lr_box_size, lr_box_size, corner_radius);
244 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
245 context->stroke_preserve ();
246 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
247 context->fill ();
249 /* add text */
251 context->move_to (right - half_lr_box + 3, (lr_box_size/2)+step_down + 13);
252 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
254 if (state == Mono) {
255 context->show_text (_("M"));
256 } else {
257 if (swidth < 0.0) {
258 context->show_text (_("L"));
259 } else {
260 context->show_text (_("R"));
264 /* draw the central box */
266 context->set_line_width (2.0);
267 context->move_to (center + (pos_box_size/2.0), top_step); /* top right */
268 context->rel_line_to (0.0, pos_box_size); /* lower right */
269 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
270 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
271 context->rel_line_to (0.0, -pos_box_size); /* upper left */
272 context->close_path ();
274 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
275 context->stroke_preserve ();
276 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
277 context->fill ();
279 return true;
282 bool
283 StereoPanner::on_button_press_event (GdkEventButton* ev)
285 drag_start_x = ev->x;
286 last_drag_x = ev->x;
288 dragging_position = false;
289 dragging_left = false;
290 dragging_right = false;
291 dragging = false;
292 accumulated_delta = 0;
293 detented = false;
295 /* Let the binding proxies get first crack at the press event
298 if (ev->y < 20) {
299 if (position_binder.button_press_handler (ev)) {
300 return true;
302 } else {
303 if (width_binder.button_press_handler (ev)) {
304 return true;
308 if (ev->button != 1) {
309 return false;
312 if (ev->type == GDK_2BUTTON_PRESS) {
313 int width = get_width();
315 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
316 /* handled by button release */
317 return true;
320 if (ev->y < 20) {
322 /* upper section: adjusts position, constrained by width */
324 const double w = fabs (width_control->get_value ());
325 const double max_pos = 1.0 - (w/2.0);
326 const double min_pos = w/2.0;
328 if (ev->x <= width/3) {
329 /* left side dbl click */
330 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
331 /* 2ndary-double click on left, collapse to hard left */
332 width_control->set_value (0);
333 position_control->set_value (0);
334 } else {
335 position_control->set_value (min_pos);
337 } else if (ev->x > 2*width/3) {
338 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
339 /* 2ndary-double click on right, collapse to hard right */
340 width_control->set_value (0);
341 position_control->set_value (1.0);
342 } else {
343 position_control->set_value (max_pos);
345 } else {
346 position_control->set_value (0.5);
349 } else {
351 /* lower section: adjusts width, constrained by position */
353 const double p = position_control->get_value ();
354 const double max_width = 2.0 * min ((1.0 - p), p);
356 if (ev->x <= width/3) {
357 /* left side dbl click */
358 width_control->set_value (max_width); // reset width to 100%
359 } else if (ev->x > 2*width/3) {
360 /* right side dbl click */
361 width_control->set_value (-max_width); // reset width to inverted 100%
362 } else {
363 /* center dbl click */
364 width_control->set_value (0); // collapse width to 0%
368 dragging = false;
370 } else if (ev->type == GDK_BUTTON_PRESS) {
372 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
373 /* handled by button release */
374 return true;
377 if (ev->y < 20) {
378 /* top section of widget is for position drags */
379 dragging_position = true;
380 StartPositionGesture ();
381 } else {
382 /* lower section is for dragging width */
384 double pos = position_control->get_value (); /* 0..1 */
385 double swidth = width_control->get_value (); /* -1..+1 */
386 double fswidth = fabs (swidth);
387 int usable_width = get_width() - lr_box_size;
388 double center = (lr_box_size/2.0) + (usable_width * pos);
389 int left = lrint (center - (fswidth * usable_width / 2.0)); // center of leftmost box
390 int right = lrint (center + (fswidth * usable_width / 2.0)); // center of rightmost box
391 const int half_box = lr_box_size/2;
393 if (ev->x >= (left - half_box) && ev->x < (left + half_box)) {
394 if (swidth < 0.0) {
395 dragging_right = true;
396 } else {
397 dragging_left = true;
399 } else if (ev->x >= (right - half_box) && ev->x < (right + half_box)) {
400 if (swidth < 0.0) {
401 dragging_left = true;
402 } else {
403 dragging_right = true;
406 StartWidthGesture ();
409 dragging = true;
412 return true;
415 bool
416 StereoPanner::on_button_release_event (GdkEventButton* ev)
418 if (ev->button != 1) {
419 return false;
422 bool dp = dragging_position;
424 dragging = false;
425 dragging_position = false;
426 dragging_left = false;
427 dragging_right = false;
428 accumulated_delta = 0;
429 detented = false;
431 if (drag_data_window) {
432 drag_data_window->hide ();
435 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
436 /* reset to default */
437 position_control->set_value (0.5);
438 width_control->set_value (1.0);
439 } else {
440 if (dp) {
441 StopPositionGesture ();
442 } else {
443 StopWidthGesture ();
447 return true;
450 bool
451 StereoPanner::on_scroll_event (GdkEventScroll* ev)
453 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
454 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
455 double wv = width_control->get_value(); // 0..1.0 ; 0 = left
456 double step;
458 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
459 step = one_degree;
460 } else {
461 step = one_degree * 5.0;
464 switch (ev->direction) {
465 case GDK_SCROLL_LEFT:
466 wv += step;
467 width_control->set_value (wv);
468 break;
469 case GDK_SCROLL_UP:
470 pv -= step;
471 position_control->set_value (pv);
472 break;
473 case GDK_SCROLL_RIGHT:
474 wv -= step;
475 width_control->set_value (wv);
476 break;
477 case GDK_SCROLL_DOWN:
478 pv += step;
479 position_control->set_value (pv);
480 break;
483 return true;
486 bool
487 StereoPanner::on_motion_notify_event (GdkEventMotion* ev)
489 if (!dragging) {
490 return false;
493 if (!drag_data_window) {
494 drag_data_window = new Window (WINDOW_POPUP);
495 drag_data_window->set_name (X_("ContrastingPopup"));
496 drag_data_window->set_position (WIN_POS_MOUSE);
497 drag_data_window->set_decorated (false);
499 drag_data_label = manage (new Label);
500 drag_data_label->set_use_markup (true);
502 drag_data_window->set_border_width (6);
503 drag_data_window->add (*drag_data_label);
504 drag_data_label->show ();
506 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
507 if (toplevel) {
508 drag_data_window->set_transient_for (*toplevel);
512 if (!drag_data_window->is_visible ()) {
513 /* move the popup window vertically down from the panner display */
514 int rx, ry;
515 get_window()->get_origin (rx, ry);
516 drag_data_window->move (rx, ry+get_height());
517 drag_data_window->present ();
520 int w = get_width();
521 double delta = (ev->x - last_drag_x) / (double) w;
522 double current_width = width_control->get_value ();
524 if (dragging_left) {
525 delta = -delta;
528 if (dragging_left || dragging_right) {
530 /* maintain position as invariant as we change the width */
533 /* create a detent close to the center */
535 if (!detented && fabs (current_width) < 0.02) {
536 detented = true;
537 /* snap to zero */
538 width_control->set_value (0);
541 if (detented) {
543 accumulated_delta += delta;
545 /* have we pulled far enough to escape ? */
547 if (fabs (accumulated_delta) >= 0.025) {
548 width_control->set_value (current_width + accumulated_delta);
549 detented = false;
550 accumulated_delta = false;
553 } else {
554 width_control->set_value (current_width + delta);
557 } else if (dragging_position) {
559 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
560 position_control->set_value (pv + delta);
563 last_drag_x = ev->x;
564 return true;
567 bool
568 StereoPanner::on_key_press_event (GdkEventKey* ev)
570 double one_degree = 1.0/180.0;
571 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
572 double wv = width_control->get_value(); // 0..1.0 ; 0 = left
573 double step;
575 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
576 step = one_degree;
577 } else {
578 step = one_degree * 5.0;
581 /* up/down control width because we consider pan position more "important"
582 (and thus having higher "sense" priority) than width.
585 switch (ev->keyval) {
586 case GDK_Up:
587 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
588 width_control->set_value (1.0);
589 } else {
590 width_control->set_value (wv + step);
592 break;
593 case GDK_Down:
594 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
595 width_control->set_value (-1.0);
596 } else {
597 width_control->set_value (wv - step);
600 case GDK_Left:
601 pv -= step;
602 position_control->set_value (pv);
603 break;
604 case GDK_Right:
605 pv += step;
606 position_control->set_value (pv);
607 break;
609 break;
610 case GDK_0:
611 case GDK_KP_0:
612 width_control->set_value (0.0);
613 break;
615 default:
616 return false;
619 return true;
622 bool
623 StereoPanner::on_key_release_event (GdkEventKey* ev)
625 return false;
628 bool
629 StereoPanner::on_enter_notify_event (GdkEventCrossing* ev)
631 grab_focus ();
632 Keyboard::magic_widget_grab_focus ();
633 return false;
636 bool
637 StereoPanner::on_leave_notify_event (GdkEventCrossing*)
639 Keyboard::magic_widget_drop_focus ();
640 return false;
643 void
644 StereoPanner::set_colors ()
646 colors[Normal].fill = ARDOUR_UI::config()->canvasvar_StereoPannerFill.get();
647 colors[Normal].outline = ARDOUR_UI::config()->canvasvar_StereoPannerOutline.get();
648 colors[Normal].text = ARDOUR_UI::config()->canvasvar_StereoPannerText.get();
649 colors[Normal].background = ARDOUR_UI::config()->canvasvar_StereoPannerBackground.get();
650 colors[Normal].rule = ARDOUR_UI::config()->canvasvar_StereoPannerRule.get();
652 colors[Mono].fill = ARDOUR_UI::config()->canvasvar_StereoPannerMonoFill.get();
653 colors[Mono].outline = ARDOUR_UI::config()->canvasvar_StereoPannerMonoOutline.get();
654 colors[Mono].text = ARDOUR_UI::config()->canvasvar_StereoPannerMonoText.get();
655 colors[Mono].background = ARDOUR_UI::config()->canvasvar_StereoPannerMonoBackground.get();
656 colors[Mono].rule = ARDOUR_UI::config()->canvasvar_StereoPannerRule.get();
658 colors[Inverted].fill = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedFill.get();
659 colors[Inverted].outline = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedOutline.get();
660 colors[Inverted].text = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedText.get();
661 colors[Inverted].background = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedBackground.get();
662 colors[Inverted].rule = ARDOUR_UI::config()->canvasvar_StereoPannerRule.get();
665 void
666 StereoPanner::color_handler ()
668 set_colors ();
669 queue_draw ();