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