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