centralize use of "from-context-menu" in Editor::get_preferred_edit_position(), and...
[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 #include "gtkmm2ext/utils.h"
34
35 #include "ardour/panner.h"
36 #include "ardour/panner.h"
37 #include "ardour/pannable.h"
38
39 #include "ardour_ui.h"
40 #include "global_signals.h"
41 #include "mono_panner.h"
42 #include "rgb_macros.h"
43 #include "utils.h"
44
45 #include "i18n.h"
46
47 using namespace std;
48 using namespace Gtk;
49 using namespace Gtkmm2ext;
50
51 static const int pos_box_size = 9;
52 static const int lr_box_size = 15;
53 static const int step_down = 10;
54 static const int top_step = 2;
55
56 MonoPanner::ColorScheme MonoPanner::colors;
57 bool MonoPanner::have_colors = false;
58
59 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::Panner> panner)
60         : PannerInterface (panner)
61         , position_control (_panner->pannable()->pan_azimuth_control)
62         , drag_start_x (0)
63         , last_drag_x (0)
64         , accumulated_delta (0)
65         , detented (false)
66         , position_binder (position_control)
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         ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
76 }
77
78 MonoPanner::~MonoPanner ()
79 {
80         
81 }
82
83 void
84 MonoPanner::set_drag_data ()
85 {
86         if (!_drag_data_label) {
87                 return;
88         }
89
90         double pos = position_control->get_value(); // 0..1
91
92         /* We show the position of the center of the image relative to the left & right.
93            This is expressed as a pair of percentage values that ranges from (100,0)
94            (hard left) through (50,50) (hard center) to (0,100) (hard right).
95
96            This is pretty wierd, but its the way audio engineers expect it. Just remember that
97            the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
98         */
99
100         char buf[64];
101         snprintf (buf, sizeof (buf), "L:%3d R:%3d",
102                   (int) rint (100.0 * (1.0 - pos)),
103                   (int) rint (100.0 * pos));
104         _drag_data_label->set_markup (buf);
105 }
106
107 bool
108 MonoPanner::on_expose_event (GdkEventExpose*)
109 {
110         Glib::RefPtr<Gdk::Window> win (get_window());
111         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
112         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
113
114         int width, height;
115         double pos = position_control->get_value (); /* 0..1 */
116         uint32_t o, f, t, b, pf, po;
117         const double corner_radius = 5;
118
119         width = get_width();
120         height = get_height ();
121
122         o = colors.outline;
123         f = colors.fill;
124         t = colors.text;
125         b = colors.background;
126         pf = colors.pos_fill;
127         po = colors.pos_outline;
128
129         /* background */
130
131         context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
132         context->rectangle (0, 0, width, height);
133         context->fill ();
134
135         double usable_width = width - pos_box_size;
136
137         /* compute the centers of the L/R boxes based on the current stereo width */
138
139         if (fmod (usable_width,2.0) == 0) {
140                 /* even width, but we need odd, so that there is an exact center.
141                    So, offset cairo by 1, and reduce effective width by 1
142                 */
143                 usable_width -= 1.0;
144                 context->translate (1.0, 0.0);
145         }
146
147         const double half_lr_box = lr_box_size/2.0;
148         double left;
149         double right;
150
151         left = 4 + half_lr_box; // center of left box
152         right = width  - 4 - half_lr_box; // center of right box
153
154         /* center line */
155         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
156         context->set_line_width (1.0);
157         context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
158         context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
159         context->stroke ();
160
161         /* left box */
162
163         rounded_rectangle (context,
164                           left - half_lr_box,
165                           half_lr_box+step_down,
166                           lr_box_size, lr_box_size, corner_radius);
167         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
168         context->stroke_preserve ();
169         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
170         context->fill ();
171
172         /* add text */
173
174         context->move_to (
175                        left - half_lr_box + 3,
176                        (lr_box_size/2) + step_down + 13);
177         context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
178         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
179         context->show_text (_("L"));
180
181         /* right box */
182
183         rounded_rectangle (context,
184                            right - half_lr_box,
185                            half_lr_box+step_down,
186                            lr_box_size, lr_box_size, corner_radius);
187         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
188         context->stroke_preserve ();
189         context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
190         context->fill ();
191
192         /* add text */
193
194         context->move_to (
195                        right - half_lr_box + 3,
196                        (lr_box_size/2)+step_down + 13);
197         context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
198         context->show_text (_("R"));
199
200         /* 2 lines that connect them both */
201         context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
202         context->set_line_width (1.0);
203
204         /* make the lines a little longer than they need to be, because the corners of
205            the boxes are rounded and we don't want a gap
206         */
207         context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
208         context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
209         context->stroke ();
210
211
212         context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
213         context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
214         context->stroke ();
215
216         /* draw the position indicator */
217
218         double spos = (pos_box_size/2.0) + (usable_width * pos);
219
220         context->set_line_width (2.0);
221         context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
222         context->rel_line_to (0.0, pos_box_size); /* lower right */
223         context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
224         context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
225         context->rel_line_to (0.0, -pos_box_size); /* upper left */
226         context->close_path ();
227
228
229         context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
230         context->stroke_preserve ();
231         context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
232         context->fill ();
233
234         /* marker line */
235
236         context->set_line_width (1.0);
237         context->move_to (spos, pos_box_size+4);
238         context->rel_line_to (0, half_lr_box+step_down);
239         context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
240         context->stroke ();
241
242         /* done */
243
244         return true;
245 }
246
247 bool
248 MonoPanner::on_button_press_event (GdkEventButton* ev)
249 {
250         drag_start_x = ev->x;
251         last_drag_x = ev->x;
252
253         _dragging = false;
254         accumulated_delta = 0;
255         detented = false;
256
257         /* Let the binding proxies get first crack at the press event
258          */
259
260         if (ev->y < 20) {
261                 if (position_binder.button_press_handler (ev)) {
262                         return true;
263                 }
264         }
265
266         if (ev->button != 1) {
267                 return false;
268         }
269
270         if (ev->type == GDK_2BUTTON_PRESS) {
271                 int width = get_width();
272
273                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
274                         /* handled by button release */
275                         return true;
276                 }
277
278
279                 if (ev->x <= width/3) {
280                         /* left side dbl click */
281                         position_control->set_value (0);
282                 } else if (ev->x > 2*width/3) {
283                         position_control->set_value (1.0);
284                 } else {
285                         position_control->set_value (0.5);
286                 }
287
288                 _dragging = false;
289
290         } else if (ev->type == GDK_BUTTON_PRESS) {
291
292                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
293                         /* handled by button release */
294                         return true;
295                 }
296
297                 _dragging = true;
298                 StartGesture ();
299                 show_drag_data_window ();
300         }
301
302         return true;
303 }
304
305 bool
306 MonoPanner::on_button_release_event (GdkEventButton* ev)
307 {
308         if (ev->button != 1) {
309                 return false;
310         }
311
312         _dragging = false;
313         accumulated_delta = 0;
314         detented = false;
315
316         hide_drag_data_window ();
317
318         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
319                 _panner->reset ();
320         } else {
321                 StopGesture ();
322         }
323
324         return true;
325 }
326
327 bool
328 MonoPanner::on_scroll_event (GdkEventScroll* ev)
329 {
330         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
331         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
332         double step;
333
334         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
335                 step = one_degree;
336         } else {
337                 step = one_degree * 5.0;
338         }
339
340         switch (ev->direction) {
341         case GDK_SCROLL_UP:
342         case GDK_SCROLL_LEFT:
343                 pv -= step;
344                 position_control->set_value (pv);
345                 break;
346         case GDK_SCROLL_DOWN:
347         case GDK_SCROLL_RIGHT:
348                 pv += step;
349                 position_control->set_value (pv);
350                 break;
351         }
352
353         return true;
354 }
355
356 bool
357 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
358 {
359         if (!_dragging) {
360                 return false;
361         }
362
363         int w = get_width();
364         double delta = (ev->x - last_drag_x) / (double) w;
365
366         /* create a detent close to the center */
367
368         if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
369                 detented = true;
370                 /* snap to center */
371                 position_control->set_value (0.5);
372         }
373
374         if (detented) {
375                 accumulated_delta += delta;
376
377                 /* have we pulled far enough to escape ? */
378
379                 if (fabs (accumulated_delta) >= 0.025) {
380                         position_control->set_value (position_control->get_value() + accumulated_delta);
381                         detented = false;
382                         accumulated_delta = false;
383                 }
384         } else {
385                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
386                 position_control->set_value (pv + delta);
387         }
388
389         last_drag_x = ev->x;
390         return true;
391 }
392
393 bool
394 MonoPanner::on_key_press_event (GdkEventKey* ev)
395 {
396         double one_degree = 1.0/180.0;
397         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
398         double step;
399
400         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
401                 step = one_degree;
402         } else {
403                 step = one_degree * 5.0;
404         }
405
406         /* up/down control width because we consider pan position more "important"
407            (and thus having higher "sense" priority) than width.
408         */
409
410         switch (ev->keyval) {
411         case GDK_Left:
412                 pv -= step;
413                 position_control->set_value (pv);
414                 break;
415         case GDK_Right:
416                 pv += step;
417                 position_control->set_value (pv);
418                 break;
419         default:
420                 return false;
421         }
422
423         return true;
424 }
425
426 void
427 MonoPanner::set_colors ()
428 {
429         colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
430         colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
431         colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
432         colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
433         colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
434         colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
435 }
436
437 void
438 MonoPanner::color_handler ()
439 {
440         set_colors ();
441         queue_draw ();
442 }
443