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