fix merge errors 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 (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
93
94         _panner_shell->Changed.connect (connections, invalidator (*this), boost::bind (&MonoPanner::bypass_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         /* background */
161         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
162         context->rectangle (0, 0, width, height);
163         context->fill ();
164
165
166         double usable_width = width - pos_box_size;
167
168         /* compute the centers of the L/R boxes based on the current stereo width */
169         if (fmod (usable_width,2.0) == 0) {
170                 usable_width -= 1.0;
171         }
172         const double half_lr_box = lr_box_size/2.0;
173         const double left = 4 + half_lr_box; // center of left box
174         const double right = width - 4 - half_lr_box; // center of right box
175
176         /* center line */
177         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
178         context->set_line_width (1.0);
179         context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
180         context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
181         context->stroke ();
182
183         context->set_line_width (1.0);
184         /* left box */
185
186         rounded_left_half_rectangle (context,
187                         left - half_lr_box + .5,
188                         half_lr_box + step_down,
189                         lr_box_size, lr_box_size, corner_radius);
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_preserve ();
192         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
193         context->stroke();
194
195         /* add text */
196         int tw, th;
197         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
198         layout->set_attributes (panner_font_attributes);
199
200         layout->set_text (_("L"));
201         layout->get_pixel_size(tw, th);
202         context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
203         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
204         pango_cairo_show_layout (context->cobj(), layout->gobj());
205
206         /* right box */
207         rounded_right_half_rectangle (context,
208                         right - half_lr_box - .5,
209                         half_lr_box + step_down,
210                         lr_box_size, lr_box_size, corner_radius);
211         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
212         context->fill_preserve ();
213         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
214         context->stroke();
215
216         /* add text */
217         layout->set_text (_("R"));
218         layout->get_pixel_size(tw, th);
219         context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
220         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
221         pango_cairo_show_layout (context->cobj(), layout->gobj());
222
223         /* 2 lines that connect them both */
224         context->set_line_width (1.0);
225
226         if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
227                 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
228                 context->move_to (left  + half_lr_box, half_lr_box + step_down);
229                 context->line_to (right - half_lr_box, half_lr_box + step_down);
230                 context->stroke ();
231
232                 context->move_to (left  + half_lr_box, half_lr_box+step_down+lr_box_size);
233                 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
234                 context->stroke ();
235         } else {
236                 context->move_to (left  + half_lr_box, half_lr_box+step_down+lr_box_size);
237                 context->line_to (left  + half_lr_box, half_lr_box + step_down);
238                 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box+step_down+lr_box_size);
239                 context->line_to (right - half_lr_box, half_lr_box + step_down);
240                 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
241                 context->close_path();
242
243                 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
244                 context->fill_preserve ();
245                 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
246                 context->stroke ();
247         }
248
249         /* draw the position indicator */
250         double spos = (pos_box_size/2.0) + (usable_width * pos);
251
252         context->set_line_width (2.0);
253         context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
254         context->rel_line_to (0.0, pos_box_size); /* lower right */
255         context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
256         context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
257         context->rel_line_to (0.0, -pos_box_size); /* upper left */
258         context->close_path ();
259
260
261         context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
262         context->stroke_preserve ();
263         context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
264         context->fill ();
265
266         /* marker line */
267         context->set_line_width (1.0);
268         context->move_to (spos, pos_box_size + 5);
269         context->rel_line_to (0, half_lr_box+step_down);
270         context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
271         context->stroke ();
272
273         /* done */
274
275         return true;
276 }
277
278 bool
279 MonoPanner::on_button_press_event (GdkEventButton* ev)
280 {
281         if (PannerInterface::on_button_press_event (ev)) {
282                 return true;
283         }
284         if (_panner_shell->bypassed()) {
285                 return false;
286         }
287
288         drag_start_x = ev->x;
289         last_drag_x = ev->x;
290
291         _dragging = false;
292         _tooltip.target_stop_drag ();
293         accumulated_delta = 0;
294         detented = false;
295
296         /* Let the binding proxies get first crack at the press event
297         */
298
299         if (ev->y < 20) {
300                 if (position_binder.button_press_handler (ev)) {
301                         return true;
302                 }
303         }
304
305         if (ev->button != 1) {
306                 return false;
307         }
308
309         if (ev->type == GDK_2BUTTON_PRESS) {
310                 int width = get_width();
311
312                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
313                         /* handled by button release */
314                         return true;
315                 }
316
317
318                 if (ev->x <= width/3) {
319                         /* left side dbl click */
320                         position_control->set_value (0);
321                 } else if (ev->x > 2*width/3) {
322                         position_control->set_value (1.0);
323                 } else {
324                         position_control->set_value (0.5);
325                 }
326
327                 _dragging = false;
328                 _tooltip.target_stop_drag ();
329
330         } else if (ev->type == GDK_BUTTON_PRESS) {
331
332                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
333                         /* handled by button release */
334                         return true;
335                 }
336
337                 _dragging = true;
338                 _tooltip.target_start_drag ();
339                 StartGesture ();
340         }
341
342         return true;
343 }
344
345 bool
346 MonoPanner::on_button_release_event (GdkEventButton* ev)
347 {
348         if (PannerInterface::on_button_release_event (ev)) {
349                 return true;
350         }
351
352         if (ev->button != 1) {
353                 return false;
354         }
355
356         if (_panner_shell->bypassed()) {
357                 return false;
358         }
359
360         _dragging = false;
361         _tooltip.target_stop_drag ();
362         accumulated_delta = 0;
363         detented = false;
364
365         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
366                 _panner->reset ();
367         } else {
368                 StopGesture ();
369         }
370
371         return true;
372 }
373
374 bool
375 MonoPanner::on_scroll_event (GdkEventScroll* ev)
376 {
377         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
378         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
379         double step;
380
381         if (_panner_shell->bypassed()) {
382                 return false;
383         }
384
385         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
386                 step = one_degree;
387         } else {
388                 step = one_degree * 5.0;
389         }
390
391         switch (ev->direction) {
392                 case GDK_SCROLL_UP:
393                 case GDK_SCROLL_LEFT:
394                         pv -= step;
395                         position_control->set_value (pv);
396                         break;
397                 case GDK_SCROLL_DOWN:
398                 case GDK_SCROLL_RIGHT:
399                         pv += step;
400                         position_control->set_value (pv);
401                         break;
402         }
403
404         return true;
405 }
406
407 bool
408 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
409 {
410         if (_panner_shell->bypassed()) {
411                 _dragging = false;
412         }
413         if (!_dragging) {
414                 return false;
415         }
416
417         int w = get_width();
418         double delta = (ev->x - last_drag_x) / (double) w;
419
420         /* create a detent close to the center */
421
422         if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
423                 detented = true;
424                 /* snap to center */
425                 position_control->set_value (0.5);
426         }
427
428         if (detented) {
429                 accumulated_delta += delta;
430
431                 /* have we pulled far enough to escape ? */
432
433                 if (fabs (accumulated_delta) >= 0.025) {
434                         position_control->set_value (position_control->get_value() + accumulated_delta);
435                         detented = false;
436                         accumulated_delta = false;
437                 }
438         } else {
439                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
440                 position_control->set_value (pv + delta);
441         }
442
443         last_drag_x = ev->x;
444         return true;
445 }
446
447 bool
448 MonoPanner::on_key_press_event (GdkEventKey* ev)
449 {
450         double one_degree = 1.0/180.0;
451         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
452         double step;
453
454         if (_panner_shell->bypassed()) {
455                 return false;
456         }
457
458         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
459                 step = one_degree;
460         } else {
461                 step = one_degree * 5.0;
462         }
463
464         switch (ev->keyval) {
465                 case GDK_Left:
466                         pv -= step;
467                         position_control->set_value (pv);
468                         break;
469                 case GDK_Right:
470                         pv += step;
471                         position_control->set_value (pv);
472                         break;
473                 case GDK_0:
474                 case GDK_KP_0:
475                         position_control->set_value (0.0);
476                         break;
477                 default:
478                         return false;
479         }
480
481         return true;
482 }
483
484 void
485 MonoPanner::set_colors ()
486 {
487         colors.fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerFill();
488         colors.outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerOutline();
489         colors.text = ARDOUR_UI::config()->get_canvasvar_MonoPannerText();
490         colors.background = ARDOUR_UI::config()->get_canvasvar_MonoPannerBackground();
491         colors.pos_outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionOutline();
492         colors.pos_fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionFill();
493 }
494
495 void
496 MonoPanner::color_handler ()
497 {
498         set_colors ();
499         queue_draw ();
500 }
501
502 void
503 MonoPanner::bypass_handler ()
504 {
505         queue_draw ();
506 }
507
508 PannerEditor*
509 MonoPanner::editor ()
510 {
511         return new MonoPannerEditor (this);
512 }