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
MonoPanner(boost::shared_ptr<ARDOUR::PannerShell> p)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
~MonoPanner()98 MonoPanner::~MonoPanner ()
99 {
100
101 }
102
103 void
set_tooltip()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
on_expose_event(GdkEventExpose *)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 = _send_mode ? colors.send_bg : colors.background;
151 pf = (_send_mode && !_panner_shell->is_linked_to_route()) ? colors.send_pan : 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 /* 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
on_button_press_event(GdkEventButton * ev)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
on_button_release_event(GdkEventButton * ev)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
on_scroll_event(GdkEventScroll * ev)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
on_motion_notify_event(GdkEventMotion * ev)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, at approx 1/180 deg */
423 if (!detented && fabsf (position_control->get_value() - .5f) < 0.006f) {
424 detented = true;
425 /* snap to center */
426 position_control->set_value (0.5, Controllable::NoGroup);
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.048) {
435 position_control->set_value (position_control->get_value() + (accumulated_delta > 0 ? 0.006 : -0.006), Controllable::NoGroup);
436 detented = false;
437 accumulated_delta = 0;
438 }
439 } else {
440 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
441 position_control->set_value (pv + delta, Controllable::NoGroup);
442 }
443
444 last_drag_x = ev->x;
445 return true;
446 }
447
448 bool
on_key_press_event(GdkEventKey * ev)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, Controllable::NoGroup);
469 break;
470 case GDK_Right:
471 pv += step;
472 position_control->set_value (pv, Controllable::NoGroup);
473 break;
474 case GDK_0:
475 case GDK_KP_0:
476 position_control->set_value (0.0, Controllable::NoGroup);
477 break;
478 default:
479 return false;
480 }
481
482 return true;
483 }
484
485 void
set_colors()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 colors.send_bg = UIConfiguration::instance().color ("send bg");
495 colors.send_pan = UIConfiguration::instance().color ("send pan");
496 }
497
498 void
color_handler()499 MonoPanner::color_handler ()
500 {
501 set_colors ();
502 queue_draw ();
503 }
504
505 void
bypass_handler()506 MonoPanner::bypass_handler ()
507 {
508 queue_draw ();
509 }
510
511 void
pannable_handler()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*
editor()522 MonoPanner::editor ()
523 {
524 return new MonoPannerEditor (this);
525 }
526