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>
27 #include "pbd/controllable.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"
39 #include "ardour_ui.h"
40 #include "global_signals.h"
41 #include "mono_panner.h"
42 #include "mono_panner_editor.h"
43 #include "rgb_macros.h"
50 using namespace Gtkmm2ext;
52 static const int pos_box_size = 9;
53 static const int lr_box_size = 15;
54 static const int step_down = 10;
55 static const int top_step = 2;
57 MonoPanner::ColorScheme MonoPanner::colors;
58 bool MonoPanner::have_colors = false;
60 MonoPanner::MonoPanner (boost::shared_ptr<ARDOUR::Panner> panner)
61 : PannerInterface (panner)
62 , position_control (_panner->pannable()->pan_azimuth_control)
65 , accumulated_delta (0)
67 , position_binder (position_control)
75 position_control->Changed.connect (connections, invalidator(*this), boost::bind (&MonoPanner::value_change, this), gui_context());
77 ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
82 MonoPanner::~MonoPanner ()
88 MonoPanner::set_tooltip ()
90 double pos = position_control->get_value(); // 0..1
92 /* We show the position of the center of the image relative to the left & right.
93 This is expressed as a pair of percentage values that ranges from (100,0)
94 (hard left) through (50,50) (hard center) to (0,100) (hard right).
96 This is pretty wierd, but its the way audio engineers expect it. Just remember that
97 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
101 snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
102 (int) rint (100.0 * (1.0 - pos)),
103 (int) rint (100.0 * pos));
104 _tooltip.set_tip (buf);
108 MonoPanner::on_expose_event (GdkEventExpose*)
110 Glib::RefPtr<Gdk::Window> win (get_window());
111 Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
112 Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
115 double pos = position_control->get_value (); /* 0..1 */
116 uint32_t o, f, t, b, pf, po;
117 const double corner_radius = 5;
120 height = get_height ();
125 b = colors.background;
126 pf = colors.pos_fill;
127 po = colors.pos_outline;
131 context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
132 context->rectangle (0, 0, width, height);
135 double usable_width = width - pos_box_size;
137 /* compute the centers of the L/R boxes based on the current stereo width */
139 if (fmod (usable_width,2.0) == 0) {
140 /* even width, but we need odd, so that there is an exact center.
141 So, offset cairo by 1, and reduce effective width by 1
144 context->translate (1.0, 0.0);
147 const double half_lr_box = lr_box_size/2.0;
151 left = 4 + half_lr_box; // center of left box
152 right = width - 4 - half_lr_box; // center of right box
155 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
156 context->set_line_width (1.0);
157 context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
158 context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
163 rounded_rectangle (context,
165 half_lr_box+step_down,
166 lr_box_size, lr_box_size, corner_radius);
167 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
168 context->stroke_preserve ();
169 context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
175 left - half_lr_box + 3,
176 (lr_box_size/2) + step_down + 13);
177 context->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
178 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
179 context->show_text (_("L"));
183 rounded_rectangle (context,
185 half_lr_box+step_down,
186 lr_box_size, lr_box_size, corner_radius);
187 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
188 context->stroke_preserve ();
189 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 right - half_lr_box + 3,
196 (lr_box_size/2)+step_down + 13);
197 context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
198 context->show_text (_("R"));
200 /* 2 lines that connect them both */
201 context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
202 context->set_line_width (1.0);
204 /* make the lines a little longer than they need to be, because the corners of
205 the boxes are rounded and we don't want a gap
207 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down);
208 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down);
212 context->move_to (left + half_lr_box - corner_radius, half_lr_box+step_down+lr_box_size);
213 context->line_to (right - half_lr_box + corner_radius, half_lr_box+step_down+lr_box_size);
216 /* draw the position indicator */
218 double spos = (pos_box_size/2.0) + (usable_width * pos);
220 context->set_line_width (2.0);
221 context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
222 context->rel_line_to (0.0, pos_box_size); /* lower right */
223 context->rel_line_to (-pos_box_size/2.0, 4.0); /* bottom point */
224 context->rel_line_to (-pos_box_size/2.0, -4.0); /* lower left */
225 context->rel_line_to (0.0, -pos_box_size); /* upper left */
226 context->close_path ();
229 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
230 context->stroke_preserve ();
231 context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
236 context->set_line_width (1.0);
237 context->move_to (spos, pos_box_size+4);
238 context->rel_line_to (0, half_lr_box+step_down);
239 context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
248 MonoPanner::on_button_press_event (GdkEventButton* ev)
250 if (PannerInterface::on_button_press_event (ev)) {
254 drag_start_x = ev->x;
258 _tooltip.target_stop_drag ();
259 accumulated_delta = 0;
262 /* Let the binding proxies get first crack at the press event
266 if (position_binder.button_press_handler (ev)) {
271 if (ev->button != 1) {
275 if (ev->type == GDK_2BUTTON_PRESS) {
276 int width = get_width();
278 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
279 /* handled by button release */
284 if (ev->x <= width/3) {
285 /* left side dbl click */
286 position_control->set_value (0);
287 } else if (ev->x > 2*width/3) {
288 position_control->set_value (1.0);
290 position_control->set_value (0.5);
294 _tooltip.target_stop_drag ();
296 } else if (ev->type == GDK_BUTTON_PRESS) {
298 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
299 /* handled by button release */
304 _tooltip.target_start_drag ();
312 MonoPanner::on_button_release_event (GdkEventButton* ev)
314 if (PannerInterface::on_button_release_event (ev)) {
318 if (ev->button != 1) {
323 _tooltip.target_stop_drag ();
324 accumulated_delta = 0;
327 if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
337 MonoPanner::on_scroll_event (GdkEventScroll* ev)
339 double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
340 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
343 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
346 step = one_degree * 5.0;
349 switch (ev->direction) {
351 case GDK_SCROLL_LEFT:
353 position_control->set_value (pv);
355 case GDK_SCROLL_DOWN:
356 case GDK_SCROLL_RIGHT:
358 position_control->set_value (pv);
366 MonoPanner::on_motion_notify_event (GdkEventMotion* ev)
373 double delta = (ev->x - last_drag_x) / (double) w;
375 /* create a detent close to the center */
377 if (!detented && ARDOUR::Panner::equivalent (position_control->get_value(), 0.5)) {
380 position_control->set_value (0.5);
384 accumulated_delta += delta;
386 /* have we pulled far enough to escape ? */
388 if (fabs (accumulated_delta) >= 0.025) {
389 position_control->set_value (position_control->get_value() + accumulated_delta);
391 accumulated_delta = false;
394 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
395 position_control->set_value (pv + delta);
403 MonoPanner::on_key_press_event (GdkEventKey* ev)
405 double one_degree = 1.0/180.0;
406 double pv = position_control->get_value(); // 0..1.0 ; 0 = left
409 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
412 step = one_degree * 5.0;
415 switch (ev->keyval) {
418 position_control->set_value (pv);
422 position_control->set_value (pv);
426 position_control->set_value (0.0);
436 MonoPanner::set_colors ()
438 colors.fill = ARDOUR_UI::config()->canvasvar_MonoPannerFill.get();
439 colors.outline = ARDOUR_UI::config()->canvasvar_MonoPannerOutline.get();
440 colors.text = ARDOUR_UI::config()->canvasvar_MonoPannerText.get();
441 colors.background = ARDOUR_UI::config()->canvasvar_MonoPannerBackground.get();
442 colors.pos_outline = ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline.get();
443 colors.pos_fill = ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill.get();
447 MonoPanner::color_handler ()
454 MonoPanner::editor ()
456 return new MonoPannerEditor (this);