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