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 = 10;
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 /* the usable width is reduced from the real width, because we need space for
148 the two halves of LR boxes that will extend past the actual left/right
149 positions (indicated by the vertical line segment above them).
152 double usable_width = width - lr_box_size;
154 /* compute the centers of the L/R boxes based on the current stereo width */
156 if (fmod (usable_width,2.0) == 0) {
157 /* even width, but we need odd, so that there is an exact center.
158 So, offset cairo by 1, and reduce effective width by 1
161 cairo_translate (cr, 1.0, 0.0);
164 double center = (lr_box_size/2.0) + (usable_width * pos);
165 const double half_lr_box = lr_box_size/2.0;
169 left = 4 + half_lr_box; // center of left box
170 right = width - 4 - half_lr_box; // center of right box
173 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));
174 cairo_set_line_width (cr, 1.0);
175 cairo_move_to (cr, width/2.0, 0);
176 cairo_line_to (cr, width/2.0, height);
183 half_lr_box+step_down,
184 lr_box_size, lr_box_size);
185 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));
186 cairo_stroke_preserve (cr);
187 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));
193 left - half_lr_box + 3,
194 (lr_box_size/2) + step_down + 13);
195 cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
196 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));
197 cairo_show_text (cr, _("L"));
203 half_lr_box+step_down,
204 lr_box_size, lr_box_size);
205 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));
206 cairo_stroke_preserve (cr);
207 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));
213 right - half_lr_box + 3,
214 (lr_box_size/2)+step_down + 13);
215 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));
216 cairo_show_text (cr, _("R"));
218 /* 2 lines that connect them both */
219 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));
220 cairo_set_line_width (cr, 1.0);
221 cairo_move_to (cr, left + half_lr_box, half_lr_box+step_down);
222 cairo_line_to (cr, right - half_lr_box, half_lr_box+step_down);
226 cairo_move_to (cr, left + half_lr_box, half_lr_box+step_down+lr_box_size);
227 cairo_line_to (cr, right - half_lr_box, half_lr_box+step_down+lr_box_size);
230 /* draw the position indicator */
232 cairo_set_line_width (cr, 2.0);
233 cairo_rectangle (cr, lrint (center - (pos_box_size/2.0)), top_step, pos_box_size, pos_box_size);
234 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));
235 cairo_stroke_preserve (cr);
236 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));
246 MonoPanner::on_button_press_event (GdkEventButton* ev)
248 drag_start_x = ev->x;
252 accumulated_delta = 0;
255 /* Let the binding proxies get first crack at the press event
259 if (position_binder.button_press_handler (ev)) {
264 if (ev->button != 1) {
268 if (ev->type == GDK_2BUTTON_PRESS) {
269 int width = get_width();
271 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
272 /* handled by button release */
277 if (ev->x <= width/3) {
278 /* left side dbl click */
279 position_control->set_value (0);
280 } else if (ev->x > 2*width/3) {
281 position_control->set_value (1.0);
283 position_control->set_value (0.5);
288 } else if (ev->type == GDK_BUTTON_PRESS) {
290 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
291 /* handled by button release */
302 MonoPanner::on_button_release_event (GdkEventButton* ev)
304 if (ev->button != 1) {
309 accumulated_delta = 0;
312 if (drag_data_window) {
313 drag_data_window->hide ();
316 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
317 /* reset to default */
318 position_control->set_value (0.5);
325 MonoPanner::on_scroll_event (GdkEventScroll* ev)
327 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
328 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
331 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
334 step = one_degree * 5.0;
337 switch (ev->direction) {
339 case GDK_SCROLL_LEFT:
341 position_control->set_value (pv);
343 case GDK_SCROLL_DOWN:
344 case GDK_SCROLL_RIGHT:
346 position_control->set_value (pv);
354 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
360 if (!drag_data_window) {
361 drag_data_window = new Window (WINDOW_POPUP);
362 drag_data_window->set_position (WIN_POS_MOUSE);
363 drag_data_window->set_decorated (false);
365 drag_data_label = manage (new Label);
366 drag_data_label->set_use_markup (true);
368 drag_data_window->set_border_width (6);
369 drag_data_window->add (*drag_data_label);
370 drag_data_label->show ();
372 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
374 drag_data_window->set_transient_for (*toplevel);
378 if (!drag_data_window->is_visible ()) {
379 /* move the window a little away from the mouse */
380 drag_data_window->move (ev->x_root+30, ev->y_root+30);
381 drag_data_window->present ();
385 double delta = (ev->x - last_drag_x) / (double) w;
387 /* create a detent close to the center */
389 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
392 position_control->set_value (0.5);
396 accumulated_delta += delta;
398 /* have we pulled far enough to escape ? */
400 if (fabs (accumulated_delta) >= 0.025) {
401 position_control->set_value (position_control->get_value() + accumulated_delta);
403 accumulated_delta = false;
406 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
407 position_control->set_value (pv + delta);
415 MonoPanner::on_key_press_event (GdkEventKey* ev)
417 double one_degree = 1.0/180.0;
418 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
421 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
424 step = one_degree * 5.0;
427 /* up/down control width because we consider pan position more "important"
428 (and thus having higher "sense" priority) than width.
431 switch (ev->keyval) {
434 position_control->set_value (pv);
438 position_control->set_value (pv);
448 MonoPanner::on_key_release_event (GdkEventKey* ev)
454 MonoPanner::on_enter_notify_event (GdkEventCrossing* ev)
457 Keyboard::magic_widget_grab_focus ();
462 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
464 Keyboard::magic_widget_drop_focus ();
469 MonoPanner::set_colors ()
471 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
472 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
473 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
474 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
475 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
476 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
480 MonoPanner::color_handler ()