make panner data popups more contrasty and appear in a better position
[ardour2.git] / gtk2_ardour / mono_panner.cc
blob9627f871062308802aaea9b80e0c3c3ddc63e8da
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 drag_data_label->set_markup (string_compose (_("L:%1 R:%2"),
109 (int) rint (100.0 * (1.0 - pos)),
110 (int) rint (100.0 * pos)));
113 void
114 MonoPanner::value_change ()
116 set_drag_data ();
117 queue_draw ();
120 bool
121 MonoPanner::on_expose_event (GdkEventExpose* ev)
123 Glib::RefPtr<Gdk::Window> win (get_window());
124 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
126 cairo_t* cr = gdk_cairo_create (win->gobj());
128 int width, height;
129 double pos = position_control->get_value (); /* 0..1 */
130 uint32_t o, f, t, b, pf, po;
132 width = get_width();
133 height = get_height ();
135 o = colors.outline;
136 f = colors.fill;
137 t = colors.text;
138 b = colors.background;
139 pf = colors.pos_fill;
140 po = colors.pos_outline;
142 /* background */
144 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));
145 cairo_rectangle (cr, 0, 0, width, height);
146 cairo_fill (cr);
148 double usable_width = width - pos_box_size;
150 /* compute the centers of the L/R boxes based on the current stereo width */
152 if (fmod (usable_width,2.0) == 0) {
153 /* even width, but we need odd, so that there is an exact center.
154 So, offset cairo by 1, and reduce effective width by 1
156 usable_width -= 1.0;
157 cairo_translate (cr, 1.0, 0.0);
160 const double half_lr_box = lr_box_size/2.0;
161 double left;
162 double right;
164 left = 4 + half_lr_box; // center of left box
165 right = width - 4 - half_lr_box; // center of right box
167 /* center line */
168 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));
169 cairo_set_line_width (cr, 1.0);
170 cairo_move_to (cr, (pos_box_size/2.0) + (usable_width/2.0), 0);
171 cairo_line_to (cr, (pos_box_size/2.0) + (usable_width/2.0), height);
172 cairo_stroke (cr);
174 /* left box */
176 cairo_rectangle (cr,
177 left - half_lr_box,
178 half_lr_box+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);
185 /* add text */
187 cairo_move_to (cr,
188 left - half_lr_box + 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 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"));
194 /* right box */
196 cairo_rectangle (cr,
197 right - half_lr_box,
198 half_lr_box+step_down,
199 lr_box_size, lr_box_size);
200 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));
201 cairo_stroke_preserve (cr);
202 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));
203 cairo_fill (cr);
205 /* add text */
207 cairo_move_to (cr,
208 right - half_lr_box + 3,
209 (lr_box_size/2)+step_down + 13);
210 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));
211 cairo_show_text (cr, _("R"));
213 /* 2 lines that connect them both */
214 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));
215 cairo_set_line_width (cr, 1.0);
216 cairo_move_to (cr, left + half_lr_box, half_lr_box+step_down);
217 cairo_line_to (cr, right - half_lr_box, half_lr_box+step_down);
218 cairo_stroke (cr);
221 cairo_move_to (cr, left + half_lr_box, half_lr_box+step_down+lr_box_size);
222 cairo_line_to (cr, right - half_lr_box, half_lr_box+step_down+lr_box_size);
223 cairo_stroke (cr);
225 /* draw the position indicator */
227 double spos = (pos_box_size/2.0) + (usable_width * pos);
229 cairo_set_line_width (cr, 2.0);
230 cairo_move_to (cr, spos + (pos_box_size/2.0), top_step); /* top right */
231 cairo_rel_line_to (cr, 0.0, pos_box_size); /* lower right */
232 cairo_rel_line_to (cr, -pos_box_size/2.0, 4.0); /* bottom point */
233 cairo_rel_line_to (cr, -pos_box_size/2.0, -4.0); /* lower left */
234 cairo_rel_line_to (cr, 0.0, -pos_box_size); /* upper left */
235 cairo_close_path (cr);
238 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));
239 cairo_stroke_preserve (cr);
240 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));
241 cairo_fill (cr);
243 /* marker line */
245 cairo_set_line_width (cr, 1.0);
246 cairo_move_to (cr, spos, pos_box_size+4);
247 cairo_rel_line_to (cr, 0, height - (pos_box_size+4));
248 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));
249 cairo_stroke (cr);
251 /* done */
253 cairo_destroy (cr);
254 return true;
257 bool
258 MonoPanner::on_button_press_event (GdkEventButton* ev)
260 drag_start_x = ev->x;
261 last_drag_x = ev->x;
263 dragging = false;
264 accumulated_delta = 0;
265 detented = false;
267 /* Let the binding proxies get first crack at the press event
270 if (ev->y < 20) {
271 if (position_binder.button_press_handler (ev)) {
272 return true;
276 if (ev->button != 1) {
277 return false;
280 if (ev->type == GDK_2BUTTON_PRESS) {
281 int width = get_width();
283 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
284 /* handled by button release */
285 return true;
289 if (ev->x <= width/3) {
290 /* left side dbl click */
291 position_control->set_value (0);
292 } else if (ev->x > 2*width/3) {
293 position_control->set_value (1.0);
294 } else {
295 position_control->set_value (0.5);
298 dragging = false;
300 } else if (ev->type == GDK_BUTTON_PRESS) {
302 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
303 /* handled by button release */
304 return true;
307 dragging = true;
308 StartGesture ();
311 return true;
314 bool
315 MonoPanner::on_button_release_event (GdkEventButton* ev)
317 if (ev->button != 1) {
318 return false;
321 dragging = false;
322 accumulated_delta = 0;
323 detented = false;
325 if (drag_data_window) {
326 drag_data_window->hide ();
329 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
330 /* reset to default */
331 position_control->set_value (0.5);
332 } else {
333 StopGesture ();
336 return true;
339 bool
340 MonoPanner::on_scroll_event (GdkEventScroll* ev)
342 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
343 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
344 double step;
346 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
347 step = one_degree;
348 } else {
349 step = one_degree * 5.0;
352 switch (ev->direction) {
353 case GDK_SCROLL_UP:
354 case GDK_SCROLL_LEFT:
355 pv -= step;
356 position_control->set_value (pv);
357 break;
358 case GDK_SCROLL_DOWN:
359 case GDK_SCROLL_RIGHT:
360 pv += step;
361 position_control->set_value (pv);
362 break;
365 return true;
368 bool
369 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
371 if (!dragging) {
372 return false;
375 if (!drag_data_window) {
376 drag_data_window = new Window (WINDOW_POPUP);
377 drag_data_window->set_name (X_("ContrastingPopup"));
378 drag_data_window->set_position (WIN_POS_MOUSE);
379 drag_data_window->set_decorated (false);
381 drag_data_label = manage (new Label);
382 drag_data_label->set_use_markup (true);
384 drag_data_window->set_border_width (6);
385 drag_data_window->add (*drag_data_label);
386 drag_data_label->show ();
388 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
389 if (toplevel) {
390 drag_data_window->set_transient_for (*toplevel);
394 if (!drag_data_window->is_visible ()) {
395 /* move the window a little away from the mouse */
396 int rx, ry;
397 get_window()->get_origin (rx, ry);
398 drag_data_window->move (rx, ry+get_height());
399 drag_data_window->present ();
402 int w = get_width();
403 double delta = (ev->x - last_drag_x) / (double) w;
405 /* create a detent close to the center */
407 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
408 detented = true;
409 /* snap to center */
410 position_control->set_value (0.5);
413 if (detented) {
414 accumulated_delta += delta;
416 /* have we pulled far enough to escape ? */
418 if (fabs (accumulated_delta) >= 0.025) {
419 position_control->set_value (position_control->get_value() + accumulated_delta);
420 detented = false;
421 accumulated_delta = false;
423 } else {
424 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
425 position_control->set_value (pv + delta);
428 last_drag_x = ev->x;
429 return true;
432 bool
433 MonoPanner::on_key_press_event (GdkEventKey* ev)
435 double one_degree = 1.0/180.0;
436 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
437 double step;
439 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
440 step = one_degree;
441 } else {
442 step = one_degree * 5.0;
445 /* up/down control width because we consider pan position more "important"
446 (and thus having higher "sense" priority) than width.
449 switch (ev->keyval) {
450 case GDK_Left:
451 pv -= step;
452 position_control->set_value (pv);
453 break;
454 case GDK_Right:
455 pv += step;
456 position_control->set_value (pv);
457 break;
458 default:
459 return false;
462 return true;
465 bool
466 MonoPanner::on_key_release_event (GdkEventKey* ev)
468 return false;
471 bool
472 MonoPanner::on_enter_notify_event (GdkEventCrossing* ev)
474 grab_focus ();
475 Keyboard::magic_widget_grab_focus ();
476 return false;
479 bool
480 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
482 Keyboard::magic_widget_drop_focus ();
483 return false;
486 void
487 MonoPanner::set_colors ()
489 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
490 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
491 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
492 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
493 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
494 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
497 void
498 MonoPanner::color_handler ()
500 set_colors ();
501 queue_draw ();