sanitycheck should be looking for SCHED_FIFO
[ardour2.git] / gtk2_ardour / mono_panner.cc
blob5500d6f555f4f00ebbb337f83f838a2f466d82f6
1 /*
2 Copyright (C) 2000-2007 Paul Davis
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.
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.
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.
20 #include <iostream>
21 #include <iomanip>
22 #include <cstring>
23 #include <cmath>
25 #include <gtkmm/window.h>
27 #include "pbd/controllable.h"
28 #include "pbd/compose.h"
30 #include "gtkmm2ext/gui_thread.h"
31 #include "gtkmm2ext/gtk_ui.h"
32 #include "gtkmm2ext/keyboard.h"
34 #include "ardour/panner.h"
35 #include "ardour/panner.h"
37 #include "ardour_ui.h"
38 #include "global_signals.h"
39 #include "mono_panner.h"
40 #include "rgb_macros.h"
41 #include "utils.h"
43 #include "i18n.h"
45 using namespace std;
46 using namespace Gtk;
47 using namespace Gtkmm2ext;
49 static const int pos_box_size = 9;
50 static const int lr_box_size = 15;
51 static const int step_down = 10;
52 static const int top_step = 2;
54 MonoPanner::ColorScheme MonoPanner::colors;
55 bool MonoPanner::have_colors = false;
57 MonoPanner::MonoPanner (boost::shared_ptr<PBD::Controllable> position)
58 : position_control (position)
59 , dragging (false)
60 , drag_start_x (0)
61 , last_drag_x (0)
62 , accumulated_delta (0)
63 , detented (false)
64 , drag_data_window (0)
65 , drag_data_label (0)
66 , position_binder (position)
68 if (!have_colors) {
69 set_colors ();
70 have_colors = true;
73 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
75 set_flags (Gtk::CAN_FOCUS);
77 add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|
78 Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|
79 Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|
80 Gdk::SCROLL_MASK|
81 Gdk::POINTER_MOTION_MASK);
83 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
86 MonoPanner::~MonoPanner ()
88 delete drag_data_window;
91 void
92 MonoPanner::set_drag_data ()
94 if (!drag_data_label) {
95 return;
98 double pos = position_control->get_value(); // 0..1
100 /* We show the position of the center of the image relative to the left & right.
101 This is expressed as a pair of percentage values that ranges from (100,0)
102 (hard left) through (50,50) (hard center) to (0,100) (hard right).
104 This is pretty wierd, but its the way audio engineers expect it. Just remember that
105 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
108 char buf[64];
109 snprintf (buf, sizeof (buf), "L:%3d R:%3d",
110 (int) rint (100.0 * (1.0 - pos)),
111 (int) rint (100.0 * pos));
112 drag_data_label->set_markup (buf);
115 void
116 MonoPanner::value_change ()
118 set_drag_data ();
119 queue_draw ();
122 bool
123 MonoPanner::on_expose_event (GdkEventExpose* ev)
125 Glib::RefPtr<Gdk::Window> win (get_window());
126 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
128 cairo_t* cr = gdk_cairo_create (win->gobj());
130 int width, height;
131 double pos = position_control->get_value (); /* 0..1 */
132 uint32_t o, f, t, b, pf, po;
134 width = get_width();
135 height = get_height ();
137 o = colors.outline;
138 f = colors.fill;
139 t = colors.text;
140 b = colors.background;
141 pf = colors.pos_fill;
142 po = colors.pos_outline;
144 /* background */
146 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));
147 cairo_rectangle (cr, 0, 0, width, height);
148 cairo_fill (cr);
150 double usable_width = width - pos_box_size;
152 /* compute the centers of the L/R boxes based on the current stereo width */
154 if (fmod (usable_width,2.0) == 0) {
155 /* even width, but we need odd, so that there is an exact center.
156 So, offset cairo by 1, and reduce effective width by 1
158 usable_width -= 1.0;
159 cairo_translate (cr, 1.0, 0.0);
162 const double half_lr_box = lr_box_size/2.0;
163 double left;
164 double right;
166 left = 4 + half_lr_box; // center of left box
167 right = width - 4 - half_lr_box; // center of right box
169 /* center line */
170 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));
171 cairo_set_line_width (cr, 1.0);
172 cairo_move_to (cr, (pos_box_size/2.0) + (usable_width/2.0), 0);
173 cairo_line_to (cr, (pos_box_size/2.0) + (usable_width/2.0), height);
174 cairo_stroke (cr);
176 /* left box */
178 cairo_rectangle (cr,
179 left - half_lr_box,
180 half_lr_box+step_down,
181 lr_box_size, lr_box_size);
182 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));
183 cairo_stroke_preserve (cr);
184 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));
185 cairo_fill (cr);
187 /* add text */
189 cairo_move_to (cr,
190 left - half_lr_box + 3,
191 (lr_box_size/2) + step_down + 13);
192 cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
193 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));
194 cairo_show_text (cr, _("L"));
196 /* right box */
198 cairo_rectangle (cr,
199 right - half_lr_box,
200 half_lr_box+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);
207 /* add text */
209 cairo_move_to (cr,
210 right - half_lr_box + 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 cairo_show_text (cr, _("R"));
215 /* 2 lines that connect them both */
216 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));
217 cairo_set_line_width (cr, 1.0);
218 cairo_move_to (cr, left + half_lr_box, half_lr_box+step_down);
219 cairo_line_to (cr, right - half_lr_box, half_lr_box+step_down);
220 cairo_stroke (cr);
223 cairo_move_to (cr, left + half_lr_box, half_lr_box+step_down+lr_box_size);
224 cairo_line_to (cr, right - half_lr_box, half_lr_box+step_down+lr_box_size);
225 cairo_stroke (cr);
227 /* draw the position indicator */
229 double spos = (pos_box_size/2.0) + (usable_width * pos);
231 cairo_set_line_width (cr, 2.0);
232 cairo_move_to (cr, spos + (pos_box_size/2.0), top_step); /* top right */
233 cairo_rel_line_to (cr, 0.0, pos_box_size); /* lower right */
234 cairo_rel_line_to (cr, -pos_box_size/2.0, 4.0); /* bottom point */
235 cairo_rel_line_to (cr, -pos_box_size/2.0, -4.0); /* lower left */
236 cairo_rel_line_to (cr, 0.0, -pos_box_size); /* upper left */
237 cairo_close_path (cr);
240 cairo_set_source_rgba (cr, UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
241 cairo_stroke_preserve (cr);
242 cairo_set_source_rgba (cr, UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
243 cairo_fill (cr);
245 /* marker line */
247 cairo_set_line_width (cr, 1.0);
248 cairo_move_to (cr, spos, pos_box_size+4);
249 cairo_rel_line_to (cr, 0, height - (pos_box_size+4));
250 cairo_set_source_rgba (cr, UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
251 cairo_stroke (cr);
253 /* done */
255 cairo_destroy (cr);
256 return true;
259 bool
260 MonoPanner::on_button_press_event (GdkEventButton* ev)
262 drag_start_x = ev->x;
263 last_drag_x = ev->x;
265 dragging = false;
266 accumulated_delta = 0;
267 detented = false;
269 /* Let the binding proxies get first crack at the press event
272 if (ev->y < 20) {
273 if (position_binder.button_press_handler (ev)) {
274 return true;
278 if (ev->button != 1) {
279 return false;
282 if (ev->type == GDK_2BUTTON_PRESS) {
283 int width = get_width();
285 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
286 /* handled by button release */
287 return true;
291 if (ev->x <= width/3) {
292 /* left side dbl click */
293 position_control->set_value (0);
294 } else if (ev->x > 2*width/3) {
295 position_control->set_value (1.0);
296 } else {
297 position_control->set_value (0.5);
300 dragging = false;
302 } else if (ev->type == GDK_BUTTON_PRESS) {
304 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
305 /* handled by button release */
306 return true;
309 dragging = true;
310 StartGesture ();
313 return true;
316 bool
317 MonoPanner::on_button_release_event (GdkEventButton* ev)
319 if (ev->button != 1) {
320 return false;
323 dragging = false;
324 accumulated_delta = 0;
325 detented = false;
327 if (drag_data_window) {
328 drag_data_window->hide ();
331 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
332 /* reset to default */
333 position_control->set_value (0.5);
334 } else {
335 StopGesture ();
338 return true;
341 bool
342 MonoPanner::on_scroll_event (GdkEventScroll* ev)
344 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
345 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
346 double step;
348 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
349 step = one_degree;
350 } else {
351 step = one_degree * 5.0;
354 switch (ev->direction) {
355 case GDK_SCROLL_UP:
356 case GDK_SCROLL_LEFT:
357 pv -= step;
358 position_control->set_value (pv);
359 break;
360 case GDK_SCROLL_DOWN:
361 case GDK_SCROLL_RIGHT:
362 pv += step;
363 position_control->set_value (pv);
364 break;
367 return true;
370 bool
371 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
373 if (!dragging) {
374 return false;
377 if (!drag_data_window) {
378 drag_data_window = new Window (WINDOW_POPUP);
379 drag_data_window->set_name (X_("ContrastingPopup"));
380 drag_data_window->set_position (WIN_POS_MOUSE);
381 drag_data_window->set_decorated (false);
383 drag_data_label = manage (new Label);
384 drag_data_label->set_use_markup (true);
386 drag_data_window->set_border_width (6);
387 drag_data_window->add (*drag_data_label);
388 drag_data_label->show ();
390 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
391 if (toplevel) {
392 drag_data_window->set_transient_for (*toplevel);
396 if (!drag_data_window->is_visible ()) {
397 /* move the window a little away from the mouse */
398 int rx, ry;
399 get_window()->get_origin (rx, ry);
400 drag_data_window->move (rx, ry+get_height());
401 drag_data_window->present ();
404 int w = get_width();
405 double delta = (ev->x - last_drag_x) / (double) w;
407 /* create a detent close to the center */
409 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
410 detented = true;
411 /* snap to center */
412 position_control->set_value (0.5);
415 if (detented) {
416 accumulated_delta += delta;
418 /* have we pulled far enough to escape ? */
420 if (fabs (accumulated_delta) >= 0.025) {
421 position_control->set_value (position_control->get_value() + accumulated_delta);
422 detented = false;
423 accumulated_delta = false;
425 } else {
426 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
427 position_control->set_value (pv + delta);
430 last_drag_x = ev->x;
431 return true;
434 bool
435 MonoPanner::on_key_press_event (GdkEventKey* ev)
437 double one_degree = 1.0/180.0;
438 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
439 double step;
441 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
442 step = one_degree;
443 } else {
444 step = one_degree * 5.0;
447 /* up/down control width because we consider pan position more "important"
448 (and thus having higher "sense" priority) than width.
451 switch (ev->keyval) {
452 case GDK_Left:
453 pv -= step;
454 position_control->set_value (pv);
455 break;
456 case GDK_Right:
457 pv += step;
458 position_control->set_value (pv);
459 break;
460 default:
461 return false;
464 return true;
467 bool
468 MonoPanner::on_key_release_event (GdkEventKey* ev)
470 return false;
473 bool
474 MonoPanner::on_enter_notify_event (GdkEventCrossing* ev)
476 grab_focus ();
477 Keyboard::magic_widget_grab_focus ();
478 return false;
481 bool
482 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
484 Keyboard::magic_widget_drop_focus ();
485 return false;
488 void
489 MonoPanner::set_colors ()
491 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
492 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
493 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
494 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
495 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
496 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
499 void
500 MonoPanner::color_handler ()
502 set_colors ();
503 queue_draw ();