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)
64 , accumulated_delta (0)
66 , position_binder (position_control)
73 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
75 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
78 MonoPanner::~MonoPanner ()
84 MonoPanner::set_drag_data ()
86 if (!_drag_data_label) {
90 double pos = position_control->get_value(); // 0..1
92 /* We show the position of the center of the image relative to the left & right.
93 This is expressed as a pair of percentage values that ranges from (100,0)
94 (hard left) through (50,50) (hard center) to (0,100) (hard right).
96 This is pretty wierd, but its the way audio engineers expect it. Just remember that
97 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
101 snprintf (buf, sizeof (buf), "L:%3d R:%3d",
102 (int) rint (100.0 * (1.0 - pos)),
103 (int) rint (100.0 * pos));
104 _drag_data_label->set_markup (buf);
108 MonoPanner::on_expose_event (GdkEventExpose*)
110 Glib::RefPtr<Gdk::Window> win (get_window());
111 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
112 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
115 double pos = position_control->get_value (); /* 0..1 */
116 uint32_t o, f, t, b, pf, po;
117 const double corner_radius = 5;
120 height = get_height ();
125 b = colors.background;
126 pf = colors.pos_fill;
127 po = colors.pos_outline;
131 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
132 context->rectangle (0, 0, width, height);
135 double usable_width = width - pos_box_size;
137 /* compute the centers of the L/R boxes based on the current stereo width */
139 if (fmod (usable_width,2.0) == 0) {
140 /* even width, but we need odd, so that there is an exact center.
141 So, offset cairo by 1, and reduce effective width by 1
144 context->translate (1.0, 0.0);
147 const double half_lr_box = lr_box_size/2.0;
151 left = 4 + half_lr_box; // center of left box
152 right = width - 4 - half_lr_box; // center of right box
155 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
156 context->set_line_width (1.0);
157 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
158 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
163 rounded_rectangle (context,
165 half_lr_box+step_down,
166 lr_box_size, lr_box_size, corner_radius);
167 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
168 context->stroke_preserve ();
169 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
175 left - half_lr_box + 3,
176 (lr_box_size/2) + step_down + 13);
177 context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
178 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
179 context->show_text (_("L"));
183 rounded_rectangle (context,
185 half_lr_box+step_down,
186 lr_box_size, lr_box_size, corner_radius);
187 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
188 context->stroke_preserve ();
189 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
195 right - half_lr_box + 3,
196 (lr_box_size/2)+step_down + 13);
197 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
198 context->show_text (_("R"));
200 /* 2 lines that connect them both */
201 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
202 context->set_line_width (1.0);
204 /* make the lines a little longer than they need to be, because the corners of
205 the boxes are rounded and we don't want a gap
207 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
208 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
212 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
213 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
216 /* draw the position indicator */
218 double spos = (pos_box_size/2.0) + (usable_width * pos);
220 context->set_line_width (2.0);
221 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
222 context->rel_line_to (0.0, pos_box_size); /* lower right */
223 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
224 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
225 context->rel_line_to (0.0, -pos_box_size); /* upper left */
226 context->close_path ();
229 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
230 context->stroke_preserve ();
231 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
236 context->set_line_width (1.0);
237 context->move_to (spos, pos_box_size+4);
238 context->rel_line_to (0, half_lr_box+step_down);
239 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 MonoPanner::on_button_press_event (GdkEventButton* ev)
250 drag_start_x = ev->x;
254 accumulated_delta = 0;
257 /* Let the binding proxies get first crack at the press event
261 if (position_binder.button_press_handler (ev)) {
266 if (ev->button != 1) {
270 if (ev->type == GDK_2BUTTON_PRESS) {
271 int width = get_width();
273 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
274 /* handled by button release */
279 if (ev->x <= width/3) {
280 /* left side dbl click */
281 position_control->set_value (0);
282 } else if (ev->x > 2*width/3) {
283 position_control->set_value (1.0);
285 position_control->set_value (0.5);
290 } else if (ev->type == GDK_BUTTON_PRESS) {
292 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
293 /* handled by button release */
299 show_drag_data_window ();
306 MonoPanner::on_button_release_event (GdkEventButton* ev)
308 if (ev->button != 1) {
313 accumulated_delta = 0;
316 hide_drag_data_window ();
318 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
328 MonoPanner::on_scroll_event (GdkEventScroll* ev)
330 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
331 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
334 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
337 step = one_degree * 5.0;
340 switch (ev->direction) {
342 case GDK_SCROLL_LEFT:
344 position_control->set_value (pv);
346 case GDK_SCROLL_DOWN:
347 case GDK_SCROLL_RIGHT:
349 position_control->set_value (pv);
357 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
364 double delta = (ev->x - last_drag_x) / (double) w;
366 /* create a detent close to the center */
368 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
371 position_control->set_value (0.5);
375 accumulated_delta += delta;
377 /* have we pulled far enough to escape ? */
379 if (fabs (accumulated_delta) >= 0.025) {
380 position_control->set_value (position_control->get_value() + accumulated_delta);
382 accumulated_delta = false;
385 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
386 position_control->set_value (pv + delta);
394 MonoPanner::on_key_press_event (GdkEventKey* ev)
396 double one_degree = 1.0/180.0;
397 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
400 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
403 step = one_degree * 5.0;
406 /* up/down control width because we consider pan position more "important"
407 (and thus having higher "sense" priority) than width.
410 switch (ev->keyval) {
413 position_control->set_value (pv);
417 position_control->set_value (pv);
427 MonoPanner::set_colors ()
429 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
430 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
431 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
432 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
433 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
434 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
438 MonoPanner::color_handler ()