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;
136 if (_panner_shell->bypassed()) {
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);
220 /* make the lines a little longer than they need to be, because the corners of
221 the boxes are rounded and we don't want a gap
223 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
224 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
228 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
229 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
232 /* draw the position indicator */
234 double spos = (pos_box_size/2.0) + (usable_width * pos);
236 context->set_line_width (2.0);
237 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
238 context->rel_line_to (0.0, pos_box_size); /* lower right */
239 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
240 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
241 context->rel_line_to (0.0, -pos_box_size); /* upper left */
242 context->close_path ();
245 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
246 context->stroke_preserve ();
247 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
252 context->set_line_width (1.0);
253 context->move_to (spos, pos_box_size+4);
254 context->rel_line_to (0, half_lr_box+step_down);
255 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
264 MonoPanner::on_button_press_event (GdkEventButton* ev)
266 if (PannerInterface::on_button_press_event (ev)) {
269 if (_panner_shell->bypassed()) {
273 drag_start_x = ev->x;
277 _tooltip.target_stop_drag ();
278 accumulated_delta = 0;
281 /* Let the binding proxies get first crack at the press event
285 if (position_binder.button_press_handler (ev)) {
290 if (ev->button != 1) {
294 if (ev->type == GDK_2BUTTON_PRESS) {
295 int width = get_width();
297 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
298 /* handled by button release */
303 if (ev->x <= width/3) {
304 /* left side dbl click */
305 position_control->set_value (0);
306 } else if (ev->x > 2*width/3) {
307 position_control->set_value (1.0);
309 position_control->set_value (0.5);
313 _tooltip.target_stop_drag ();
315 } else if (ev->type == GDK_BUTTON_PRESS) {
317 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
318 /* handled by button release */
323 _tooltip.target_start_drag ();
331 MonoPanner::on_button_release_event (GdkEventButton* ev)
333 if (PannerInterface::on_button_release_event (ev)) {
337 if (ev->button != 1) {
341 if (_panner_shell->bypassed()) {
346 _tooltip.target_stop_drag ();
347 accumulated_delta = 0;
350 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
360 MonoPanner::on_scroll_event (GdkEventScroll* ev)
362 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
363 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
366 if (_panner_shell->bypassed()) {
370 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
373 step = one_degree * 5.0;
376 switch (ev->direction) {
378 case GDK_SCROLL_LEFT:
380 position_control->set_value (pv);
382 case GDK_SCROLL_DOWN:
383 case GDK_SCROLL_RIGHT:
385 position_control->set_value (pv);
393 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
395 if (_panner_shell->bypassed()) {
403 double delta = (ev->x - last_drag_x) / (double) w;
405 /* create a detent close to the center */
407 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
410 position_control->set_value (0.5);
414 accumulated_delta += delta;
416 /* have we pulled far enough to escape ? */
418 if (fabs (accumulated_delta) >= 0.025) {
419 position_control->set_value (position_control->get_value() + accumulated_delta);
421 accumulated_delta = false;
424 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
425 position_control->set_value (pv + delta);
433 MonoPanner::on_key_press_event (GdkEventKey* ev)
435 double one_degree = 1.0/180.0;
436 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
439 if (_panner_shell->bypassed()) {
443 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
446 step = one_degree * 5.0;
449 switch (ev->keyval) {
452 position_control->set_value (pv);
456 position_control->set_value (pv);
460 position_control->set_value (0.0);
470 MonoPanner::set_colors ()
472 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
473 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
474 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
475 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
476 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
477 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
481 MonoPanner::color_handler ()
488 MonoPanner::bypass_handler ()
494 MonoPanner::editor ()
496 return new MonoPannerEditor (this);