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.
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"
36 #include "ardour_ui.h"
37 #include "global_signals.h"
38 #include "stereo_panner.h"
39 #include "rgb_macros.h"
46 using namespace Gtkmm2ext;
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;
53 StereoPanner::ColorScheme StereoPanner::colors[3];
54 bool StereoPanner::have_colors = false;
55 PBD::Signal0<void> StereoPanner::color_change;
57 StereoPanner::StereoPanner (boost::shared_ptr<PBD::Controllable> position, boost::shared_ptr<PBD::Controllable> width)
58 : position_control (position)
59 , width_control (width)
61 , dragging_position (false)
62 , dragging_left (false)
63 , dragging_right (false)
66 , accumulated_delta (0)
73 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
74 width_control->Changed.connect (connections, invalidator(*this), boost::bind (&StereoPanner::value_change, this), gui_context());
77 set_flags (Gtk::CAN_FOCUS);
79 add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|
80 Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|
81 Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|
83 Gdk::POINTER_MOTION_MASK);
85 color_change.connect (connections, invalidator (*this), boost::bind (&DrawingArea::queue_draw, this), gui_context());
88 StereoPanner::~StereoPanner ()
93 StereoPanner::set_tooltip ()
95 double pos = position_control->get_value(); // 0..1
97 /* We show the position of the center of the image relative to the left & right.
98 This is expressed as a pair of percentage values that ranges from (100,0)
99 (hard left) through (50,50) (hard center) to (0,100) (hard right).
101 This is pretty wierd, but its the way audio engineers expect it. Just remember that
102 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
105 Gtkmm2ext::UI::instance()->set_tip (this,
106 string_compose (_("L:%1 R:%2 Width: %3%%\n\n0 -> set width to zero\n%4-uparrow -> set width to 100\n%4-downarrow -> set width to -100"),
107 (int) rint (100.0 * (1.0 - pos)),
108 (int) rint (100.0 * pos),
109 (int) floor (100.0 * width_control->get_value()),
110 Keyboard::secondary_modifier_name()).c_str());
114 StereoPanner::value_change ()
121 StereoPanner::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());
129 double pos = position_control->get_value (); /* 0..1 */
130 double swidth = width_control->get_value (); /* -1..+1 */
131 double fswidth = fabs (swidth);
136 height = get_height ();
140 } else if (swidth < 0.0) {
146 o = colors[state].outline;
147 f = colors[state].fill;
148 t = colors[state].text;
149 b = colors[state].background;
153 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));
154 cairo_rectangle (cr, 0, 0, width, height);
157 double usable_width = width - lr_box_size;
159 /* compute the centers of the L/R boxes based on the current stereo width */
161 if (fmod (usable_width,2.0) == 0) {
162 /* even width, but we need odd, so that there is an exact center.
163 So, offset cairo by 1, and reduce effective width by 1
166 cairo_translate (cr, 1.0, 0.0);
169 double center = (lr_box_size/2.0) + (usable_width * pos);
170 const double pan_spread = (fswidth * usable_width)/2.0;
171 const double half_lr_box = lr_box_size/2.0;
175 left = center - pan_spread; // center of left box
176 right = center + pan_spread; // right of right box
178 /* compute & draw the line through the box */
180 cairo_set_line_width (cr, 2);
181 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));
182 cairo_move_to (cr, left, top_step+(pos_box_size/2)+step_down);
183 cairo_line_to (cr, left, top_step+(pos_box_size/2));
184 cairo_line_to (cr, right, top_step+(pos_box_size/2));
185 cairo_line_to (cr, right, top_step+(pos_box_size/2) + step_down);
192 (lr_box_size/2)+step_down,
193 lr_box_size, lr_box_size);
194 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));
195 cairo_stroke_preserve (cr);
196 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));
202 left - half_lr_box + 3,
203 (lr_box_size/2) + step_down + 13);
204 cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
207 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));
209 cairo_show_text (cr, _("R"));
211 cairo_show_text (cr, _("L"));
219 (lr_box_size/2)+step_down,
220 lr_box_size, lr_box_size);
221 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));
222 cairo_stroke_preserve (cr);
223 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));
229 right - half_lr_box + 3,
230 (lr_box_size/2)+step_down + 13);
231 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));
234 cairo_show_text (cr, _("M"));
237 cairo_show_text (cr, _("L"));
239 cairo_show_text (cr, _("R"));
243 /* draw the central box */
245 cairo_set_line_width (cr, 1);
246 cairo_rectangle (cr, lrint (center - (pos_box_size/2.0)), top_step, pos_box_size, pos_box_size);
247 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));
248 cairo_stroke_preserve (cr);
249 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));
259 StereoPanner::on_button_press_event (GdkEventButton* ev)
261 drag_start_x = ev->x;
264 dragging_position = false;
265 dragging_left = false;
266 dragging_right = false;
267 accumulated_delta = 0;
270 /* top section of widget is for position drags */
271 dragging_position = true;
273 /* lower section is for dragging width */
275 double pos = position_control->get_value (); /* 0..1 */
276 double swidth = width_control->get_value (); /* -1..+1 */
277 double fswidth = fabs (swidth);
278 int usable_width = get_width() - lr_box_size;
279 double center = (lr_box_size/2.0) + (usable_width * pos);
280 int left = lrint (center - (fswidth * usable_width / 2.0)); // center of leftmost box
281 int right = lrint (center + (fswidth * usable_width / 2.0)); // center of rightmost box
282 const int half_box = lr_box_size/2;
284 if (ev->x >= (left - half_box) && ev->x < (left + half_box)) {
285 dragging_left = true;
286 } else if (ev->x >= (right - half_box) && ev->x < (right + half_box)) {
287 dragging_right = true;
292 if (ev->type == GDK_2BUTTON_PRESS) {
293 if (dragging_position) {
294 int width = get_width();
295 if (ev->x >= width/2 - 10 && ev->x <= width/2 + 10) {
296 /* double click near center, reset position to center */
297 position_control->set_value (0.5);
299 if (ev->x < width/2) {
300 /* double click on left, collapse to hard left */
301 width_control->set_value (0);
302 position_control->set_value (0);
304 /* double click on right, collapse to hard right */
305 width_control->set_value (0);
306 position_control->set_value (1.0);
310 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
311 width_control->set_value (-1.0); // reset position to reversed full, LR
313 width_control->set_value (1.0); // reset position to full, LR
325 StereoPanner::on_button_release_event (GdkEventButton* ev)
328 dragging_position = false;
329 dragging_left = false;
330 dragging_right = false;
331 accumulated_delta = 0;
336 StereoPanner::on_scroll_event (GdkEventScroll* ev)
338 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
339 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
340 double wv = width_control->get_value(); // 0..1.0 ; 0 = left
343 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
346 step = one_degree * 5.0;
349 switch (ev->direction) {
350 case GDK_SCROLL_LEFT:
352 width_control->set_value (wv);
356 position_control->set_value (pv);
358 case GDK_SCROLL_RIGHT:
360 width_control->set_value (wv);
362 case GDK_SCROLL_DOWN:
364 position_control->set_value (pv);
372 StereoPanner::on_motion_notify_event (GdkEventMotion* ev)
379 double delta = (ev->x - last_drag_x) / (double) w;
385 if (dragging_left || dragging_right) {
387 /* maintain position as invariant as we change the width */
389 double current_width = width_control->get_value ();
391 if (fabs (current_width) < 0.1) {
392 accumulated_delta += delta;
393 /* in the detent - have we pulled far enough to escape ? */
394 if (fabs (accumulated_delta) >= 0.1) {
395 width_control->set_value (current_width + accumulated_delta);
396 accumulated_delta = 0;
399 width_control->set_value (current_width + delta);
402 } else if (dragging_position) {
404 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
405 position_control->set_value (pv + delta);
413 StereoPanner::on_key_press_event (GdkEventKey* ev)
415 double one_degree = 1.0/180.0;
416 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
417 double wv = width_control->get_value(); // 0..1.0 ; 0 = left
420 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
423 step = one_degree * 5.0;
426 /* up/down control width because we consider pan position more "important"
427 (and thus having higher "sense" priority) than width.
430 switch (ev->keyval) {
432 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
433 width_control->set_value (1.0);
435 width_control->set_value (wv + step);
439 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
440 width_control->set_value (-1.0);
442 width_control->set_value (wv - step);
447 position_control->set_value (pv);
451 position_control->set_value (pv);
457 width_control->set_value (0.0);
468 StereoPanner::on_key_release_event (GdkEventKey* ev)
474 StereoPanner::on_enter_notify_event (GdkEventCrossing* ev)
477 Keyboard::magic_widget_grab_focus ();
482 StereoPanner::on_leave_notify_event (GdkEventCrossing*)
484 Keyboard::magic_widget_drop_focus ();
489 StereoPanner::set_colors ()
491 colors[Normal].fill = ARDOUR_UI::config()->canvasvar_StereoPannerFill.get();
492 colors[Normal].outline = ARDOUR_UI::config()->canvasvar_StereoPannerOutline.get();
493 colors[Normal].text = ARDOUR_UI::config()->canvasvar_StereoPannerText.get();
494 colors[Normal].background = ARDOUR_UI::config()->canvasvar_StereoPannerBackground.get();
496 colors[Mono].fill = ARDOUR_UI::config()->canvasvar_StereoPannerMonoFill.get();
497 colors[Mono].outline = ARDOUR_UI::config()->canvasvar_StereoPannerMonoOutline.get();
498 colors[Mono].text = ARDOUR_UI::config()->canvasvar_StereoPannerMonoText.get();
499 colors[Mono].background = ARDOUR_UI::config()->canvasvar_StereoPannerMonoBackground.get();
501 colors[Inverted].fill = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedFill.get();
502 colors[Inverted].outline = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedOutline.get();
503 colors[Inverted].text = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedText.get();
504 colors[Inverted].background = ARDOUR_UI::config()->canvasvar_StereoPannerInvertedBackground.get();
506 color_change (); /* EMIT SIGNAL */