2 Copyright (C) 2002 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/menu.h>
27 #include "pbd/error.h"
28 #include "ardour/panner.h"
29 #include <gtkmm2ext/gtk_ui.h>
33 #include "gui_thread.h"
39 using namespace ARDOUR
;
41 using Gtkmm2ext::Keyboard
;
43 Panner2d::Target::Target (float xa
, float ya
, const char *txt
)
44 : x (xa
, 0.0, 1.0, 0.01, 0.1)
45 , y (ya
, 0.0, 1.0, 0.01, 0.1)
46 , azimuth (M_PI
/2.0, 0.0, 2.0 * M_PI
, 0.1, 0.5)
47 , text (txt
? strdup (txt
) : 0)
49 azimuth
.set_value ((random() / (double) INT_MAX
) * (2.0 * M_PI
));
52 Panner2d::Target::~Target ()
60 Panner2d::Target::set_text (const char* txt
)
68 Panner2d::Panner2d (boost::shared_ptr
<Panner
> p
, int32_t h
)
69 : panner (p
), width (0), height (h
)
75 panner
->StateChanged
.connect (state_connection
, invalidator (*this), boost::bind (&Panner2d::handle_state_change
, this), gui_context());
76 panner
->Changed
.connect (change_connection
, invalidator (*this), boost::bind (&Panner2d::handle_position_change
, this), gui_context());
79 set_events (Gdk::BUTTON_PRESS_MASK
|Gdk::BUTTON_RELEASE_MASK
|Gdk::POINTER_MOTION_MASK
);
84 for (Targets::iterator i
= targets
.begin(); i
!= targets
.end(); ++i
) {
90 Panner2d::reset (uint32_t n_inputs
)
92 Targets::size_type existing_pucks
= pucks
.size();
96 while (pucks
.size() < n_inputs
) {
97 add_puck ("", 0.0, 0.0);
100 if (pucks
.size() > n_inputs
) {
101 for (uint32_t i
= pucks
.size(); i
< n_inputs
; ++i
) {
105 pucks
.resize (n_inputs
);
108 for (Targets::iterator x
= pucks
.begin(); x
!= pucks
.end(); ++x
) {
109 (*x
)->visible
= false;
117 pucks
[0]->set_text ("");
121 pucks
[0]->set_text ("R");
122 pucks
[1]->set_text ("L");
126 for (uint32_t i
= 0; i
< n_inputs
; ++i
) {
128 snprintf (buf
, sizeof (buf
), "%" PRIu32
, i
);
129 pucks
[i
]->set_text (buf
);
134 for (uint32_t i
= existing_pucks
; i
< n_inputs
; ++i
) {
136 panner
->streampanner (i
).get_position (x
, y
);
137 pucks
[i
]->x
.set_value (x
);
138 pucks
[i
]->y
.set_value (y
);
139 pucks
[i
]->visible
= true;
142 /* add all outputs */
144 while (targets
.size() < panner
->nouts()) {
145 add_target (0.0, 0.0);
148 if (targets
.size() > panner
->nouts()) {
149 for (uint32_t i
= panner
->nouts(); i
< targets
.size(); ++i
) {
153 targets
.resize (panner
->nouts ());
156 for (Targets::iterator x
= targets
.begin(); x
!= targets
.end(); ++x
) {
157 (*x
)->visible
= false;
160 for (uint32_t n
= 0; n
< panner
->nouts(); ++n
) {
163 snprintf (buf
, sizeof (buf
), "%d", n
+1);
164 targets
[n
]->set_text (buf
);
165 targets
[n
]->x
.set_value (panner
->output(n
).x
);
166 targets
[n
]->y
.set_value (panner
->output(n
).y
);
167 targets
[n
]->visible
= true;
170 allow_x_motion (true);
171 allow_y_motion (true);
172 allow_target_motion (true);
178 Panner2d::azimuth (uint32_t which
)
180 assert (which
< pucks
.size());
181 return pucks
[which
]->azimuth
;
185 Panner2d::on_size_allocate (Gtk::Allocation
& alloc
)
187 width
= alloc
.get_width();
188 height
= alloc
.get_height();
195 DrawingArea::on_size_allocate (alloc
);
199 Panner2d::add_puck (const char* text
, float x
, float y
)
201 Target
* puck
= new Target (x
, y
, text
);
202 pucks
.push_back (puck
);
203 puck
->visible
= true;
209 Panner2d::add_target (float x
, float y
)
211 Target
* target
= new Target (x
, y
, "");
212 targets
.push_back (target
);
213 target
->visible
= true;
216 return targets
.size() - 1;
220 Panner2d::handle_state_change ()
222 ENSURE_GUI_THREAD (*this, &Panner2d::handle_state_change
)
228 Panner2d::handle_position_change ()
231 ENSURE_GUI_THREAD (*this, &Panner2d::handle_position_change
)
233 for (n
= 0; n
< pucks
.size(); ++n
) {
235 panner
->streampanner(n
).get_position (x
, y
);
236 pucks
[n
]->x
.set_value (x
);
237 pucks
[n
]->y
.set_value (y
);
240 for (n
= 0; n
< targets
.size(); ++n
) {
241 targets
[n
]->x
.set_value (panner
->output(n
).x
);
242 targets
[n
]->y
.set_value (panner
->output(n
).y
);
249 Panner2d::move_puck (int which
, float x
, float y
)
251 if (which
>= int (targets
.size())) {
255 targets
[which
]->x
.set_value (x
);
256 targets
[which
]->y
.set_value (y
);
261 Panner2d::find_closest_object (gdouble x
, gdouble y
, int& which
, bool& is_puck
) const
268 float best_distance
= FLT_MAX
;
277 for (Targets::const_iterator i
= targets
.begin(); i
!= targets
.end(); ++i
, ++which
) {
280 cx
= candidate
->x
.get_value();
281 cy
= candidate
->y
.get_value();
283 distance
= sqrt ((cx
- efx
) * (cx
- efx
) +
284 (cy
- efy
) * (cy
- efy
));
286 if (distance
< best_distance
) {
288 best_distance
= distance
;
292 for (Targets::const_iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
, ++pwhich
) {
295 cx
= candidate
->x
.get_value();
296 cy
= candidate
->y
.get_value();
298 distance
= sqrt ((cx
- efx
) * (cx
- efx
) +
299 (cy
- efy
) * (cy
- efy
));
301 if (distance
< best_distance
) {
303 best_distance
= distance
;
313 Panner2d::on_motion_notify_event (GdkEventMotion
*ev
)
316 GdkModifierType state
;
319 gdk_window_get_pointer (ev
->window
, &x
, &y
, &state
);
321 x
= (int) floor (ev
->x
);
322 y
= (int) floor (ev
->y
);
323 state
= (GdkModifierType
) ev
->state
;
326 return handle_motion (x
, y
, state
);
329 Panner2d::on_expose_event (GdkEventExpose
*event
)
335 cr
= gdk_cairo_create (get_window()->gobj());
337 cairo_set_line_width (cr
, 1.0);
339 cairo_rectangle (cr
, event
->area
.x
, event
->area
.y
, event
->area
.width
, event
->area
.height
);
340 if (!panner
->bypassed()) {
341 cairo_set_source_rgba (cr
, 0.1, 0.1, 0.1, 1.0);
343 cairo_set_source_rgba (cr
, 0.1, 0.1, 0.1, 0.2);
345 cairo_fill_preserve (cr
);
349 cairo_translate (cr
, 10.0, 10.0);
352 cairo_set_source_rgb (cr
, 0.0, 0.1, 0.7);
353 cairo_move_to (cr
, 0.5, height
/2.0+0.5);
354 cairo_line_to (cr
, height
+0.5, height
/2+0.5);
357 cairo_move_to (cr
, height
/2+0.5, 0.5);
358 cairo_line_to (cr
, height
/2+0.5, height
+0.5);
361 cairo_arc (cr
, height
/2, height
/2, height
/2, 0, 2.0 * M_PI
);
364 if (!panner
->bypassed()) {
367 cairo_select_font_face (cr
, "sans", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
370 cairo_set_font_size (cr
, 10);
373 cairo_set_font_size (cr
, 16);
377 for (Targets::iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
) {
384 fx
= min (puck
->x
.get_value(), 1.0);
385 fx
= max (fx
, -1.0f
);
386 x
= (gint
) floor (width
* fx
- 4);
388 fy
= min (puck
->y
.get_value(), 1.0);
389 fy
= max (fy
, -1.0f
);
390 y
= (gint
) floor (height
* fy
- 4);
392 cairo_arc (cr
, x
, y
, arc_radius
, 0, 2.0 * M_PI
);
393 cairo_set_source_rgb (cr
, 0.8, 0.2, 0.1);
394 cairo_close_path (cr
);
399 if (height
> 100.0f
) {
406 cairo_translate (cr
, x
, y
);
407 cairo_rotate (cr
, puck
->azimuth
.get_value());
409 /* horizontal left-to-right line (rotation will rotate it, duh) */
415 cairo_set_line_width (cr
, 4.0);
416 cairo_move_to (cr
, 0.0, 0.0);
417 cairo_line_to (cr
, endx
, endy
);
422 cairo_move_to (cr
, endx
- 10.0, endy
+ 10.0);
423 cairo_line_to (cr
, endx
, endy
);
424 cairo_line_to (cr
, endx
- 10.0, endy
- 10.0);
425 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_ROUND
);
431 cairo_move_to (cr
, x
+ 6, y
+ 6);
432 cairo_show_text (cr
, puck
->text
);
436 /* redraw any visible targets */
440 for (Targets::iterator i
= targets
.begin(); i
!= targets
.end(); ++i
) {
445 if (target
->visible
) {
447 fx
= min (target
->x
.get_value(), 1.0);
448 fx
= max (fx
, -1.0f
);
449 x
= (gint
) floor (width
* fx
);
451 fy
= min (target
->y
.get_value(), 1.0);
452 fy
= max (fy
, -1.0f
);
453 y
= (gint
) floor (height
* fy
);
455 snprintf (buf
, sizeof (buf
), "%d", n
);
457 cairo_set_source_rgb (cr
, 0.0, 0.8, 0.1);
458 cairo_rectangle (cr
, x
-2, y
-2, 4, 4);
460 cairo_move_to (cr
, x
+6, y
+6);
461 cairo_show_text (cr
, buf
);
472 Panner2d::on_button_press_event (GdkEventButton
*ev
)
474 GdkModifierType state
;
476 if (ev
->type
== GDK_2BUTTON_PRESS
&& ev
->button
== 1) {
480 switch (ev
->button
) {
483 drag_target
= find_closest_object (ev
->x
, ev
->y
, drag_index
, drag_is_puck
);
484 drag_x
= (int) floor (ev
->x
);
485 drag_y
= (int) floor (ev
->y
);
486 state
= (GdkModifierType
) ev
->state
;
488 return handle_motion (drag_x
, drag_y
, state
);
499 Panner2d::on_button_release_event (GdkEventButton
*ev
)
502 GdkModifierType state
;
505 switch (ev
->button
) {
507 x
= (int) floor (ev
->x
);
508 y
= (int) floor (ev
->y
);
509 state
= (GdkModifierType
) ev
->state
;
511 if (drag_is_puck
&& (Keyboard::modifier_state_contains (state
, Keyboard::TertiaryModifier
))) {
514 for (Targets::iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
) {
515 //Target* puck = i->second;
517 /* XXX DO SOMETHING TO SET PUCK BACK TO "normal" */
525 ret
= handle_motion (x
, y
, state
);
532 x
= (int) floor (ev
->x
);
533 y
= (int) floor (ev
->y
);
534 state
= (GdkModifierType
) ev
->state
;
536 if (drag_is_puck
&& (Keyboard::modifier_state_contains (state
, Keyboard::TertiaryModifier
))) {
540 ret
= handle_motion (x
, y
, state
);
555 Panner2d::handle_motion (gint evx
, gint evy
, GdkModifierType state
)
557 if (drag_target
== 0) {
561 if ((state
& (GDK_BUTTON1_MASK
|GDK_BUTTON2_MASK
)) == 0) {
566 bool need_move
= false;
568 if (!drag_is_puck
&& !allow_target
) {
569 cerr
<< "dip = " << drag_is_puck
<< " at = " << allow_target
<< endl
;
573 if (state
& GDK_BUTTON1_MASK
&& !(state
& GDK_BUTTON2_MASK
)) {
575 if (allow_x
|| !drag_is_puck
) {
577 x
= min (evx
, width
- 1);
579 new_x
= (float) x
/ (width
- 1);
580 if (new_x
!= drag_target
->x
.get_value()) {
581 drag_target
->x
.set_value (new_x
);
586 if (allow_y
|| drag_is_puck
) {
588 y
= min (evy
, height
- 1);
590 new_y
= (float) y
/ (height
- 1);
591 if (new_y
!= drag_target
->y
.get_value()) {
592 drag_target
->y
.set_value (new_y
);
601 panner
->streampanner(drag_index
).set_position (
602 drag_target
->x
.get_value(), drag_target
->y
.get_value(), false);
606 TargetMoved (drag_index
);
613 } else if ((state
& GDK_BUTTON2_MASK
) && !(state
& GDK_BUTTON1_MASK
)) {
619 int xdelta
= drag_x
- evx
;
620 int ydelta
= drag_x
- evy
;
622 drag_target
->azimuth
.set_value (drag_target
->azimuth
.get_value() + (2 * M_PI
) * ((float)ydelta
)/height
* ((float) -xdelta
)/height
);
630 Panner2d::toggle_bypass ()
632 panner
->set_bypassed (!panner
->bypassed());
636 Panner2d::allow_x_motion (bool yn
)
642 Panner2d::allow_target_motion (bool yn
)
648 Panner2d::allow_y_motion (bool yn
)
653 Panner2dWindow::Panner2dWindow (boost::shared_ptr
<Panner
> p
, int32_t h
, uint32_t inputs
)
655 , reset_button (_("Reset"))
656 , bypass_button (_("Bypass"))
657 , mute_button (_("Mute"))
659 widget
.set_name ("MixerPanZone");
661 set_title (_("Panner"));
662 widget
.set_size_request (h
, h
);
664 button_box
.set_spacing (6);
665 button_box
.pack_start (reset_button
, false, false);
666 button_box
.pack_start (bypass_button
, false, false);
667 button_box
.pack_start (mute_button
, false, false);
669 spinner_box
.set_spacing (6);
670 left_side
.set_spacing (6);
672 left_side
.pack_start (button_box
, false, false);
673 left_side
.pack_start (spinner_box
, false, false);
675 reset_button
.show ();
676 bypass_button
.show ();
682 hpacker
.set_spacing (6);
683 hpacker
.set_border_width (12);
684 hpacker
.pack_start (widget
, false, false);
685 hpacker
.pack_start (left_side
, false, false);
694 Panner2dWindow::reset (uint32_t n_inputs
)
696 widget
.reset (n_inputs
);
698 while (spinners
.size() < n_inputs
) {
699 spinners
.push_back (new Gtk::SpinButton (widget
.azimuth (spinners
.size())));
700 spinner_box
.pack_start (*spinners
.back(), false, false);
701 spinners
.back()->set_digits (4);
702 spinners
.back()->show ();
705 while (spinners
.size() > n_inputs
) {
706 spinner_box
.remove (*spinners
.back());
707 delete spinners
.back();
708 spinners
.erase (--spinners
.end());