ardour
speaker_dialog.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 Paul Davis
3 
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.
8 
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.
13 
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.
17 
18 */
19 
20 #include "pbd/cartesian.h"
21 
22 #include "gtkmm2ext/keyboard.h"
23 
24 #include "speaker_dialog.h"
25 #include "gui_thread.h"
26 
27 #include "i18n.h"
28 
29 using namespace ARDOUR;
30 using namespace PBD;
31 using namespace std;
32 using namespace Gtk;
33 using namespace Gtkmm2ext;
34 
36  : ArdourWindow (_("Speaker Configuration"))
37  , aspect_frame ("", 0.5, 0.5, 1.5, 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 */
43  , selected_index (0)
44  , ignore_speaker_position_change (false)
45  , ignore_azimuth_change (false)
46 {
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);
51 
52  aspect_frame.set_size_request (300, 200);
53  aspect_frame.set_shadow_type (SHADOW_NONE);
54  aspect_frame.add (darea);
55 
56  hbox.set_spacing (6);
57  hbox.set_border_width (6);
58  hbox.pack_start (aspect_frame, true, true);
59  hbox.pack_start (side_vbox, false, false);
60 
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);
66 
67  VBox* vbox = manage (new VBox);
68  vbox->pack_start (hbox);
69  vbox->pack_start (*current_speaker_hbox, true, true);
70  vbox->show_all ();
71  add (*vbox);
72 
73  darea.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
74 
75  darea.signal_size_allocate().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_size_allocate));
76  darea.signal_expose_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_expose_event));
77  darea.signal_button_press_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_button_press_event));
78  darea.signal_button_release_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_button_release_event));
79  darea.signal_motion_notify_event().connect (sigc::mem_fun (*this, &SpeakerDialog::darea_motion_notify_event));
80 
81  add_speaker_button.signal_clicked().connect (sigc::mem_fun (*this, &SpeakerDialog::add_speaker));
82  remove_speaker_button.signal_clicked().connect (sigc::mem_fun (*this, &SpeakerDialog::remove_speaker));
83  azimuth_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &SpeakerDialog::azimuth_changed));
84 
85  drag_index = -1;
86 
87  /* selected index initialised to 0 above; this will set `no selection' and
88  sensitize widgets accordingly.
89  */
90  set_selected (-1);
91 }
92 
93 void
95 {
96  _speakers = s;
97 }
98 
101 {
102  return _speakers.lock ();
103 }
104 
105 bool
106 SpeakerDialog::darea_expose_event (GdkEventExpose* event)
107 {
108  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
109  if (!speakers) {
110  return false;
111  }
112 
113  gint x, y;
114  cairo_t* cr;
115 
116  cr = gdk_cairo_create (darea.get_window()->gobj());
117 
118  cairo_set_line_width (cr, 1.0);
119 
120  cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height);
121  cairo_set_source_rgba (cr, 0.1, 0.1, 0.1, 1.0);
122  cairo_fill_preserve (cr);
123  cairo_clip (cr);
124 
125  cairo_translate (cr, x_origin, y_origin);
126 
127  /* horizontal line of "crosshairs" */
128 
129  cairo_set_source_rgb (cr, 0.0, 0.1, 0.7);
130  cairo_move_to (cr, 0.5, height/2.0+0.5);
131  cairo_line_to (cr, width+0.5, height/2+0.5);
132  cairo_stroke (cr);
133 
134  /* vertical line of "crosshairs" */
135 
136  cairo_move_to (cr, width/2+0.5, 0.5);
137  cairo_line_to (cr, width/2+0.5, height+0.5);
138  cairo_stroke (cr);
139 
140  /* the circle on which signals live */
141 
142  cairo_arc (cr, width/2, height/2, height/2, 0, 2.0 * M_PI);
143  cairo_stroke (cr);
144 
145  float arc_radius;
146 
147  cairo_select_font_face (cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
148 
149  if (height < 100) {
150  cairo_set_font_size (cr, 10);
151  arc_radius = 2.0;
152  } else {
153  cairo_set_font_size (cr, 16);
154  arc_radius = 4.0;
155  }
156 
157  int n = 0;
158  for (vector<Speaker>::iterator i = speakers->speakers().begin(); i != speakers->speakers().end(); ++i) {
159 
160  Speaker& s (*i);
161  CartesianVector c (s.coords());
162 
163  cart_to_gtk (c);
164 
165  /* We have already moved our plotting origin to x_origin, y_origin,
166  so compensate for that.
167  */
168  c.x -= x_origin;
169  c.y -= y_origin;
170 
171  x = (gint) floor (c.x);
172  y = (gint) floor (c.y);
173 
174  /* XXX need to shift circles so that they are centered on the circle */
175 
176  cairo_arc (cr, x, y, arc_radius, 0, 2.0 * M_PI);
177  if (selected_index == n) {
178  cairo_set_source_rgb (cr, 0.8, 0.8, 0.2);
179  } else {
180  cairo_set_source_rgb (cr, 0.8, 0.2, 0.1);
181  }
182  cairo_close_path (cr);
183  cairo_fill (cr);
184 
185  cairo_move_to (cr, x + 6, y + 6);
186 
187  char buf[256];
188  if (n == selected_index) {
189  snprintf (buf, sizeof (buf), "%d:%d", n+1, (int) lrint (s.angles().azi));
190  } else {
191  snprintf (buf, sizeof (buf), "%d", n + 1);
192  }
193  cairo_show_text (cr, buf);
194  ++n;
195  }
196 
197  cairo_destroy (cr);
198 
199  return true;
200 
201 }
202 
203 void
205 {
206  /* "c" uses a coordinate space that is:
207 
208  center = 0.0
209  dimension = 2.0 * 2.0
210  so max values along each axis are -1..+1
211 
212  GTK uses a coordinate space that is:
213 
214  top left = 0.0
215  dimension = width * height
216  so max values along each axis are 0,width and
217  0,height
218  */
219 
220  c.x = (width / 2) * (c.x + 1) + x_origin;
221  c.y = (height / 2) * (1 - c.y) + y_origin;
222 
223  /* XXX z-axis not handled - 2D for now */
224 }
225 
226 void
228 {
229  c.x = ((c.x - x_origin) / (width / 2.0)) - 1.0;
230  c.y = -(((c.y - y_origin) / (height / 2.0)) - 1.0);
231 
232  /* XXX z-axis not handled - 2D for now */
233 }
234 
235 void
236 SpeakerDialog::clamp_to_circle (double& x, double& y)
237 {
238  double azi, ele;
239  double z = 0.0;
240  double l;
241 
242  PBD::cartesian_to_spherical (x, y, z, azi, ele, l);
243  PBD::spherical_to_cartesian (azi, ele, 1.0, x, y, z);
244 }
245 
246 void
247 SpeakerDialog::darea_size_allocate (Gtk::Allocation& alloc)
248 {
249  width = alloc.get_width();
250  height = alloc.get_height();
251 
252  /* The allocation will (should) be rectangualar, but make the basic
253  drawing square; space to the right of the square is for over-hanging
254  text labels.
255  */
256  width = height;
257 
258  if (height > 100) {
259  width -= 20;
260  height -= 20;
261  }
262 
263  /* Put the x origin to the left of the rectangular allocation */
264  x_origin = (alloc.get_width() - width) / 3;
265  y_origin = (alloc.get_height() - height) / 2;
266 }
267 
268 bool
270 {
271  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
272  if (!speakers) {
273  return false;
274  }
275 
276  GdkModifierType state;
277 
278  if (ev->type == GDK_2BUTTON_PRESS && ev->button == 1) {
279  return false;
280  }
281 
282  drag_index = -1;
283 
284  switch (ev->button) {
285  case 1:
286  case 2:
287  {
288  int const index = find_closest_object (ev->x, ev->y);
289  set_selected (index);
290 
291  drag_index = index;
292  int const drag_x = (int) floor (ev->x);
293  int const drag_y = (int) floor (ev->y);
294  state = (GdkModifierType) ev->state;
295 
296  if (drag_index >= 0) {
297  CartesianVector c;
298  speakers->speakers()[drag_index].angles().cartesian (c);
299  cart_to_gtk (c);
300  drag_offset_x = drag_x - x_origin - c.x;
301  drag_offset_y = drag_y - y_origin - c.y;
302  }
303 
304  return handle_motion (drag_x, drag_y, state);
305  break;
306  }
307 
308  default:
309  break;
310  }
311 
312  return false;
313 }
314 
315 bool
317 {
318  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
319  if (!speakers) {
320  return false;
321  }
322 
323  gint x, y;
324  GdkModifierType state;
325  bool ret = false;
326 
327  switch (ev->button) {
328  case 1:
329  x = (int) floor (ev->x);
330  y = (int) floor (ev->y);
331  state = (GdkModifierType) ev->state;
332 
333  if (Keyboard::modifier_state_contains (state, Keyboard::TertiaryModifier)) {
334 
335  for (vector<Speaker>::iterator i = speakers->speakers().begin(); i != speakers->speakers().end(); ++i) {
336  /* XXX DO SOMETHING TO SET SPEAKER BACK TO "normal" */
337  }
338 
339  queue_draw ();
340  ret = true;
341 
342  } else {
343  ret = handle_motion (x, y, state);
344  }
345 
346  break;
347 
348  case 2:
349  x = (int) floor (ev->x);
350  y = (int) floor (ev->y);
351  state = (GdkModifierType) ev->state;
352 
353  ret = handle_motion (x, y, state);
354  break;
355 
356  case 3:
357  break;
358 
359  }
360 
361  drag_index = -1;
362 
363  return ret;
364 }
365 
366 int
368 {
369  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
370  if (!speakers) {
371  return -1;
372  }
373 
374  float distance;
375  float best_distance = FLT_MAX;
376  int n = 0;
377  int which = -1;
378 
379  for (vector<Speaker>::iterator i = speakers->speakers().begin(); i != speakers->speakers().end(); ++i, ++n) {
380 
381  Speaker& candidate (*i);
382  CartesianVector c;
383 
384  candidate.angles().cartesian (c);
385  cart_to_gtk (c);
386 
387  distance = sqrt ((c.x - x) * (c.x - x) +
388  (c.y - y) * (c.y - y));
389 
390 
391  if (distance < best_distance) {
392  best_distance = distance;
393  which = n;
394  }
395  }
396 
397  if (best_distance > 20) { // arbitrary
398  return -1;
399  }
400 
401  return which;
402 }
403 
404 bool
406 {
407  gint x, y;
408  GdkModifierType state;
409 
410  if (ev->is_hint) {
411  gdk_window_get_pointer (ev->window, &x, &y, &state);
412  } else {
413  x = (int) floor (ev->x);
414  y = (int) floor (ev->y);
415  state = (GdkModifierType) ev->state;
416  }
417 
418  return handle_motion (x, y, state);
419 }
420 
421 bool
422 SpeakerDialog::handle_motion (gint evx, gint evy, GdkModifierType state)
423 {
424  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
425  if (!speakers) {
426  return false;
427  }
428 
429  if (drag_index < 0) {
430  return false;
431  }
432 
433  if ((state & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK)) == 0) {
434  return false;
435  }
436 
437  /* correct event coordinates to have their origin at the corner of our graphic
438  rather than the corner of our allocation */
439 
440  double obx = evx - x_origin;
441  double oby = evy - y_origin;
442 
443  /* and compensate for any distance between the mouse pointer and the centre
444  of the object being dragged */
445 
446  obx -= drag_offset_x;
447  oby -= drag_offset_y;
448 
449  if (state & GDK_BUTTON1_MASK && !(state & GDK_BUTTON2_MASK)) {
450  CartesianVector c;
451  bool need_move = false;
452  Speaker& moving (speakers->speakers()[drag_index]);
453 
454  moving.angles().cartesian (c);
455  cart_to_gtk (c);
456 
457  if (obx != c.x || oby != c.y) {
458  need_move = true;
459  }
460 
461  if (need_move) {
462  CartesianVector cp (obx, oby, 0.0);
463 
464  /* canonicalize position */
465 
466  gtk_to_cart (cp);
467 
468  /* position actual signal on circle */
469 
470  clamp_to_circle (cp.x, cp.y);
471 
472  /* generate an angular representation and set drag target (GUI) position */
473 
474  AngularVector a;
475 
476  cp.angular (a);
477 
478  moving.move (a);
479 
480  queue_draw ();
481  }
482  }
483 
484  return true;
485 }
486 
487 void
489 {
490  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
491  if (!speakers) {
492  return;
493  }
494 
495  speakers->add_speaker (PBD::AngularVector (0, 0, 0));
496  queue_draw ();
497 }
498 
499 void
501 {
502  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
503  if (!speakers) {
504  return;
505  }
506 
507  if (i == selected_index) {
508  return;
509  }
510 
511  selected_index = i;
512  queue_draw ();
513 
515 
516  azimuth_spinner.set_sensitive (selected_index != -1);
517  remove_speaker_button.set_sensitive (selected_index != -1);
518 
519  if (selected_index != -1) {
520  azimuth_adjustment.set_value (speakers->speakers()[selected_index].angles().azi);
521  speakers->speakers()[selected_index].PositionChanged.connect (
523  boost::bind (&SpeakerDialog::speaker_position_changed, this),
524  gui_context ()
525  );
526  }
527 }
528 
529 void
531 {
532  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
533  if (!speakers) {
534  return;
535  }
536 
537  assert (selected_index != -1);
538 
539  if (ignore_azimuth_change) {
540  return;
541  }
542 
544  speakers->move_speaker (speakers->speakers()[selected_index].id, PBD::AngularVector (azimuth_adjustment.get_value (), 0, 0));
546 
547  queue_draw ();
548 }
549 
550 void
552 {
553  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
554  if (!speakers) {
555  return;
556  }
557 
558  assert (selected_index != -1);
559 
561  return;
562  }
563 
564  ignore_azimuth_change = true;
565  azimuth_adjustment.set_value (speakers->speakers()[selected_index].angles().azi);
566  ignore_azimuth_change = false;
567 
568  queue_draw ();
569 }
570 
571 void
573 {
574  boost::shared_ptr<Speakers> speakers = _speakers.lock ();
575  if (!speakers) {
576  return;
577  }
578 
579  assert (selected_index != -1);
580 
581  speakers->remove_speaker (speakers->speakers()[selected_index].id);
582  set_selected (-1);
583 
584  queue_draw ();
585 }
int height
height of the circle
int y_origin
y origin of our stuff within the drawing area
void set_speakers(boost::shared_ptr< ARDOUR::Speakers >)
Gtk::AspectFrame aspect_frame
Gtk::HBox hbox
Definition: ardour_ui.h:130
double drag_offset_y
void darea_size_allocate(Gtk::Allocation &alloc)
void azimuth_changed()
bool darea_motion_notify_event(GdkEventMotion *ev)
Definition: Beats.hpp:239
void cartesian(CartesianVector &c) const
Definition: cartesian.h:95
bool darea_button_release_event(GdkEventButton *ev)
Gtk::VBox side_vbox
void speaker_position_changed()
bool darea_button_press_event(GdkEventButton *ev)
#define _(Text)
Definition: i18n.h:11
virtual void remove_speaker(int id)
Definition: speakers.cc:126
bool ignore_azimuth_change
Gtk::Adjustment azimuth_adjustment
const PBD::CartesianVector & coords() const
Definition: speaker.h:37
void angular(AngularVector &a) const
Definition: cartesian.h:100
void gtk_to_cart(PBD::CartesianVector &c) const
int find_closest_object(gdouble x, gdouble y)
virtual int add_speaker(const PBD::AngularVector &)
Definition: speakers.cc:113
Gtk::DrawingArea darea
Gtk::Button add_speaker_button
Definition: amp.h:29
int width
width of the circle
#define gui_context()
Definition: gui_thread.h:36
boost::shared_ptr< ARDOUR::Speakers > get_speakers() const
void set_selected(int)
std::vector< Speaker > & speakers()
Definition: speakers.h:50
boost::weak_ptr< ARDOUR::Speakers > _speakers
double drag_offset_x
LIBPBD_API void cartesian_to_spherical(double x, double y, double z, double &azi, double &ele, double &len)
Definition: cartesian.cc:42
bool darea_expose_event(GdkEventExpose *)
Gtk::SpinButton azimuth_spinner
int x_origin
x origin of our stuff within the drawing area
Gtk::Button remove_speaker_button
Definition: debug.h:30
void cart_to_gtk(PBD::CartesianVector &c) const
virtual void move_speaker(int id, const PBD::AngularVector &new_position)
Definition: speakers.cc:138
LIBPBD_API void spherical_to_cartesian(double azi, double ele, double len, double &x, double &y, double &z)
Definition: cartesian.cc:26
int selected_index
index of any selected speaker, or -1
#define MISSING_INVALIDATOR
Definition: event_loop.h:86
bool ignore_speaker_position_change
PBD::ScopedConnection selected_speaker_connection
const PBD::AngularVector & angles() const
Definition: speaker.h:38
void clamp_to_circle(double &x, double &y)
bool handle_motion(gint evx, gint evy, GdkModifierType state)