update mono & balance panner GUI
[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 (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
93
94         _panner_shell->Changed.connect (connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
95         ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
96
97         set_tooltip ();
98 }
99
100 MonoPanner::~MonoPanner ()
101 {
102         
103 }
104
105 void
106 MonoPanner::set_tooltip ()
107 {
108         if (_panner_shell->bypassed()) {
109                 _tooltip.set_tip (_("bypassed"));
110                 return;
111         }
112         double pos = position_control->get_value(); // 0..1
113
114         /* We show the position of the center of the image relative to the left & right.
115                  This is expressed as a pair of percentage values that ranges from (100,0)
116                  (hard left) through (50,50) (hard center) to (0,100) (hard right).
117
118                  This is pretty wierd, but its the way audio engineers expect it. Just remember that
119                  the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
120                  */
121
122         char buf[64];
123         snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
124                         (int) rint (100.0 * (1.0 - pos)),
125                         (int) rint (100.0 * pos));
126         _tooltip.set_tip (buf);
127 }
128
129 bool
130 MonoPanner::on_expose_event (GdkEventExpose*)
131 {
132         Glib::RefPtr<Gdk::Window> win (get_window());
133         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
134         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
135
136         int width, height;
137         double pos = position_control->get_value (); /* 0..1 */
138         uint32_t o, f, t, b, pf, po;
139         const double corner_radius = 5;
140
141         width = get_width();
142         height = get_height ();
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         /* background */
161         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
162         context->rectangle (0, 0, width, height);
163         context->fill ();
164
165
166         double usable_width = width - pos_box_size;
167
168         /* compute the centers of the L/R boxes based on the current stereo width */
169         if (fmod (usable_width,2.0) == 0) {
170                 usable_width -= 1.0;
171         }
172         const double half_lr_box = lr_box_size/2.0;
173         const double left = 4 + half_lr_box; // center of left box
174         const double right = width - 4 - half_lr_box; // center of right box
175
176         /* center line */
177         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
178         context->set_line_width (1.0);
179         context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
180         context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
181         context->stroke ();
182
183         context->set_line_width (1.0);
184         /* left box */
185
186         rounded_left_half_rectangle (context,
187                         left - half_lr_box + .5,
188                         half_lr_box + step_down,
189                         lr_box_size, lr_box_size, corner_radius);
190         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
191         context->fill_preserve ();
192         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
193         context->stroke();
194
195         /* add text */
196         int tw, th;
197         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
198         layout->set_attributes (panner_font_attributes);
199
200         layout->set_text (_("L"));
201         layout->get_pixel_size(tw, th);
202         context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
203         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
204         pango_cairo_show_layout (context->cobj(), layout->gobj());
205
206         /* right box */
207         rounded_right_half_rectangle (context,
208                         right - half_lr_box - .5,
209                         half_lr_box + step_down,
210                         lr_box_size, lr_box_size, corner_radius);
211         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
212         context->fill_preserve ();
213         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
214         context->stroke();
215
216         /* add text */
217         layout->set_text (_("R"));
218         layout->get_pixel_size(tw, th);
219         context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
220         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
221         pango_cairo_show_layout (context->cobj(), layout->gobj());
222
223         /* 2 lines that connect them both */
224         context->set_line_width (1.0);
225
226         if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
227                 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
228                 context->move_to (left  + half_lr_box, half_lr_box + step_down);
229                 context->line_to (right - half_lr_box, half_lr_box + step_down);
230                 context->stroke ();
231
232                 context->move_to (left  + half_lr_box, half_lr_box+step_down+lr_box_size);
233                 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
234                 context->stroke ();
235         } else {
236                 context->move_to (left  + half_lr_box, half_lr_box+step_down+lr_box_size);
237                 context->line_to (right - 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->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box + step_down);
240                 context->close_path();
241                 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
242                 context->fill_preserve ();
243                 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
244                 context->stroke ();
245
246                 context->move_to (left  + half_lr_box, half_lr_box + step_down);
247                 context->line_to (right - half_lr_box, half_lr_box + step_down);
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); /* bottom point */
258         context->rel_line_to (-pos_box_size/2.0, -4.0); /* 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, pos_box_size + 5);
271         context->rel_line_to (0, half_lr_box+step_down);
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);
323                 } else if (ev->x > 2*width/3) {
324                         position_control->set_value (1.0);
325                 } else {
326                         position_control->set_value (0.5);
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);
398                         break;
399                 case GDK_SCROLL_DOWN:
400                 case GDK_SCROLL_RIGHT:
401                         pv += step;
402                         position_control->set_value (pv);
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);
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);
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);
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);
470                         break;
471                 case GDK_Right:
472                         pv += step;
473                         position_control->set_value (pv);
474                         break;
475                 case GDK_0:
476                 case GDK_KP_0:
477                         position_control->set_value (0.0);
478                         break;
479                 default:
480                         return false;
481         }
482
483         return true;
484 }
485
486         void
487 MonoPanner::set_colors ()
488 {
489         colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
490         colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
491         colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
492         colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
493         colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
494         colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
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         PannerEditor*
511 MonoPanner::editor ()
512 {
513         return new MonoPannerEditor (this);
514 }