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 (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
94 _panner_shell->Changed.connect (connections, invalidator (*this), boost::bind (&MonoPanner::bypass_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 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
162 context->rectangle (0, 0, width, height);
166 double usable_width = width - pos_box_size;
168 /* compute the centers of the L/R boxes based on the current stereo width */
169 if (fmod (usable_width,2.0) == 0) {
172 const double half_lr_box = lr_box_size/2.0;
173 const double left = 4 + half_lr_box; // center of left box
174 const double right = width - 4 - half_lr_box; // center of right box
177 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
178 context->set_line_width (1.0);
179 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
180 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
183 context->set_line_width (1.0);
186 rounded_left_half_rectangle (context,
187 left - half_lr_box + .5,
188 half_lr_box + step_down,
189 lr_box_size, lr_box_size, corner_radius);
190 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
191 context->fill_preserve ();
192 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
197 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
198 layout->set_attributes (panner_font_attributes);
200 layout->set_text (_("L"));
201 layout->get_pixel_size(tw, th);
202 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
203 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
204 pango_cairo_show_layout (context->cobj(), layout->gobj());
207 rounded_right_half_rectangle (context,
208 right - half_lr_box - .5,
209 half_lr_box + step_down,
210 lr_box_size, lr_box_size, corner_radius);
211 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
212 context->fill_preserve ();
213 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
217 layout->set_text (_("R"));
218 layout->get_pixel_size(tw, th);
219 context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
220 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
221 pango_cairo_show_layout (context->cobj(), layout->gobj());
223 /* 2 lines that connect them both */
224 context->set_line_width (1.0);
226 if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
227 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
228 context->move_to (left + half_lr_box, half_lr_box + step_down);
229 context->line_to (right - half_lr_box, half_lr_box + step_down);
232 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
233 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
236 context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
237 context->line_to (left + half_lr_box, half_lr_box + step_down);
238 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box+step_down+lr_box_size);
239 context->line_to (right - half_lr_box, half_lr_box + step_down);
240 context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
241 context->close_path();
243 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
244 context->fill_preserve ();
245 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
249 /* draw the position indicator */
250 double spos = (pos_box_size/2.0) + (usable_width * pos);
252 context->set_line_width (2.0);
253 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
254 context->rel_line_to (0.0, pos_box_size); /* lower right */
255 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
256 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
257 context->rel_line_to (0.0, -pos_box_size); /* upper left */
258 context->close_path ();
261 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
262 context->stroke_preserve ();
263 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
267 context->set_line_width (1.0);
268 context->move_to (spos, pos_box_size + 5);
269 context->rel_line_to (0, half_lr_box+step_down);
270 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
279 MonoPanner::on_button_press_event (GdkEventButton* ev)
281 if (PannerInterface::on_button_press_event (ev)) {
284 if (_panner_shell->bypassed()) {
288 drag_start_x = ev->x;
292 _tooltip.target_stop_drag ();
293 accumulated_delta = 0;
296 /* Let the binding proxies get first crack at the press event
300 if (position_binder.button_press_handler (ev)) {
305 if (ev->button != 1) {
309 if (ev->type == GDK_2BUTTON_PRESS) {
310 int width = get_width();
312 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
313 /* handled by button release */
318 if (ev->x <= width/3) {
319 /* left side dbl click */
320 position_control->set_value (0);
321 } else if (ev->x > 2*width/3) {
322 position_control->set_value (1.0);
324 position_control->set_value (0.5);
328 _tooltip.target_stop_drag ();
330 } else if (ev->type == GDK_BUTTON_PRESS) {
332 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
333 /* handled by button release */
338 _tooltip.target_start_drag ();
346 MonoPanner::on_button_release_event (GdkEventButton* ev)
348 if (PannerInterface::on_button_release_event (ev)) {
352 if (ev->button != 1) {
356 if (_panner_shell->bypassed()) {
361 _tooltip.target_stop_drag ();
362 accumulated_delta = 0;
365 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
375 MonoPanner::on_scroll_event (GdkEventScroll* ev)
377 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
378 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
381 if (_panner_shell->bypassed()) {
385 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
388 step = one_degree * 5.0;
391 switch (ev->direction) {
393 case GDK_SCROLL_LEFT:
395 position_control->set_value (pv);
397 case GDK_SCROLL_DOWN:
398 case GDK_SCROLL_RIGHT:
400 position_control->set_value (pv);
408 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
410 if (_panner_shell->bypassed()) {
418 double delta = (ev->x - last_drag_x) / (double) w;
420 /* create a detent close to the center */
422 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
425 position_control->set_value (0.5);
429 accumulated_delta += delta;
431 /* have we pulled far enough to escape ? */
433 if (fabs (accumulated_delta) >= 0.025) {
434 position_control->set_value (position_control->get_value() + accumulated_delta);
436 accumulated_delta = false;
439 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
440 position_control->set_value (pv + delta);
448 MonoPanner::on_key_press_event (GdkEventKey* ev)
450 double one_degree = 1.0/180.0;
451 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
454 if (_panner_shell->bypassed()) {
458 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
461 step = one_degree * 5.0;
464 switch (ev->keyval) {
467 position_control->set_value (pv);
471 position_control->set_value (pv);
475 position_control->set_value (0.0);
485 MonoPanner::set_colors ()
487 colors.fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerFill();
488 colors.outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerOutline();
489 colors.text = ARDOUR_UI::config()->get_canvasvar_MonoPannerText();
490 colors.background = ARDOUR_UI::config()->get_canvasvar_MonoPannerBackground();
491 colors.pos_outline = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionOutline();
492 colors.pos_fill = ARDOUR_UI::config()->get_canvasvar_MonoPannerPositionFill();
496 MonoPanner::color_handler ()
503 MonoPanner::bypass_handler ()
509 MonoPanner::editor ()
511 return new MonoPannerEditor (this);