draw settings of bypassed panners
[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
27 #include "pbd/controllable.h"
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 "ardour_ui.h"
41 #include "global_signals.h"
42 #include "mono_panner.h"
43 #include "mono_panner_editor.h"
44 #include "rgb_macros.h"
45 #include "utils.h"
46
47 #include "i18n.h"
48
49 using namespace std;
50 using namespace Gtk;
51 using namespace Gtkmm2ext;
52
53 static const int pos_box_size = 9;
54 static const int lr_box_size = 15;
55 static const int step_down = 10;
56 static const int top_step = 2;
57
58 MonoPanner::ColorScheme MonoPanner::colors;
59 bool MonoPanner::have_colors = 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
77         position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
78
79         _panner_shell->Changed.connect (connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
80         ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
81
82         set_tooltip ();
83 }
84
85 MonoPanner::~MonoPanner ()
86 {
87         
88 }
89
90 void
91 MonoPanner::set_tooltip ()
92 {
93         if (_panner_shell->bypassed()) {
94                 _tooltip.set_tip (_("bypassed"));
95                 return;
96         }
97         double pos = position_control->get_value(); // 0..1
98
99         /* We show the position of the center of the image relative to the left & right.
100            This is expressed as a pair of percentage values that ranges from (100,0)
101            (hard left) through (50,50) (hard center) to (0,100) (hard right).
102
103            This is pretty wierd, but its the way audio engineers expect it. Just remember that
104            the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
105         */
106
107         char buf[64];
108         snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
109                   (int) rint (100.0 * (1.0 - pos)),
110                   (int) rint (100.0 * pos));
111         _tooltip.set_tip (buf);
112 }
113
114 bool
115 MonoPanner::on_expose_event (GdkEventExpose*)
116 {
117         Glib::RefPtr<Gdk::Window> win (get_window());
118         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
119         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
120
121         int width, height;
122         double pos = position_control->get_value (); /* 0..1 */
123         uint32_t o, f, t, b, pf, po;
124         const double corner_radius = 5;
125
126         width = get_width();
127         height = get_height ();
128
129         o = colors.outline;
130         f = colors.fill;
131         t = colors.text;
132         b = colors.background;
133         pf = colors.pos_fill;
134         po = colors.pos_outline;
135
136         if (_panner_shell->bypassed()) {
137                 b  = 0x20202040;
138                 f  = 0x404040ff;
139                 o  = 0x606060ff;
140                 po = 0x606060ff;
141                 pf = 0x404040ff;
142                 t  = 0x606060ff;
143         }
144
145         /* background */
146
147         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
148         context->rectangle (0, 0, width, height);
149         context->fill ();
150
151         double usable_width = width - pos_box_size;
152
153         /* compute the centers of the L/R boxes based on the current stereo width */
154
155         if (fmod (usable_width,2.0) == 0) {
156                 /* even width, but we need odd, so that there is an exact center.
157                    So, offset cairo by 1, and reduce effective width by 1
158                 */
159                 usable_width -= 1.0;
160                 context->translate (1.0, 0.0);
161         }
162
163         const double half_lr_box = lr_box_size/2.0;
164         double left;
165         double right;
166
167         left = 4 + half_lr_box; // center of left box
168         right = width  - 4 - half_lr_box; // center of right box
169
170         /* center line */
171         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
172         context->set_line_width (1.0);
173         context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
174         context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
175         context->stroke ();
176
177         /* left box */
178
179         rounded_rectangle (context,
180                           left - half_lr_box,
181                           half_lr_box+step_down,
182                           lr_box_size, lr_box_size, corner_radius);
183         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
184         context->stroke_preserve ();
185         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
186         context->fill ();
187
188         /* add text */
189
190         context->move_to (
191                        left - half_lr_box + 3,
192                        (lr_box_size/2) + step_down + 13);
193         context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
194         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
195         context->show_text (_("L"));
196
197         /* right box */
198
199         rounded_rectangle (context,
200                            right - half_lr_box,
201                            half_lr_box+step_down,
202                            lr_box_size, lr_box_size, corner_radius);
203         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
204         context->stroke_preserve ();
205         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
206         context->fill ();
207
208         /* add text */
209
210         context->move_to (
211                        right - half_lr_box + 3,
212                        (lr_box_size/2)+step_down + 13);
213         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
214         context->show_text (_("R"));
215
216         /* 2 lines that connect them both */
217         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
218         context->set_line_width (1.0);
219
220         /* make the lines a little longer than they need to be, because the corners of
221            the boxes are rounded and we don't want a gap
222         */
223         context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
224         context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
225         context->stroke ();
226
227
228         context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
229         context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
230         context->stroke ();
231
232         /* draw the position indicator */
233
234         double spos = (pos_box_size/2.0) + (usable_width * pos);
235
236         context->set_line_width (2.0);
237         context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
238         context->rel_line_to (0.0, pos_box_size); /* lower right */
239         context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
240         context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
241         context->rel_line_to (0.0, -pos_box_size); /* upper left */
242         context->close_path ();
243
244
245         context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
246         context->stroke_preserve ();
247         context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
248         context->fill ();
249
250         /* marker line */
251
252         context->set_line_width (1.0);
253         context->move_to (spos, pos_box_size+4);
254         context->rel_line_to (0, half_lr_box+step_down);
255         context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
256         context->stroke ();
257
258         /* done */
259
260         return true;
261 }
262
263 bool
264 MonoPanner::on_button_press_event (GdkEventButton* ev)
265 {
266         if (PannerInterface::on_button_press_event (ev)) {
267                 return true;
268         }
269         if (_panner_shell->bypassed()) {
270                 return false;
271         }
272         
273         drag_start_x = ev->x;
274         last_drag_x = ev->x;
275
276         _dragging = false;
277         _tooltip.target_stop_drag ();
278         accumulated_delta = 0;
279         detented = false;
280
281         /* Let the binding proxies get first crack at the press event
282          */
283
284         if (ev->y < 20) {
285                 if (position_binder.button_press_handler (ev)) {
286                         return true;
287                 }
288         }
289
290         if (ev->button != 1) {
291                 return false;
292         }
293
294         if (ev->type == GDK_2BUTTON_PRESS) {
295                 int width = get_width();
296
297                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
298                         /* handled by button release */
299                         return true;
300                 }
301
302
303                 if (ev->x <= width/3) {
304                         /* left side dbl click */
305                         position_control->set_value (0);
306                 } else if (ev->x > 2*width/3) {
307                         position_control->set_value (1.0);
308                 } else {
309                         position_control->set_value (0.5);
310                 }
311
312                 _dragging = false;
313                 _tooltip.target_stop_drag ();
314
315         } else if (ev->type == GDK_BUTTON_PRESS) {
316
317                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
318                         /* handled by button release */
319                         return true;
320                 }
321
322                 _dragging = true;
323                 _tooltip.target_start_drag ();
324                 StartGesture ();
325         }
326
327         return true;
328 }
329
330 bool
331 MonoPanner::on_button_release_event (GdkEventButton* ev)
332 {
333         if (PannerInterface::on_button_release_event (ev)) {
334                 return true;
335         }
336
337         if (ev->button != 1) {
338                 return false;
339         }
340
341         if (_panner_shell->bypassed()) {
342                 return false;
343         }
344
345         _dragging = false;
346         _tooltip.target_stop_drag ();
347         accumulated_delta = 0;
348         detented = false;
349
350         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
351                 _panner->reset ();
352         } else {
353                 StopGesture ();
354         }
355
356         return true;
357 }
358
359 bool
360 MonoPanner::on_scroll_event (GdkEventScroll* ev)
361 {
362         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
363         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
364         double step;
365
366         if (_panner_shell->bypassed()) {
367                 return false;
368         }
369
370         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
371                 step = one_degree;
372         } else {
373                 step = one_degree * 5.0;
374         }
375
376         switch (ev->direction) {
377         case GDK_SCROLL_UP:
378         case GDK_SCROLL_LEFT:
379                 pv -= step;
380                 position_control->set_value (pv);
381                 break;
382         case GDK_SCROLL_DOWN:
383         case GDK_SCROLL_RIGHT:
384                 pv += step;
385                 position_control->set_value (pv);
386                 break;
387         }
388
389         return true;
390 }
391
392 bool
393 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
394 {
395         if (_panner_shell->bypassed()) {
396                 _dragging = false;
397         }
398         if (!_dragging) {
399                 return false;
400         }
401
402         int w = get_width();
403         double delta = (ev->x - last_drag_x) / (double) w;
404
405         /* create a detent close to the center */
406
407         if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
408                 detented = true;
409                 /* snap to center */
410                 position_control->set_value (0.5);
411         }
412
413         if (detented) {
414                 accumulated_delta += delta;
415
416                 /* have we pulled far enough to escape ? */
417
418                 if (fabs (accumulated_delta) >= 0.025) {
419                         position_control->set_value (position_control->get_value() + accumulated_delta);
420                         detented = false;
421                         accumulated_delta = false;
422                 }
423         } else {
424                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
425                 position_control->set_value (pv + delta);
426         }
427
428         last_drag_x = ev->x;
429         return true;
430 }
431
432 bool
433 MonoPanner::on_key_press_event (GdkEventKey* ev)
434 {
435         double one_degree = 1.0/180.0;
436         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
437         double step;
438
439         if (_panner_shell->bypassed()) {
440                 return false;
441         }
442
443         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
444                 step = one_degree;
445         } else {
446                 step = one_degree * 5.0;
447         }
448
449         switch (ev->keyval) {
450         case GDK_Left:
451                 pv -= step;
452                 position_control->set_value (pv);
453                 break;
454         case GDK_Right:
455                 pv += step;
456                 position_control->set_value (pv);
457                 break;
458         case GDK_0:
459         case GDK_KP_0:
460                 position_control->set_value (0.0);
461                 break;
462         default:
463                 return false;
464         }
465
466         return true;
467 }
468
469 void
470 MonoPanner::set_colors ()
471 {
472         colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
473         colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
474         colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
475         colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
476         colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
477         colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
478 }
479
480 void
481 MonoPanner::color_handler ()
482 {
483         set_colors ();
484         queue_draw ();
485 }
486
487 void
488 MonoPanner::bypass_handler ()
489 {
490         queue_draw ();
491 }
492
493 PannerEditor*
494 MonoPanner::editor ()
495 {
496         return new MonoPannerEditor (this);
497 }