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()) {
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);
166 double usable_width = width - pos_box_size;
168 /* compute the centers of the L/R boxes based on the current stereo width */
170 if (fmod (usable_width,2.0) == 0) {
171 /* even width, but we need odd, so that there is an exact center.
172 So, offset cairo by 1, and reduce effective width by 1
175 context->translate (1.0, 0.0);
178 const double half_lr_box = lr_box_size/2.0;
182 left = 4 + half_lr_box; // center of left box
183 right = width - 4 - half_lr_box; // center of right box
186 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
187 context->set_line_width (1.0);
188 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
189 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
194 rounded_rectangle (context,
196 half_lr_box+step_down,
197 lr_box_size, lr_box_size, corner_radius);
198 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 context->stroke_preserve ();
200 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
205 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
206 layout->set_attributes (panner_font_attributes);
208 layout->set_text (_("L"));
209 layout->get_pixel_size(tw, th);
210 context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
211 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
212 pango_cairo_show_layout (context->cobj(), layout->gobj());
216 rounded_rectangle (context,
218 half_lr_box+step_down,
219 lr_box_size, lr_box_size, corner_radius);
220 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 context->stroke_preserve ();
222 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
226 layout->set_text (_("R"));
227 layout->get_pixel_size(tw, th);
228 context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
229 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
230 pango_cairo_show_layout (context->cobj(), layout->gobj());
232 /* 2 lines that connect them both */
233 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
234 context->set_line_width (1.0);
236 /* make the lines a little longer than they need to be, because the corners of
237 the boxes are rounded and we don't want a gap
239 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
240 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
244 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
245 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
248 /* 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));
268 context->set_line_width (1.0);
269 context->move_to (spos, pos_box_size+4);
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()->canvasvar_MonoPannerFill.get();
489 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
490 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
491 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
492 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
493 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
497 MonoPanner::color_handler ()
504 MonoPanner::bypass_handler ()
510 MonoPanner::editor ()
512 return new MonoPannerEditor (this);