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/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 "mono_panner.h"
41 #include "mono_panner_editor.h"
42 #include "rgb_macros.h"
43 #include "ui_config.h"
50 using namespace Gtkmm2ext;
51 using namespace ARDOUR_UI_UTILS;
53 using PBD::Controllable;
55 MonoPanner::ColorScheme MonoPanner::colors;
56 bool MonoPanner::have_colors = false;
58 Pango::AttrList MonoPanner::panner_font_attributes;
59 bool MonoPanner::have_font = 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 Pango::FontDescription font;
78 Pango::AttrFontDesc* font_attr;
79 font = Pango::FontDescription (UIConfiguration::instance().get_SmallBoldMonospaceFont());
80 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
81 panner_font_attributes.change(*font_attr);
86 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
88 _panner_shell->Changed.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::bypass_handler, this), gui_context());
89 _panner_shell->PannableChanged.connect (panshell_connections, invalidator (*this), boost::bind (&MonoPanner::pannable_handler, this), gui_context());
90 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
95 MonoPanner::~MonoPanner ()
101 MonoPanner::set_tooltip ()
103 if (_panner_shell->bypassed()) {
104 _tooltip.set_tip (_("bypassed"));
107 double pos = position_control->get_value(); // 0..1
109 /* We show the position of the center of the image relative to the left & right.
110 This is expressed as a pair of percentage values that ranges from (100,0)
111 (hard left) through (50,50) (hard center) to (0,100) (hard right).
113 This is pretty wierd, but its the way audio engineers expect it. Just remember that
114 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
118 snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
119 (int) rint (100.0 * (1.0 - pos)),
120 (int) rint (100.0 * pos));
121 _tooltip.set_tip (buf);
125 MonoPanner::on_expose_event (GdkEventExpose*)
127 Glib::RefPtr<Gdk::Window> win (get_window());
128 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
129 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
132 double pos = position_control->get_value (); /* 0..1 */
133 uint32_t o, f, t, b, pf, po;
136 height = get_height ();
138 const int step_down = rint(height / 3.5);
139 const int lr_box_size = height - 2 * step_down;
140 const int pos_box_size = (int)(rint(step_down * .8)) | 1;
141 const int top_step = step_down - pos_box_size;
142 const double corner_radius = 5 * UIConfiguration::instance().get_ui_scale();
147 b = colors.background;
148 pf = colors.pos_fill;
149 po = colors.pos_outline;
151 if (_panner_shell->bypassed()) {
161 b = UIConfiguration::instance().color ("send bg");
164 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
165 context->rectangle (0, 0, width, height);
168 double usable_width = width - pos_box_size;
170 /* compute the centers of the L/R boxes based on the current stereo width */
171 if (fmod (usable_width,2.0) == 0) {
174 const double half_lr_box = lr_box_size/2.0;
175 const double left = pos_box_size * .5 + half_lr_box; // center of left box
176 const double right = width - pos_box_size * .5 - half_lr_box; // center of right box
179 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
180 context->set_line_width (1.0);
181 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
182 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
185 context->set_line_width (1.0);
188 rounded_left_half_rectangle (context,
189 left - half_lr_box + .5,
190 half_lr_box + step_down,
191 lr_box_size, lr_box_size, corner_radius);
192 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
193 context->fill_preserve ();
194 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
199 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
200 layout->set_attributes (panner_font_attributes);
202 layout->set_text (S_("Panner|L"));
203 layout->get_pixel_size(tw, th);
204 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
205 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
206 pango_cairo_show_layout (context->cobj(), layout->gobj());
209 rounded_right_half_rectangle (context,
210 right - half_lr_box - .5,
211 half_lr_box + step_down,
212 lr_box_size, lr_box_size, corner_radius);
213 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
214 context->fill_preserve ();
215 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
219 layout->set_text (S_("Panner|R"));
220 layout->get_pixel_size(tw, th);
221 context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
222 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
223 pango_cairo_show_layout (context->cobj(), layout->gobj());
225 /* 2 lines that connect them both */
226 context->set_line_width (1.0);
228 if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
229 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
230 context->move_to (left + half_lr_box, half_lr_box + step_down);
231 context->line_to (right - half_lr_box, half_lr_box + step_down);
234 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
235 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
238 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
239 context->line_to (left + half_lr_box, half_lr_box + step_down);
240 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box+step_down+lr_box_size);
241 context->line_to (right - half_lr_box, half_lr_box + step_down);
242 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
243 context->close_path();
245 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
246 context->fill_preserve ();
247 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
251 /* draw the position indicator */
252 double spos = (pos_box_size/2.0) + (usable_width * pos);
254 context->set_line_width (2.0);
255 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
256 context->rel_line_to (0.0, pos_box_size); /* lower right */
257 context->rel_line_to (-pos_box_size/2.0, 4.0 * UIConfiguration::instance().get_ui_scale()); /* bottom point */
258 context->rel_line_to (-pos_box_size/2.0, -4.0 * UIConfiguration::instance().get_ui_scale()); /* lower left */
259 context->rel_line_to (0.0, -pos_box_size); /* upper left */
260 context->close_path ();
263 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 context->stroke_preserve ();
265 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
269 context->set_line_width (1.0);
270 context->move_to (spos, 1 + top_step + pos_box_size + 4.0 * UIConfiguration::instance().get_ui_scale());
271 context->line_to (spos, half_lr_box + step_down + lr_box_size - 1);
272 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
281 MonoPanner::on_button_press_event (GdkEventButton* ev)
283 if (PannerInterface::on_button_press_event (ev)) {
286 if (_panner_shell->bypassed()) {
290 drag_start_x = ev->x;
294 _tooltip.target_stop_drag ();
295 accumulated_delta = 0;
298 /* Let the binding proxies get first crack at the press event
302 if (position_binder.button_press_handler (ev)) {
307 if (ev->button != 1) {
311 if (ev->type == GDK_2BUTTON_PRESS) {
312 int width = get_width();
314 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
315 /* handled by button release */
320 if (ev->x <= width/3) {
321 /* left side dbl click */
322 position_control->set_value (0, Controllable::NoGroup);
323 } else if (ev->x > 2*width/3) {
324 position_control->set_value (1.0, Controllable::NoGroup);
326 position_control->set_value (0.5, Controllable::NoGroup);
330 _tooltip.target_stop_drag ();
332 } else if (ev->type == GDK_BUTTON_PRESS) {
334 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
335 /* handled by button release */
340 _tooltip.target_start_drag ();
348 MonoPanner::on_button_release_event (GdkEventButton* ev)
350 if (PannerInterface::on_button_release_event (ev)) {
354 if (ev->button != 1) {
358 if (_panner_shell->bypassed()) {
363 _tooltip.target_stop_drag ();
364 accumulated_delta = 0;
367 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
377 MonoPanner::on_scroll_event (GdkEventScroll* ev)
379 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
380 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
383 if (_panner_shell->bypassed()) {
387 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
390 step = one_degree * 5.0;
393 switch (ev->direction) {
395 case GDK_SCROLL_LEFT:
397 position_control->set_value (pv, Controllable::NoGroup);
399 case GDK_SCROLL_DOWN:
400 case GDK_SCROLL_RIGHT:
402 position_control->set_value (pv, Controllable::NoGroup);
410 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
412 if (_panner_shell->bypassed()) {
420 double delta = (ev->x - last_drag_x) / (double) w;
422 /* create a detent close to the center */
424 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
427 position_control->set_value (0.5, Controllable::NoGroup);
431 accumulated_delta += delta;
433 /* have we pulled far enough to escape ? */
435 if (fabs (accumulated_delta) >= 0.025) {
436 position_control->set_value (position_control->get_value() + accumulated_delta, Controllable::NoGroup);
438 accumulated_delta = false;
441 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
442 position_control->set_value (pv + delta, Controllable::NoGroup);
450 MonoPanner::on_key_press_event (GdkEventKey* ev)
452 double one_degree = 1.0/180.0;
453 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
456 if (_panner_shell->bypassed()) {
460 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
463 step = one_degree * 5.0;
466 switch (ev->keyval) {
469 position_control->set_value (pv, Controllable::NoGroup);
473 position_control->set_value (pv, Controllable::NoGroup);
477 position_control->set_value (0.0, Controllable::NoGroup);
487 MonoPanner::set_colors ()
489 colors.fill = UIConfiguration::instance().color_mod ("mono panner fill", "panner fill");
490 colors.outline = UIConfiguration::instance().color ("mono panner outline");
491 colors.text = UIConfiguration::instance().color ("mono panner text");
492 colors.background = UIConfiguration::instance().color ("mono panner bg");
493 colors.pos_outline = UIConfiguration::instance().color ("mono panner position outline");
494 colors.pos_fill = UIConfiguration::instance().color_mod ("mono panner position fill", "mono panner position fill");
498 MonoPanner::color_handler ()
505 MonoPanner::bypass_handler ()
511 MonoPanner::pannable_handler ()
513 panvalue_connections.drop_connections();
514 position_control = _panner->pannable()->pan_azimuth_control;
515 position_binder.set_controllable(position_control);
516 position_control->Changed.connect (panvalue_connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
521 MonoPanner::editor ()
523 return new MonoPannerEditor (this);