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)
60 : PannerInterface (panner)
61 , position_control (_panner->pannable()->pan_azimuth_control)
65 , accumulated_delta (0)
67 , position_binder (position_control)
74 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
76 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
79 MonoPanner::~MonoPanner ()
85 MonoPanner::set_drag_data ()
87 if (!_drag_data_label) {
91 double pos = position_control->get_value(); // 0..1
93 /* We show the position of the center of the image relative to the left & right.
94 This is expressed as a pair of percentage values that ranges from (100,0)
95 (hard left) through (50,50) (hard center) to (0,100) (hard right).
97 This is pretty wierd, but its the way audio engineers expect it. Just remember that
98 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
102 snprintf (buf, sizeof (buf), "L:%3d R:%3d",
103 (int) rint (100.0 * (1.0 - pos)),
104 (int) rint (100.0 * pos));
105 _drag_data_label->set_markup (buf);
109 MonoPanner::on_expose_event (GdkEventExpose*)
111 Glib::RefPtr<Gdk::Window> win (get_window());
112 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
113 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
116 double pos = position_control->get_value (); /* 0..1 */
117 uint32_t o, f, t, b, pf, po;
118 const double corner_radius = 5;
121 height = get_height ();
126 b = colors.background;
127 pf = colors.pos_fill;
128 po = colors.pos_outline;
132 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
133 context->rectangle (0, 0, width, height);
136 double usable_width = width - pos_box_size;
138 /* compute the centers of the L/R boxes based on the current stereo width */
140 if (fmod (usable_width,2.0) == 0) {
141 /* even width, but we need odd, so that there is an exact center.
142 So, offset cairo by 1, and reduce effective width by 1
145 context->translate (1.0, 0.0);
148 const double half_lr_box = lr_box_size/2.0;
152 left = 4 + half_lr_box; // center of left box
153 right = width - 4 - half_lr_box; // center of right box
156 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
157 context->set_line_width (1.0);
158 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
159 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
164 rounded_rectangle (context,
166 half_lr_box+step_down,
167 lr_box_size, lr_box_size, corner_radius);
168 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
169 context->stroke_preserve ();
170 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
176 left - half_lr_box + 3,
177 (lr_box_size/2) + step_down + 13);
178 context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
179 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
180 context->show_text (_("L"));
184 rounded_rectangle (context,
186 half_lr_box+step_down,
187 lr_box_size, lr_box_size, corner_radius);
188 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
189 context->stroke_preserve ();
190 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
196 right - half_lr_box + 3,
197 (lr_box_size/2)+step_down + 13);
198 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
199 context->show_text (_("R"));
201 /* 2 lines that connect them both */
202 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
203 context->set_line_width (1.0);
205 /* make the lines a little longer than they need to be, because the corners of
206 the boxes are rounded and we don't want a gap
208 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
209 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
213 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
214 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
217 /* draw the position indicator */
219 double spos = (pos_box_size/2.0) + (usable_width * pos);
221 context->set_line_width (2.0);
222 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
223 context->rel_line_to (0.0, pos_box_size); /* lower right */
224 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
225 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
226 context->rel_line_to (0.0, -pos_box_size); /* upper left */
227 context->close_path ();
230 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
231 context->stroke_preserve ();
232 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
237 context->set_line_width (1.0);
238 context->move_to (spos, pos_box_size+4);
239 context->rel_line_to (0, half_lr_box+step_down);
240 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
249 MonoPanner::on_button_press_event (GdkEventButton* ev)
251 drag_start_x = ev->x;
255 accumulated_delta = 0;
258 /* Let the binding proxies get first crack at the press event
262 if (position_binder.button_press_handler (ev)) {
267 if (ev->button != 1) {
271 if (ev->type == GDK_2BUTTON_PRESS) {
272 int width = get_width();
274 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
275 /* handled by button release */
280 if (ev->x <= width/3) {
281 /* left side dbl click */
282 position_control->set_value (0);
283 } else if (ev->x > 2*width/3) {
284 position_control->set_value (1.0);
286 position_control->set_value (0.5);
291 } else if (ev->type == GDK_BUTTON_PRESS) {
293 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
294 /* handled by button release */
306 MonoPanner::on_button_release_event (GdkEventButton* ev)
308 if (ev->button != 1) {
313 accumulated_delta = 0;
316 if (_drag_data_window) {
317 _drag_data_window->hide ();
320 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
330 MonoPanner::on_scroll_event (GdkEventScroll* ev)
332 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
333 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
336 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
339 step = one_degree * 5.0;
342 switch (ev->direction) {
344 case GDK_SCROLL_LEFT:
346 position_control->set_value (pv);
348 case GDK_SCROLL_DOWN:
349 case GDK_SCROLL_RIGHT:
351 position_control->set_value (pv);
359 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
365 show_drag_data_window ();
368 double delta = (ev->x - last_drag_x) / (double) w;
370 /* create a detent close to the center */
372 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
375 position_control->set_value (0.5);
379 accumulated_delta += delta;
381 /* have we pulled far enough to escape ? */
383 if (fabs (accumulated_delta) >= 0.025) {
384 position_control->set_value (position_control->get_value() + accumulated_delta);
386 accumulated_delta = false;
389 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
390 position_control->set_value (pv + delta);
398 MonoPanner::on_key_press_event (GdkEventKey* ev)
400 double one_degree = 1.0/180.0;
401 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
404 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
407 step = one_degree * 5.0;
410 /* up/down control width because we consider pan position more "important"
411 (and thus having higher "sense" priority) than width.
414 switch (ev->keyval) {
417 position_control->set_value (pv);
421 position_control->set_value (pv);
431 MonoPanner::set_colors ()
433 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
434 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
435 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
436 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
437 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
438 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
442 MonoPanner::color_handler ()