2 Copyright (C) 2011 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.
20 #include "pbd/cartesian.h"
22 #include "gtkmm2ext/keyboard.h"
24 #include "speaker_dialog.h"
25 #include "gui_thread.h"
29 using namespace ARDOUR
;
33 using namespace Gtkmm2ext
;
35 SpeakerDialog::SpeakerDialog ()
36 : ArdourDialog (_("Speaker Configuration"))
37 , aspect_frame ("", 0.5, 0.5, 1.0, false)
38 , azimuth_adjustment (0, 0.0, 360.0, 10.0, 1.0)
39 , azimuth_spinner (azimuth_adjustment
)
40 , add_speaker_button (_("Add Speaker"))
41 , remove_speaker_button (_("Remove Speaker"))
42 /* initialize to 0 so that set_selected works below */
44 , ignore_speaker_position_change (false)
45 , ignore_azimuth_change (false)
47 side_vbox
.set_homogeneous (false);
48 side_vbox
.set_border_width (6);
49 side_vbox
.set_spacing (6);
50 side_vbox
.pack_start (add_speaker_button
, false, false);
52 aspect_frame
.set_size_request (200, 200);
53 aspect_frame
.set_shadow_type (SHADOW_NONE
);
54 aspect_frame
.add (darea
);
57 hbox
.set_border_width (6);
58 hbox
.pack_start (aspect_frame
, true, true);
59 hbox
.pack_start (side_vbox
, true, true);
61 HBox
* current_speaker_hbox
= manage (new HBox
);
62 current_speaker_hbox
->set_spacing (4);
63 current_speaker_hbox
->pack_start (*manage (new Label (_("Azimuth:"))), false, false);
64 current_speaker_hbox
->pack_start (azimuth_spinner
, true, true);
65 current_speaker_hbox
->pack_start (remove_speaker_button
, true, true);
67 get_vbox()->pack_start (hbox
);
68 get_vbox()->pack_start (*current_speaker_hbox
, true, true);
69 get_vbox()->show_all ();
71 darea
.add_events (Gdk::BUTTON_PRESS_MASK
|Gdk::BUTTON_RELEASE_MASK
|Gdk::POINTER_MOTION_MASK
);
73 darea
.signal_size_allocate().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_size_allocate
));
74 darea
.signal_expose_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_expose_event
));
75 darea
.signal_button_press_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_button_press_event
));
76 darea
.signal_button_release_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_button_release_event
));
77 darea
.signal_motion_notify_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_motion_notify_event
));
79 add_speaker_button
.signal_clicked().connect (sigc::mem_fun (*this, &SpeakerDialog::add_speaker
));
80 remove_speaker_button
.signal_clicked().connect (sigc::mem_fun (*this, &SpeakerDialog::remove_speaker
));
81 azimuth_adjustment
.signal_value_changed().connect (sigc::mem_fun (*this, &SpeakerDialog::azimuth_changed
));
85 /* selected index initialised to 0 above; this will set `no selection' and
86 sensitize widgets accordingly.
92 SpeakerDialog::set_speakers (boost::shared_ptr
<Speakers
> s
)
97 boost::shared_ptr
<Speakers
>
98 SpeakerDialog::get_speakers () const
100 return _speakers
.lock ();
104 SpeakerDialog::darea_expose_event (GdkEventExpose
* event
)
106 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
114 cr
= gdk_cairo_create (darea
.get_window()->gobj());
116 cairo_set_line_width (cr
, 1.0);
118 cairo_rectangle (cr
, event
->area
.x
, event
->area
.y
, event
->area
.width
, event
->area
.height
);
119 cairo_set_source_rgba (cr
, 0.1, 0.1, 0.1, 1.0);
120 cairo_fill_preserve (cr
);
123 cairo_translate (cr
, x_origin
, y_origin
);
125 /* horizontal line of "crosshairs" */
127 cairo_set_source_rgb (cr
, 0.0, 0.1, 0.7);
128 cairo_move_to (cr
, 0.5, height
/2.0+0.5);
129 cairo_line_to (cr
, width
+0.5, height
/2+0.5);
132 /* vertical line of "crosshairs" */
134 cairo_move_to (cr
, width
/2+0.5, 0.5);
135 cairo_line_to (cr
, width
/2+0.5, height
+0.5);
138 /* the circle on which signals live */
140 cairo_arc (cr
, width
/2, height
/2, height
/2, 0, 2.0 * M_PI
);
145 cairo_select_font_face (cr
, "sans", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
148 cairo_set_font_size (cr
, 10);
151 cairo_set_font_size (cr
, 16);
156 for (vector
<Speaker
>::iterator i
= speakers
->speakers().begin(); i
!= speakers
->speakers().end(); ++i
) {
159 CartesianVector
c (s
.coords());
163 x
= (gint
) floor (c
.x
);
164 y
= (gint
) floor (c
.y
);
166 /* XXX need to shift circles so that they are centered on the circle */
168 cairo_arc (cr
, x
, y
, arc_radius
, 0, 2.0 * M_PI
);
169 if (selected_index
== n
) {
170 cairo_set_source_rgb (cr
, 0.8, 0.8, 0.2);
172 cairo_set_source_rgb (cr
, 0.8, 0.2, 0.1);
174 cairo_close_path (cr
);
177 cairo_move_to (cr
, x
+ 6, y
+ 6);
180 snprintf (buf
, sizeof (buf
), "%d:%d", n
+1, (int) lrint (s
.angles().azi
));
181 cairo_show_text (cr
, buf
);
192 SpeakerDialog::cart_to_gtk (CartesianVector
& c
) const
194 /* "c" uses a coordinate space that is:
197 dimension = 2.0 * 2.0
198 so max values along each axis are -1..+1
200 GTK uses a coordinate space that is:
203 dimension = width * height
204 so max values along each axis are 0,width and
208 c
.x
= (width
/ 2) * (c
.x
+ 1);
209 c
.y
= (height
/ 2) * (1 - c
.y
);
211 /* XXX z-axis not handled - 2D for now */
215 SpeakerDialog::gtk_to_cart (CartesianVector
& c
) const
217 c
.x
= (c
.x
/ (width
/ 2.0)) - 1.0;
218 c
.y
= -((c
.y
/ (height
/ 2.0)) - 1.0);
220 /* XXX z-axis not handled - 2D for now */
224 SpeakerDialog::clamp_to_circle (double& x
, double& y
)
230 PBD::cartesian_to_spherical (x
, y
, z
, azi
, ele
, l
);
231 PBD::spherical_to_cartesian (azi
, ele
, 1.0, x
, y
, z
);
235 SpeakerDialog::darea_size_allocate (Gtk::Allocation
& alloc
)
237 width
= alloc
.get_width();
238 height
= alloc
.get_height();
245 x_origin
= (alloc
.get_width() - width
) / 2;
246 y_origin
= (alloc
.get_height() - height
) / 2;
250 SpeakerDialog::darea_button_press_event (GdkEventButton
*ev
)
252 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
257 GdkModifierType state
;
259 if (ev
->type
== GDK_2BUTTON_PRESS
&& ev
->button
== 1) {
265 switch (ev
->button
) {
269 int const index
= find_closest_object (ev
->x
, ev
->y
);
270 set_selected (index
);
273 int const drag_x
= (int) floor (ev
->x
);
274 int const drag_y
= (int) floor (ev
->y
);
275 state
= (GdkModifierType
) ev
->state
;
277 if (drag_index
>= 0) {
279 speakers
->speakers()[drag_index
].angles().cartesian (c
);
281 drag_offset_x
= drag_x
- x_origin
- c
.x
;
282 drag_offset_y
= drag_y
- y_origin
- c
.y
;
285 return handle_motion (drag_x
, drag_y
, state
);
297 SpeakerDialog::darea_button_release_event (GdkEventButton
*ev
)
299 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
305 GdkModifierType state
;
308 switch (ev
->button
) {
310 x
= (int) floor (ev
->x
);
311 y
= (int) floor (ev
->y
);
312 state
= (GdkModifierType
) ev
->state
;
314 if (Keyboard::modifier_state_contains (state
, Keyboard::TertiaryModifier
)) {
316 for (vector
<Speaker
>::iterator i
= speakers
->speakers().begin(); i
!= speakers
->speakers().end(); ++i
) {
317 /* XXX DO SOMETHING TO SET SPEAKER BACK TO "normal" */
324 ret
= handle_motion (x
, y
, state
);
330 x
= (int) floor (ev
->x
);
331 y
= (int) floor (ev
->y
);
332 state
= (GdkModifierType
) ev
->state
;
334 ret
= handle_motion (x
, y
, state
);
348 SpeakerDialog::find_closest_object (gdouble x
, gdouble y
)
350 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
356 float best_distance
= FLT_MAX
;
360 for (vector
<Speaker
>::iterator i
= speakers
->speakers().begin(); i
!= speakers
->speakers().end(); ++i
, ++n
) {
362 Speaker
& candidate (*i
);
365 candidate
.angles().cartesian (c
);
368 distance
= sqrt ((c
.x
- x
) * (c
.x
- x
) +
369 (c
.y
- y
) * (c
.y
- y
));
372 if (distance
< best_distance
) {
373 best_distance
= distance
;
378 if (best_distance
> 20) { // arbitrary
386 SpeakerDialog::darea_motion_notify_event (GdkEventMotion
*ev
)
389 GdkModifierType state
;
392 gdk_window_get_pointer (ev
->window
, &x
, &y
, &state
);
394 x
= (int) floor (ev
->x
);
395 y
= (int) floor (ev
->y
);
396 state
= (GdkModifierType
) ev
->state
;
399 return handle_motion (x
, y
, state
);
403 SpeakerDialog::handle_motion (gint evx
, gint evy
, GdkModifierType state
)
405 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
410 if (drag_index
< 0) {
414 if ((state
& (GDK_BUTTON1_MASK
|GDK_BUTTON2_MASK
)) == 0) {
418 /* correct event coordinates to have their origin at the corner of our graphic
419 rather than the corner of our allocation */
421 double obx
= evx
- x_origin
;
422 double oby
= evy
- y_origin
;
424 /* and compensate for any distance between the mouse pointer and the centre
425 of the object being dragged */
427 obx
-= drag_offset_x
;
428 oby
-= drag_offset_y
;
430 if (state
& GDK_BUTTON1_MASK
&& !(state
& GDK_BUTTON2_MASK
)) {
432 bool need_move
= false;
433 Speaker
& moving (speakers
->speakers()[drag_index
]);
435 moving
.angles().cartesian (c
);
438 if (obx
!= c
.x
|| oby
!= c
.y
) {
443 CartesianVector
cp (obx
, oby
, 0.0);
445 /* canonicalize position */
449 /* position actual signal on circle */
451 clamp_to_circle (cp
.x
, cp
.y
);
453 /* generate an angular representation and set drag target (GUI) position */
469 SpeakerDialog::add_speaker ()
471 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
476 speakers
->add_speaker (PBD::AngularVector (0, 0, 0));
481 SpeakerDialog::set_selected (int i
)
483 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
488 if (i
== selected_index
) {
495 selected_speaker_connection
.disconnect ();
497 azimuth_spinner
.set_sensitive (selected_index
!= -1);
498 remove_speaker_button
.set_sensitive (selected_index
!= -1);
500 if (selected_index
!= -1) {
501 azimuth_adjustment
.set_value (speakers
->speakers()[selected_index
].angles().azi
);
502 speakers
->speakers()[selected_index
].PositionChanged
.connect (
503 selected_speaker_connection
, MISSING_INVALIDATOR
,
504 boost::bind (&SpeakerDialog::speaker_position_changed
, this),
511 SpeakerDialog::azimuth_changed ()
513 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
518 assert (selected_index
!= -1);
520 if (ignore_azimuth_change
) {
524 ignore_speaker_position_change
= true;
525 speakers
->move_speaker (speakers
->speakers()[selected_index
].id
, PBD::AngularVector (azimuth_adjustment
.get_value (), 0, 0));
526 ignore_speaker_position_change
= false;
532 SpeakerDialog::speaker_position_changed ()
534 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
539 assert (selected_index
!= -1);
541 if (ignore_speaker_position_change
) {
545 ignore_azimuth_change
= true;
546 azimuth_adjustment
.set_value (speakers
->speakers()[selected_index
].angles().azi
);
547 ignore_azimuth_change
= false;
553 SpeakerDialog::remove_speaker ()
555 boost::shared_ptr
<Speakers
> speakers
= _speakers
.lock ();
560 assert (selected_index
!= -1);
562 speakers
->remove_speaker (speakers
->speakers()[selected_index
].id
);