various fixes to MidiRegionView selection handling, key handling, drawing of ghost...
[ardour2.git] / gtk2_ardour / mono_panner.cc
blob78fec4e6796b7522b0ee50ad95d1c30cb88b6a43
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"
33 #include "gtkmm2ext/utils.h"
35 #include "ardour/panner.h"
36 #include "ardour/panner.h"
38 #include "ardour_ui.h"
39 #include "global_signals.h"
40 #include "mono_panner.h"
41 #include "rgb_macros.h"
42 #include "utils.h"
44 #include "i18n.h"
46 using namespace std;
47 using namespace Gtk;
48 using namespace Gtkmm2ext;
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;
55 MonoPanner::ColorScheme MonoPanner::colors;
56 bool MonoPanner::have_colors = false;
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)
69 if (!have_colors) {
70 set_colors ();
71 have_colors = true;
74 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
76 set_flags (Gtk::CAN_FOCUS);
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);
84 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
87 MonoPanner::~MonoPanner ()
89 delete drag_data_window;
92 void
93 MonoPanner::set_drag_data ()
95 if (!drag_data_label) {
96 return;
99 double pos = position_control->get_value(); // 0..1
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).
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.
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);
116 void
117 MonoPanner::value_change ()
119 set_drag_data ();
120 queue_draw ();
123 bool
124 MonoPanner::on_expose_event (GdkEventExpose* ev)
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();
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;
135 width = get_width();
136 height = get_height ();
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;
145 /* background */
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 ();
151 double usable_width = width - pos_box_size;
153 /* compute the centers of the L/R boxes based on the current stereo width */
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
159 usable_width -= 1.0;
160 context->translate (1.0, 0.0);
163 const double half_lr_box = lr_box_size/2.0;
164 double left;
165 double right;
167 left = 4 + half_lr_box; // center of left box
168 right = width - 4 - half_lr_box; // center of right box
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 ();
177 /* left box */
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 ();
188 /* add text */
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"));
197 /* right box */
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 ();
208 /* add text */
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"));
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);
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
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 ();
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 ();
232 /* draw the position indicator */
234 double spos = (pos_box_size/2.0) + (usable_width * pos);
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 ();
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 ();
250 /* marker line */
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 ();
258 /* done */
260 return true;
263 bool
264 MonoPanner::on_button_press_event (GdkEventButton* ev)
266 drag_start_x = ev->x;
267 last_drag_x = ev->x;
269 dragging = false;
270 accumulated_delta = 0;
271 detented = false;
273 /* Let the binding proxies get first crack at the press event
276 if (ev->y < 20) {
277 if (position_binder.button_press_handler (ev)) {
278 return true;
282 if (ev->button != 1) {
283 return false;
286 if (ev->type == GDK_2BUTTON_PRESS) {
287 int width = get_width();
289 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
290 /* handled by button release */
291 return true;
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);
304 dragging = false;
306 } else if (ev->type == GDK_BUTTON_PRESS) {
308 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
309 /* handled by button release */
310 return true;
313 dragging = true;
314 StartGesture ();
317 return true;
320 bool
321 MonoPanner::on_button_release_event (GdkEventButton* ev)
323 if (ev->button != 1) {
324 return false;
327 dragging = false;
328 accumulated_delta = 0;
329 detented = false;
331 if (drag_data_window) {
332 drag_data_window->hide ();
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 ();
342 return true;
345 bool
346 MonoPanner::on_scroll_event (GdkEventScroll* ev)
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;
352 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
353 step = one_degree;
354 } else {
355 step = one_degree * 5.0;
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;
371 return true;
374 bool
375 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
377 if (!dragging) {
378 return false;
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);
387 drag_data_label = manage (new Label);
388 drag_data_label->set_use_markup (true);
390 drag_data_window->set_border_width (6);
391 drag_data_window->add (*drag_data_label);
392 drag_data_label->show ();
394 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
395 if (toplevel) {
396 drag_data_window->set_transient_for (*toplevel);
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 ();
408 int w = get_width();
409 double delta = (ev->x - last_drag_x) / (double) w;
411 /* create a detent close to the center */
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);
419 if (detented) {
420 accumulated_delta += delta;
422 /* have we pulled far enough to escape ? */
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;
429 } else {
430 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
431 position_control->set_value (pv + delta);
434 last_drag_x = ev->x;
435 return true;
438 bool
439 MonoPanner::on_key_press_event (GdkEventKey* ev)
441 double one_degree = 1.0/180.0;
442 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
443 double step;
445 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
446 step = one_degree;
447 } else {
448 step = one_degree * 5.0;
451 /* up/down control width because we consider pan position more "important"
452 (and thus having higher "sense" priority) than width.
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;
468 return true;
471 bool
472 MonoPanner::on_key_release_event (GdkEventKey* ev)
474 return false;
477 bool
478 MonoPanner::on_enter_notify_event (GdkEventCrossing* ev)
480 grab_focus ();
481 Keyboard::magic_widget_grab_focus ();
482 return false;
485 bool
486 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
488 Keyboard::magic_widget_drop_focus ();
489 return false;
492 void
493 MonoPanner::set_colors ()
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();
503 void
504 MonoPanner::color_handler ()
506 set_colors ();
507 queue_draw ();