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"
40 using namespace ARDOUR
;
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 (mem_fun(*this, &Panner2d::handle_state_change
));
76 panner
->Changed
.connect (mem_fun(*this, &Panner2d::handle_position_change
));
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 ("");
114 pucks
[0]->x
.set_value (0.0);
115 pucks
[0]->y
.set_value (0.5);
116 pucks
[0]->visible
= true;
120 pucks
[0]->set_text ("R");
121 pucks
[0]->visible
= true;
122 pucks
[1]->set_text ("L");
123 if (existing_pucks
< 2) {
124 pucks
[1]->x
.set_value (0.25f
);
125 pucks
[1]->y
.set_value (0.5f
);
127 pucks
[1]->visible
= true;
131 for (uint32_t i
= 0; i
< n_inputs
; ++i
) {
133 snprintf (buf
, sizeof (buf
), "%" PRIu32
, i
);
134 pucks
[i
]->set_text (buf
);
136 if (existing_pucks
< i
) {
138 panner
->streampanner (i
).get_position (x
, y
);
139 pucks
[i
]->x
.set_value (x
);
140 pucks
[i
]->y
.set_value (y
);
143 pucks
[i
]->visible
= true;
148 /* add all outputs */
150 while (targets
.size() < panner
->nouts()) {
151 add_target (0.0, 0.0);
154 while (targets
.size() > panner
->nouts()) {
155 targets
.erase (targets
.begin());
158 for (Targets::iterator x
= targets
.begin(); x
!= targets
.end(); ++x
) {
159 (*x
).second
->visible
= false;
162 for (uint32_t n
= 0; n
< panner
->nouts(); ++n
) {
165 snprintf (buf
, sizeof (buf
), "%d", n
+1);
166 targets
[n
]->set_text (buf
);
167 targets
[n
]->x
.set_value (panner
->output(n
).x
);
168 targets
[n
]->y
.set_value (panner
->output(n
).y
);
169 targets
[n
]->visible
= true;
172 allow_x_motion (true);
173 allow_y_motion (true);
174 allow_target_motion (true);
180 Panner2d::azimuth (uint32_t which
)
182 assert (which
< pucks
.size());
183 return pucks
[which
]->azimuth
;
187 Panner2d::on_size_allocate (Gtk::Allocation
& alloc
)
189 width
= alloc
.get_width();
190 height
= alloc
.get_height();
197 DrawingArea::on_size_allocate (alloc
);
201 Panner2d::add_puck (const char* text
, float x
, float y
)
203 Target
* puck
= new Target (x
, y
, text
);
205 pair
<int,Target
*> newpair
;
206 newpair
.first
= pucks
.size();
207 newpair
.second
= puck
;
209 pucks
.insert (newpair
);
210 puck
->visible
= true;
216 Panner2d::add_target (float x
, float y
)
218 Target
*target
= new Target (x
, y
, "");
220 pair
<int,Target
*> newpair
;
221 newpair
.first
= targets
.size();
222 newpair
.second
= target
;
224 targets
.insert (newpair
);
225 target
->visible
= true;
228 return newpair
.first
;
232 Panner2d::drop_targets ()
234 for (Targets::iterator i
= targets
.begin(); i
!= targets
.end(); ) {
236 Targets::iterator tmp
;
251 Panner2d::remove_target (int which
)
253 Targets::iterator i
= targets
.find (which
);
255 if (i
!= targets
.end()) {
263 Panner2d::handle_state_change ()
265 ENSURE_GUI_THREAD(mem_fun(*this, &Panner2d::handle_state_change
));
271 Panner2d::handle_position_change ()
274 ENSURE_GUI_THREAD(mem_fun(*this, &Panner2d::handle_position_change
));
276 for (n
= 0; n
< pucks
.size(); ++n
) {
278 panner
->streampanner(n
).get_position (x
, y
);
279 pucks
[n
]->x
.set_value (x
);
280 pucks
[n
]->y
.set_value (y
);
283 for (n
= 0; n
< targets
.size(); ++n
) {
284 targets
[n
]->x
.set_value (panner
->output(n
).x
);
285 targets
[n
]->y
.set_value (panner
->output(n
).y
);
292 Panner2d::move_target (int which
, float x
, float y
)
294 Targets::iterator i
= targets
.find (which
);
301 if (i
!= targets
.end()) {
303 target
->x
.set_value (x
);
304 target
->y
.set_value (y
);
311 Panner2d::move_puck (int which
, float x
, float y
)
313 Targets::iterator i
= pucks
.find (which
);
316 if (i
!= pucks
.end()) {
318 target
->x
.set_value (x
);
319 target
->y
.set_value (y
);
326 Panner2d::show_puck (int which
)
328 Targets::iterator i
= pucks
.find (which
);
330 if (i
!= pucks
.end()) {
331 Target
* puck
= i
->second
;
332 if (!puck
->visible
) {
333 puck
->visible
= true;
340 Panner2d::hide_puck (int which
)
342 Targets::iterator i
= pucks
.find (which
);
344 if (i
!= pucks
.end()) {
345 Target
* puck
= i
->second
;
346 if (!puck
->visible
) {
347 puck
->visible
= false;
354 Panner2d::show_target (int which
)
356 Targets::iterator i
= targets
.find (which
);
357 if (i
!= targets
.end()) {
358 if (!i
->second
->visible
) {
359 i
->second
->visible
= true;
366 Panner2d::hide_target (int which
)
368 Targets::iterator i
= targets
.find (which
);
369 if (i
!= targets
.end()) {
370 if (i
->second
->visible
) {
371 i
->second
->visible
= false;
378 Panner2d::find_closest_object (gdouble x
, gdouble y
, int& which
, bool& is_puck
) const
385 float best_distance
= FLT_MAX
;
394 for (Targets::const_iterator i
= targets
.begin(); i
!= targets
.end(); ++i
, ++which
) {
395 candidate
= i
->second
;
397 cx
= candidate
->x
.get_value();
398 cy
= candidate
->y
.get_value();
400 distance
= sqrt ((cx
- efx
) * (cx
- efx
) +
401 (cy
- efy
) * (cy
- efy
));
403 if (distance
< best_distance
) {
405 best_distance
= distance
;
409 for (Targets::const_iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
, ++pwhich
) {
410 candidate
= i
->second
;
412 cx
= candidate
->x
.get_value();
413 cy
= candidate
->y
.get_value();
415 distance
= sqrt ((cx
- efx
) * (cx
- efx
) +
416 (cy
- efy
) * (cy
- efy
));
418 if (distance
< best_distance
) {
420 best_distance
= distance
;
430 Panner2d::on_motion_notify_event (GdkEventMotion
*ev
)
433 GdkModifierType state
;
436 gdk_window_get_pointer (ev
->window
, &x
, &y
, &state
);
438 x
= (int) floor (ev
->x
);
439 y
= (int) floor (ev
->y
);
440 state
= (GdkModifierType
) ev
->state
;
443 return handle_motion (x
, y
, state
);
446 Panner2d::on_expose_event (GdkEventExpose
*event
)
452 cr
= gdk_cairo_create (get_window()->gobj());
454 cairo_set_line_width (cr
, 1.0);
456 cairo_rectangle (cr
, event
->area
.x
, event
->area
.y
, event
->area
.width
, event
->area
.height
);
457 if (!panner
->bypassed()) {
458 cairo_set_source_rgba (cr
, 0.1, 0.1, 0.1, 1.0);
460 cairo_set_source_rgba (cr
, 0.1, 0.1, 0.1, 0.2);
462 cairo_fill_preserve (cr
);
466 cairo_translate (cr
, 10.0, 10.0);
469 cairo_set_source_rgb (cr
, 0.0, 0.1, 0.7);
470 cairo_move_to (cr
, 0.5, height
/2.0+0.5);
471 cairo_line_to (cr
, height
+0.5, height
/2+0.5);
474 cairo_move_to (cr
, height
/2+0.5, 0.5);
475 cairo_line_to (cr
, height
/2+0.5, height
+0.5);
478 cairo_arc (cr
, height
/2, height
/2, height
/2, 0, 2.0 * M_PI
);
481 if (!panner
->bypassed()) {
484 cairo_select_font_face (cr
, "sans", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
487 cairo_set_font_size (cr
, 10);
490 cairo_set_font_size (cr
, 16);
494 for (Targets::iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
) {
496 Target
* puck
= i
->second
;
501 fx
= min (puck
->x
.get_value(), 1.0);
502 fx
= max (fx
, -1.0f
);
503 x
= (gint
) floor (width
* fx
- 4);
505 fy
= min (puck
->y
.get_value(), 1.0);
506 fy
= max (fy
, -1.0f
);
507 y
= (gint
) floor (height
* fy
- 4);
509 cairo_arc (cr
, x
, y
, arc_radius
, 0, 2.0 * M_PI
);
510 cairo_set_source_rgb (cr
, 0.8, 0.2, 0.1);
511 cairo_close_path (cr
);
516 if (height
> 100.0f
) {
523 cairo_translate (cr
, x
, y
);
524 cairo_rotate (cr
, puck
->azimuth
.get_value());
526 /* horizontal left-to-right line (rotation will rotate it, duh) */
532 cairo_set_line_width (cr
, 4.0);
533 cairo_move_to (cr
, 0.0, 0.0);
534 cairo_line_to (cr
, endx
, endy
);
539 cairo_move_to (cr
, endx
- 10.0, endy
+ 10.0);
540 cairo_line_to (cr
, endx
, endy
);
541 cairo_line_to (cr
, endx
- 10.0, endy
- 10.0);
542 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_ROUND
);
548 cairo_move_to (cr
, x
+ 6, y
+ 6);
549 cairo_show_text (cr
, puck
->text
);
553 /* redraw any visible targets */
557 for (Targets::iterator i
= targets
.begin(); i
!= targets
.end(); ++i
) {
558 Target
*target
= i
->second
;
562 if (target
->visible
) {
564 fx
= min (target
->x
.get_value(), 1.0);
565 fx
= max (fx
, -1.0f
);
566 x
= (gint
) floor (width
* fx
);
568 fy
= min (target
->y
.get_value(), 1.0);
569 fy
= max (fy
, -1.0f
);
570 y
= (gint
) floor (height
* fy
);
572 snprintf (buf
, sizeof (buf
), "%d", n
);
574 cairo_set_source_rgb (cr
, 0.0, 0.8, 0.1);
575 cairo_rectangle (cr
, x
-2, y
-2, 4, 4);
577 cairo_move_to (cr
, x
+6, y
+6);
578 cairo_show_text (cr
, buf
);
589 Panner2d::on_button_press_event (GdkEventButton
*ev
)
591 GdkModifierType state
;
593 if (ev
->type
== GDK_2BUTTON_PRESS
&& ev
->button
== 1) {
597 switch (ev
->button
) {
600 drag_target
= find_closest_object (ev
->x
, ev
->y
, drag_index
, drag_is_puck
);
601 drag_x
= (int) floor (ev
->x
);
602 drag_y
= (int) floor (ev
->y
);
603 state
= (GdkModifierType
) ev
->state
;
605 return handle_motion (drag_x
, drag_y
, state
);
616 Panner2d::on_button_release_event (GdkEventButton
*ev
)
619 GdkModifierType state
;
622 switch (ev
->button
) {
624 x
= (int) floor (ev
->x
);
625 y
= (int) floor (ev
->y
);
626 state
= (GdkModifierType
) ev
->state
;
628 if (drag_is_puck
&& (Keyboard::modifier_state_contains (state
, Keyboard::TertiaryModifier
))) {
631 for (Targets::iterator i
= pucks
.begin(); i
!= pucks
.end(); ++i
) {
632 //Target* puck = i->second;
634 /* XXX DO SOMETHING TO SET PUCK BACK TO "normal" */
642 ret
= handle_motion (x
, y
, state
);
649 x
= (int) floor (ev
->x
);
650 y
= (int) floor (ev
->y
);
651 state
= (GdkModifierType
) ev
->state
;
653 if (drag_is_puck
&& (Keyboard::modifier_state_contains (state
, Keyboard::TertiaryModifier
))) {
657 ret
= handle_motion (x
, y
, state
);
672 Panner2d::handle_motion (gint evx
, gint evy
, GdkModifierType state
)
674 if (drag_target
== 0) {
678 if ((state
& (GDK_BUTTON1_MASK
|GDK_BUTTON2_MASK
)) == 0) {
683 bool need_move
= false;
685 if (!drag_is_puck
&& !allow_target
) {
686 cerr
<< "dip = " << drag_is_puck
<< " at = " << allow_target
<< endl
;
690 if (state
& GDK_BUTTON1_MASK
&& !(state
& GDK_BUTTON2_MASK
)) {
692 if (allow_x
|| !drag_is_puck
) {
694 x
= min (evx
, width
- 1);
696 new_x
= (float) x
/ (width
- 1);
697 if (new_x
!= drag_target
->x
.get_value()) {
698 drag_target
->x
.set_value (new_x
);
703 if (allow_y
|| drag_is_puck
) {
705 y
= min (evy
, height
- 1);
707 new_y
= (float) y
/ (height
- 1);
708 if (new_y
!= drag_target
->y
.get_value()) {
709 drag_target
->y
.set_value (new_y
);
718 panner
->streampanner(drag_index
).set_position (
719 drag_target
->x
.get_value(), drag_target
->y
.get_value(), false);
723 TargetMoved (drag_index
);
730 } else if ((state
& GDK_BUTTON2_MASK
) && !(state
& GDK_BUTTON1_MASK
)) {
736 int xdelta
= drag_x
- evx
;
737 int ydelta
= drag_x
- evy
;
739 drag_target
->azimuth
.set_value (drag_target
->azimuth
.get_value() + (2 * M_PI
) * ((float)ydelta
)/height
* ((float) -xdelta
)/height
);
747 Panner2d::toggle_bypass ()
749 panner
->set_bypassed (!panner
->bypassed());
753 Panner2d::allow_x_motion (bool yn
)
759 Panner2d::allow_target_motion (bool yn
)
765 Panner2d::allow_y_motion (bool yn
)
770 Panner2dWindow::Panner2dWindow (boost::shared_ptr
<Panner
> p
, int32_t h
, uint32_t inputs
)
772 , reset_button (_("Reset"))
773 , bypass_button (_("Bypass"))
774 , mute_button (_("Mute"))
776 widget
.set_name ("MixerPanZone");
778 set_title (_("Panner"));
779 widget
.set_size_request (h
, h
);
781 button_box
.set_spacing (6);
782 button_box
.pack_start (reset_button
, false, false);
783 button_box
.pack_start (bypass_button
, false, false);
784 button_box
.pack_start (mute_button
, false, false);
786 spinner_box
.set_spacing (6);
787 left_side
.set_spacing (6);
789 left_side
.pack_start (button_box
, false, false);
790 left_side
.pack_start (spinner_box
, false, false);
792 reset_button
.show ();
793 bypass_button
.show ();
799 hpacker
.set_spacing (6);
800 hpacker
.set_border_width (12);
801 hpacker
.pack_start (widget
, false, false);
802 hpacker
.pack_start (left_side
, false, false);
811 Panner2dWindow::reset (uint32_t n_inputs
)
813 widget
.reset (n_inputs
);
815 while (spinners
.size() < n_inputs
) {
816 spinners
.push_back (new Gtk::SpinButton (widget
.azimuth (spinners
.size())));
817 spinner_box
.pack_start (*spinners
.back(), false, false);
818 spinners
.back()->set_digits (4);
819 spinners
.back()->show ();
822 while (spinners
.size() > n_inputs
) {
823 spinner_box
.remove (*spinners
.back());
824 delete spinners
.back();
825 spinners
.erase (--spinners
.end());