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