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