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"
28 using namespace ARDOUR
;
32 using namespace Gtkmm2ext
;
34 SpeakerDialog::SpeakerDialog ()
35 : ArdourDialog (_("Speaker Configuration"))
36 , aspect_frame ("", 0.5, 0.5, 1.0, false)
37 , azimuth_adjustment (0, 0.0, 360.0, 10.0, 1.0)
38 , azimuth_spinner (azimuth_adjustment
)
39 , add_speaker_button (_("Add Speaker"))
40 , use_system_button (_("Use System"))
44 side_vbox
.set_homogeneous (false);
45 side_vbox
.set_border_width (9);
46 side_vbox
.set_spacing (6);
47 side_vbox
.pack_start (azimuth_spinner
, false, false);
48 side_vbox
.pack_start (add_speaker_button
, false, false);
49 side_vbox
.pack_start (use_system_button
, false, false);
51 aspect_frame
.set_size_request (200, 200);
52 aspect_frame
.set_shadow_type (SHADOW_NONE
);
53 aspect_frame
.add (darea
);
56 hbox
.set_border_width (6);
57 hbox
.pack_start (aspect_frame
, true, true);
58 hbox
.pack_start (side_vbox
, false, false);
60 get_vbox()->pack_start (hbox
);
61 get_vbox()->show_all ();
63 darea
.add_events (Gdk::BUTTON_PRESS_MASK
|Gdk::BUTTON_RELEASE_MASK
|Gdk::POINTER_MOTION_MASK
);
65 darea
.signal_size_allocate().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_size_allocate
));
66 darea
.signal_expose_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_expose_event
));
67 darea
.signal_button_press_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_button_press_event
));
68 darea
.signal_button_release_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_button_release_event
));
69 darea
.signal_motion_notify_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_motion_notify_event
));
75 SpeakerDialog::set_speakers (boost::shared_ptr
<Speakers
> s
)
81 SpeakerDialog::get_speakers () const
87 SpeakerDialog::darea_expose_event (GdkEventExpose
* event
)
92 cr
= gdk_cairo_create (darea
.get_window()->gobj());
94 cairo_set_line_width (cr
, 1.0);
96 cairo_rectangle (cr
, event
->area
.x
, event
->area
.y
, event
->area
.width
, event
->area
.height
);
97 cairo_set_source_rgba (cr
, 0.1, 0.1, 0.1, 1.0);
98 cairo_fill_preserve (cr
);
102 cairo_translate (cr
, 10.0, 10.0);
105 /* horizontal line of "crosshairs" */
107 cairo_set_source_rgb (cr
, 0.0, 0.1, 0.7);
108 cairo_move_to (cr
, 0.5, height
/2.0+0.5);
109 cairo_line_to (cr
, width
+0.5, height
/2+0.5);
112 /* vertical line of "crosshairs" */
114 cairo_move_to (cr
, width
/2+0.5, 0.5);
115 cairo_line_to (cr
, width
/2+0.5, height
+0.5);
118 /* the circle on which signals live */
120 cairo_arc (cr
, width
/2, height
/2, height
/2, 0, 2.0 * M_PI
);
125 cairo_select_font_face (cr
, "sans", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
128 cairo_set_font_size (cr
, 10);
131 cairo_set_font_size (cr
, 16);
136 for (vector
<Speaker
>::iterator i
= speakers
.speakers().begin(); i
!= speakers
.speakers().end(); ++i
) {
139 CartesianVector
c (s
.coords());
143 x
= (gint
) floor (c
.x
);
144 y
= (gint
) floor (c
.y
);
146 /* XXX need to shift circles so that they are centered on the circle */
148 cairo_arc (cr
, x
, y
, arc_radius
, 0, 2.0 * M_PI
);
149 cairo_set_source_rgb (cr
, 0.8, 0.2, 0.1);
150 cairo_close_path (cr
);
153 cairo_move_to (cr
, x
+ 6, y
+ 6);
156 snprintf (buf
, sizeof (buf
), "%d:%d", n
+1, (int) lrint (s
.angles().azi
));
157 cairo_show_text (cr
, buf
);
168 SpeakerDialog::cart_to_gtk (CartesianVector
& c
) const
170 /* "c" uses a coordinate space that is:
173 dimension = 2.0 * 2.0
174 so max values along each axis are -1..+1
176 GTK uses a coordinate space that is:
179 dimension = width * height
180 so max values along each axis are 0,width and
184 c
.x
= (width
/ 2) * (c
.x
+ 1);
185 c
.y
= (height
/ 2) * (1 - c
.y
);
187 /* XXX z-axis not handled - 2D for now */
191 SpeakerDialog::gtk_to_cart (CartesianVector
& c
) const
193 c
.x
= (c
.x
/ (width
/ 2.0)) - 1.0;
194 c
.y
= -((c
.y
/ (height
/ 2.0)) - 1.0);
196 /* XXX z-axis not handled - 2D for now */
200 SpeakerDialog::clamp_to_circle (double& x
, double& y
)
206 PBD::cartesian_to_spherical (x
, y
, z
, azi
, ele
, l
);
207 PBD::spherical_to_cartesian (azi
, ele
, 1.0, x
, y
, z
);
211 SpeakerDialog::darea_size_allocate (Gtk::Allocation
& alloc
)
213 width
= alloc
.get_width();
214 height
= alloc
.get_height();
223 SpeakerDialog::darea_button_press_event (GdkEventButton
*ev
)
225 GdkModifierType state
;
227 if (ev
->type
== GDK_2BUTTON_PRESS
&& ev
->button
== 1) {
233 switch (ev
->button
) {
236 drag_index
= find_closest_object (ev
->x
, ev
->y
);
237 drag_x
= (int) floor (ev
->x
);
238 drag_y
= (int) floor (ev
->y
);
239 state
= (GdkModifierType
) ev
->state
;
241 return handle_motion (drag_x
, drag_y
, state
);
252 SpeakerDialog::darea_button_release_event (GdkEventButton
*ev
)
255 GdkModifierType state
;
258 switch (ev
->button
) {
260 x
= (int) floor (ev
->x
);
261 y
= (int) floor (ev
->y
);
262 state
= (GdkModifierType
) ev
->state
;
264 if (Keyboard::modifier_state_contains (state
, Keyboard::TertiaryModifier
)) {
266 for (vector
<Speaker
>::iterator i
= speakers
.speakers().begin(); i
!= speakers
.speakers().end(); ++i
) {
267 /* XXX DO SOMETHING TO SET SPEAKER BACK TO "normal" */
274 ret
= handle_motion (x
, y
, state
);
280 x
= (int) floor (ev
->x
);
281 y
= (int) floor (ev
->y
);
282 state
= (GdkModifierType
) ev
->state
;
284 ret
= handle_motion (x
, y
, state
);
298 SpeakerDialog::find_closest_object (gdouble x
, gdouble y
)
301 float best_distance
= FLT_MAX
;
305 for (vector
<Speaker
>::iterator i
= speakers
.speakers().begin(); i
!= speakers
.speakers().end(); ++i
, ++n
) {
307 Speaker
& candidate (*i
);
310 candidate
.angles().cartesian (c
);
313 distance
= sqrt ((c
.x
- x
) * (c
.x
- x
) +
314 (c
.y
- y
) * (c
.y
- y
));
317 if (distance
< best_distance
) {
318 best_distance
= distance
;
323 if (best_distance
> 20) { // arbitrary
331 SpeakerDialog::darea_motion_notify_event (GdkEventMotion
*ev
)
334 GdkModifierType state
;
337 gdk_window_get_pointer (ev
->window
, &x
, &y
, &state
);
339 x
= (int) floor (ev
->x
);
340 y
= (int) floor (ev
->y
);
341 state
= (GdkModifierType
) ev
->state
;
344 return handle_motion (x
, y
, state
);
348 SpeakerDialog::handle_motion (gint evx
, gint evy
, GdkModifierType state
)
350 if (drag_index
< 0) {
354 if ((state
& (GDK_BUTTON1_MASK
|GDK_BUTTON2_MASK
)) == 0) {
359 if (state
& GDK_BUTTON1_MASK
&& !(state
& GDK_BUTTON2_MASK
)) {
361 bool need_move
= false;
362 Speaker
& moving (speakers
.speakers()[drag_index
]);
364 moving
.angles().cartesian (c
);
367 if ((evx
!= c
.x
) || (evy
!= c
.y
)) {
372 CartesianVector
cp (evx
, evy
, 0.0);
374 /* canonicalize position */
378 /* position actual signal on circle */
380 clamp_to_circle (cp
.x
, cp
.y
);
382 /* generate an angular representation and set drag target (GUI) position */