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 */
313 MonoPanner::on_button_release_event (GdkEventButton* ev)
315 if (ev->button != 1) {
320 accumulated_delta = 0;
323 if (drag_data_window) {
324 drag_data_window->hide ();
327 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
328 /* reset to default */
329 position_control->set_value (0.5);
336 MonoPanner::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
342 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
345 step = one_degree * 5.0;
348 switch (ev->direction) {
350 case GDK_SCROLL_LEFT:
352 position_control->set_value (pv);
354 case GDK_SCROLL_DOWN:
355 case GDK_SCROLL_RIGHT:
357 position_control->set_value (pv);
365 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
371 if (!drag_data_window) {
372 drag_data_window = new Window (WINDOW_POPUP);
373 drag_data_window->set_position (WIN_POS_MOUSE);
374 drag_data_window->set_decorated (false);
376 drag_data_label = manage (new Label);
377 drag_data_label->set_use_markup (true);
379 drag_data_window->set_border_width (6);
380 drag_data_window->add (*drag_data_label);
381 drag_data_label->show ();
383 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
385 drag_data_window->set_transient_for (*toplevel);
389 if (!drag_data_window->is_visible ()) {
390 /* move the window a little away from the mouse */
391 drag_data_window->move (ev->x_root+30, ev->y_root+30);
392 drag_data_window->present ();
396 double delta = (ev->x - last_drag_x) / (double) w;
398 /* create a detent close to the center */
400 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
403 position_control->set_value (0.5);
407 accumulated_delta += delta;
409 /* have we pulled far enough to escape ? */
411 if (fabs (accumulated_delta) >= 0.025) {
412 position_control->set_value (position_control->get_value() + accumulated_delta);
414 accumulated_delta = false;
417 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
418 position_control->set_value (pv + delta);
426 MonoPanner::on_key_press_event (GdkEventKey* ev)
428 double one_degree = 1.0/180.0;
429 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
432 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
435 step = one_degree * 5.0;
438 /* up/down control width because we consider pan position more "important"
439 (and thus having higher "sense" priority) than width.
442 switch (ev->keyval) {
445 position_control->set_value (pv);
449 position_control->set_value (pv);
459 MonoPanner::on_key_release_event (GdkEventKey* ev)
465 MonoPanner::on_enter_notify_event (GdkEventCrossing* ev)
468 Keyboard::magic_widget_grab_focus ();
473 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
475 Keyboard::magic_widget_drop_focus ();
480 MonoPanner::set_colors ()
482 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
483 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
484 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
485 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
486 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
487 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
491 MonoPanner::color_handler ()