Implement reset() in panners, and use it from the GUI (fixes #4196).
[ardour.git] / gtk2_ardour / mono_panner.cc
1 /*
2     Copyright (C) 2000-2007 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 <iostream>
21 #include <iomanip>
22 #include <cstring>
23 #include <cmath>
24
25 #include <gtkmm/window.h>
26
27 #include "pbd/controllable.h"
28 #include "pbd/compose.h"
29
30 #include "gtkmm2ext/gui_thread.h"
31 #include "gtkmm2ext/gtk_ui.h"
32 #include "gtkmm2ext/keyboard.h"
33 #include "gtkmm2ext/utils.h"
34
35 #include "ardour/panner.h"
36 #include "ardour/panner.h"
37 #include "ardour/pannable.h"
38
39 #include "ardour_ui.h"
40 #include "global_signals.h"
41 #include "mono_panner.h"
42 #include "rgb_macros.h"
43 #include "utils.h"
44
45 #include "i18n.h"
46
47 using namespace std;
48 using namespace Gtk;
49 using namespace Gtkmm2ext;
50
51 static const int pos_box_size = 9;
52 static const int lr_box_size = 15;
53 static const int step_down = 10;
54 static const int top_step = 2;
55
56 MonoPanner::ColorScheme MonoPanner::colors;
57 bool MonoPanner::have_colors = false;
58
59 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::Panner> panner)
60         : _panner (panner)
61         , position_control (_panner->pannable()->pan_azimuth_control)
62         , dragging (false)
63         , drag_start_x (0)
64         , last_drag_x (0)
65         , accumulated_delta (0)
66         , detented (false)
67         , drag_data_window (0)
68         , drag_data_label (0)
69         , position_binder (position_control)
70 {
71         if (!have_colors) {
72                 set_colors ();
73                 have_colors = true;
74         }
75
76         position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
77
78         set_flags (Gtk::CAN_FOCUS);
79
80         add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|
81                     Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|
82                     Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|
83                     Gdk::SCROLL_MASK|
84                     Gdk::POINTER_MOTION_MASK);
85
86         ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
87 }
88
89 MonoPanner::~MonoPanner ()
90 {
91         delete drag_data_window;
92 }
93
94 void
95 MonoPanner::set_drag_data ()
96 {
97         if (!drag_data_label) {
98                 return;
99         }
100
101         double pos = position_control->get_value(); // 0..1
102
103         /* We show the position of the center of the image relative to the left & right.
104            This is expressed as a pair of percentage values that ranges from (100,0)
105            (hard left) through (50,50) (hard center) to (0,100) (hard right).
106
107            This is pretty wierd, but its the way audio engineers expect it. Just remember that
108            the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
109         */
110
111         char buf[64];
112         snprintf (buf, sizeof (buf), "L:%3d R:%3d",
113                   (int) rint (100.0 * (1.0 - pos)),
114                   (int) rint (100.0 * pos));
115         drag_data_label->set_markup (buf);
116 }
117
118 void
119 MonoPanner::value_change ()
120 {
121         set_drag_data ();
122         queue_draw ();
123 }
124
125 bool
126 MonoPanner::on_expose_event (GdkEventExpose*)
127 {
128         Glib::RefPtr<Gdk::Window> win (get_window());
129         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
130         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
131
132         int width, height;
133         double pos = position_control->get_value (); /* 0..1 */
134         uint32_t o, f, t, b, pf, po;
135         const double corner_radius = 5;
136
137         width = get_width();
138         height = get_height ();
139
140         o = colors.outline;
141         f = colors.fill;
142         t = colors.text;
143         b = colors.background;
144         pf = colors.pos_fill;
145         po = colors.pos_outline;
146
147         /* background */
148
149         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
150         context->rectangle (0, 0, width, height);
151         context->fill ();
152
153         double usable_width = width - pos_box_size;
154
155         /* compute the centers of the L/R boxes based on the current stereo width */
156
157         if (fmod (usable_width,2.0) == 0) {
158                 /* even width, but we need odd, so that there is an exact center.
159                    So, offset cairo by 1, and reduce effective width by 1
160                 */
161                 usable_width -= 1.0;
162                 context->translate (1.0, 0.0);
163         }
164
165         const double half_lr_box = lr_box_size/2.0;
166         double left;
167         double right;
168
169         left = 4 + half_lr_box; // center of left box
170         right = width  - 4 - half_lr_box; // center of right box
171
172         /* center line */
173         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
174         context->set_line_width (1.0);
175         context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
176         context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
177         context->stroke ();
178
179         /* left box */
180
181         rounded_rectangle (context,
182                           left - half_lr_box,
183                           half_lr_box+step_down,
184                           lr_box_size, lr_box_size, corner_radius);
185         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
186         context->stroke_preserve ();
187         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
188         context->fill ();
189
190         /* add text */
191
192         context->move_to (
193                        left - half_lr_box + 3,
194                        (lr_box_size/2) + step_down + 13);
195         context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
196         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
197         context->show_text (_("L"));
198
199         /* right box */
200
201         rounded_rectangle (context,
202                            right - half_lr_box,
203                            half_lr_box+step_down,
204                            lr_box_size, lr_box_size, corner_radius);
205         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
206         context->stroke_preserve ();
207         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
208         context->fill ();
209
210         /* add text */
211
212         context->move_to (
213                        right - half_lr_box + 3,
214                        (lr_box_size/2)+step_down + 13);
215         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
216         context->show_text (_("R"));
217
218         /* 2 lines that connect them both */
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->set_line_width (1.0);
221
222         /* make the lines a little longer than they need to be, because the corners of
223            the boxes are rounded and we don't want a gap
224         */
225         context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
226         context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
227         context->stroke ();
228
229
230         context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
231         context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
232         context->stroke ();
233
234         /* draw the position indicator */
235
236         double spos = (pos_box_size/2.0) + (usable_width * pos);
237
238         context->set_line_width (2.0);
239         context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
240         context->rel_line_to (0.0, pos_box_size); /* lower right */
241         context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
242         context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
243         context->rel_line_to (0.0, -pos_box_size); /* upper left */
244         context->close_path ();
245
246
247         context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
248         context->stroke_preserve ();
249         context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
250         context->fill ();
251
252         /* marker line */
253
254         context->set_line_width (1.0);
255         context->move_to (spos, pos_box_size+4);
256         context->rel_line_to (0, half_lr_box+step_down);
257         context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
258         context->stroke ();
259
260         /* done */
261
262         return true;
263 }
264
265 bool
266 MonoPanner::on_button_press_event (GdkEventButton* ev)
267 {
268         drag_start_x = ev->x;
269         last_drag_x = ev->x;
270
271         dragging = false;
272         accumulated_delta = 0;
273         detented = false;
274
275         /* Let the binding proxies get first crack at the press event
276          */
277
278         if (ev->y < 20) {
279                 if (position_binder.button_press_handler (ev)) {
280                         return true;
281                 }
282         }
283
284         if (ev->button != 1) {
285                 return false;
286         }
287
288         if (ev->type == GDK_2BUTTON_PRESS) {
289                 int width = get_width();
290
291                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
292                         /* handled by button release */
293                         return true;
294                 }
295
296
297                 if (ev->x <= width/3) {
298                         /* left side dbl click */
299                         position_control->set_value (0);
300                 } else if (ev->x > 2*width/3) {
301                         position_control->set_value (1.0);
302                 } else {
303                         position_control->set_value (0.5);
304                 }
305
306                 dragging = false;
307
308         } else if (ev->type == GDK_BUTTON_PRESS) {
309
310                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
311                         /* handled by button release */
312                         return true;
313                 }
314
315                 dragging = true;
316                 StartGesture ();
317         }
318
319         return true;
320 }
321
322 bool
323 MonoPanner::on_button_release_event (GdkEventButton* ev)
324 {
325         if (ev->button != 1) {
326                 return false;
327         }
328
329         dragging = false;
330         accumulated_delta = 0;
331         detented = false;
332
333         if (drag_data_window) {
334                 drag_data_window->hide ();
335         }
336
337         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
338                 _panner->reset ();
339         } else {
340                 StopGesture ();
341         }
342
343         return true;
344 }
345
346 bool
347 MonoPanner::on_scroll_event (GdkEventScroll* ev)
348 {
349         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
350         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
351         double step;
352
353         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
354                 step = one_degree;
355         } else {
356                 step = one_degree * 5.0;
357         }
358
359         switch (ev->direction) {
360         case GDK_SCROLL_UP:
361         case GDK_SCROLL_LEFT:
362                 pv -= step;
363                 position_control->set_value (pv);
364                 break;
365         case GDK_SCROLL_DOWN:
366         case GDK_SCROLL_RIGHT:
367                 pv += step;
368                 position_control->set_value (pv);
369                 break;
370         }
371
372         return true;
373 }
374
375 bool
376 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
377 {
378         if (!dragging) {
379                 return false;
380         }
381
382         if (!drag_data_window) {
383                 drag_data_window = new Window (WINDOW_POPUP);
384                 drag_data_window->set_name (X_("ContrastingPopup"));
385                 drag_data_window->set_position (WIN_POS_MOUSE);
386                 drag_data_window->set_decorated (false);
387
388                 drag_data_label = manage (new Label);
389                 drag_data_label->set_use_markup (true);
390
391                 drag_data_window->set_border_width (6);
392                 drag_data_window->add (*drag_data_label);
393                 drag_data_label->show ();
394
395                 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
396                 if (toplevel) {
397                         drag_data_window->set_transient_for (*toplevel);
398                 }
399         }
400
401         if (!drag_data_window->is_visible ()) {
402                 /* move the window a little away from the mouse */
403                 int rx, ry;
404                 get_window()->get_origin (rx, ry);
405                 drag_data_window->move (rx, ry+get_height());
406                 drag_data_window->present ();
407         }
408
409         int w = get_width();
410         double delta = (ev->x - last_drag_x) / (double) w;
411
412         /* create a detent close to the center */
413
414         if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
415                 detented = true;
416                 /* snap to center */
417                 position_control->set_value (0.5);
418         }
419
420         if (detented) {
421                 accumulated_delta += delta;
422
423                 /* have we pulled far enough to escape ? */
424
425                 if (fabs (accumulated_delta) >= 0.025) {
426                         position_control->set_value (position_control->get_value() + accumulated_delta);
427                         detented = false;
428                         accumulated_delta = false;
429                 }
430         } else {
431                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
432                 position_control->set_value (pv + delta);
433         }
434
435         last_drag_x = ev->x;
436         return true;
437 }
438
439 bool
440 MonoPanner::on_key_press_event (GdkEventKey* ev)
441 {
442         double one_degree = 1.0/180.0;
443         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
444         double step;
445
446         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
447                 step = one_degree;
448         } else {
449                 step = one_degree * 5.0;
450         }
451
452         /* up/down control width because we consider pan position more "important"
453            (and thus having higher "sense" priority) than width.
454         */
455
456         switch (ev->keyval) {
457         case GDK_Left:
458                 pv -= step;
459                 position_control->set_value (pv);
460                 break;
461         case GDK_Right:
462                 pv += step;
463                 position_control->set_value (pv);
464                 break;
465         default:
466                 return false;
467         }
468
469         return true;
470 }
471
472 bool
473 MonoPanner::on_key_release_event (GdkEventKey*)
474 {
475         return false;
476 }
477
478 bool
479 MonoPanner::on_enter_notify_event (GdkEventCrossing*)
480 {
481         grab_focus ();
482         Keyboard::magic_widget_grab_focus ();
483         return false;
484 }
485
486 bool
487 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
488 {
489         Keyboard::magic_widget_drop_focus ();
490         return false;
491 }
492
493 void
494 MonoPanner::set_colors ()
495 {
496         colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
497         colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
498         colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
499         colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
500         colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
501         colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
502 }
503
504 void
505 MonoPanner::color_handler ()
506 {
507         set_colors ();
508         queue_draw ();
509 }