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