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;
53 using namespace ARDOUR_UI_UTILS;
55 static const int pos_box_size = 9;
56 static const int lr_box_size = 15;
57 static const int step_down = 10;
58 static const int top_step = 2;
60 MonoPanner::ColorScheme MonoPanner::colors;
61 bool MonoPanner::have_colors = false;
63 Pango::AttrList MonoPanner::panner_font_attributes;
64 bool MonoPanner::have_font = false;
66 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::PannerShell> p)
67 : PannerInterface (p->panner())
69 , position_control (_panner->pannable()->pan_azimuth_control)
72 , accumulated_delta (0)
74 , position_binder (position_control)
82 Pango::FontDescription font;
83 Pango::AttrFontDesc* font_attr;
84 font = Pango::FontDescription (ARDOUR_UI::config()->get_canvasvar_SmallBoldMonospaceFont());
85 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
86 panner_font_attributes.change(*font_attr);
91 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
93 _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
94 _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::pannable_handler, this), gui_context());
95 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
100 MonoPanner::~MonoPanner ()
106 MonoPanner::set_tooltip ()
108 if (_panner_shell->bypassed()) {
109 _tooltip.set_tip (_("bypassed"));
112 double pos = position_control->get_value(); // 0..1
114 /* We show the position of the center of the image relative to the left & right.
115 This is expressed as a pair of percentage values that ranges from (100,0)
116 (hard left) through (50,50) (hard center) to (0,100) (hard right).
118 This is pretty wierd, but its the way audio engineers expect it. Just remember that
119 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
123 snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
124 (int) rint (100.0 * (1.0 - pos)),
125 (int) rint (100.0 * pos));
126 _tooltip.set_tip (buf);
130 MonoPanner::on_expose_event (GdkEventExpose*)
132 Glib::RefPtr<Gdk::Window> win (get_window());
133 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
134 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
137 double pos = position_control->get_value (); /* 0..1 */
138 uint32_t o, f, t, b, pf, po;
139 const double corner_radius = 5;
142 height = get_height ();
147 b = colors.background;
148 pf = colors.pos_fill;
149 po = colors.pos_outline;
151 if (_panner_shell->bypassed()) {
161 b = rgba_from_style("SendStripBase",
162 UINT_RGBA_R(b), UINT_RGBA_G(b), UINT_RGBA_B(b), 255, "fg");
165 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
166 context->rectangle (0, 0, width, height);
170 double usable_width = width - pos_box_size;
172 /* compute the centers of the L/R boxes based on the current stereo width */
173 if (fmod (usable_width,2.0) == 0) {
176 const double half_lr_box = lr_box_size/2.0;
177 const double left = 4 + half_lr_box; // center of left box
178 const double right = width - 4 - half_lr_box; // center of right box
181 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
182 context->set_line_width (1.0);
183 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
184 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
187 context->set_line_width (1.0);
190 rounded_left_half_rectangle (context,
191 left - half_lr_box + .5,
192 half_lr_box + step_down,
193 lr_box_size, lr_box_size, corner_radius);
194 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 context->fill_preserve ();
196 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
201 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
202 layout->set_attributes (panner_font_attributes);
204 layout->set_text (_("L"));
205 layout->get_pixel_size(tw, th);
206 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
207 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
208 pango_cairo_show_layout (context->cobj(), layout->gobj());
211 rounded_right_half_rectangle (context,
212 right - half_lr_box - .5,
213 half_lr_box + step_down,
214 lr_box_size, lr_box_size, corner_radius);
215 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
216 context->fill_preserve ();
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));
221 layout->set_text (_("R"));
222 layout->get_pixel_size(tw, th);
223 context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
224 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
225 pango_cairo_show_layout (context->cobj(), layout->gobj());
227 /* 2 lines that connect them both */
228 context->set_line_width (1.0);
230 if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
231 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
232 context->move_to (left + half_lr_box, half_lr_box + step_down);
233 context->line_to (right - half_lr_box, half_lr_box + step_down);
236 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
237 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
240 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
241 context->line_to (left + half_lr_box, half_lr_box + step_down);
242 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box+step_down+lr_box_size);
243 context->line_to (right - half_lr_box, half_lr_box + step_down);
244 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
245 context->close_path();
247 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
248 context->fill_preserve ();
249 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
253 /* draw the position indicator */
254 double spos = (pos_box_size/2.0) + (usable_width * pos);
256 context->set_line_width (2.0);
257 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
258 context->rel_line_to (0.0, pos_box_size); /* lower right */
259 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
260 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
261 context->rel_line_to (0.0, -pos_box_size); /* upper left */
262 context->close_path ();
265 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
266 context->stroke_preserve ();
267 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
271 context->set_line_width (1.0);
272 context->move_to (spos, pos_box_size + 5);
273 context->rel_line_to (0, half_lr_box+step_down);
274 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
283 MonoPanner::on_button_press_event (GdkEventButton* ev)
285 if (PannerInterface::on_button_press_event (ev)) {
288 if (_panner_shell->bypassed()) {
292 drag_start_x = ev->x;
296 _tooltip.target_stop_drag ();
297 accumulated_delta = 0;
300 /* Let the binding proxies get first crack at the press event
304 if (position_binder.button_press_handler (ev)) {
309 if (ev->button != 1) {
313 if (ev->type == GDK_2BUTTON_PRESS) {
314 int width = get_width();
316 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
317 /* handled by button release */
322 if (ev->x <= width/3) {
323 /* left side dbl click */
324 position_control->set_value (0);
325 } else if (ev->x > 2*width/3) {
326 position_control->set_value (1.0);
328 position_control->set_value (0.5);
332 _tooltip.target_stop_drag ();
334 } else if (ev->type == GDK_BUTTON_PRESS) {
336 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
337 /* handled by button release */
342 _tooltip.target_start_drag ();
350 MonoPanner::on_button_release_event (GdkEventButton* ev)
352 if (PannerInterface::on_button_release_event (ev)) {
356 if (ev->button != 1) {
360 if (_panner_shell->bypassed()) {
365 _tooltip.target_stop_drag ();
366 accumulated_delta = 0;
369 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
379 MonoPanner::on_scroll_event (GdkEventScroll* ev)
381 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
382 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
385 if (_panner_shell->bypassed()) {
389 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
392 step = one_degree * 5.0;
395 switch (ev->direction) {
397 case GDK_SCROLL_LEFT:
399 position_control->set_value (pv);
401 case GDK_SCROLL_DOWN:
402 case GDK_SCROLL_RIGHT:
404 position_control->set_value (pv);
412 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
414 if (_panner_shell->bypassed()) {
422 double delta = (ev->x - last_drag_x) / (double) w;
424 /* create a detent close to the center */
426 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
429 position_control->set_value (0.5);
433 accumulated_delta += delta;
435 /* have we pulled far enough to escape ? */
437 if (fabs (accumulated_delta) >= 0.025) {
438 position_control->set_value (position_control->get_value() + accumulated_delta);
440 accumulated_delta = false;
443 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
444 position_control->set_value (pv + delta);
452 MonoPanner::on_key_press_event (GdkEventKey* ev)
454 double one_degree = 1.0/180.0;
455 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
458 if (_panner_shell->bypassed()) {
462 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
465 step = one_degree * 5.0;
468 switch (ev->keyval) {
471 position_control->set_value (pv);
475 position_control->set_value (pv);
479 position_control->set_value (0.0);
489 MonoPanner::set_colors ()
491 colors.fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerFill();
492 colors.outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerOutline();
493 colors.text = ARDOUR_UI::config()->get_canvasvar_MonoPannerText();
494 colors.background = ARDOUR_UI::config()->get_canvasvar_MonoPannerBackground();
495 colors.pos_outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionOutline();
496 colors.pos_fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionFill();
500 MonoPanner::color_handler ()
507 MonoPanner::bypass_handler ()
513 MonoPanner::pannable_handler ()
515 panvalue_connections.drop_connections();
516 position_control = _panner->pannable()->pan_azimuth_control;
517 position_binder.set_controllable(position_control);
518 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
523 MonoPanner::editor ()
525 return new MonoPannerEditor (this);