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 while (pucks
.size() > n_inputs
) {
101 pucks
.erase (pucks
.begin());
104 for (Targets::iterator x
= pucks
.begin(); x
!= pucks
.end(); ++x
) {
105 (*x
).second
->visible
= false;
113 pucks
[0]->set_text ("");
117 pucks
[0]->set_text ("R");
118 pucks
[1]->set_text ("L");
122 for (uint32_t i
= 0; i
< n_inputs
; ++i
) {
124 snprintf (buf
, sizeof (buf
), "%" PRIu32
, i
);
125 pucks
[i
]->set_text (buf
);
130 for (uint32_t i
= existing_pucks
; i
< n_inputs
; ++i
) {
132 panner
->streampanner (i
).get_position (x
, y
);
133 pucks
[i
]->x
.set_value (x
);
134 pucks
[i
]->y
.set_value (y
);
135 pucks
[i
]->visible
= true;
138 /* add all outputs */
140 while (targets
.size() < panner
->nouts()) {
141 add_target (0.0, 0.0);
144 while (targets
.size() > panner
->nouts()) {
145 targets
.erase (targets
.begin());
148 for (Targets::iterator x
= targets
.begin(); x
!= targets
.end(); ++x
) {
149 (*x
).second
->visible
= false;
152 for (uint32_t n
= 0; n
< panner
->nouts(); ++n
) {
155 snprintf (buf
, sizeof (buf
), "%d", n
+1);
156 targets
[n
]->set_text (buf
);
157 targets
[n
]->x
.set_value (panner
->output(n
).x
);
158 targets
[n
]->y
.set_value (panner
->output(n
).y
);
159 targets
[n
]->visible
= true;
162 allow_x_motion (true);
163 allow_y_motion (true);
164 allow_target_motion (true);
170 Panner2d::azimuth (uint32_t which
)
172 assert (which
< pucks
.size());
173 return pucks
[which
]->azimuth
;
177 Panner2d::on_size_allocate (Gtk::Allocation
& alloc
)
179 width
= alloc
.get_width();
180 height
= alloc
.get_height();
187 DrawingArea::on_size_allocate (alloc
);
191 Panner2d::add_puck (const char* text
, float x
, float y
)
193 Target
* puck
= new Target (x
, y
, text
);
195 pair
<int,Target
*> newpair
;
196 newpair
.first
= pucks
.size();
197 newpair
.second
= puck
;
199 pucks
.insert (newpair
);
200 puck
->visible
= true;
206 Panner2d::add_target (float x
, float y
)
208 Target
*target
= new Target (x
, y
, "");
210 pair
<int,Target
*> newpair
;
211 newpair
.first
= targets
.size();
212 newpair
.second
= target
;
214 targets
.insert (newpair
);
215 target
->visible
= true;
218 return newpair
.first
;
222 Panner2d::drop_targets ()
224 for (Targets::iterator i
= targets
.begin(); i
!= targets
.end(); ) {
226 Targets::iterator tmp
;
241 Panner2d::remove_target (int which
)
243 Targets::iterator i
= targets
.find (which
);
245 if (i
!= targets
.end()) {
253 Panner2d::handle_state_change ()
255 ENSURE_GUI_THREAD (*this, &Panner2d::handle_state_change
)
261 Panner2d::handle_position_change ()
264 ENSURE_GUI_THREAD (*this, &Panner2d::handle_position_change
)
266 for (n
= 0; n
< pucks
.size(); ++n
) {
268 panner
->streampanner(n
).get_position (x
, y
);
269 pucks
[n
]->x
.set_value (x
);
270 pucks
[n
]->y
.set_value (y
);
273 for (n
= 0; n
< targets
.size(); ++n
) {
274 targets
[n
]->x
.set_value (panner
->output(n
).x
);
275 targets
[n
]->y
.set_value (panner
->output(n
).y
);
282 Panner2d::move_target (int which
, float x
, float y
)
284 Targets::iterator i
= targets
.find (which
);
291 if (i
!= targets
.end()) {
293 target
->x
.set_value (x
);
294 target
->y
.set_value (y
);
301 Panner2d::move_puck (int which
, float x
, float y
)
303 Targets::iterator i
= pucks
.find (which
);
306 if (i
!= pucks
.end()) {
308 target
->x
.set_value (x
);
309 target
->y
.set_value (y
);
316 Panner2d::show_puck (int which
)
318 Targets::iterator i
= pucks
.find (which
);
320 if (i
!= pucks
.end()) {
321 Target
* puck
= i
->second
;
322 if (!puck
->visible
) {
323 puck
->visible
= true;
330 Panner2d::hide_puck (int which
)
332 Targets::iterator i
= pucks
.find (which
);
334 if (i
!= pucks
.end()) {
335 Target
* puck
= i
->second
;
336 if (!puck
->visible
) {
337 puck
->visible
= false;
344 Panner2d::show_target (int which
)
346 Targets::iterator i
= targets
.find (which
);
347 if (i
!= targets
.end()) {
348 if (!i
->second
->visible
) {
349 i
->second
->visible
= true;
356 Panner2d::hide_target (int which
)
358 Targets::iterator i
= targets
.find (which
);
359 if (i
!= targets
.end()) {
360 if (i
->second
->visible
) {
361 i
->second
->visible
= false;
368 Panner2d::find_closest_object (gdouble x
, gdouble y
, int& which
, bool& is_puck
) const
375 float best_distance
= FLT_MAX
;
384 for (Targets::const_iterator i
= targets
.begin(); i
!= targets
.end(); ++i
, ++which
) {
385 candidate
= i
->second
;
387 cx
= candidate
->x
.get_value();
388 cy
= candidate
->y
.get_value();
390 distance
= sqrt ((cx
- efx
) * (cx
- efx
) +
391 (cy
- efy
) * (cy
- efy
));
393 if (distance
< best_distance
) {
395 best_distance
= distance
;
399 for (Targets::const_iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
, ++pwhich
) {
400 candidate
= i
->second
;
402 cx
= candidate
->x
.get_value();
403 cy
= candidate
->y
.get_value();
405 distance
= sqrt ((cx
- efx
) * (cx
- efx
) +
406 (cy
- efy
) * (cy
- efy
));
408 if (distance
< best_distance
) {
410 best_distance
= distance
;
420 Panner2d::on_motion_notify_event (GdkEventMotion
*ev
)
423 GdkModifierType state
;
426 gdk_window_get_pointer (ev
->window
, &x
, &y
, &state
);
428 x
= (int) floor (ev
->x
);
429 y
= (int) floor (ev
->y
);
430 state
= (GdkModifierType
) ev
->state
;
433 return handle_motion (x
, y
, state
);
436 Panner2d::on_expose_event (GdkEventExpose
*event
)
442 cr
= gdk_cairo_create (get_window()->gobj());
444 cairo_set_line_width (cr
, 1.0);
446 cairo_rectangle (cr
, event
->area
.x
, event
->area
.y
, event
->area
.width
, event
->area
.height
);
447 if (!panner
->bypassed()) {
448 cairo_set_source_rgba (cr
, 0.1, 0.1, 0.1, 1.0);
450 cairo_set_source_rgba (cr
, 0.1, 0.1, 0.1, 0.2);
452 cairo_fill_preserve (cr
);
456 cairo_translate (cr
, 10.0, 10.0);
459 cairo_set_source_rgb (cr
, 0.0, 0.1, 0.7);
460 cairo_move_to (cr
, 0.5, height
/2.0+0.5);
461 cairo_line_to (cr
, height
+0.5, height
/2+0.5);
464 cairo_move_to (cr
, height
/2+0.5, 0.5);
465 cairo_line_to (cr
, height
/2+0.5, height
+0.5);
468 cairo_arc (cr
, height
/2, height
/2, height
/2, 0, 2.0 * M_PI
);
471 if (!panner
->bypassed()) {
474 cairo_select_font_face (cr
, "sans", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
477 cairo_set_font_size (cr
, 10);
480 cairo_set_font_size (cr
, 16);
484 for (Targets::iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
) {
486 Target
* puck
= i
->second
;
491 fx
= min (puck
->x
.get_value(), 1.0);
492 fx
= max (fx
, -1.0f
);
493 x
= (gint
) floor (width
* fx
- 4);
495 fy
= min (puck
->y
.get_value(), 1.0);
496 fy
= max (fy
, -1.0f
);
497 y
= (gint
) floor (height
* fy
- 4);
499 cairo_arc (cr
, x
, y
, arc_radius
, 0, 2.0 * M_PI
);
500 cairo_set_source_rgb (cr
, 0.8, 0.2, 0.1);
501 cairo_close_path (cr
);
506 if (height
> 100.0f
) {
513 cairo_translate (cr
, x
, y
);
514 cairo_rotate (cr
, puck
->azimuth
.get_value());
516 /* horizontal left-to-right line (rotation will rotate it, duh) */
522 cairo_set_line_width (cr
, 4.0);
523 cairo_move_to (cr
, 0.0, 0.0);
524 cairo_line_to (cr
, endx
, endy
);
529 cairo_move_to (cr
, endx
- 10.0, endy
+ 10.0);
530 cairo_line_to (cr
, endx
, endy
);
531 cairo_line_to (cr
, endx
- 10.0, endy
- 10.0);
532 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_ROUND
);
538 cairo_move_to (cr
, x
+ 6, y
+ 6);
539 cairo_show_text (cr
, puck
->text
);
543 /* redraw any visible targets */
547 for (Targets::iterator i
= targets
.begin(); i
!= targets
.end(); ++i
) {
548 Target
*target
= i
->second
;
552 if (target
->visible
) {
554 fx
= min (target
->x
.get_value(), 1.0);
555 fx
= max (fx
, -1.0f
);
556 x
= (gint
) floor (width
* fx
);
558 fy
= min (target
->y
.get_value(), 1.0);
559 fy
= max (fy
, -1.0f
);
560 y
= (gint
) floor (height
* fy
);
562 snprintf (buf
, sizeof (buf
), "%d", n
);
564 cairo_set_source_rgb (cr
, 0.0, 0.8, 0.1);
565 cairo_rectangle (cr
, x
-2, y
-2, 4, 4);
567 cairo_move_to (cr
, x
+6, y
+6);
568 cairo_show_text (cr
, buf
);
579 Panner2d::on_button_press_event (GdkEventButton
*ev
)
581 GdkModifierType state
;
583 if (ev
->type
== GDK_2BUTTON_PRESS
&& ev
->button
== 1) {
587 switch (ev
->button
) {
590 drag_target
= find_closest_object (ev
->x
, ev
->y
, drag_index
, drag_is_puck
);
591 drag_x
= (int) floor (ev
->x
);
592 drag_y
= (int) floor (ev
->y
);
593 state
= (GdkModifierType
) ev
->state
;
595 return handle_motion (drag_x
, drag_y
, state
);
606 Panner2d::on_button_release_event (GdkEventButton
*ev
)
609 GdkModifierType state
;
612 switch (ev
->button
) {
614 x
= (int) floor (ev
->x
);
615 y
= (int) floor (ev
->y
);
616 state
= (GdkModifierType
) ev
->state
;
618 if (drag_is_puck
&& (Keyboard::modifier_state_contains (state
, Keyboard::TertiaryModifier
))) {
621 for (Targets::iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
) {
622 //Target* puck = i->second;
624 /* XXX DO SOMETHING TO SET PUCK BACK TO "normal" */
632 ret
= handle_motion (x
, y
, state
);
639 x
= (int) floor (ev
->x
);
640 y
= (int) floor (ev
->y
);
641 state
= (GdkModifierType
) ev
->state
;
643 if (drag_is_puck
&& (Keyboard::modifier_state_contains (state
, Keyboard::TertiaryModifier
))) {
647 ret
= handle_motion (x
, y
, state
);
662 Panner2d::handle_motion (gint evx
, gint evy
, GdkModifierType state
)
664 if (drag_target
== 0) {
668 if ((state
& (GDK_BUTTON1_MASK
|GDK_BUTTON2_MASK
)) == 0) {
673 bool need_move
= false;
675 if (!drag_is_puck
&& !allow_target
) {
676 cerr
<< "dip = " << drag_is_puck
<< " at = " << allow_target
<< endl
;
680 if (state
& GDK_BUTTON1_MASK
&& !(state
& GDK_BUTTON2_MASK
)) {
682 if (allow_x
|| !drag_is_puck
) {
684 x
= min (evx
, width
- 1);
686 new_x
= (float) x
/ (width
- 1);
687 if (new_x
!= drag_target
->x
.get_value()) {
688 drag_target
->x
.set_value (new_x
);
693 if (allow_y
|| drag_is_puck
) {
695 y
= min (evy
, height
- 1);
697 new_y
= (float) y
/ (height
- 1);
698 if (new_y
!= drag_target
->y
.get_value()) {
699 drag_target
->y
.set_value (new_y
);
708 panner
->streampanner(drag_index
).set_position (
709 drag_target
->x
.get_value(), drag_target
->y
.get_value(), false);
713 TargetMoved (drag_index
);
720 } else if ((state
& GDK_BUTTON2_MASK
) && !(state
& GDK_BUTTON1_MASK
)) {
726 int xdelta
= drag_x
- evx
;
727 int ydelta
= drag_x
- evy
;
729 drag_target
->azimuth
.set_value (drag_target
->azimuth
.get_value() + (2 * M_PI
) * ((float)ydelta
)/height
* ((float) -xdelta
)/height
);
737 Panner2d::toggle_bypass ()
739 panner
->set_bypassed (!panner
->bypassed());
743 Panner2d::allow_x_motion (bool yn
)
749 Panner2d::allow_target_motion (bool yn
)
755 Panner2d::allow_y_motion (bool yn
)
760 Panner2dWindow::Panner2dWindow (boost::shared_ptr
<Panner
> p
, int32_t h
, uint32_t inputs
)
762 , reset_button (_("Reset"))
763 , bypass_button (_("Bypass"))
764 , mute_button (_("Mute"))
766 widget
.set_name ("MixerPanZone");
768 set_title (_("Panner"));
769 widget
.set_size_request (h
, h
);
771 button_box
.set_spacing (6);
772 button_box
.pack_start (reset_button
, false, false);
773 button_box
.pack_start (bypass_button
, false, false);
774 button_box
.pack_start (mute_button
, false, false);
776 spinner_box
.set_spacing (6);
777 left_side
.set_spacing (6);
779 left_side
.pack_start (button_box
, false, false);
780 left_side
.pack_start (spinner_box
, false, false);
782 reset_button
.show ();
783 bypass_button
.show ();
789 hpacker
.set_spacing (6);
790 hpacker
.set_border_width (12);
791 hpacker
.pack_start (widget
, false, false);
792 hpacker
.pack_start (left_side
, false, false);
801 Panner2dWindow::reset (uint32_t n_inputs
)
803 widget
.reset (n_inputs
);
805 while (spinners
.size() < n_inputs
) {
806 spinners
.push_back (new Gtk::SpinButton (widget
.azimuth (spinners
.size())));
807 spinner_box
.pack_start (*spinners
.back(), false, false);
808 spinners
.back()->set_digits (4);
809 spinners
.back()->show ();
812 while (spinners
.size() > n_inputs
) {
813 spinner_box
.remove (*spinners
.back());
814 delete spinners
.back();
815 spinners
.erase (--spinners
.end());