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 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
163 context->rectangle (0, 0, width, height);
167 double usable_width = width - pos_box_size;
169 /* compute the centers of the L/R boxes based on the current stereo width */
170 if (fmod (usable_width,2.0) == 0) {
173 const double half_lr_box = lr_box_size/2.0;
174 const double left = 4 + half_lr_box; // center of left box
175 const double right = width - 4 - half_lr_box; // center of right box
178 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
179 context->set_line_width (1.0);
180 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
181 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
184 context->set_line_width (1.0);
187 rounded_left_half_rectangle (context,
188 left - half_lr_box + .5,
189 half_lr_box + step_down,
190 lr_box_size, lr_box_size, corner_radius);
191 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
192 context->fill_preserve ();
193 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
198 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
199 layout->set_attributes (panner_font_attributes);
201 layout->set_text (_("L"));
202 layout->get_pixel_size(tw, th);
203 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
204 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
205 pango_cairo_show_layout (context->cobj(), layout->gobj());
208 rounded_right_half_rectangle (context,
209 right - half_lr_box - .5,
210 half_lr_box + step_down,
211 lr_box_size, lr_box_size, corner_radius);
212 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
213 context->fill_preserve ();
214 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 layout->set_text (_("R"));
219 layout->get_pixel_size(tw, th);
220 context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
221 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
222 pango_cairo_show_layout (context->cobj(), layout->gobj());
224 /* 2 lines that connect them both */
225 context->set_line_width (1.0);
227 if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
228 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
229 context->move_to (left + half_lr_box, half_lr_box + step_down);
230 context->line_to (right - half_lr_box, half_lr_box + step_down);
233 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
234 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
237 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
238 context->line_to (left + half_lr_box, half_lr_box + step_down);
239 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box+step_down+lr_box_size);
240 context->line_to (right - half_lr_box, half_lr_box + step_down);
241 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
242 context->close_path();
244 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
245 context->fill_preserve ();
246 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
250 /* draw the position indicator */
251 double spos = (pos_box_size/2.0) + (usable_width * pos);
253 context->set_line_width (2.0);
254 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
255 context->rel_line_to (0.0, pos_box_size); /* lower right */
256 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
257 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
258 context->rel_line_to (0.0, -pos_box_size); /* upper left */
259 context->close_path ();
262 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 context->stroke_preserve ();
264 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
268 context->set_line_width (1.0);
269 context->move_to (spos, pos_box_size + 5);
270 context->rel_line_to (0, half_lr_box+step_down);
271 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
280 MonoPanner::on_button_press_event (GdkEventButton* ev)
282 if (PannerInterface::on_button_press_event (ev)) {
285 if (_panner_shell->bypassed()) {
289 drag_start_x = ev->x;
293 _tooltip.target_stop_drag ();
294 accumulated_delta = 0;
297 /* Let the binding proxies get first crack at the press event
301 if (position_binder.button_press_handler (ev)) {
306 if (ev->button != 1) {
310 if (ev->type == GDK_2BUTTON_PRESS) {
311 int width = get_width();
313 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
314 /* handled by button release */
319 if (ev->x <= width/3) {
320 /* left side dbl click */
321 position_control->set_value (0);
322 } else if (ev->x > 2*width/3) {
323 position_control->set_value (1.0);
325 position_control->set_value (0.5);
329 _tooltip.target_stop_drag ();
331 } else if (ev->type == GDK_BUTTON_PRESS) {
333 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
334 /* handled by button release */
339 _tooltip.target_start_drag ();
347 MonoPanner::on_button_release_event (GdkEventButton* ev)
349 if (PannerInterface::on_button_release_event (ev)) {
353 if (ev->button != 1) {
357 if (_panner_shell->bypassed()) {
362 _tooltip.target_stop_drag ();
363 accumulated_delta = 0;
366 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
376 MonoPanner::on_scroll_event (GdkEventScroll* ev)
378 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
379 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
382 if (_panner_shell->bypassed()) {
386 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
389 step = one_degree * 5.0;
392 switch (ev->direction) {
394 case GDK_SCROLL_LEFT:
396 position_control->set_value (pv);
398 case GDK_SCROLL_DOWN:
399 case GDK_SCROLL_RIGHT:
401 position_control->set_value (pv);
409 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
411 if (_panner_shell->bypassed()) {
419 double delta = (ev->x - last_drag_x) / (double) w;
421 /* create a detent close to the center */
423 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
426 position_control->set_value (0.5);
430 accumulated_delta += delta;
432 /* have we pulled far enough to escape ? */
434 if (fabs (accumulated_delta) >= 0.025) {
435 position_control->set_value (position_control->get_value() + accumulated_delta);
437 accumulated_delta = false;
440 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
441 position_control->set_value (pv + delta);
449 MonoPanner::on_key_press_event (GdkEventKey* ev)
451 double one_degree = 1.0/180.0;
452 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
455 if (_panner_shell->bypassed()) {
459 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
462 step = one_degree * 5.0;
465 switch (ev->keyval) {
468 position_control->set_value (pv);
472 position_control->set_value (pv);
476 position_control->set_value (0.0);
486 MonoPanner::set_colors ()
488 colors.fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerFill();
489 colors.outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerOutline();
490 colors.text = ARDOUR_UI::config()->get_canvasvar_MonoPannerText();
491 colors.background = ARDOUR_UI::config()->get_canvasvar_MonoPannerBackground();
492 colors.pos_outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionOutline();
493 colors.pos_fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionFill();
497 MonoPanner::color_handler ()
504 MonoPanner::bypass_handler ()
510 MonoPanner::pannable_handler ()
512 panvalue_connections.drop_connections();
513 position_control = _panner->pannable()->pan_azimuth_control;
514 position_binder.set_controllable(position_control);
515 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
520 MonoPanner::editor ()
522 return new MonoPannerEditor (this);