Preferences/Config changes for image-surface settings
[ardour.git] / gtk2_ardour / mono_panner.cc
1 /*
2  * Copyright (C) 2011-2012 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2011-2012 David Robillard <d@drobilla.net>
4  * Copyright (C) 2011-2016 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2014-2017 Robin Gareus <robin@gareus.org>
6  * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22
23 #include <iostream>
24 #include <iomanip>
25 #include <cstring>
26 #include <cmath>
27
28 #include <gtkmm/window.h>
29 #include <pangomm/layout.h>
30
31 #include "pbd/compose.h"
32
33 #include "gtkmm2ext/gui_thread.h"
34 #include "gtkmm2ext/gtk_ui.h"
35 #include "gtkmm2ext/keyboard.h"
36 #include "gtkmm2ext/utils.h"
37 #include "gtkmm2ext/persistent_tooltip.h"
38
39 #include "ardour/pannable.h"
40 #include "ardour/panner.h"
41 #include "ardour/panner_shell.h"
42
43 #include "mono_panner.h"
44 #include "mono_panner_editor.h"
45 #include "rgb_macros.h"
46 #include "ui_config.h"
47 #include "utils.h"
48
49 #include "pbd/i18n.h"
50
51 using namespace std;
52 using namespace Gtk;
53 using namespace Gtkmm2ext;
54 using namespace ARDOUR_UI_UTILS;
55
56 using PBD::Controllable;
57
58 MonoPanner::ColorScheme MonoPanner::colors;
59 bool MonoPanner::have_colors = false;
60
61 Pango::AttrList MonoPanner::panner_font_attributes;
62 bool            MonoPanner::have_font = false;
63
64 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::PannerShell> p)
65         : PannerInterface (p->panner())
66         , _panner_shell (p)
67         , position_control (_panner->pannable()->pan_azimuth_control)
68         , drag_start_x (0)
69         , last_drag_x (0)
70         , accumulated_delta (0)
71         , detented (false)
72         , position_binder (position_control)
73         , _dragging (false)
74 {
75         if (!have_colors) {
76                 set_colors ();
77                 have_colors = true;
78         }
79         if (!have_font) {
80                 Pango::FontDescription font;
81                 Pango::AttrFontDesc* font_attr;
82                 font = Pango::FontDescription (UIConfiguration::instance().get_SmallBoldMonospaceFont());
83                 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
84                 panner_font_attributes.change(*font_attr);
85                 delete font_attr;
86                 have_font = true;
87         }
88
89         position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
90
91         _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
92         _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::pannable_handler, this), gui_context());
93         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
94
95         set_tooltip ();
96 }
97
98 MonoPanner::~MonoPanner ()
99 {
100
101 }
102
103 void
104 MonoPanner::set_tooltip ()
105 {
106         if (_panner_shell->bypassed()) {
107                 _tooltip.set_tip (_("bypassed"));
108                 return;
109         }
110         double pos = position_control->get_value(); // 0..1
111
112         /* We show the position of the center of the image relative to the left & right.
113                  This is expressed as a pair of percentage values that ranges from (100,0)
114                  (hard left) through (50,50) (hard center) to (0,100) (hard right).
115
116                  This is pretty wierd, but its the way audio engineers expect it. Just remember that
117                  the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
118                  */
119
120         char buf[64];
121         snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
122                         (int) rint (100.0 * (1.0 - pos)),
123                         (int) rint (100.0 * pos));
124         _tooltip.set_tip (buf);
125 }
126
127 bool
128 MonoPanner::on_expose_event (GdkEventExpose*)
129 {
130         Glib::RefPtr<Gdk::Window> win (get_window());
131         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
132         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
133
134         int width, height;
135         double pos = position_control->get_value (); /* 0..1 */
136         uint32_t o, f, t, b, pf, po;
137
138         width = get_width();
139         height = get_height ();
140
141         const int step_down = rint(height / 3.5);
142         const int lr_box_size = height - 2 * step_down;
143         const int pos_box_size = (int)(rint(step_down * .8)) | 1;
144         const int top_step = step_down - pos_box_size;
145         const double corner_radius = 5 * UIConfiguration::instance().get_ui_scale();
146
147         o = colors.outline;
148         f = colors.fill;
149         t = colors.text;
150         b = colors.background;
151         pf = colors.pos_fill;
152         po = colors.pos_outline;
153
154         if (_panner_shell->bypassed()) {
155                 b  = 0x20202040;
156                 f  = 0x404040ff;
157                 o  = 0x606060ff;
158                 po = 0x606060ff;
159                 pf = 0x404040ff;
160                 t  = 0x606060ff;
161         }
162
163         if (_send_mode) {
164                 b = UIConfiguration::instance().color ("send bg");
165         }
166         /* background */
167         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
168         context->rectangle (0, 0, width, height);
169         context->fill ();
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 = pos_box_size * .5 + half_lr_box; // center of left box
179         const double right = width - pos_box_size * .5 - 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 (S_("Panner|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 (S_("Panner|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 * UIConfiguration::instance().get_ui_scale()); /* bottom point */
261         context->rel_line_to (-pos_box_size/2.0, -4.0 * UIConfiguration::instance().get_ui_scale()); /* 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, 1 + top_step + pos_box_size + 4.0 * UIConfiguration::instance().get_ui_scale());
274         context->line_to (spos, half_lr_box + step_down + lr_box_size - 1);
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, Controllable::NoGroup);
326                 } else if (ev->x > 2*width/3) {
327                         position_control->set_value (1.0, Controllable::NoGroup);
328                 } else {
329                         position_control->set_value (0.5, Controllable::NoGroup);
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, Controllable::NoGroup);
401                         break;
402                 case GDK_SCROLL_DOWN:
403                 case GDK_SCROLL_RIGHT:
404                         pv += step;
405                         position_control->set_value (pv, Controllable::NoGroup);
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, Controllable::NoGroup);
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, Controllable::NoGroup);
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, Controllable::NoGroup);
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, Controllable::NoGroup);
473                         break;
474                 case GDK_Right:
475                         pv += step;
476                         position_control->set_value (pv, Controllable::NoGroup);
477                         break;
478                 case GDK_0:
479                 case GDK_KP_0:
480                         position_control->set_value (0.0, Controllable::NoGroup);
481                         break;
482                 default:
483                         return false;
484         }
485
486         return true;
487 }
488
489 void
490 MonoPanner::set_colors ()
491 {
492         colors.fill = UIConfiguration::instance().color_mod ("mono panner fill", "panner fill");
493         colors.outline = UIConfiguration::instance().color ("mono panner outline");
494         colors.text = UIConfiguration::instance().color ("mono panner text");
495         colors.background = UIConfiguration::instance().color ("mono panner bg");
496         colors.pos_outline = UIConfiguration::instance().color ("mono panner position outline");
497         colors.pos_fill = UIConfiguration::instance().color_mod ("mono panner position fill", "mono panner position fill");
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 }