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