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