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 "mono_panner.h"
42 #include "mono_panner_editor.h"
43 #include "rgb_macros.h"
44 #include "ui_config.h"
51 using namespace Gtkmm2ext;
52 using namespace ARDOUR_UI_UTILS;
54 using PBD::Controllable;
56 MonoPanner::ColorScheme MonoPanner::colors;
57 bool MonoPanner::have_colors = false;
59 Pango::AttrList MonoPanner::panner_font_attributes;
60 bool MonoPanner::have_font = false;
62 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::PannerShell> p)
63 : PannerInterface (p->panner())
65 , position_control (_panner->pannable()->pan_azimuth_control)
68 , accumulated_delta (0)
70 , position_binder (position_control)
78 Pango::FontDescription font;
79 Pango::AttrFontDesc* font_attr;
80 font = Pango::FontDescription (UIConfiguration::instance().get_SmallBoldMonospaceFont());
81 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
82 panner_font_attributes.change(*font_attr);
87 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
89 _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
90 _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::pannable_handler, this), gui_context());
91 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
96 MonoPanner::~MonoPanner ()
102 MonoPanner::set_tooltip ()
104 if (_panner_shell->bypassed()) {
105 _tooltip.set_tip (_("bypassed"));
108 double pos = position_control->get_value(); // 0..1
110 /* We show the position of the center of the image relative to the left & right.
111 This is expressed as a pair of percentage values that ranges from (100,0)
112 (hard left) through (50,50) (hard center) to (0,100) (hard right).
114 This is pretty wierd, but its the way audio engineers expect it. Just remember that
115 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
119 snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
120 (int) rint (100.0 * (1.0 - pos)),
121 (int) rint (100.0 * pos));
122 _tooltip.set_tip (buf);
126 MonoPanner::on_expose_event (GdkEventExpose*)
128 Glib::RefPtr<Gdk::Window> win (get_window());
129 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
130 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
133 double pos = position_control->get_value (); /* 0..1 */
134 uint32_t o, f, t, b, pf, po;
137 height = get_height ();
139 const int step_down = rint(height / 3.5);
140 const int lr_box_size = height - 2 * step_down;
141 const int pos_box_size = (int)(rint(step_down * .8)) | 1;
142 const int top_step = step_down - pos_box_size;
143 const double corner_radius = 5 * UIConfiguration::instance().get_ui_scale();
148 b = colors.background;
149 pf = colors.pos_fill;
150 po = colors.pos_outline;
152 if (_panner_shell->bypassed()) {
162 b = UIConfiguration::instance().color ("send bg");
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);
169 double usable_width = width - pos_box_size;
171 /* compute the centers of the L/R boxes based on the current stereo width */
172 if (fmod (usable_width,2.0) == 0) {
175 const double half_lr_box = lr_box_size/2.0;
176 const double left = pos_box_size * .5 + half_lr_box; // center of left box
177 const double right = width - pos_box_size * .5 - half_lr_box; // center of right box
180 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
181 context->set_line_width (1.0);
182 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
183 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
186 context->set_line_width (1.0);
189 rounded_left_half_rectangle (context,
190 left - half_lr_box + .5,
191 half_lr_box + step_down,
192 lr_box_size, lr_box_size, corner_radius);
193 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
194 context->fill_preserve ();
195 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
200 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
201 layout->set_attributes (panner_font_attributes);
203 layout->set_text (S_("Panner|L"));
204 layout->get_pixel_size(tw, th);
205 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
206 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
207 pango_cairo_show_layout (context->cobj(), layout->gobj());
210 rounded_right_half_rectangle (context,
211 right - half_lr_box - .5,
212 half_lr_box + step_down,
213 lr_box_size, lr_box_size, corner_radius);
214 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
215 context->fill_preserve ();
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));
220 layout->set_text (S_("Panner|R"));
221 layout->get_pixel_size(tw, th);
222 context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
223 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
224 pango_cairo_show_layout (context->cobj(), layout->gobj());
226 /* 2 lines that connect them both */
227 context->set_line_width (1.0);
229 if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
230 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
231 context->move_to (left + half_lr_box, half_lr_box + step_down);
232 context->line_to (right - half_lr_box, half_lr_box + step_down);
235 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
236 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
239 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
240 context->line_to (left + half_lr_box, half_lr_box + step_down);
241 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box+step_down+lr_box_size);
242 context->line_to (right - half_lr_box, half_lr_box + step_down);
243 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
244 context->close_path();
246 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
247 context->fill_preserve ();
248 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
252 /* draw the position indicator */
253 double spos = (pos_box_size/2.0) + (usable_width * pos);
255 context->set_line_width (2.0);
256 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
257 context->rel_line_to (0.0, pos_box_size); /* lower right */
258 context->rel_line_to (-pos_box_size/2.0, 4.0 * UIConfiguration::instance().get_ui_scale()); /* bottom point */
259 context->rel_line_to (-pos_box_size/2.0, -4.0 * UIConfiguration::instance().get_ui_scale()); /* lower left */
260 context->rel_line_to (0.0, -pos_box_size); /* upper left */
261 context->close_path ();
264 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
265 context->stroke_preserve ();
266 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
270 context->set_line_width (1.0);
271 context->move_to (spos, 1 + top_step + pos_box_size + 4.0 * UIConfiguration::instance().get_ui_scale());
272 context->line_to (spos, half_lr_box + step_down + lr_box_size - 1);
273 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
282 MonoPanner::on_button_press_event (GdkEventButton* ev)
284 if (PannerInterface::on_button_press_event (ev)) {
287 if (_panner_shell->bypassed()) {
291 drag_start_x = ev->x;
295 _tooltip.target_stop_drag ();
296 accumulated_delta = 0;
299 /* Let the binding proxies get first crack at the press event
303 if (position_binder.button_press_handler (ev)) {
308 if (ev->button != 1) {
312 if (ev->type == GDK_2BUTTON_PRESS) {
313 int width = get_width();
315 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
316 /* handled by button release */
321 if (ev->x <= width/3) {
322 /* left side dbl click */
323 position_control->set_value (0, Controllable::NoGroup);
324 } else if (ev->x > 2*width/3) {
325 position_control->set_value (1.0, Controllable::NoGroup);
327 position_control->set_value (0.5, Controllable::NoGroup);
331 _tooltip.target_stop_drag ();
333 } else if (ev->type == GDK_BUTTON_PRESS) {
335 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
336 /* handled by button release */
341 _tooltip.target_start_drag ();
349 MonoPanner::on_button_release_event (GdkEventButton* ev)
351 if (PannerInterface::on_button_release_event (ev)) {
355 if (ev->button != 1) {
359 if (_panner_shell->bypassed()) {
364 _tooltip.target_stop_drag ();
365 accumulated_delta = 0;
368 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
378 MonoPanner::on_scroll_event (GdkEventScroll* ev)
380 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
381 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
384 if (_panner_shell->bypassed()) {
388 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
391 step = one_degree * 5.0;
394 switch (ev->direction) {
396 case GDK_SCROLL_LEFT:
398 position_control->set_value (pv, Controllable::NoGroup);
400 case GDK_SCROLL_DOWN:
401 case GDK_SCROLL_RIGHT:
403 position_control->set_value (pv, Controllable::NoGroup);
411 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
413 if (_panner_shell->bypassed()) {
421 double delta = (ev->x - last_drag_x) / (double) w;
423 /* create a detent close to the center */
425 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
428 position_control->set_value (0.5, Controllable::NoGroup);
432 accumulated_delta += delta;
434 /* have we pulled far enough to escape ? */
436 if (fabs (accumulated_delta) >= 0.025) {
437 position_control->set_value (position_control->get_value() + accumulated_delta, Controllable::NoGroup);
439 accumulated_delta = false;
442 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
443 position_control->set_value (pv + delta, Controllable::NoGroup);
451 MonoPanner::on_key_press_event (GdkEventKey* ev)
453 double one_degree = 1.0/180.0;
454 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
457 if (_panner_shell->bypassed()) {
461 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
464 step = one_degree * 5.0;
467 switch (ev->keyval) {
470 position_control->set_value (pv, Controllable::NoGroup);
474 position_control->set_value (pv, Controllable::NoGroup);
478 position_control->set_value (0.0, Controllable::NoGroup);
488 MonoPanner::set_colors ()
490 colors.fill = UIConfiguration::instance().color_mod ("mono panner fill", "panner fill");
491 colors.outline = UIConfiguration::instance().color ("mono panner outline");
492 colors.text = UIConfiguration::instance().color ("mono panner text");
493 colors.background = UIConfiguration::instance().color ("mono panner bg");
494 colors.pos_outline = UIConfiguration::instance().color ("mono panner position outline");
495 colors.pos_fill = UIConfiguration::instance().color_mod ("mono panner position fill", "mono panner position fill");
499 MonoPanner::color_handler ()
506 MonoPanner::bypass_handler ()
512 MonoPanner::pannable_handler ()
514 panvalue_connections.drop_connections();
515 position_control = _panner->pannable()->pan_azimuth_control;
516 position_binder.set_controllable(position_control);
517 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
522 MonoPanner::editor ()
524 return new MonoPannerEditor (this);