polygon and marker line for mono panner
[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         }
308
309         return true;
310 }
311
312 bool
313 MonoPanner::on_button_release_event (GdkEventButton* ev)
314 {
315         if (ev->button != 1) {
316                 return false;
317         }
318
319         dragging = false;
320         accumulated_delta = 0;
321         detented = false;
322
323         if (drag_data_window) {
324                 drag_data_window->hide ();
325         }
326         
327         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
328                 /* reset to default */
329                 position_control->set_value (0.5);
330         }
331
332         return true;
333 }
334
335 bool
336 MonoPanner::on_scroll_event (GdkEventScroll* ev)
337 {
338         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
339         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
340         double step;
341         
342         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
343                 step = one_degree;
344         } else {
345                 step = one_degree * 5.0;
346         }
347
348         switch (ev->direction) {
349         case GDK_SCROLL_UP:
350         case GDK_SCROLL_LEFT:
351                 pv -= step;
352                 position_control->set_value (pv);
353                 break;
354         case GDK_SCROLL_DOWN:
355         case GDK_SCROLL_RIGHT:
356                 pv += step;
357                 position_control->set_value (pv);
358                 break;
359         }
360
361         return true;
362 }
363
364 bool
365 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
366 {
367         if (!dragging) {
368                 return false;
369         }
370
371         if (!drag_data_window) {
372                 drag_data_window = new Window (WINDOW_POPUP);
373                 drag_data_window->set_position (WIN_POS_MOUSE);
374                 drag_data_window->set_decorated (false);
375                 
376                 drag_data_label = manage (new Label);
377                 drag_data_label->set_use_markup (true);
378
379                 drag_data_window->set_border_width (6);
380                 drag_data_window->add (*drag_data_label);
381                 drag_data_label->show ();
382                 
383                 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
384                 if (toplevel) {
385                         drag_data_window->set_transient_for (*toplevel);
386                 }
387         }
388
389         if (!drag_data_window->is_visible ()) {
390                 /* move the window a little away from the mouse */
391                 drag_data_window->move (ev->x_root+30, ev->y_root+30);
392                 drag_data_window->present ();
393         }
394
395         int w = get_width();
396         double delta = (ev->x - last_drag_x) / (double) w;
397         
398         /* create a detent close to the center */
399         
400         if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
401                 detented = true;
402                 /* snap to center */
403                 position_control->set_value (0.5);
404         }
405         
406         if (detented) {
407                 accumulated_delta += delta;
408                 
409                 /* have we pulled far enough to escape ? */
410                 
411                 if (fabs (accumulated_delta) >= 0.025) {
412                         position_control->set_value (position_control->get_value() + accumulated_delta);
413                         detented = false;
414                         accumulated_delta = false;
415                 }
416         } else {
417                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
418                 position_control->set_value (pv + delta);
419         }
420
421         last_drag_x = ev->x;
422         return true;
423 }
424
425 bool
426 MonoPanner::on_key_press_event (GdkEventKey* ev)
427 {
428         double one_degree = 1.0/180.0;
429         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
430         double step;
431
432         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
433                 step = one_degree;
434         } else {
435                 step = one_degree * 5.0;
436         }
437
438         /* up/down control width because we consider pan position more "important"
439            (and thus having higher "sense" priority) than width.
440         */
441
442         switch (ev->keyval) {
443         case GDK_Left:
444                 pv -= step;
445                 position_control->set_value (pv);
446                 break;
447         case GDK_Right:
448                 pv += step;
449                 position_control->set_value (pv);
450                 break;
451         default: 
452                 return false;
453         }
454                 
455         return true;
456 }
457
458 bool
459 MonoPanner::on_key_release_event (GdkEventKey* ev)
460 {
461         return false;
462 }
463
464 bool
465 MonoPanner::on_enter_notify_event (GdkEventCrossing* ev)
466 {
467         grab_focus ();
468         Keyboard::magic_widget_grab_focus ();
469         return false;
470 }
471
472 bool
473 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
474 {
475         Keyboard::magic_widget_drop_focus ();
476         return false;
477 }
478
479 void
480 MonoPanner::set_colors ()
481 {
482         colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
483         colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
484         colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
485         colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
486         colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
487         colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
488 }
489
490 void
491 MonoPanner::color_handler ()
492 {
493         set_colors ();
494         queue_draw ();
495 }