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 "mono_panner.h"
39 #include "rgb_macros.h"
46 using namespace Gtkmm2ext;
48 static const int pos_box_size = 9;
49 static const int lr_box_size = 15;
50 static const int step_down = 10;
51 static const int top_step = 2;
53 MonoPanner::ColorScheme MonoPanner::colors;
54 bool MonoPanner::have_colors = false;
56 MonoPanner::MonoPanner (boost::shared_ptr<PBD::Controllable> position)
57 : position_control (position)
61 , accumulated_delta (0)
63 , drag_data_window (0)
65 , position_binder (position)
72 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
74 set_flags (Gtk::CAN_FOCUS);
76 add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|
77 Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|
78 Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|
80 Gdk::POINTER_MOTION_MASK);
82 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
85 MonoPanner::~MonoPanner ()
87 delete drag_data_window;
91 MonoPanner::set_drag_data ()
93 if (!drag_data_label) {
97 double pos = position_control->get_value(); // 0..1
99 /* We show the position of the center of the image relative to the left & right.
100 This is expressed as a pair of percentage values that ranges from (100,0)
101 (hard left) through (50,50) (hard center) to (0,100) (hard right).
103 This is pretty wierd, but its the way audio engineers expect it. Just remember that
104 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
107 drag_data_label->set_markup (string_compose (_("L:%1 R:%2"),
108 (int) rint (100.0 * (1.0 - pos)),
109 (int) rint (100.0 * pos)));
113 MonoPanner::value_change ()
120 MonoPanner::on_expose_event (GdkEventExpose* ev)
122 Glib::RefPtr<Gdk::Window> win (get_window());
123 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
125 cairo_t* cr = gdk_cairo_create (win->gobj());
128 double pos = position_control->get_value (); /* 0..1 */
129 uint32_t o, f, t, b, pf, po;
132 height = get_height ();
137 b = colors.background;
138 pf = colors.pos_fill;
139 po = colors.pos_outline;
143 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));
144 cairo_rectangle (cr, 0, 0, width, height);
147 double usable_width = width - pos_box_size;
149 /* compute the centers of the L/R boxes based on the current stereo width */
151 if (fmod (usable_width,2.0) == 0) {
152 /* even width, but we need odd, so that there is an exact center.
153 So, offset cairo by 1, and reduce effective width by 1
156 cairo_translate (cr, 1.0, 0.0);
159 const double half_lr_box = lr_box_size/2.0;
163 left = 4 + half_lr_box; // center of left box
164 right = width - 4 - half_lr_box; // center of right box
167 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));
168 cairo_set_line_width (cr, 1.0);
169 cairo_move_to (cr, (pos_box_size/2.0) + (usable_width/2.0), 0);
170 cairo_line_to (cr, (pos_box_size/2.0) + (usable_width/2.0), height);
177 half_lr_box+step_down,
178 lr_box_size, lr_box_size);
179 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));
180 cairo_stroke_preserve (cr);
181 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));
187 left - half_lr_box + 3,
188 (lr_box_size/2) + step_down + 13);
189 cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
190 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));
191 cairo_show_text (cr, _("L"));
197 half_lr_box+step_down,
198 lr_box_size, lr_box_size);
199 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));
200 cairo_stroke_preserve (cr);
201 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));
207 right - half_lr_box + 3,
208 (lr_box_size/2)+step_down + 13);
209 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));
210 cairo_show_text (cr, _("R"));
212 /* 2 lines that connect them both */
213 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));
214 cairo_set_line_width (cr, 1.0);
215 cairo_move_to (cr, left + half_lr_box, half_lr_box+step_down);
216 cairo_line_to (cr, right - half_lr_box, half_lr_box+step_down);
220 cairo_move_to (cr, left + half_lr_box, half_lr_box+step_down+lr_box_size);
221 cairo_line_to (cr, right - half_lr_box, half_lr_box+step_down+lr_box_size);
224 /* draw the position indicator */
226 double spos = (pos_box_size/2.0) + (usable_width * pos);
228 cairo_set_line_width (cr, 2.0);
229 cairo_move_to (cr, spos + (pos_box_size/2.0), top_step); /* top right */
230 cairo_rel_line_to (cr, 0.0, pos_box_size); /* lower right */
231 cairo_rel_line_to (cr, -pos_box_size/2.0, 4.0); /* bottom point */
232 cairo_rel_line_to (cr, -pos_box_size/2.0, -4.0); /* lower left */
233 cairo_rel_line_to (cr, 0.0, -pos_box_size); /* upper left */
234 cairo_close_path (cr);
237 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));
238 cairo_stroke_preserve (cr);
239 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));
244 cairo_set_line_width (cr, 1.0);
245 cairo_move_to (cr, spos, pos_box_size+4);
246 cairo_rel_line_to (cr, 0, height - (pos_box_size+4));
247 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));
257 MonoPanner::on_button_press_event (GdkEventButton* ev)
259 drag_start_x = ev->x;
263 accumulated_delta = 0;
266 /* Let the binding proxies get first crack at the press event
270 if (position_binder.button_press_handler (ev)) {
275 if (ev->button != 1) {
279 if (ev->type == GDK_2BUTTON_PRESS) {
280 int width = get_width();
282 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
283 /* handled by button release */
288 if (ev->x <= width/3) {
289 /* left side dbl click */
290 position_control->set_value (0);
291 } else if (ev->x > 2*width/3) {
292 position_control->set_value (1.0);
294 position_control->set_value (0.5);
299 } else if (ev->type == GDK_BUTTON_PRESS) {
301 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
302 /* handled by button release */
314 MonoPanner::on_button_release_event (GdkEventButton* ev)
316 if (ev->button != 1) {
321 accumulated_delta = 0;
324 if (drag_data_window) {
325 drag_data_window->hide ();
328 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
329 /* reset to default */
330 position_control->set_value (0.5);
339 MonoPanner::on_scroll_event (GdkEventScroll* ev)
341 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
342 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
345 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
348 step = one_degree * 5.0;
351 switch (ev->direction) {
353 case GDK_SCROLL_LEFT:
355 position_control->set_value (pv);
357 case GDK_SCROLL_DOWN:
358 case GDK_SCROLL_RIGHT:
360 position_control->set_value (pv);
368 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
374 if (!drag_data_window) {
375 drag_data_window = new Window (WINDOW_POPUP);
376 drag_data_window->set_position (WIN_POS_MOUSE);
377 drag_data_window->set_decorated (false);
379 drag_data_label = manage (new Label);
380 drag_data_label->set_use_markup (true);
382 drag_data_window->set_border_width (6);
383 drag_data_window->add (*drag_data_label);
384 drag_data_label->show ();
386 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
388 drag_data_window->set_transient_for (*toplevel);
392 if (!drag_data_window->is_visible ()) {
393 /* move the window a little away from the mouse */
394 drag_data_window->move (ev->x_root+30, ev->y_root+30);
395 drag_data_window->present ();
399 double delta = (ev->x - last_drag_x) / (double) w;
401 /* create a detent close to the center */
403 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
406 position_control->set_value (0.5);
410 accumulated_delta += delta;
412 /* have we pulled far enough to escape ? */
414 if (fabs (accumulated_delta) >= 0.025) {
415 position_control->set_value (position_control->get_value() + accumulated_delta);
417 accumulated_delta = false;
420 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
421 position_control->set_value (pv + delta);
429 MonoPanner::on_key_press_event (GdkEventKey* ev)
431 double one_degree = 1.0/180.0;
432 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
435 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
438 step = one_degree * 5.0;
441 /* up/down control width because we consider pan position more "important"
442 (and thus having higher "sense" priority) than width.
445 switch (ev->keyval) {
448 position_control->set_value (pv);
452 position_control->set_value (pv);
462 MonoPanner::on_key_release_event (GdkEventKey* ev)
468 MonoPanner::on_enter_notify_event (GdkEventCrossing* ev)
471 Keyboard::magic_widget_grab_focus ();
476 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
478 Keyboard::magic_widget_drop_focus ();
483 MonoPanner::set_colors ()
485 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
486 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
487 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
488 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
489 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
490 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
494 MonoPanner::color_handler ()