23b64eee28a18f9c178973384977d40b4de86104
[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 "mono_panner.h"
42 #include "mono_panner_editor.h"
43 #include "rgb_macros.h"
44 #include "ui_config.h"
45 #include "utils.h"
46
47 #include "i18n.h"
48
49 using namespace std;
50 using namespace Gtk;
51 using namespace Gtkmm2ext;
52 using namespace ARDOUR_UI_UTILS;
53
54 MonoPanner::ColorScheme MonoPanner::colors;
55 bool MonoPanner::have_colors = false;
56
57 Pango::AttrList MonoPanner::panner_font_attributes;
58 bool            MonoPanner::have_font = false;
59
60 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::PannerShell> p)
61         : PannerInterface (p->panner())
62         , _panner_shell (p)
63         , position_control (_panner->pannable()->pan_azimuth_control)
64         , drag_start_x (0)
65         , last_drag_x (0)
66         , accumulated_delta (0)
67         , detented (false)
68         , position_binder (position_control)
69         , _dragging (false)
70 {
71         if (!have_colors) {
72                 set_colors ();
73                 have_colors = true;
74         }
75         if (!have_font) {
76                 Pango::FontDescription font;
77                 Pango::AttrFontDesc* font_attr;
78                 font = Pango::FontDescription (UIConfiguration::instance().get_SmallBoldMonospaceFont());
79                 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
80                 panner_font_attributes.change(*font_attr);
81                 delete font_attr;
82                 have_font = true;
83         }
84
85         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
86
87         _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
88         _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::pannable_handler, this), gui_context());
89         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
90
91         set_tooltip ();
92 }
93
94 MonoPanner::~MonoPanner ()
95 {
96
97 }
98
99 void
100 MonoPanner::set_tooltip ()
101 {
102         if (_panner_shell->bypassed()) {
103                 _tooltip.set_tip (_("bypassed"));
104                 return;
105         }
106         double pos = position_control->get_value(); // 0..1
107
108         /* We show the position of the center of the image relative to the left & right.
109                  This is expressed as a pair of percentage values that ranges from (100,0)
110                  (hard left) through (50,50) (hard center) to (0,100) (hard right).
111
112                  This is pretty wierd, but its the way audio engineers expect it. Just remember that
113                  the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
114                  */
115
116         char buf[64];
117         snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
118                         (int) rint (100.0 * (1.0 - pos)),
119                         (int) rint (100.0 * pos));
120         _tooltip.set_tip (buf);
121 }
122
123 bool
124 MonoPanner::on_expose_event (GdkEventExpose*)
125 {
126         Glib::RefPtr<Gdk::Window> win (get_window());
127         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
128         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
129
130         int width, height;
131         double pos = position_control->get_value (); /* 0..1 */
132         uint32_t o, f, t, b, pf, po;
133
134         width = get_width();
135         height = get_height ();
136
137         const int step_down = rint(height / 3.5);
138         const int lr_box_size = height - 2 * step_down;
139         const int pos_box_size = (int)(rint(step_down * .8)) | 1;
140         const int top_step = step_down - pos_box_size;
141         const double corner_radius = 5 * UIConfiguration::instance().get_ui_scale();
142
143         o = colors.outline;
144         f = colors.fill;
145         t = colors.text;
146         b = colors.background;
147         pf = colors.pos_fill;
148         po = colors.pos_outline;
149
150         if (_panner_shell->bypassed()) {
151                 b  = 0x20202040;
152                 f  = 0x404040ff;
153                 o  = 0x606060ff;
154                 po = 0x606060ff;
155                 pf = 0x404040ff;
156                 t  = 0x606060ff;
157         }
158
159         if (_send_mode) {
160                 b = UIConfiguration::instance().color ("send bg");
161         }
162         /* background */
163         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
164         context->rectangle (0, 0, width, height);
165         context->fill ();
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 = pos_box_size * .5 + half_lr_box; // center of left box
175         const double right = width - pos_box_size * .5 - 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 (S_("Panner|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 (S_("Panner|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 * UIConfiguration::instance().get_ui_scale()); /* bottom point */
257         context->rel_line_to (-pos_box_size/2.0, -4.0 * UIConfiguration::instance().get_ui_scale()); /* 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, 1 + top_step + pos_box_size + 4.0 * UIConfiguration::instance().get_ui_scale());
270         context->line_to (spos, half_lr_box + step_down + lr_box_size - 1);
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 = UIConfiguration::instance().color_mod ("mono panner fill", "panner fill");
489         colors.outline = UIConfiguration::instance().color ("mono panner outline");
490         colors.text = UIConfiguration::instance().color ("mono panner text");
491         colors.background = UIConfiguration::instance().color ("mono panner bg");
492         colors.pos_outline = UIConfiguration::instance().color ("mono panner position outline");
493         colors.pos_fill = UIConfiguration::instance().color_mod ("mono panner position fill", "mono panner position fill");
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 }