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"
34 #include "gtkmm2ext/persistent_tooltip.h"
36 #include "ardour/pannable.h"
37 #include "ardour/panner.h"
38 #include "ardour/panner_shell.h"
40 #include "ardour_ui.h"
41 #include "global_signals.h"
42 #include "mono_panner.h"
43 #include "mono_panner_editor.h"
44 #include "rgb_macros.h"
51 using namespace Gtkmm2ext;
53 static const int pos_box_size = 9;
54 static const int lr_box_size = 15;
55 static const int step_down = 10;
56 static const int top_step = 2;
58 MonoPanner::ColorScheme MonoPanner::colors;
59 bool MonoPanner::have_colors = false;
61 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::PannerShell> p)
62 : PannerInterface (p->panner())
64 , position_control (_panner->pannable()->pan_azimuth_control)
67 , accumulated_delta (0)
69 , position_binder (position_control)
77 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
79 _panner_shell->Changed.connect (connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
80 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
85 MonoPanner::~MonoPanner ()
91 MonoPanner::set_tooltip ()
93 if (_panner_shell->bypassed()) {
94 _tooltip.set_tip (_("bypassed"));
97 double pos = position_control->get_value(); // 0..1
99 /* We show the position of the center of the image relative to the left & right.
100 This is expressed as a pair of percentage values that ranges from (100,0)
101 (hard left) through (50,50) (hard center) to (0,100) (hard right).
103 This is pretty wierd, but its the way audio engineers expect it. Just remember that
104 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
108 snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
109 (int) rint (100.0 * (1.0 - pos)),
110 (int) rint (100.0 * pos));
111 _tooltip.set_tip (buf);
115 MonoPanner::on_expose_event (GdkEventExpose*)
117 Glib::RefPtr<Gdk::Window> win (get_window());
118 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
119 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
122 double pos = position_control->get_value (); /* 0..1 */
123 uint32_t o, f, t, b, pf, po;
124 const double corner_radius = 5;
127 height = get_height ();
132 b = colors.background;
133 pf = colors.pos_fill;
134 po = colors.pos_outline;
138 if (!_panner_shell->bypassed()) {
139 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
141 context->set_source_rgba (0.1, 0.1, 0.1, 0.2);
143 context->rectangle (0, 0, width, height);
146 double usable_width = width - pos_box_size;
148 /* compute the centers of the L/R boxes based on the current stereo width */
150 if (fmod (usable_width,2.0) == 0) {
151 /* even width, but we need odd, so that there is an exact center.
152 So, offset cairo by 1, and reduce effective width by 1
155 context->translate (1.0, 0.0);
158 const double half_lr_box = lr_box_size/2.0;
162 left = 4 + half_lr_box; // center of left box
163 right = width - 4 - half_lr_box; // center of right box
166 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
167 context->set_line_width (1.0);
168 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
169 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
172 if (_panner_shell->bypassed()) {
178 rounded_rectangle (context,
180 half_lr_box+step_down,
181 lr_box_size, lr_box_size, corner_radius);
182 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
183 context->stroke_preserve ();
184 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
190 left - half_lr_box + 3,
191 (lr_box_size/2) + step_down + 13);
192 context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
193 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
194 context->show_text (_("L"));
198 rounded_rectangle (context,
200 half_lr_box+step_down,
201 lr_box_size, lr_box_size, corner_radius);
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->stroke_preserve ();
204 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
210 right - half_lr_box + 3,
211 (lr_box_size/2)+step_down + 13);
212 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
213 context->show_text (_("R"));
215 /* 2 lines that connect them both */
216 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
217 context->set_line_width (1.0);
219 /* make the lines a little longer than they need to be, because the corners of
220 the boxes are rounded and we don't want a gap
222 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
223 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
227 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
228 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
231 /* draw the position indicator */
233 double spos = (pos_box_size/2.0) + (usable_width * pos);
235 context->set_line_width (2.0);
236 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
237 context->rel_line_to (0.0, pos_box_size); /* lower right */
238 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
239 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
240 context->rel_line_to (0.0, -pos_box_size); /* upper left */
241 context->close_path ();
244 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
245 context->stroke_preserve ();
246 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
251 context->set_line_width (1.0);
252 context->move_to (spos, pos_box_size+4);
253 context->rel_line_to (0, half_lr_box+step_down);
254 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
263 MonoPanner::on_button_press_event (GdkEventButton* ev)
265 if (PannerInterface::on_button_press_event (ev)) {
268 if (_panner_shell->bypassed()) {
272 drag_start_x = ev->x;
276 _tooltip.target_stop_drag ();
277 accumulated_delta = 0;
280 /* Let the binding proxies get first crack at the press event
284 if (position_binder.button_press_handler (ev)) {
289 if (ev->button != 1) {
293 if (ev->type == GDK_2BUTTON_PRESS) {
294 int width = get_width();
296 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
297 /* handled by button release */
302 if (ev->x <= width/3) {
303 /* left side dbl click */
304 position_control->set_value (0);
305 } else if (ev->x > 2*width/3) {
306 position_control->set_value (1.0);
308 position_control->set_value (0.5);
312 _tooltip.target_stop_drag ();
314 } else if (ev->type == GDK_BUTTON_PRESS) {
316 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
317 /* handled by button release */
322 _tooltip.target_start_drag ();
330 MonoPanner::on_button_release_event (GdkEventButton* ev)
332 if (PannerInterface::on_button_release_event (ev)) {
336 if (ev->button != 1) {
340 if (_panner_shell->bypassed()) {
345 _tooltip.target_stop_drag ();
346 accumulated_delta = 0;
349 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
359 MonoPanner::on_scroll_event (GdkEventScroll* ev)
361 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
362 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
365 if (_panner_shell->bypassed()) {
369 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
372 step = one_degree * 5.0;
375 switch (ev->direction) {
377 case GDK_SCROLL_LEFT:
379 position_control->set_value (pv);
381 case GDK_SCROLL_DOWN:
382 case GDK_SCROLL_RIGHT:
384 position_control->set_value (pv);
392 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
394 if (_panner_shell->bypassed()) {
402 double delta = (ev->x - last_drag_x) / (double) w;
404 /* create a detent close to the center */
406 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
409 position_control->set_value (0.5);
413 accumulated_delta += delta;
415 /* have we pulled far enough to escape ? */
417 if (fabs (accumulated_delta) >= 0.025) {
418 position_control->set_value (position_control->get_value() + accumulated_delta);
420 accumulated_delta = false;
423 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
424 position_control->set_value (pv + delta);
432 MonoPanner::on_key_press_event (GdkEventKey* ev)
434 double one_degree = 1.0/180.0;
435 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
438 if (_panner_shell->bypassed()) {
442 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
445 step = one_degree * 5.0;
448 switch (ev->keyval) {
451 position_control->set_value (pv);
455 position_control->set_value (pv);
459 position_control->set_value (0.0);
469 MonoPanner::set_colors ()
471 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
472 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
473 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
474 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
475 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
476 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
480 MonoPanner::color_handler ()
487 MonoPanner::bypass_handler ()
493 MonoPanner::editor ()
495 return new MonoPannerEditor (this);