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"
35 #include "ardour/panner.h"
36 #include "ardour/panner.h"
38 #include "ardour_ui.h"
39 #include "global_signals.h"
40 #include "mono_panner.h"
41 #include "rgb_macros.h"
48 using namespace Gtkmm2ext
;
50 static const int pos_box_size
= 9;
51 static const int lr_box_size
= 15;
52 static const int step_down
= 10;
53 static const int top_step
= 2;
55 MonoPanner::ColorScheme
MonoPanner::colors
;
56 bool MonoPanner::have_colors
= false;
58 MonoPanner::MonoPanner (boost::shared_ptr
<PBD::Controllable
> position
)
59 : position_control (position
)
63 , accumulated_delta (0)
65 , drag_data_window (0)
67 , position_binder (position
)
74 position_control
->Changed
.connect (connections
, invalidator(*this), boost::bind (&MonoPanner::value_change
, this), gui_context());
76 set_flags (Gtk::CAN_FOCUS
);
78 add_events (Gdk::ENTER_NOTIFY_MASK
|Gdk::LEAVE_NOTIFY_MASK
|
79 Gdk::KEY_PRESS_MASK
|Gdk::KEY_RELEASE_MASK
|
80 Gdk::BUTTON_PRESS_MASK
|Gdk::BUTTON_RELEASE_MASK
|
82 Gdk::POINTER_MOTION_MASK
);
84 ColorsChanged
.connect (sigc::mem_fun (*this, &MonoPanner::color_handler
));
87 MonoPanner::~MonoPanner ()
89 delete drag_data_window
;
93 MonoPanner::set_drag_data ()
95 if (!drag_data_label
) {
99 double pos
= position_control
->get_value(); // 0..1
101 /* We show the position of the center of the image relative to the left & right.
102 This is expressed as a pair of percentage values that ranges from (100,0)
103 (hard left) through (50,50) (hard center) to (0,100) (hard right).
105 This is pretty wierd, but its the way audio engineers expect it. Just remember that
106 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
110 snprintf (buf
, sizeof (buf
), "L:%3d R:%3d",
111 (int) rint (100.0 * (1.0 - pos
)),
112 (int) rint (100.0 * pos
));
113 drag_data_label
->set_markup (buf
);
117 MonoPanner::value_change ()
124 MonoPanner::on_expose_event (GdkEventExpose
* ev
)
126 Glib::RefPtr
<Gdk::Window
> win (get_window());
127 Glib::RefPtr
<Gdk::GC
> gc (get_style()->get_base_gc (get_state()));
128 Cairo::RefPtr
<Cairo::Context
> context
= get_window()->create_cairo_context();
131 double pos
= position_control
->get_value (); /* 0..1 */
132 uint32_t o
, f
, t
, b
, pf
, po
;
133 const double corner_radius
= 5;
136 height
= get_height ();
141 b
= colors
.background
;
142 pf
= colors
.pos_fill
;
143 po
= colors
.pos_outline
;
147 context
->set_source_rgba (UINT_RGBA_R_FLT(b
), UINT_RGBA_G_FLT(b
), UINT_RGBA_B_FLT(b
), UINT_RGBA_A_FLT(b
));
148 context
->rectangle (0, 0, width
, height
);
151 double usable_width
= width
- pos_box_size
;
153 /* compute the centers of the L/R boxes based on the current stereo width */
155 if (fmod (usable_width
,2.0) == 0) {
156 /* even width, but we need odd, so that there is an exact center.
157 So, offset cairo by 1, and reduce effective width by 1
160 context
->translate (1.0, 0.0);
163 const double half_lr_box
= lr_box_size
/2.0;
167 left
= 4 + half_lr_box
; // center of left box
168 right
= width
- 4 - half_lr_box
; // center of right box
171 context
->set_source_rgba (UINT_RGBA_R_FLT(o
), UINT_RGBA_G_FLT(o
), UINT_RGBA_B_FLT(o
), UINT_RGBA_A_FLT(o
));
172 context
->set_line_width (1.0);
173 context
->move_to ((pos_box_size
/2.0) + (usable_width
/2.0), 0);
174 context
->line_to ((pos_box_size
/2.0) + (usable_width
/2.0), height
);
179 rounded_rectangle (context
,
181 half_lr_box
+step_down
,
182 lr_box_size
, lr_box_size
, corner_radius
);
183 context
->set_source_rgba (UINT_RGBA_R_FLT(o
), UINT_RGBA_G_FLT(o
), UINT_RGBA_B_FLT(o
), UINT_RGBA_A_FLT(o
));
184 context
->stroke_preserve ();
185 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 left
- half_lr_box
+ 3,
192 (lr_box_size
/2) + step_down
+ 13);
193 context
->select_font_face ("sans-serif", Cairo::FONT_SLANT_NORMAL
, Cairo::FONT_WEIGHT_BOLD
);
194 context
->set_source_rgba (UINT_RGBA_R_FLT(t
), UINT_RGBA_G_FLT(t
), UINT_RGBA_B_FLT(t
), UINT_RGBA_A_FLT(t
));
195 context
->show_text (_("L"));
199 rounded_rectangle (context
,
201 half_lr_box
+step_down
,
202 lr_box_size
, lr_box_size
, corner_radius
);
203 context
->set_source_rgba (UINT_RGBA_R_FLT(o
), UINT_RGBA_G_FLT(o
), UINT_RGBA_B_FLT(o
), UINT_RGBA_A_FLT(o
));
204 context
->stroke_preserve ();
205 context
->set_source_rgba (UINT_RGBA_R_FLT(f
), UINT_RGBA_G_FLT(f
), UINT_RGBA_B_FLT(f
), UINT_RGBA_A_FLT(f
));
211 right
- half_lr_box
+ 3,
212 (lr_box_size
/2)+step_down
+ 13);
213 context
->set_source_rgba (UINT_RGBA_R_FLT(t
), UINT_RGBA_G_FLT(t
), UINT_RGBA_B_FLT(t
), UINT_RGBA_A_FLT(t
));
214 context
->show_text (_("R"));
216 /* 2 lines that connect them both */
217 context
->set_source_rgba (UINT_RGBA_R_FLT(o
), UINT_RGBA_G_FLT(o
), UINT_RGBA_B_FLT(o
), UINT_RGBA_A_FLT(o
));
218 context
->set_line_width (1.0);
220 /* make the lines a little longer than they need to be, because the corners of
221 the boxes are rounded and we don't want a gap
223 context
->move_to (left
+ half_lr_box
- corner_radius
, half_lr_box
+step_down
);
224 context
->line_to (right
- half_lr_box
+ corner_radius
, half_lr_box
+step_down
);
228 context
->move_to (left
+ half_lr_box
- corner_radius
, half_lr_box
+step_down
+lr_box_size
);
229 context
->line_to (right
- half_lr_box
+ corner_radius
, half_lr_box
+step_down
+lr_box_size
);
232 /* draw the position indicator */
234 double spos
= (pos_box_size
/2.0) + (usable_width
* pos
);
236 context
->set_line_width (2.0);
237 context
->move_to (spos
+ (pos_box_size
/2.0), top_step
); /* top right */
238 context
->rel_line_to (0.0, pos_box_size
); /* lower right */
239 context
->rel_line_to (-pos_box_size
/2.0, 4.0); /* bottom point */
240 context
->rel_line_to (-pos_box_size
/2.0, -4.0); /* lower left */
241 context
->rel_line_to (0.0, -pos_box_size
); /* upper left */
242 context
->close_path ();
245 context
->set_source_rgba (UINT_RGBA_R_FLT(po
), UINT_RGBA_G_FLT(po
), UINT_RGBA_B_FLT(po
), UINT_RGBA_A_FLT(po
));
246 context
->stroke_preserve ();
247 context
->set_source_rgba (UINT_RGBA_R_FLT(pf
), UINT_RGBA_G_FLT(pf
), UINT_RGBA_B_FLT(pf
), UINT_RGBA_A_FLT(pf
));
252 context
->set_line_width (1.0);
253 context
->move_to (spos
, pos_box_size
+4);
254 context
->rel_line_to (0, half_lr_box
+step_down
);
255 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 MonoPanner::on_button_press_event (GdkEventButton
* ev
)
266 drag_start_x
= ev
->x
;
270 accumulated_delta
= 0;
273 /* Let the binding proxies get first crack at the press event
277 if (position_binder
.button_press_handler (ev
)) {
282 if (ev
->button
!= 1) {
286 if (ev
->type
== GDK_2BUTTON_PRESS
) {
287 int width
= get_width();
289 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
290 /* handled by button release */
295 if (ev
->x
<= width
/3) {
296 /* left side dbl click */
297 position_control
->set_value (0);
298 } else if (ev
->x
> 2*width
/3) {
299 position_control
->set_value (1.0);
301 position_control
->set_value (0.5);
306 } else if (ev
->type
== GDK_BUTTON_PRESS
) {
308 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
309 /* handled by button release */
321 MonoPanner::on_button_release_event (GdkEventButton
* ev
)
323 if (ev
->button
!= 1) {
328 accumulated_delta
= 0;
331 if (drag_data_window
) {
332 drag_data_window
->hide ();
335 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
336 /* reset to default */
337 position_control
->set_value (0.5);
346 MonoPanner::on_scroll_event (GdkEventScroll
* ev
)
348 double one_degree
= 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
349 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
352 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
355 step
= one_degree
* 5.0;
358 switch (ev
->direction
) {
360 case GDK_SCROLL_LEFT
:
362 position_control
->set_value (pv
);
364 case GDK_SCROLL_DOWN
:
365 case GDK_SCROLL_RIGHT
:
367 position_control
->set_value (pv
);
375 MonoPanner::on_motion_notify_event (GdkEventMotion
* ev
)
381 if (!drag_data_window
) {
382 drag_data_window
= new Window (WINDOW_POPUP
);
383 drag_data_window
->set_name (X_("ContrastingPopup"));
384 drag_data_window
->set_position (WIN_POS_MOUSE
);
385 drag_data_window
->set_decorated (false);
387 drag_data_label
= manage (new Label
);
388 drag_data_label
->set_use_markup (true);
390 drag_data_window
->set_border_width (6);
391 drag_data_window
->add (*drag_data_label
);
392 drag_data_label
->show ();
394 Window
* toplevel
= dynamic_cast<Window
*> (get_toplevel());
396 drag_data_window
->set_transient_for (*toplevel
);
400 if (!drag_data_window
->is_visible ()) {
401 /* move the window a little away from the mouse */
403 get_window()->get_origin (rx
, ry
);
404 drag_data_window
->move (rx
, ry
+get_height());
405 drag_data_window
->present ();
409 double delta
= (ev
->x
- last_drag_x
) / (double) w
;
411 /* create a detent close to the center */
413 if (!detented
&& ARDOUR::Panner::equivalent (position_control
->get_value(), 0.5)) {
416 position_control
->set_value (0.5);
420 accumulated_delta
+= delta
;
422 /* have we pulled far enough to escape ? */
424 if (fabs (accumulated_delta
) >= 0.025) {
425 position_control
->set_value (position_control
->get_value() + accumulated_delta
);
427 accumulated_delta
= false;
430 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
431 position_control
->set_value (pv
+ delta
);
439 MonoPanner::on_key_press_event (GdkEventKey
* ev
)
441 double one_degree
= 1.0/180.0;
442 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
445 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
448 step
= one_degree
* 5.0;
451 /* up/down control width because we consider pan position more "important"
452 (and thus having higher "sense" priority) than width.
455 switch (ev
->keyval
) {
458 position_control
->set_value (pv
);
462 position_control
->set_value (pv
);
472 MonoPanner::on_key_release_event (GdkEventKey
* ev
)
478 MonoPanner::on_enter_notify_event (GdkEventCrossing
* ev
)
481 Keyboard::magic_widget_grab_focus ();
486 MonoPanner::on_leave_notify_event (GdkEventCrossing
*)
488 Keyboard::magic_widget_drop_focus ();
493 MonoPanner::set_colors ()
495 colors
.fill
= ARDOUR_UI::config()->canvasvar_MonoPannerFill
.get();
496 colors
.outline
= ARDOUR_UI::config()->canvasvar_MonoPannerOutline
.get();
497 colors
.text
= ARDOUR_UI::config()->canvasvar_MonoPannerText
.get();
498 colors
.background
= ARDOUR_UI::config()->canvasvar_MonoPannerBackground
.get();
499 colors
.pos_outline
= ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline
.get();
500 colors
.pos_fill
= ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill
.get();
504 MonoPanner::color_handler ()