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