more stereo panner work
[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
24 #include "pbd/controllable.h"
25 #include "pbd/compose.h"
26
27 #include "gtkmm2ext/gui_thread.h"
28 #include "gtkmm2ext/gtk_ui.h"
29
30 #include "ardour/panner.h"
31 #include "stereo_panner.h"
32
33 #include "i18n.h"
34
35 using namespace std;
36 using namespace Gtk;
37
38 static const int pos_box_size = 10;
39 static const int lr_box_size = 18;
40 static const int step_down = 10;
41
42 StereoPanner::StereoPanner (boost::shared_ptr<PBD::Controllable> position, boost::shared_ptr<PBD::Controllable> width)
43         : position_control (position)
44         , width_control (width)
45         , dragging (false)
46         , dragging_position (false)
47         , drag_start_x (0)
48         , last_drag_x (0)
49 {
50         position_control->Changed.connect (connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
51         width_control->Changed.connect (connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
52         set_tooltip ();
53
54         add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::SCROLL_MASK|Gdk::POINTER_MOTION_MASK);
55 }
56
57 StereoPanner::~StereoPanner ()
58 {
59 }
60
61 void
62 StereoPanner::set_tooltip ()
63 {
64         Gtkmm2ext::UI::instance()->set_tip (this, string_compose (_("L:%1 R:%2 Width: %3%%"), 
65                                                                   (int) floor (position_control->get_value() * 100.0),
66                                                                   (int) floor ((1.0 - position_control->get_value() * 100)),
67                                                                   (int) floor (width_control->get_value() * 100.0)).c_str());
68 }
69
70 void
71 StereoPanner::value_change ()
72 {
73         set_tooltip ();
74         queue_draw ();
75 }
76
77 bool
78 StereoPanner::on_expose_event (GdkEventExpose* ev)
79 {
80         Glib::RefPtr<Gdk::Window> win (get_window());
81         Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
82
83         cairo_t* cr = gdk_cairo_create (win->gobj());
84        
85         int width, height;
86         double pos = position_control->get_value (); /* 0..1 */
87         double swidth = width_control->get_value (); /* -1..+1 */
88         double fswidth = fabs (swidth);
89
90         width = get_width();
91         height = get_height ();
92
93         /* background */
94
95         cairo_set_source_rgb (cr, 0.184, 0.172, 0.172);
96         cairo_rectangle (cr, 0, 0, width, height);
97         cairo_fill (cr);
98
99         /* compute the outer edges of the L/R boxes based on the current stereo width */
100         
101         int usable_width = width - lr_box_size;
102         int center = lr_box_size/2 + (int) floor (usable_width * pos);
103         int left = center - (int) floor (fswidth * usable_width / 2.0); // center of leftmost box
104         int right = center + (int) floor (fswidth * usable_width / 2.0); // center of rightmost box
105
106         // cerr << "pos " << pos << " width = " << width << " swidth = " << swidth << " center @ " << center << " L = " << left << " R = " << right << endl;
107
108         /* compute & draw the line through the box */
109         
110         cairo_set_line_width (cr, 2);
111         cairo_set_source_rgba (cr, 0.3137, 0.4431, 0.7843, 1.0);
112         cairo_move_to (cr, left, 4+(pos_box_size/2)+step_down);
113         cairo_line_to (cr, left, 4+(pos_box_size/2));
114         cairo_line_to (cr, right, 4+(pos_box_size/2));
115         cairo_line_to (cr, right, 4+(pos_box_size/2) + step_down);
116         cairo_stroke (cr);
117
118         if (swidth < 0.0) {
119                 /* flip where the L/R boxes are drawn */
120                 swap (left, right);
121         }
122
123         /* left box */
124
125         left -= lr_box_size/2;
126         right -= lr_box_size/2;
127
128         cairo_rectangle (cr, 
129                          left,
130                          (lr_box_size/2)+step_down, 
131                          lr_box_size, lr_box_size);
132         cairo_set_source_rgba (cr, 0.3137, 0.4431, 0.7843, 1.0);
133         cairo_stroke_preserve (cr);
134         cairo_set_source_rgba (cr, 0.4509, 0.7686, 0.8627, 0.8);
135         cairo_fill (cr);
136         
137         /* add text */
138
139         cairo_move_to (cr, 
140                        left + 4,
141                        (lr_box_size/2) + step_down + 13);
142         cairo_set_source_rgba (cr, 0.129, 0.054, 0.588, 1.0);
143         cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
144         cairo_show_text (cr, "L");
145
146         /* right box */
147
148         cairo_rectangle (cr, 
149                          right,
150                          (lr_box_size/2)+step_down, 
151                          lr_box_size, lr_box_size);
152         cairo_set_source_rgba (cr, 0.3137, 0.4431, 0.7843, 1.0);
153         cairo_stroke_preserve (cr);
154         cairo_set_source_rgba (cr, 0.4509, 0.7686, 0.8627, 0.8);
155         cairo_fill (cr);
156
157         /* add text */
158
159         cairo_move_to (cr, 
160                        right + 4,
161                        (lr_box_size/2)+step_down + 13);
162         cairo_set_source_rgba (cr, 0.129, 0.054, 0.588, 1.0);
163         cairo_show_text (cr, "R");
164
165         /* draw the central box */
166
167         cairo_set_line_width (cr, 1);
168         cairo_rectangle (cr, center - (pos_box_size/2), 4, pos_box_size, pos_box_size);
169         cairo_set_source_rgba (cr, 0.3137, 0.4431, 0.7843, 1.0);
170         cairo_stroke_preserve (cr);
171         cairo_set_source_rgba (cr, 0.4509, 0.7686, 0.8627, 0.8);
172         cairo_fill (cr);
173
174         /* done */
175
176         cairo_destroy (cr);
177         return true;
178 }
179
180 bool
181 StereoPanner::on_button_press_event (GdkEventButton* ev)
182 {
183         drag_start_x = ev->x;
184         last_drag_x = ev->x;
185
186         if (ev->y < 20) {
187                 /* top section of widget is for position drags */
188                 dragging_position = true;
189         } else {
190                 dragging_position = false;
191         }
192
193         if (ev->type == GDK_2BUTTON_PRESS) {
194                 if (dragging_position) {
195                         cerr << "Reset pos\n";
196                         position_control->set_value (0.5); // reset position to center
197                 } else {
198                         cerr << "Reset width\n";
199                         width_control->set_value (1.0); // reset position to full, LR
200                 }
201                 dragging = false;
202         } else {
203                 dragging = true;
204         }
205
206         return true;
207 }
208
209 bool
210 StereoPanner::on_button_release_event (GdkEventButton* ev)
211 {
212         dragging = false;
213         dragging_position = false;
214         return true;
215 }
216
217 bool
218 StereoPanner::on_scroll_event (GdkEventScroll* ev)
219 {
220         return true;
221 }
222
223 bool
224 StereoPanner::on_motion_notify_event (GdkEventMotion* ev)
225 {
226         if (!dragging) {
227                 return false;
228         }
229
230         int w = get_width();
231         double delta = (abs (ev->x - last_drag_x)) / (w/2.0);
232         int drag_dir = 0;
233
234         if (!dragging_position) {
235                 double wv = width_control->get_value();        
236                 int inc;
237                 double old_wv;
238                 double opx; // compute the operational x-coordinate given the current pos+width
239                 
240                 if (wv > 0) {
241                         /* positive value: increasing width means adding */
242                         inc = 1;
243                 } else {
244                         /* positive value: increasing width means subtracting */
245                         inc = -1;
246                 }
247
248                 if (drag_start_x < w/2) {
249                         /* started left of center */
250
251                         opx = position_control->get_value() - (wv/2.0);
252
253                         if (opx < 0.5) {
254                                 /* still left */
255                                 if (ev->x > last_drag_x) {
256                                         /* motion to left */
257                                         drag_dir = -inc;
258                                 } else {
259                                         drag_dir = inc;
260                                 }
261                         } else {
262                                 /* now right */
263                                 if (ev->x > last_drag_x) {
264                                         /* motion to left */
265                                         drag_dir = inc;
266                                 } else {
267                                         drag_dir = -inc;
268                                 }
269                         }
270                 } else {
271                         /* started right of center */
272                         
273                         opx = position_control->get_value() + (wv/2.0);
274
275                         if (opx > 0.5) {
276                                 /* still right */
277                                 if (ev->x < last_drag_x) {
278                                         /* motion to right */
279                                         drag_dir = -inc;
280                                 } else {
281                                         drag_dir = inc;
282                                 }
283                         } else {
284                                 /* now left */
285                                 if (ev->x < last_drag_x) {
286                                         /* motion to right */
287                                         drag_dir = inc;
288                                 } else {
289                                         drag_dir = -inc;
290                                 }
291                         }
292
293                 }
294
295                 old_wv = wv;
296                 wv = wv + (drag_dir * delta);
297                 
298                 width_control->set_value (wv);
299
300         } else {
301
302                 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
303                 
304                 if (ev->x > last_drag_x) { // increasing 
305                         pv = pv * (1.0 + delta);
306                 } else {
307                         pv = pv * (1.0 - delta);
308                 }
309
310                 position_control->set_value (pv);
311         }
312
313         last_drag_x = ev->x;
314         return true;
315 }