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