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/pannable.h"
36 #include "ardour/panner.h"
38 #include "ardour_ui.h"
39 #include "global_signals.h"
40 #include "mono_panner.h"
41 #include "mono_panner_editor.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 if (PannerInterface::on_button_press_event (ev)) {
254 drag_start_x = ev->x;
258 accumulated_delta = 0;
261 /* Let the binding proxies get first crack at the press event
265 if (position_binder.button_press_handler (ev)) {
270 if (ev->button != 1) {
274 if (ev->type == GDK_2BUTTON_PRESS) {
275 int width = get_width();
277 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
278 /* handled by button release */
283 if (ev->x <= width/3) {
284 /* left side dbl click */
285 position_control->set_value (0);
286 } else if (ev->x > 2*width/3) {
287 position_control->set_value (1.0);
289 position_control->set_value (0.5);
294 } else if (ev->type == GDK_BUTTON_PRESS) {
296 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
297 /* handled by button release */
303 show_drag_data_window ();
310 MonoPanner::on_button_release_event (GdkEventButton* ev)
312 if (PannerInterface::on_button_release_event (ev)) {
316 if (ev->button != 1) {
321 accumulated_delta = 0;
324 hide_drag_data_window ();
326 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
336 MonoPanner::on_scroll_event (GdkEventScroll* ev)
338 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
339 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
342 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
345 step = one_degree * 5.0;
348 switch (ev->direction) {
350 case GDK_SCROLL_LEFT:
352 position_control->set_value (pv);
354 case GDK_SCROLL_DOWN:
355 case GDK_SCROLL_RIGHT:
357 position_control->set_value (pv);
365 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
372 double delta = (ev->x - last_drag_x) / (double) w;
374 /* create a detent close to the center */
376 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
379 position_control->set_value (0.5);
383 accumulated_delta += delta;
385 /* have we pulled far enough to escape ? */
387 if (fabs (accumulated_delta) >= 0.025) {
388 position_control->set_value (position_control->get_value() + accumulated_delta);
390 accumulated_delta = false;
393 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
394 position_control->set_value (pv + delta);
402 MonoPanner::on_key_press_event (GdkEventKey* ev)
404 double one_degree = 1.0/180.0;
405 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
408 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
411 step = one_degree * 5.0;
414 /* up/down control width because we consider pan position more "important"
415 (and thus having higher "sense" priority) than width.
418 switch (ev->keyval) {
421 position_control->set_value (pv);
425 position_control->set_value (pv);
435 MonoPanner::set_colors ()
437 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
438 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
439 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
440 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
441 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
442 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
446 MonoPanner::color_handler ()
453 MonoPanner::editor ()
455 return new MonoPannerEditor (this);