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>
26 #include <pangomm/layout.h>
28 #include "pbd/controllable.h"
29 #include "pbd/compose.h"
31 #include "gtkmm2ext/gui_thread.h"
32 #include "gtkmm2ext/gtk_ui.h"
33 #include "gtkmm2ext/keyboard.h"
34 #include "gtkmm2ext/utils.h"
35 #include "gtkmm2ext/persistent_tooltip.h"
37 #include "ardour/pannable.h"
38 #include "ardour/panner.h"
39 #include "ardour/panner_shell.h"
41 #include "ardour_ui.h"
42 #include "global_signals.h"
43 #include "mono_panner.h"
44 #include "mono_panner_editor.h"
45 #include "rgb_macros.h"
52 using namespace Gtkmm2ext;
54 static const int pos_box_size = 9;
55 static const int lr_box_size = 15;
56 static const int step_down = 10;
57 static const int top_step = 2;
59 MonoPanner::ColorScheme MonoPanner::colors;
60 bool MonoPanner::have_colors = false;
62 Pango::AttrList MonoPanner::panner_font_attributes;
63 bool MonoPanner::have_font = false;
65 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::PannerShell> p)
66 : PannerInterface (p->panner())
68 , position_control (_panner->pannable()->pan_azimuth_control)
71 , accumulated_delta (0)
73 , position_binder (position_control)
81 Pango::FontDescription font;
82 Pango::AttrFontDesc* font_attr;
83 font = Pango::FontDescription ("ArdourMono");
84 font.set_weight (Pango::WEIGHT_BOLD);
85 font.set_size(9 * PANGO_SCALE);
86 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
87 panner_font_attributes.change(*font_attr);
92 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
94 _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
95 _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::pannable_handler, this), gui_context());
96 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
101 MonoPanner::~MonoPanner ()
107 MonoPanner::set_tooltip ()
109 if (_panner_shell->bypassed()) {
110 _tooltip.set_tip (_("bypassed"));
113 double pos = position_control->get_value(); // 0..1
115 /* We show the position of the center of the image relative to the left & right.
116 This is expressed as a pair of percentage values that ranges from (100,0)
117 (hard left) through (50,50) (hard center) to (0,100) (hard right).
119 This is pretty wierd, but its the way audio engineers expect it. Just remember that
120 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
124 snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
125 (int) rint (100.0 * (1.0 - pos)),
126 (int) rint (100.0 * pos));
127 _tooltip.set_tip (buf);
131 MonoPanner::on_expose_event (GdkEventExpose*)
133 Glib::RefPtr<Gdk::Window> win (get_window());
134 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
135 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
138 double pos = position_control->get_value (); /* 0..1 */
139 uint32_t o, f, t, b, pf, po;
140 const double corner_radius = 5;
143 height = get_height ();
148 b = colors.background;
149 pf = colors.pos_fill;
150 po = colors.pos_outline;
152 if (_panner_shell->bypassed()) {
162 b = rgba_from_style("SendStripBase",
163 UINT_RGBA_R(b), UINT_RGBA_G(b), UINT_RGBA_B(b), 255, "fg");
166 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
167 context->rectangle (0, 0, width, height);
171 double usable_width = width - pos_box_size;
173 /* compute the centers of the L/R boxes based on the current stereo width */
174 if (fmod (usable_width,2.0) == 0) {
177 const double half_lr_box = lr_box_size/2.0;
178 const double left = 4 + half_lr_box; // center of left box
179 const double right = width - 4 - half_lr_box; // center of right box
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->set_line_width (1.0);
184 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
185 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
188 context->set_line_width (1.0);
191 rounded_left_half_rectangle (context,
192 left - half_lr_box + .5,
193 half_lr_box + step_down,
194 lr_box_size, lr_box_size, corner_radius);
195 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 context->fill_preserve ();
197 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 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
203 layout->set_attributes (panner_font_attributes);
205 layout->set_text (_("L"));
206 layout->get_pixel_size(tw, th);
207 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
208 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
209 pango_cairo_show_layout (context->cobj(), layout->gobj());
212 rounded_right_half_rectangle (context,
213 right - half_lr_box - .5,
214 half_lr_box + step_down,
215 lr_box_size, lr_box_size, corner_radius);
216 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
217 context->fill_preserve ();
218 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
222 layout->set_text (_("R"));
223 layout->get_pixel_size(tw, th);
224 context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
225 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
226 pango_cairo_show_layout (context->cobj(), layout->gobj());
228 /* 2 lines that connect them both */
229 context->set_line_width (1.0);
231 if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
232 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
233 context->move_to (left + half_lr_box, half_lr_box + step_down);
234 context->line_to (right - half_lr_box, half_lr_box + step_down);
237 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
238 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
241 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
242 context->line_to (left + half_lr_box, half_lr_box + step_down);
243 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box+step_down+lr_box_size);
244 context->line_to (right - half_lr_box, half_lr_box + step_down);
245 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
246 context->close_path();
248 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
249 context->fill_preserve ();
250 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
254 /* draw the position indicator */
255 double spos = (pos_box_size/2.0) + (usable_width * pos);
257 context->set_line_width (2.0);
258 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
259 context->rel_line_to (0.0, pos_box_size); /* lower right */
260 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
261 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
262 context->rel_line_to (0.0, -pos_box_size); /* upper left */
263 context->close_path ();
266 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
267 context->stroke_preserve ();
268 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
272 context->set_line_width (1.0);
273 context->move_to (spos, pos_box_size + 5);
274 context->rel_line_to (0, half_lr_box+step_down);
275 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
284 MonoPanner::on_button_press_event (GdkEventButton* ev)
286 if (PannerInterface::on_button_press_event (ev)) {
289 if (_panner_shell->bypassed()) {
293 drag_start_x = ev->x;
297 _tooltip.target_stop_drag ();
298 accumulated_delta = 0;
301 /* Let the binding proxies get first crack at the press event
305 if (position_binder.button_press_handler (ev)) {
310 if (ev->button != 1) {
314 if (ev->type == GDK_2BUTTON_PRESS) {
315 int width = get_width();
317 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
318 /* handled by button release */
323 if (ev->x <= width/3) {
324 /* left side dbl click */
325 position_control->set_value (0);
326 } else if (ev->x > 2*width/3) {
327 position_control->set_value (1.0);
329 position_control->set_value (0.5);
333 _tooltip.target_stop_drag ();
335 } else if (ev->type == GDK_BUTTON_PRESS) {
337 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
338 /* handled by button release */
343 _tooltip.target_start_drag ();
351 MonoPanner::on_button_release_event (GdkEventButton* ev)
353 if (PannerInterface::on_button_release_event (ev)) {
357 if (ev->button != 1) {
361 if (_panner_shell->bypassed()) {
366 _tooltip.target_stop_drag ();
367 accumulated_delta = 0;
370 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
380 MonoPanner::on_scroll_event (GdkEventScroll* ev)
382 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
383 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
386 if (_panner_shell->bypassed()) {
390 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
393 step = one_degree * 5.0;
396 switch (ev->direction) {
398 case GDK_SCROLL_LEFT:
400 position_control->set_value (pv);
402 case GDK_SCROLL_DOWN:
403 case GDK_SCROLL_RIGHT:
405 position_control->set_value (pv);
413 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
415 if (_panner_shell->bypassed()) {
423 double delta = (ev->x - last_drag_x) / (double) w;
425 /* create a detent close to the center */
427 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
430 position_control->set_value (0.5);
434 accumulated_delta += delta;
436 /* have we pulled far enough to escape ? */
438 if (fabs (accumulated_delta) >= 0.025) {
439 position_control->set_value (position_control->get_value() + accumulated_delta);
441 accumulated_delta = false;
444 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
445 position_control->set_value (pv + delta);
453 MonoPanner::on_key_press_event (GdkEventKey* ev)
455 double one_degree = 1.0/180.0;
456 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
459 if (_panner_shell->bypassed()) {
463 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
466 step = one_degree * 5.0;
469 switch (ev->keyval) {
472 position_control->set_value (pv);
476 position_control->set_value (pv);
480 position_control->set_value (0.0);
490 MonoPanner::set_colors ()
492 colors.fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerFill();
493 colors.outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerOutline();
494 colors.text = ARDOUR_UI::config()->get_canvasvar_MonoPannerText();
495 colors.background = ARDOUR_UI::config()->get_canvasvar_MonoPannerBackground();
496 colors.pos_outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionOutline();
497 colors.pos_fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionFill();
501 MonoPanner::color_handler ()
508 MonoPanner::bypass_handler ()
514 MonoPanner::pannable_handler ()
516 panvalue_connections.drop_connections();
517 position_control = _panner->pannable()->pan_azimuth_control;
518 position_binder.set_controllable(position_control);
519 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
524 MonoPanner::editor ()
526 return new MonoPannerEditor (this);