Merge remote-tracking branch 'remotes/origin/cairocanvas' into windows
[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 #include "gtkmm2ext/persistent_tooltip.h"
35
36 #include "ardour/pannable.h"
37 #include "ardour/panner.h"
38
39 #include "ardour_ui.h"
40 #include "global_signals.h"
41 #include "mono_panner.h"
42 #include "mono_panner_editor.h"
43 #include "rgb_macros.h"
44 #include "utils.h"
45
46 #include "i18n.h"
47
48 using namespace std;
49 using namespace Gtk;
50 using namespace Gtkmm2ext;
51
52 static const int pos_box_size = 9;
53 static const int lr_box_size = 15;
54 static const int step_down = 10;
55 static const int top_step = 2;
56
57 MonoPanner::ColorScheme MonoPanner::colors;
58 bool MonoPanner::have_colors = false;
59
60 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::Panner> panner)
61         : PannerInterface (panner)
62         , position_control (_panner->pannable()->pan_azimuth_control)
63         , drag_start_x (0)
64         , last_drag_x (0)
65         , accumulated_delta (0)
66         , detented (false)
67         , position_binder (position_control)
68         , _dragging (false)
69 {
70         if (!have_colors) {
71                 set_colors ();
72                 have_colors = true;
73         }
74
75         position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
76
77         ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
78
79         set_tooltip ();
80 }
81
82 MonoPanner::~MonoPanner ()
83 {
84         
85 }
86
87 void
88 MonoPanner::set_tooltip ()
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         _tooltip.set_tip (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         if (PannerInterface::on_button_press_event (ev)) {
251                 return true;
252         }
253         
254         drag_start_x = ev->x;
255         last_drag_x = ev->x;
256
257         _dragging = false;
258         _tooltip.target_stop_drag ();
259         accumulated_delta = 0;
260         detented = false;
261
262         /* Let the binding proxies get first crack at the press event
263          */
264
265         if (ev->y < 20) {
266                 if (position_binder.button_press_handler (ev)) {
267                         return true;
268                 }
269         }
270
271         if (ev->button != 1) {
272                 return false;
273         }
274
275         if (ev->type == GDK_2BUTTON_PRESS) {
276                 int width = get_width();
277
278                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
279                         /* handled by button release */
280                         return true;
281                 }
282
283
284                 if (ev->x <= width/3) {
285                         /* left side dbl click */
286                         position_control->set_value (0);
287                 } else if (ev->x > 2*width/3) {
288                         position_control->set_value (1.0);
289                 } else {
290                         position_control->set_value (0.5);
291                 }
292
293                 _dragging = false;
294                 _tooltip.target_stop_drag ();
295
296         } else if (ev->type == GDK_BUTTON_PRESS) {
297
298                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
299                         /* handled by button release */
300                         return true;
301                 }
302
303                 _dragging = true;
304                 _tooltip.target_start_drag ();
305                 StartGesture ();
306         }
307
308         return true;
309 }
310
311 bool
312 MonoPanner::on_button_release_event (GdkEventButton* ev)
313 {
314         if (PannerInterface::on_button_release_event (ev)) {
315                 return true;
316         }
317
318         if (ev->button != 1) {
319                 return false;
320         }
321
322         _dragging = false;
323         _tooltip.target_stop_drag ();
324         accumulated_delta = 0;
325         detented = false;
326
327         if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
328                 _panner->reset ();
329         } else {
330                 StopGesture ();
331         }
332
333         return true;
334 }
335
336 bool
337 MonoPanner::on_scroll_event (GdkEventScroll* ev)
338 {
339         double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
340         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
341         double step;
342
343         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
344                 step = one_degree;
345         } else {
346                 step = one_degree * 5.0;
347         }
348
349         switch (ev->direction) {
350         case GDK_SCROLL_UP:
351         case GDK_SCROLL_LEFT:
352                 pv -= step;
353                 position_control->set_value (pv);
354                 break;
355         case GDK_SCROLL_DOWN:
356         case GDK_SCROLL_RIGHT:
357                 pv += step;
358                 position_control->set_value (pv);
359                 break;
360         }
361
362         return true;
363 }
364
365 bool
366 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
367 {
368         if (!_dragging) {
369                 return false;
370         }
371
372         int w = get_width();
373         double delta = (ev->x - last_drag_x) / (double) w;
374
375         /* create a detent close to the center */
376
377         if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
378                 detented = true;
379                 /* snap to center */
380                 position_control->set_value (0.5);
381         }
382
383         if (detented) {
384                 accumulated_delta += delta;
385
386                 /* have we pulled far enough to escape ? */
387
388                 if (fabs (accumulated_delta) >= 0.025) {
389                         position_control->set_value (position_control->get_value() + accumulated_delta);
390                         detented = false;
391                         accumulated_delta = false;
392                 }
393         } else {
394                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
395                 position_control->set_value (pv + delta);
396         }
397
398         last_drag_x = ev->x;
399         return true;
400 }
401
402 bool
403 MonoPanner::on_key_press_event (GdkEventKey* ev)
404 {
405         double one_degree = 1.0/180.0;
406         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
407         double step;
408
409         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
410                 step = one_degree;
411         } else {
412                 step = one_degree * 5.0;
413         }
414
415         switch (ev->keyval) {
416         case GDK_Left:
417                 pv -= step;
418                 position_control->set_value (pv);
419                 break;
420         case GDK_Right:
421                 pv += step;
422                 position_control->set_value (pv);
423                 break;
424         case GDK_0:
425         case GDK_KP_0:
426                 position_control->set_value (0.0);
427                 break;
428         default:
429                 return false;
430         }
431
432         return true;
433 }
434
435 void
436 MonoPanner::set_colors ()
437 {
438         colors.fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerFill();
439         colors.outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerOutline();
440         colors.text = ARDOUR_UI::config()->get_canvasvar_MonoPannerText();
441         colors.background = ARDOUR_UI::config()->get_canvasvar_MonoPannerBackground();
442         colors.pos_outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionOutline();
443         colors.pos_fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionFill();
444 }
445
446 void
447 MonoPanner::color_handler ()
448 {
449         set_colors ();
450         queue_draw ();
451 }
452
453 PannerEditor*
454 MonoPanner::editor ()
455 {
456         return new MonoPannerEditor (this);
457 }