code tweaks in stereo panner (no functional changes)
[ardour.git] / gtk2_ardour / stereo_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 "stereo_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 = 10;
49 static const int lr_box_size = 15;
50 static const int step_down = 10;
51 static const int top_step = 2;
52
53 StereoPanner::ColorScheme StereoPanner::colors[3];
54 bool StereoPanner::have_colors = false;
55 PBD::Signal0<void> StereoPanner::color_change;
56
57 StereoPanner::StereoPanner (boost::shared_ptr<PBD::Controllable> position, boost::shared_ptr<PBD::Controllable> width)
58         : position_control (position)
59         , width_control (width)
60         , dragging (false)
61         , dragging_position (false)
62         , drag_start_x (0)
63         , last_drag_x (0)
64 {
65         if (!have_colors) {
66                 set_colors ();
67                 have_colors = true;
68         }
69
70         position_control->Changed.connect (connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
71         width_control->Changed.connect (connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
72         set_tooltip ();
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         color_change.connect (connections, invalidator (*this), boost::bind (&DrawingArea::queue_draw, this), gui_context());
83 }
84
85 StereoPanner::~StereoPanner ()
86 {
87 }
88
89 void
90 StereoPanner::set_tooltip ()
91 {
92         double pos = position_control->get_value(); // 0..1
93         double w = width_control->get_value (); // -1..+1
94         int lpos = (int) lrint ((pos - (w/2.0)) * 100.0);
95         int rpos = (int) lrint ((pos + (w/2.0)) * 100.0);
96                                 
97         Gtkmm2ext::UI::instance()->set_tip (this, string_compose (_("L:%1 R:%2 Width: %3%%"), 
98                                                                   lpos, rpos,
99                                                                   (int) floor (w * 100.0)).c_str());
100 }
101
102 void
103 StereoPanner::value_change ()
104 {
105         set_tooltip ();
106         queue_draw ();
107 }
108
109 bool
110 StereoPanner::on_expose_event (GdkEventExpose* ev)
111 {
112         Glib::RefPtr<Gdk::Window> win (get_window());
113         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
114
115         cairo_t* cr = gdk_cairo_create (win->gobj());
116        
117         int width, height;
118         double pos = position_control->get_value (); /* 0..1 */
119         double swidth = width_control->get_value (); /* -1..+1 */
120         double fswidth = fabs (swidth);
121         uint32_t o, f, t, b;
122         State state;
123
124         width = get_width();
125         height = get_height ();
126
127         if (swidth == 0.0) {
128                 state = Mono;
129         } else if (swidth < 0.0) {
130                 state = Inverted;
131         } else { 
132                 state = Normal;
133         }
134
135         o = colors[state].outline;
136         f = colors[state].fill;
137         t = colors[state].text;
138         b = colors[state].background;
139
140         /* background */
141
142         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));
143         cairo_rectangle (cr, 0, 0, width, height);
144         cairo_fill (cr);
145
146         /* compute the centers of the L/R boxes based on the current stereo width */
147         
148         int usable_width = width - lr_box_size;
149         int center = lr_box_size/2 + (int) floor (usable_width * pos);
150         int left = center - (int) floor (fswidth * usable_width / 2.0); // center of leftmost box
151         int right = center + (int) floor (fswidth * usable_width / 2.0); // center of rightmost box
152
153         // cerr << "pos " << pos << " width = " << width << " swidth = " << swidth << " center @ " << center << " L = " << left << " R = " << right << endl;
154
155         /* compute & draw the line through the box */
156         
157         cairo_set_line_width (cr, 2);
158         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));
159         cairo_move_to (cr, left, top_step+(pos_box_size/2)+step_down);
160         cairo_line_to (cr, left, top_step+(pos_box_size/2));
161         cairo_line_to (cr, right, top_step+(pos_box_size/2));
162         cairo_line_to (cr, right, top_step+(pos_box_size/2) + step_down);
163         cairo_stroke (cr);
164
165         if (swidth < 0.0) {
166                 /* flip where the L/R boxes are drawn */
167                 swap (left, right);
168         }
169
170         /* left box */
171
172         left -= lr_box_size/2;
173         right -= lr_box_size/2;
174
175         cairo_rectangle (cr, 
176                          left,
177                          (lr_box_size/2)+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 + 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         if (state != Mono) {
191                 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));
192                 cairo_show_text (cr, "L");
193         }
194
195         /* right box */
196
197         cairo_rectangle (cr, 
198                          right,
199                          (lr_box_size/2)+step_down, 
200                          lr_box_size, lr_box_size);
201         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));
202         cairo_stroke_preserve (cr);
203         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));
204         cairo_fill (cr);
205
206         /* add text */
207
208         cairo_move_to (cr, 
209                        right + 3,
210                        (lr_box_size/2)+step_down + 13);
211         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));
212         if (state == Mono) {
213                 cairo_show_text (cr, "M");
214         } else {
215                 cairo_show_text (cr, "R");
216         }
217
218         /* draw the central box */
219
220         cairo_set_line_width (cr, 1);
221         cairo_rectangle (cr, center - (pos_box_size/2), top_step, pos_box_size, pos_box_size);
222         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));
223         cairo_stroke_preserve (cr);
224         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));
225         cairo_fill (cr);
226
227         /* done */
228
229         cairo_destroy (cr);
230         return true;
231 }
232
233 bool
234 StereoPanner::on_button_press_event (GdkEventButton* ev)
235 {
236         drag_start_x = ev->x;
237         last_drag_x = ev->x;
238
239         if (ev->y < 20) {
240                 /* top section of widget is for position drags */
241                 dragging_position = true;
242         } else {
243                 dragging_position = false;
244         }
245
246         if (ev->type == GDK_2BUTTON_PRESS) {
247                 if (dragging_position) {
248                         cerr << "Reset pos\n";
249                         position_control->set_value (0.5); // reset position to center
250                 } else {
251                         cerr << "Reset width\n";
252                         width_control->set_value (1.0); // reset position to full, LR
253                 }
254                 dragging = false;
255         } else {
256                 dragging = true;
257         }
258
259         return true;
260 }
261
262 bool
263 StereoPanner::on_button_release_event (GdkEventButton* ev)
264 {
265         dragging = false;
266         dragging_position = false;
267         return true;
268 }
269
270 bool
271 StereoPanner::on_scroll_event (GdkEventScroll* ev)
272 {
273         double one_degree = 1.0/180.0;
274         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
275         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
276         double step;
277         
278         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
279                 step = one_degree;
280         } else {
281                 step = one_degree * 5.0;
282         }
283
284         switch (ev->direction) {
285         case GDK_SCROLL_LEFT:
286                 wv += step;
287                 width_control->set_value (wv);
288                 break;
289         case GDK_SCROLL_UP:
290                 pv -= step;
291                 position_control->set_value (pv);
292                 break;
293         case GDK_SCROLL_RIGHT:
294                 wv -= step;
295                 width_control->set_value (wv);
296                 break;
297         case GDK_SCROLL_DOWN:
298                 pv += step;
299                 position_control->set_value (pv);
300                 break;
301         }
302
303         return true;
304 }
305
306 bool
307 StereoPanner::on_motion_notify_event (GdkEventMotion* ev)
308 {
309         if (!dragging) {
310                 return false;
311         }
312
313         int w = get_width();
314         double delta = (abs (ev->x - last_drag_x)) / (w/2.0);
315         int drag_dir = 0;
316
317         if (!dragging_position) {
318                 double wv = width_control->get_value();        
319                 int inc;
320                 double old_wv;
321                 double opx; // compute the operational x-coordinate given the current pos+width
322                 
323                 if (wv > 0) {
324                         /* positive value: increasing width means adding */
325                         inc = 1;
326                 } else {
327                         /* positive value: increasing width means subtracting */
328                         inc = -1;
329                 }
330
331                 if (drag_start_x < w/2) {
332                         /* started left of center */
333
334                         opx = position_control->get_value() - (wv/2.0);
335
336                         if (opx < 0.5) {
337                                 /* still left */
338                                 if (ev->x > last_drag_x) {
339                                         /* motion to left */
340                                         drag_dir = -inc;
341                                 } else {
342                                         drag_dir = inc;
343                                 }
344                         } else {
345                                 /* now right */
346                                 if (ev->x > last_drag_x) {
347                                         /* motion to left */
348                                         drag_dir = inc;
349                                 } else {
350                                         drag_dir = -inc;
351                                 }
352                         }
353                 } else {
354                         /* started right of center */
355                         
356                         opx = position_control->get_value() + (wv/2.0);
357
358                         if (opx > 0.5) {
359                                 /* still right */
360                                 if (ev->x < last_drag_x) {
361                                         /* motion to right */
362                                         drag_dir = -inc;
363                                 } else {
364                                         drag_dir = inc;
365                                 }
366                         } else {
367                                 /* now left */
368                                 if (ev->x < last_drag_x) {
369                                         /* motion to right */
370                                         drag_dir = inc;
371                                 } else {
372                                         drag_dir = -inc;
373                                 }
374                         }
375
376                 }
377
378                 old_wv = wv;
379                 wv = wv + (drag_dir * delta);
380                 
381                 width_control->set_value (wv);
382
383         } else {
384
385                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
386                 
387                 if (ev->x > last_drag_x) { // increasing 
388                         pv = pv + delta;
389                 } else {
390                         pv = pv - delta;
391                 }
392
393                 position_control->set_value (pv);
394         }
395
396         last_drag_x = ev->x;
397         return true;
398 }
399
400 bool
401 StereoPanner::on_key_press_event (GdkEventKey* ev)
402 {
403         double one_degree = 1.0/180.0;
404         double pv = position_control->get_value(); // 0..1.0 ; 0 = left
405         double wv = width_control->get_value(); // 0..1.0 ; 0 = left
406         double step;
407
408         if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
409                 step = one_degree;
410         } else {
411                 step = one_degree * 5.0;
412         }
413
414         /* up/down control width because we consider pan position more "important"
415            (and thus having higher "sense" priority) than width.
416         */
417
418         switch (ev->keyval) {
419         case GDK_Up:
420                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
421                         width_control->set_value (1.0);
422                 } else {
423                         width_control->set_value (wv + step);
424                 }
425                 break;
426         case GDK_Down:
427                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
428                         width_control->set_value (-1.0);
429                 } else {
430                         width_control->set_value (wv - step);
431                 }
432
433         case GDK_Left:
434                 pv -= step;
435                 position_control->set_value (pv);
436                 break;
437         case GDK_Right:
438                 pv += step;
439                 position_control->set_value (pv);
440                 break;
441
442                 break;
443         case GDK_0:
444         case GDK_KP_0:
445                 width_control->set_value (0.0);
446                 break;
447
448         default: 
449                 return false;
450         }
451                 
452         return true;
453 }
454
455 bool
456 StereoPanner::on_key_release_event (GdkEventKey* ev)
457 {
458         return false;
459 }
460
461 bool
462 StereoPanner::on_enter_notify_event (GdkEventCrossing* ev)
463 {
464         grab_focus ();
465         Keyboard::magic_widget_grab_focus ();
466         return false;
467 }
468
469 bool
470 StereoPanner::on_leave_notify_event (GdkEventCrossing*)
471 {
472         Keyboard::magic_widget_drop_focus ();
473         return false;
474 }
475
476 void
477 StereoPanner::set_colors ()
478 {
479         colors[Normal].fill = ARDOUR_UI::config()->canvasvar_StereoPannerFill.get();
480         colors[Normal].outline = ARDOUR_UI::config()->canvasvar_StereoPannerOutline.get();
481         colors[Normal].text = ARDOUR_UI::config()->canvasvar_StereoPannerText.get();
482         colors[Normal].background = ARDOUR_UI::config()->canvasvar_StereoPannerBackground.get();
483
484         colors[Mono].fill = ARDOUR_UI::config()->canvasvar_StereoPannerMonoFill.get();
485         colors[Mono].outline = ARDOUR_UI::config()->canvasvar_StereoPannerMonoOutline.get();
486         colors[Mono].text = ARDOUR_UI::config()->canvasvar_StereoPannerMonoText.get();
487         colors[Mono].background = ARDOUR_UI::config()->canvasvar_StereoPannerMonoBackground.get();
488
489         colors[Inverted].fill = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedFill.get();
490         colors[Inverted].outline = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedOutline.get();
491         colors[Inverted].text = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedText.get();
492         colors[Inverted].background = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedBackground.get();
493
494         color_change (); /* EMIT SIGNAL */
495 }