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