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