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"
33 #include "gtkmm2ext/utils.h"
35 #include "ardour/panner.h"
36 #include "ardour/panner.h"
37 #include "ardour/pannable.h"
39 #include "ardour_ui.h"
40 #include "global_signals.h"
41 #include "mono_panner.h"
42 #include "rgb_macros.h"
49 using namespace Gtkmm2ext;
51 static const int pos_box_size = 9;
52 static const int lr_box_size = 15;
53 static const int step_down = 10;
54 static const int top_step = 2;
56 MonoPanner::ColorScheme MonoPanner::colors;
57 bool MonoPanner::have_colors = false;
59 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::Panner> panner)
61 , position_control (_panner->pannable()->pan_azimuth_control)
65 , accumulated_delta (0)
67 , drag_data_window (0)
69 , position_binder (position_control)
76 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
78 set_flags (Gtk::CAN_FOCUS);
80 add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|
81 Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|
82 Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|
84 Gdk::POINTER_MOTION_MASK);
86 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
89 MonoPanner::~MonoPanner ()
91 delete drag_data_window;
95 MonoPanner::set_drag_data ()
97 if (!drag_data_label) {
101 double pos = position_control->get_value(); // 0..1
103 /* We show the position of the center of the image relative to the left & right.
104 This is expressed as a pair of percentage values that ranges from (100,0)
105 (hard left) through (50,50) (hard center) to (0,100) (hard right).
107 This is pretty wierd, but its the way audio engineers expect it. Just remember that
108 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
112 snprintf (buf, sizeof (buf), "L:%3d R:%3d",
113 (int) rint (100.0 * (1.0 - pos)),
114 (int) rint (100.0 * pos));
115 drag_data_label->set_markup (buf);
119 MonoPanner::value_change ()
126 MonoPanner::on_expose_event (GdkEventExpose*)
128 Glib::RefPtr<Gdk::Window> win (get_window());
129 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
130 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
133 double pos = position_control->get_value (); /* 0..1 */
134 uint32_t o, f, t, b, pf, po;
135 const double corner_radius = 5;
138 height = get_height ();
143 b = colors.background;
144 pf = colors.pos_fill;
145 po = colors.pos_outline;
149 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
150 context->rectangle (0, 0, width, height);
153 double usable_width = width - pos_box_size;
155 /* compute the centers of the L/R boxes based on the current stereo width */
157 if (fmod (usable_width,2.0) == 0) {
158 /* even width, but we need odd, so that there is an exact center.
159 So, offset cairo by 1, and reduce effective width by 1
162 context->translate (1.0, 0.0);
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 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
174 context->set_line_width (1.0);
175 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
176 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
181 rounded_rectangle (context,
183 half_lr_box+step_down,
184 lr_box_size, lr_box_size, corner_radius);
185 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
186 context->stroke_preserve ();
187 context->set_source_rgba (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 context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
196 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
197 context->show_text (_("L"));
201 rounded_rectangle (context,
203 half_lr_box+step_down,
204 lr_box_size, lr_box_size, corner_radius);
205 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
206 context->stroke_preserve ();
207 context->set_source_rgba (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 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
216 context->show_text (_("R"));
218 /* 2 lines that connect them both */
219 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
220 context->set_line_width (1.0);
222 /* make the lines a little longer than they need to be, because the corners of
223 the boxes are rounded and we don't want a gap
225 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
226 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
230 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
231 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
234 /* draw the position indicator */
236 double spos = (pos_box_size/2.0) + (usable_width * pos);
238 context->set_line_width (2.0);
239 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
240 context->rel_line_to (0.0, pos_box_size); /* lower right */
241 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
242 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
243 context->rel_line_to (0.0, -pos_box_size); /* upper left */
244 context->close_path ();
247 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
248 context->stroke_preserve ();
249 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
254 context->set_line_width (1.0);
255 context->move_to (spos, pos_box_size+4);
256 context->rel_line_to (0, half_lr_box+step_down);
257 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
266 MonoPanner::on_button_press_event (GdkEventButton* ev)
268 drag_start_x = ev->x;
272 accumulated_delta = 0;
275 /* Let the binding proxies get first crack at the press event
279 if (position_binder.button_press_handler (ev)) {
284 if (ev->button != 1) {
288 if (ev->type == GDK_2BUTTON_PRESS) {
289 int width = get_width();
291 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
292 /* handled by button release */
297 if (ev->x <= width/3) {
298 /* left side dbl click */
299 position_control->set_value (0);
300 } else if (ev->x > 2*width/3) {
301 position_control->set_value (1.0);
303 position_control->set_value (0.5);
308 } else if (ev->type == GDK_BUTTON_PRESS) {
310 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
311 /* handled by button release */
323 MonoPanner::on_button_release_event (GdkEventButton* ev)
325 if (ev->button != 1) {
330 accumulated_delta = 0;
333 if (drag_data_window) {
334 drag_data_window->hide ();
337 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
347 MonoPanner::on_scroll_event (GdkEventScroll* ev)
349 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
350 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
353 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
356 step = one_degree * 5.0;
359 switch (ev->direction) {
361 case GDK_SCROLL_LEFT:
363 position_control->set_value (pv);
365 case GDK_SCROLL_DOWN:
366 case GDK_SCROLL_RIGHT:
368 position_control->set_value (pv);
376 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
382 if (!drag_data_window) {
383 drag_data_window = new Window (WINDOW_POPUP);
384 drag_data_window->set_name (X_("ContrastingPopup"));
385 drag_data_window->set_position (WIN_POS_MOUSE);
386 drag_data_window->set_decorated (false);
388 drag_data_label = manage (new Label);
389 drag_data_label->set_use_markup (true);
391 drag_data_window->set_border_width (6);
392 drag_data_window->add (*drag_data_label);
393 drag_data_label->show ();
395 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
397 drag_data_window->set_transient_for (*toplevel);
401 if (!drag_data_window->is_visible ()) {
402 /* move the window a little away from the mouse */
404 get_window()->get_origin (rx, ry);
405 drag_data_window->move (rx, ry+get_height());
406 drag_data_window->present ();
410 double delta = (ev->x - last_drag_x) / (double) w;
412 /* create a detent close to the center */
414 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
417 position_control->set_value (0.5);
421 accumulated_delta += delta;
423 /* have we pulled far enough to escape ? */
425 if (fabs (accumulated_delta) >= 0.025) {
426 position_control->set_value (position_control->get_value() + accumulated_delta);
428 accumulated_delta = false;
431 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
432 position_control->set_value (pv + delta);
440 MonoPanner::on_key_press_event (GdkEventKey* ev)
442 double one_degree = 1.0/180.0;
443 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
446 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
449 step = one_degree * 5.0;
452 /* up/down control width because we consider pan position more "important"
453 (and thus having higher "sense" priority) than width.
456 switch (ev->keyval) {
459 position_control->set_value (pv);
463 position_control->set_value (pv);
473 MonoPanner::on_key_release_event (GdkEventKey*)
479 MonoPanner::on_enter_notify_event (GdkEventCrossing*)
482 Keyboard::magic_widget_grab_focus ();
487 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
489 Keyboard::magic_widget_drop_focus ();
494 MonoPanner::set_colors ()
496 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
497 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
498 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
499 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
500 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
501 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
505 MonoPanner::color_handler ()