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"
38 #include "ardour_ui.h"
39 #include "global_signals.h"
40 #include "mono_panner.h"
41 #include "rgb_macros.h"
48 using namespace Gtkmm2ext;
50 static const int pos_box_size = 9;
51 static const int lr_box_size = 15;
52 static const int step_down = 10;
53 static const int top_step = 2;
55 MonoPanner::ColorScheme MonoPanner::colors;
56 bool MonoPanner::have_colors = false;
58 MonoPanner::MonoPanner (boost::shared_ptr<PBD::Controllable> position)
59 : position_control (position)
63 , accumulated_delta (0)
65 , drag_data_window (0)
67 , position_binder (position)
74 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
76 set_flags (Gtk::CAN_FOCUS);
78 add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|
79 Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|
80 Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|
82 Gdk::POINTER_MOTION_MASK);
84 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
87 MonoPanner::~MonoPanner ()
89 delete drag_data_window;
93 MonoPanner::set_drag_data ()
95 if (!drag_data_label) {
99 double pos = position_control->get_value(); // 0..1
101 /* We show the position of the center of the image relative to the left & right.
102 This is expressed as a pair of percentage values that ranges from (100,0)
103 (hard left) through (50,50) (hard center) to (0,100) (hard right).
105 This is pretty wierd, but its the way audio engineers expect it. Just remember that
106 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
110 snprintf (buf, sizeof (buf), "L:%3d R:%3d",
111 (int) rint (100.0 * (1.0 - pos)),
112 (int) rint (100.0 * pos));
113 drag_data_label->set_markup (buf);
117 MonoPanner::value_change ()
124 MonoPanner::on_expose_event (GdkEventExpose* ev)
126 Glib::RefPtr<Gdk::Window> win (get_window());
127 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
128 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
131 double pos = position_control->get_value (); /* 0..1 */
132 uint32_t o, f, t, b, pf, po;
133 const double corner_radius = 5;
136 height = get_height ();
141 b = colors.background;
142 pf = colors.pos_fill;
143 po = colors.pos_outline;
147 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
148 context->rectangle (0, 0, width, height);
151 double usable_width = width - pos_box_size;
153 /* compute the centers of the L/R boxes based on the current stereo width */
155 if (fmod (usable_width,2.0) == 0) {
156 /* even width, but we need odd, so that there is an exact center.
157 So, offset cairo by 1, and reduce effective width by 1
160 context->translate (1.0, 0.0);
163 const double half_lr_box = lr_box_size/2.0;
167 left = 4 + half_lr_box; // center of left box
168 right = width - 4 - half_lr_box; // center of right box
171 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
172 context->set_line_width (1.0);
173 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
174 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
179 rounded_rectangle (context,
181 half_lr_box+step_down,
182 lr_box_size, lr_box_size, corner_radius);
183 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
184 context->stroke_preserve ();
185 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
191 left - half_lr_box + 3,
192 (lr_box_size/2) + step_down + 13);
193 context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
194 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
195 context->show_text (_("L"));
199 rounded_rectangle (context,
201 half_lr_box+step_down,
202 lr_box_size, lr_box_size, corner_radius);
203 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
204 context->stroke_preserve ();
205 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
211 right - half_lr_box + 3,
212 (lr_box_size/2)+step_down + 13);
213 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
214 context->show_text (_("R"));
216 /* 2 lines that connect them both */
217 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
218 context->set_line_width (1.0);
219 context->move_to (left + half_lr_box, half_lr_box+step_down);
220 context->line_to (right - half_lr_box, half_lr_box+step_down);
224 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
225 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
228 /* draw the position indicator */
230 double spos = (pos_box_size/2.0) + (usable_width * pos);
232 context->set_line_width (2.0);
233 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
234 context->rel_line_to (0.0, pos_box_size); /* lower right */
235 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
236 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
237 context->rel_line_to (0.0, -pos_box_size); /* upper left */
238 context->close_path ();
241 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
242 context->stroke_preserve ();
243 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
248 context->set_line_width (1.0);
249 context->move_to (spos, pos_box_size+4);
250 context->rel_line_to (0, half_lr_box+step_down);
251 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
260 MonoPanner::on_button_press_event (GdkEventButton* ev)
262 drag_start_x = ev->x;
266 accumulated_delta = 0;
269 /* Let the binding proxies get first crack at the press event
273 if (position_binder.button_press_handler (ev)) {
278 if (ev->button != 1) {
282 if (ev->type == GDK_2BUTTON_PRESS) {
283 int width = get_width();
285 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
286 /* handled by button release */
291 if (ev->x <= width/3) {
292 /* left side dbl click */
293 position_control->set_value (0);
294 } else if (ev->x > 2*width/3) {
295 position_control->set_value (1.0);
297 position_control->set_value (0.5);
302 } else if (ev->type == GDK_BUTTON_PRESS) {
304 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
305 /* handled by button release */
317 MonoPanner::on_button_release_event (GdkEventButton* ev)
319 if (ev->button != 1) {
324 accumulated_delta = 0;
327 if (drag_data_window) {
328 drag_data_window->hide ();
331 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
332 /* reset to default */
333 position_control->set_value (0.5);
342 MonoPanner::on_scroll_event (GdkEventScroll* ev)
344 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
345 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
348 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
351 step = one_degree * 5.0;
354 switch (ev->direction) {
356 case GDK_SCROLL_LEFT:
358 position_control->set_value (pv);
360 case GDK_SCROLL_DOWN:
361 case GDK_SCROLL_RIGHT:
363 position_control->set_value (pv);
371 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
377 if (!drag_data_window) {
378 drag_data_window = new Window (WINDOW_POPUP);
379 drag_data_window->set_name (X_("ContrastingPopup"));
380 drag_data_window->set_position (WIN_POS_MOUSE);
381 drag_data_window->set_decorated (false);
383 drag_data_label = manage (new Label);
384 drag_data_label->set_use_markup (true);
386 drag_data_window->set_border_width (6);
387 drag_data_window->add (*drag_data_label);
388 drag_data_label->show ();
390 Window* toplevel = dynamic_cast<Window*> (get_toplevel());
392 drag_data_window->set_transient_for (*toplevel);
396 if (!drag_data_window->is_visible ()) {
397 /* move the window a little away from the mouse */
399 get_window()->get_origin (rx, ry);
400 drag_data_window->move (rx, ry+get_height());
401 drag_data_window->present ();
405 double delta = (ev->x - last_drag_x) / (double) w;
407 /* create a detent close to the center */
409 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
412 position_control->set_value (0.5);
416 accumulated_delta += delta;
418 /* have we pulled far enough to escape ? */
420 if (fabs (accumulated_delta) >= 0.025) {
421 position_control->set_value (position_control->get_value() + accumulated_delta);
423 accumulated_delta = false;
426 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
427 position_control->set_value (pv + delta);
435 MonoPanner::on_key_press_event (GdkEventKey* ev)
437 double one_degree = 1.0/180.0;
438 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
441 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
444 step = one_degree * 5.0;
447 /* up/down control width because we consider pan position more "important"
448 (and thus having higher "sense" priority) than width.
451 switch (ev->keyval) {
454 position_control->set_value (pv);
458 position_control->set_value (pv);
468 MonoPanner::on_key_release_event (GdkEventKey* ev)
474 MonoPanner::on_enter_notify_event (GdkEventCrossing* ev)
477 Keyboard::magic_widget_grab_focus ();
482 MonoPanner::on_leave_notify_event (GdkEventCrossing*)
484 Keyboard::magic_widget_drop_focus ();
489 MonoPanner::set_colors ()
491 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
492 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
493 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
494 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
495 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
496 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
500 MonoPanner::color_handler ()