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"
34 #include "ardour/panner.h"
35 #include "ardour/panner.h"
37 #include "ardour_ui.h"
38 #include "global_signals.h"
39 #include "mono_panner.h"
40 #include "rgb_macros.h"
47 using namespace Gtkmm2ext
;
49 static const int pos_box_size
= 9;
50 static const int lr_box_size
= 15;
51 static const int step_down
= 10;
52 static const int top_step
= 2;
54 MonoPanner::ColorScheme
MonoPanner::colors
;
55 bool MonoPanner::have_colors
= false;
57 MonoPanner::MonoPanner (boost::shared_ptr
<PBD::Controllable
> position
)
58 : position_control (position
)
62 , accumulated_delta (0)
64 , drag_data_window (0)
66 , position_binder (position
)
73 position_control
->Changed
.connect (connections
, invalidator(*this), boost::bind (&MonoPanner::value_change
, this), gui_context());
75 set_flags (Gtk::CAN_FOCUS
);
77 add_events (Gdk::ENTER_NOTIFY_MASK
|Gdk::LEAVE_NOTIFY_MASK
|
78 Gdk::KEY_PRESS_MASK
|Gdk::KEY_RELEASE_MASK
|
79 Gdk::BUTTON_PRESS_MASK
|Gdk::BUTTON_RELEASE_MASK
|
81 Gdk::POINTER_MOTION_MASK
);
83 ColorsChanged
.connect (sigc::mem_fun (*this, &MonoPanner::color_handler
));
86 MonoPanner::~MonoPanner ()
88 delete drag_data_window
;
92 MonoPanner::set_drag_data ()
94 if (!drag_data_label
) {
98 double pos
= position_control
->get_value(); // 0..1
100 /* We show the position of the center of the image relative to the left & right.
101 This is expressed as a pair of percentage values that ranges from (100,0)
102 (hard left) through (50,50) (hard center) to (0,100) (hard right).
104 This is pretty wierd, but its the way audio engineers expect it. Just remember that
105 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
109 snprintf (buf
, sizeof (buf
), "L:%3d R:%3d",
110 (int) rint (100.0 * (1.0 - pos
)),
111 (int) rint (100.0 * pos
));
112 drag_data_label
->set_markup (buf
);
116 MonoPanner::value_change ()
123 MonoPanner::on_expose_event (GdkEventExpose
* ev
)
125 Glib::RefPtr
<Gdk::Window
> win (get_window());
126 Glib::RefPtr
<Gdk::GC
> gc (get_style()->get_base_gc (get_state()));
128 cairo_t
* cr
= gdk_cairo_create (win
->gobj());
131 double pos
= position_control
->get_value (); /* 0..1 */
132 uint32_t o
, f
, t
, b
, pf
, po
;
135 height
= get_height ();
140 b
= colors
.background
;
141 pf
= colors
.pos_fill
;
142 po
= colors
.pos_outline
;
146 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(b
), UINT_RGBA_G_FLT(b
), UINT_RGBA_B_FLT(b
), UINT_RGBA_A_FLT(b
));
147 cairo_rectangle (cr
, 0, 0, width
, height
);
150 double usable_width
= width
- pos_box_size
;
152 /* compute the centers of the L/R boxes based on the current stereo width */
154 if (fmod (usable_width
,2.0) == 0) {
155 /* even width, but we need odd, so that there is an exact center.
156 So, offset cairo by 1, and reduce effective width by 1
159 cairo_translate (cr
, 1.0, 0.0);
162 const double half_lr_box
= lr_box_size
/2.0;
166 left
= 4 + half_lr_box
; // center of left box
167 right
= width
- 4 - half_lr_box
; // center of right box
170 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(o
), UINT_RGBA_G_FLT(o
), UINT_RGBA_B_FLT(o
), UINT_RGBA_A_FLT(o
));
171 cairo_set_line_width (cr
, 1.0);
172 cairo_move_to (cr
, (pos_box_size
/2.0) + (usable_width
/2.0), 0);
173 cairo_line_to (cr
, (pos_box_size
/2.0) + (usable_width
/2.0), height
);
180 half_lr_box
+step_down
,
181 lr_box_size
, lr_box_size
);
182 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(o
), UINT_RGBA_G_FLT(o
), UINT_RGBA_B_FLT(o
), UINT_RGBA_A_FLT(o
));
183 cairo_stroke_preserve (cr
);
184 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(f
), UINT_RGBA_G_FLT(f
), UINT_RGBA_B_FLT(f
), UINT_RGBA_A_FLT(f
));
190 left
- half_lr_box
+ 3,
191 (lr_box_size
/2) + step_down
+ 13);
192 cairo_select_font_face (cr
, "sans-serif", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_BOLD
);
193 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(t
), UINT_RGBA_G_FLT(t
), UINT_RGBA_B_FLT(t
), UINT_RGBA_A_FLT(t
));
194 cairo_show_text (cr
, _("L"));
200 half_lr_box
+step_down
,
201 lr_box_size
, lr_box_size
);
202 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(o
), UINT_RGBA_G_FLT(o
), UINT_RGBA_B_FLT(o
), UINT_RGBA_A_FLT(o
));
203 cairo_stroke_preserve (cr
);
204 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(f
), UINT_RGBA_G_FLT(f
), UINT_RGBA_B_FLT(f
), UINT_RGBA_A_FLT(f
));
210 right
- half_lr_box
+ 3,
211 (lr_box_size
/2)+step_down
+ 13);
212 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(t
), UINT_RGBA_G_FLT(t
), UINT_RGBA_B_FLT(t
), UINT_RGBA_A_FLT(t
));
213 cairo_show_text (cr
, _("R"));
215 /* 2 lines that connect them both */
216 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(o
), UINT_RGBA_G_FLT(o
), UINT_RGBA_B_FLT(o
), UINT_RGBA_A_FLT(o
));
217 cairo_set_line_width (cr
, 1.0);
218 cairo_move_to (cr
, left
+ half_lr_box
, half_lr_box
+step_down
);
219 cairo_line_to (cr
, right
- half_lr_box
, half_lr_box
+step_down
);
223 cairo_move_to (cr
, left
+ half_lr_box
, half_lr_box
+step_down
+lr_box_size
);
224 cairo_line_to (cr
, right
- half_lr_box
, half_lr_box
+step_down
+lr_box_size
);
227 /* draw the position indicator */
229 double spos
= (pos_box_size
/2.0) + (usable_width
* pos
);
231 cairo_set_line_width (cr
, 2.0);
232 cairo_move_to (cr
, spos
+ (pos_box_size
/2.0), top_step
); /* top right */
233 cairo_rel_line_to (cr
, 0.0, pos_box_size
); /* lower right */
234 cairo_rel_line_to (cr
, -pos_box_size
/2.0, 4.0); /* bottom point */
235 cairo_rel_line_to (cr
, -pos_box_size
/2.0, -4.0); /* lower left */
236 cairo_rel_line_to (cr
, 0.0, -pos_box_size
); /* upper left */
237 cairo_close_path (cr
);
240 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(po
), UINT_RGBA_G_FLT(po
), UINT_RGBA_B_FLT(po
), UINT_RGBA_A_FLT(po
));
241 cairo_stroke_preserve (cr
);
242 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(pf
), UINT_RGBA_G_FLT(pf
), UINT_RGBA_B_FLT(pf
), UINT_RGBA_A_FLT(pf
));
247 cairo_set_line_width (cr
, 1.0);
248 cairo_move_to (cr
, spos
, pos_box_size
+4);
249 cairo_rel_line_to (cr
, 0, height
- (pos_box_size
+4));
250 cairo_set_source_rgba (cr
, UINT_RGBA_R_FLT(po
), UINT_RGBA_G_FLT(po
), UINT_RGBA_B_FLT(po
), UINT_RGBA_A_FLT(po
));
260 MonoPanner::on_button_press_event (GdkEventButton
* ev
)
262 drag_start_x
= ev
->x
;
266 accumulated_delta
= 0;
269 /* Let the binding proxies get first crack at the press event
273 if (position_binder
.button_press_handler (ev
)) {
278 if (ev
->button
!= 1) {
282 if (ev
->type
== GDK_2BUTTON_PRESS
) {
283 int width
= get_width();
285 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
286 /* handled by button release */
291 if (ev
->x
<= width
/3) {
292 /* left side dbl click */
293 position_control
->set_value (0);
294 } else if (ev
->x
> 2*width
/3) {
295 position_control
->set_value (1.0);
297 position_control
->set_value (0.5);
302 } else if (ev
->type
== GDK_BUTTON_PRESS
) {
304 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
305 /* handled by button release */
317 MonoPanner::on_button_release_event (GdkEventButton
* ev
)
319 if (ev
->button
!= 1) {
324 accumulated_delta
= 0;
327 if (drag_data_window
) {
328 drag_data_window
->hide ();
331 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
332 /* reset to default */
333 position_control
->set_value (0.5);
342 MonoPanner::on_scroll_event (GdkEventScroll
* ev
)
344 double one_degree
= 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
345 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
348 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
351 step
= one_degree
* 5.0;
354 switch (ev
->direction
) {
356 case GDK_SCROLL_LEFT
:
358 position_control
->set_value (pv
);
360 case GDK_SCROLL_DOWN
:
361 case GDK_SCROLL_RIGHT
:
363 position_control
->set_value (pv
);
371 MonoPanner::on_motion_notify_event (GdkEventMotion
* ev
)
377 if (!drag_data_window
) {
378 drag_data_window
= new Window (WINDOW_POPUP
);
379 drag_data_window
->set_name (X_("ContrastingPopup"));
380 drag_data_window
->set_position (WIN_POS_MOUSE
);
381 drag_data_window
->set_decorated (false);
383 drag_data_label
= manage (new Label
);
384 drag_data_label
->set_use_markup (true);
386 drag_data_window
->set_border_width (6);
387 drag_data_window
->add (*drag_data_label
);
388 drag_data_label
->show ();
390 Window
* toplevel
= dynamic_cast<Window
*> (get_toplevel());
392 drag_data_window
->set_transient_for (*toplevel
);
396 if (!drag_data_window
->is_visible ()) {
397 /* move the window a little away from the mouse */
399 get_window()->get_origin (rx
, ry
);
400 drag_data_window
->move (rx
, ry
+get_height());
401 drag_data_window
->present ();
405 double delta
= (ev
->x
- last_drag_x
) / (double) w
;
407 /* create a detent close to the center */
409 if (!detented
&& ARDOUR::Panner::equivalent (position_control
->get_value(), 0.5)) {
412 position_control
->set_value (0.5);
416 accumulated_delta
+= delta
;
418 /* have we pulled far enough to escape ? */
420 if (fabs (accumulated_delta
) >= 0.025) {
421 position_control
->set_value (position_control
->get_value() + accumulated_delta
);
423 accumulated_delta
= false;
426 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
427 position_control
->set_value (pv
+ delta
);
435 MonoPanner::on_key_press_event (GdkEventKey
* ev
)
437 double one_degree
= 1.0/180.0;
438 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
441 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
444 step
= one_degree
* 5.0;
447 /* up/down control width because we consider pan position more "important"
448 (and thus having higher "sense" priority) than width.
451 switch (ev
->keyval
) {
454 position_control
->set_value (pv
);
458 position_control
->set_value (pv
);
468 MonoPanner::on_key_release_event (GdkEventKey
* ev
)
474 MonoPanner::on_enter_notify_event (GdkEventCrossing
* ev
)
477 Keyboard::magic_widget_grab_focus ();
482 MonoPanner::on_leave_notify_event (GdkEventCrossing
*)
484 Keyboard::magic_widget_drop_focus ();
489 MonoPanner::set_colors ()
491 colors
.fill
= ARDOUR_UI::config()->canvasvar_MonoPannerFill
.get();
492 colors
.outline
= ARDOUR_UI::config()->canvasvar_MonoPannerOutline
.get();
493 colors
.text
= ARDOUR_UI::config()->canvasvar_MonoPannerText
.get();
494 colors
.background
= ARDOUR_UI::config()->canvasvar_MonoPannerBackground
.get();
495 colors
.pos_outline
= ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline
.get();
496 colors
.pos_fill
= ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill
.get();
500 MonoPanner::color_handler ()