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.
108 drag_data_label
->set_markup (string_compose (_("L:%1 R:%2"),
109 (int) rint (100.0 * (1.0 - pos
)),
110 (int) rint (100.0 * pos
)));
114 MonoPanner::value_change ()
121 MonoPanner::on_expose_event (GdkEventExpose
* ev
)
123 Glib::RefPtr
<Gdk::Window
> win (get_window());
124 Glib::RefPtr
<Gdk::GC
> gc (get_style()->get_base_gc (get_state()));
126 cairo_t
* cr
= gdk_cairo_create (win
->gobj());
129 double pos
= position_control
->get_value (); /* 0..1 */
130 uint32_t o
, f
, t
, b
, pf
, po
;
133 height
= get_height ();
138 b
= colors
.background
;
139 pf
= colors
.pos_fill
;
140 po
= colors
.pos_outline
;
144 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
));
145 cairo_rectangle (cr
, 0, 0, width
, height
);
148 double usable_width
= width
- pos_box_size
;
150 /* compute the centers of the L/R boxes based on the current stereo width */
152 if (fmod (usable_width
,2.0) == 0) {
153 /* even width, but we need odd, so that there is an exact center.
154 So, offset cairo by 1, and reduce effective width by 1
157 cairo_translate (cr
, 1.0, 0.0);
160 const double half_lr_box
= lr_box_size
/2.0;
164 left
= 4 + half_lr_box
; // center of left box
165 right
= width
- 4 - half_lr_box
; // center of right box
168 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
));
169 cairo_set_line_width (cr
, 1.0);
170 cairo_move_to (cr
, (pos_box_size
/2.0) + (usable_width
/2.0), 0);
171 cairo_line_to (cr
, (pos_box_size
/2.0) + (usable_width
/2.0), height
);
178 half_lr_box
+step_down
,
179 lr_box_size
, lr_box_size
);
180 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
));
181 cairo_stroke_preserve (cr
);
182 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
));
188 left
- half_lr_box
+ 3,
189 (lr_box_size
/2) + step_down
+ 13);
190 cairo_select_font_face (cr
, "sans-serif", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_BOLD
);
191 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
));
192 cairo_show_text (cr
, _("L"));
198 half_lr_box
+step_down
,
199 lr_box_size
, lr_box_size
);
200 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
));
201 cairo_stroke_preserve (cr
);
202 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
));
208 right
- half_lr_box
+ 3,
209 (lr_box_size
/2)+step_down
+ 13);
210 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
));
211 cairo_show_text (cr
, _("R"));
213 /* 2 lines that connect them both */
214 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
));
215 cairo_set_line_width (cr
, 1.0);
216 cairo_move_to (cr
, left
+ half_lr_box
, half_lr_box
+step_down
);
217 cairo_line_to (cr
, right
- half_lr_box
, half_lr_box
+step_down
);
221 cairo_move_to (cr
, left
+ half_lr_box
, half_lr_box
+step_down
+lr_box_size
);
222 cairo_line_to (cr
, right
- half_lr_box
, half_lr_box
+step_down
+lr_box_size
);
225 /* draw the position indicator */
227 double spos
= (pos_box_size
/2.0) + (usable_width
* pos
);
229 cairo_set_line_width (cr
, 2.0);
230 cairo_move_to (cr
, spos
+ (pos_box_size
/2.0), top_step
); /* top right */
231 cairo_rel_line_to (cr
, 0.0, pos_box_size
); /* lower right */
232 cairo_rel_line_to (cr
, -pos_box_size
/2.0, 4.0); /* bottom point */
233 cairo_rel_line_to (cr
, -pos_box_size
/2.0, -4.0); /* lower left */
234 cairo_rel_line_to (cr
, 0.0, -pos_box_size
); /* upper left */
235 cairo_close_path (cr
);
238 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
));
239 cairo_stroke_preserve (cr
);
240 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
));
245 cairo_set_line_width (cr
, 1.0);
246 cairo_move_to (cr
, spos
, pos_box_size
+4);
247 cairo_rel_line_to (cr
, 0, height
- (pos_box_size
+4));
248 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
));
258 MonoPanner::on_button_press_event (GdkEventButton
* ev
)
260 drag_start_x
= ev
->x
;
264 accumulated_delta
= 0;
267 /* Let the binding proxies get first crack at the press event
271 if (position_binder
.button_press_handler (ev
)) {
276 if (ev
->button
!= 1) {
280 if (ev
->type
== GDK_2BUTTON_PRESS
) {
281 int width
= get_width();
283 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
284 /* handled by button release */
289 if (ev
->x
<= width
/3) {
290 /* left side dbl click */
291 position_control
->set_value (0);
292 } else if (ev
->x
> 2*width
/3) {
293 position_control
->set_value (1.0);
295 position_control
->set_value (0.5);
300 } else if (ev
->type
== GDK_BUTTON_PRESS
) {
302 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
303 /* handled by button release */
315 MonoPanner::on_button_release_event (GdkEventButton
* ev
)
317 if (ev
->button
!= 1) {
322 accumulated_delta
= 0;
325 if (drag_data_window
) {
326 drag_data_window
->hide ();
329 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
330 /* reset to default */
331 position_control
->set_value (0.5);
340 MonoPanner::on_scroll_event (GdkEventScroll
* ev
)
342 double one_degree
= 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
343 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
346 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
349 step
= one_degree
* 5.0;
352 switch (ev
->direction
) {
354 case GDK_SCROLL_LEFT
:
356 position_control
->set_value (pv
);
358 case GDK_SCROLL_DOWN
:
359 case GDK_SCROLL_RIGHT
:
361 position_control
->set_value (pv
);
369 MonoPanner::on_motion_notify_event (GdkEventMotion
* ev
)
375 if (!drag_data_window
) {
376 drag_data_window
= new Window (WINDOW_POPUP
);
377 drag_data_window
->set_name (X_("ContrastingPopup"));
378 drag_data_window
->set_position (WIN_POS_MOUSE
);
379 drag_data_window
->set_decorated (false);
381 drag_data_label
= manage (new Label
);
382 drag_data_label
->set_use_markup (true);
384 drag_data_window
->set_border_width (6);
385 drag_data_window
->add (*drag_data_label
);
386 drag_data_label
->show ();
388 Window
* toplevel
= dynamic_cast<Window
*> (get_toplevel());
390 drag_data_window
->set_transient_for (*toplevel
);
394 if (!drag_data_window
->is_visible ()) {
395 /* move the window a little away from the mouse */
397 get_window()->get_origin (rx
, ry
);
398 drag_data_window
->move (rx
, ry
+get_height());
399 drag_data_window
->present ();
403 double delta
= (ev
->x
- last_drag_x
) / (double) w
;
405 /* create a detent close to the center */
407 if (!detented
&& ARDOUR::Panner::equivalent (position_control
->get_value(), 0.5)) {
410 position_control
->set_value (0.5);
414 accumulated_delta
+= delta
;
416 /* have we pulled far enough to escape ? */
418 if (fabs (accumulated_delta
) >= 0.025) {
419 position_control
->set_value (position_control
->get_value() + accumulated_delta
);
421 accumulated_delta
= false;
424 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
425 position_control
->set_value (pv
+ delta
);
433 MonoPanner::on_key_press_event (GdkEventKey
* ev
)
435 double one_degree
= 1.0/180.0;
436 double pv
= position_control
->get_value(); // 0..1.0 ; 0 = left
439 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
442 step
= one_degree
* 5.0;
445 /* up/down control width because we consider pan position more "important"
446 (and thus having higher "sense" priority) than width.
449 switch (ev
->keyval
) {
452 position_control
->set_value (pv
);
456 position_control
->set_value (pv
);
466 MonoPanner::on_key_release_event (GdkEventKey
* ev
)
472 MonoPanner::on_enter_notify_event (GdkEventCrossing
* ev
)
475 Keyboard::magic_widget_grab_focus ();
480 MonoPanner::on_leave_notify_event (GdkEventCrossing
*)
482 Keyboard::magic_widget_drop_focus ();
487 MonoPanner::set_colors ()
489 colors
.fill
= ARDOUR_UI::config()->canvasvar_MonoPannerFill
.get();
490 colors
.outline
= ARDOUR_UI::config()->canvasvar_MonoPannerOutline
.get();
491 colors
.text
= ARDOUR_UI::config()->canvasvar_MonoPannerText
.get();
492 colors
.background
= ARDOUR_UI::config()->canvasvar_MonoPannerBackground
.get();
493 colors
.pos_outline
= ARDOUR_UI::config()->canvasvar_MonoPannerPositionOutline
.get();
494 colors
.pos_fill
= ARDOUR_UI::config()->canvasvar_MonoPannerPositionFill
.get();
498 MonoPanner::color_handler ()