2 Copyright (C) 2002-2009 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.
21 #include <gtkmm/scrolledwindow.h>
22 #include <gtkmm/adjustment.h>
23 #include <gtkmm/label.h>
24 #include <gtkmm/menu.h>
25 #include <gtkmm/menushell.h>
26 #include <gtkmm/menu_elems.h>
27 #include <gtkmm/window.h>
28 #include "ardour/bundle.h"
29 #include "ardour/types.h"
30 #include "ardour/session.h"
31 #include "ardour/route.h"
32 #include "ardour/audioengine.h"
33 #include "port_matrix.h"
34 #include "port_matrix_body.h"
35 #include "port_matrix_component.h"
37 #include "gui_thread.h"
42 using namespace ARDOUR
;
44 /** PortMatrix constructor.
45 * @param session Our session.
46 * @param type Port type that we are handling.
48 PortMatrix::PortMatrix (Window
* parent
, Session
* session
, DataType type
)
53 , _arrangement (TOP_TO_RIGHT
)
56 , _min_height_divisor (1)
57 , _show_only_bundles (false)
58 , _inhibit_toggle_show_only_bundles (false)
59 , _ignore_notebook_page_selected (false)
61 set_session (session
);
63 _body
= new PortMatrixBody (this);
64 _body
->DimensionsChanged
.connect (sigc::mem_fun (*this, &PortMatrix::body_dimensions_changed
));
66 _vbox
.pack_start (_vspacer
, false, false);
67 _vbox
.pack_start (_vnotebook
, false, false);
68 _vbox
.pack_start (_vlabel
, true, true);
69 _hbox
.pack_start (_hspacer
, false, false);
70 _hbox
.pack_start (_hnotebook
, false, false);
71 _hbox
.pack_start (_hlabel
, true, true);
73 _vnotebook
.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected
));
74 _vnotebook
.property_tab_border() = 4;
75 _vnotebook
.set_name (X_("PortMatrixLabel"));
76 _hnotebook
.signal_switch_page().connect (sigc::mem_fun (*this, &PortMatrix::notebook_page_selected
));
77 _hnotebook
.property_tab_border() = 4;
78 _hnotebook
.set_name (X_("PortMatrixLabel"));
80 for (int i
= 0; i
< 2; ++i
) {
81 _ports
[i
].set_type (type
);
84 _vlabel
.set_use_markup ();
85 _vlabel
.set_alignment (1, 1);
86 _vlabel
.set_padding (4, 16);
87 _vlabel
.set_name (X_("PortMatrixLabel"));
88 _hlabel
.set_use_markup ();
89 _hlabel
.set_alignment (1, 0.5);
90 _hlabel
.set_padding (16, 4);
91 _hlabel
.set_name (X_("PortMatrixLabel"));
106 PortMatrix::~PortMatrix ()
112 /** Perform initial and once-only setup. This must be called by
113 * subclasses after they have set up _ports[] to at least some
114 * reasonable extent. Two-part initialisation is necessary because
115 * setting up _ports is largely done by virtual functions in
122 select_arrangement ();
124 /* Signal handling is kind of split into three parts:
126 * 1. When _ports[] changes, we call setup(). This essentially sorts out our visual
127 * representation of the information in _ports[].
129 * 2. When certain other things change, we need to get our subclass to clear and
130 * re-fill _ports[], which in turn causes appropriate signals to be raised to
131 * hook into part (1).
133 * 3. Assorted other signals.
137 /* Part 1: the basic _ports[] change -> reset visuals */
139 for (int i
= 0; i
< 2; ++i
) {
140 /* watch for the content of _ports[] changing */
141 _ports
[i
].Changed
.connect (_changed_connections
, invalidator (*this), boost::bind (&PortMatrix::setup
, this), gui_context());
143 /* and for bundles in _ports[] changing */
144 _ports
[i
].BundleChanged
.connect (_bundle_changed_connections
, invalidator (*this), boost::bind (&PortMatrix::setup
, this), gui_context());
147 /* Part 2: notice when things have changed that require our subclass to clear and refill _ports[] */
149 /* watch for routes being added or removed */
150 _session
->RouteAdded
.connect (_session_connections
, invalidator (*this), boost::bind (&PortMatrix::routes_changed
, this), gui_context());
152 /* and also bundles */
153 _session
->BundleAdded
.connect (_session_connections
, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports
, this), gui_context());
156 _session
->engine().PortRegisteredOrUnregistered
.connect (_session_connections
, invalidator (*this), boost::bind (&PortMatrix::setup_global_ports
, this), gui_context());
159 /* Part 3: other stuff */
161 _session
->engine().PortConnectedOrDisconnected
.connect (_session_connections
, invalidator (*this), boost::bind (&PortMatrix::port_connected_or_disconnected
, this), gui_context ());
163 _hscroll
.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::hscroll_changed
));
164 _vscroll
.signal_value_changed().connect (sigc::mem_fun (*this, &PortMatrix::vscroll_changed
));
166 reconnect_to_routes ();
171 /** Disconnect from and reconnect to routes' signals that we need to watch for things that affect the matrix */
173 PortMatrix::reconnect_to_routes ()
175 _route_connections
.drop_connections ();
177 boost::shared_ptr
<RouteList
> routes
= _session
->get_routes ();
178 for (RouteList::iterator i
= routes
->begin(); i
!= routes
->end(); ++i
) {
179 (*i
)->processors_changed
.connect (_route_connections
, invalidator (*this), ui_bind (&PortMatrix::route_processors_changed
, this, _1
), gui_context());
184 PortMatrix::route_processors_changed (RouteProcessorChange c
)
186 if (c
.type
== RouteProcessorChange::MeterPointChange
) {
187 /* this change has no impact on the port matrix */
191 setup_global_ports ();
194 /** A route has been added to or removed from the session */
196 PortMatrix::routes_changed ()
198 reconnect_to_routes ();
199 setup_global_ports ();
202 /** Set up everything that depends on the content of _ports[] */
206 /* this needs to be done first, as the visible_ports() method uses the
207 notebook state to decide which ports are being shown */
217 PortMatrix::set_type (DataType t
)
220 _ports
[0].set_type (_type
);
221 _ports
[1].set_type (_type
);
227 PortMatrix::hscroll_changed ()
229 _body
->set_xoffset (_hscroll
.get_adjustment()->get_value());
233 PortMatrix::vscroll_changed ()
235 _body
->set_yoffset (_vscroll
.get_adjustment()->get_value());
239 PortMatrix::setup_scrollbars ()
241 Adjustment
* a
= _hscroll
.get_adjustment ();
243 a
->set_upper (_body
->full_scroll_width());
244 a
->set_page_size (_body
->alloc_scroll_width());
245 a
->set_step_increment (32);
246 a
->set_page_increment (128);
248 a
= _vscroll
.get_adjustment ();
250 a
->set_upper (_body
->full_scroll_height());
251 a
->set_page_size (_body
->alloc_scroll_height());
252 a
->set_step_increment (32);
253 a
->set_page_increment (128);
256 /** Disassociate all of our ports from each other */
258 PortMatrix::disassociate_all ()
260 PortGroup::BundleList a
= _ports
[0].bundles ();
261 PortGroup::BundleList b
= _ports
[1].bundles ();
263 for (PortGroup::BundleList::iterator i
= a
.begin(); i
!= a
.end(); ++i
) {
264 for (uint32_t j
= 0; j
< (*i
)->bundle
->nchannels(); ++j
) {
265 for (PortGroup::BundleList::iterator k
= b
.begin(); k
!= b
.end(); ++k
) {
266 for (uint32_t l
= 0; l
< (*k
)->bundle
->nchannels(); ++l
) {
268 BundleChannel c
[2] = {
269 BundleChannel ((*i
)->bundle
, j
),
270 BundleChannel ((*k
)->bundle
, l
)
273 if (get_state (c
) == PortMatrixNode::ASSOCIATED
) {
274 set_state (c
, false);
282 _body
->rebuild_and_draw_grid ();
285 /* Decide how to arrange the components of the matrix */
287 PortMatrix::select_arrangement ()
289 uint32_t const N
[2] = {
290 _ports
[0].total_channels (),
291 _ports
[1].total_channels ()
294 /* The list with the most channels goes on left or right, so that the most channel
295 names are printed horizontally and hence more readable. However we also
296 maintain notional `signal flow' vaguely from left to right. Subclasses
297 should choose where to put ports based on signal flowing from _ports[0]
304 _arrangement
= LEFT_TO_BOTTOM
;
305 _vlabel
.set_label (_("<b>Sources</b>"));
306 _hlabel
.set_label (_("<b>Destinations</b>"));
307 _vlabel
.set_angle (90);
309 attach (*_body
, 1, 2, 0, 1);
310 attach (_vscroll
, 2, 3, 0, 1, SHRINK
);
311 attach (_hscroll
, 1, 2, 2, 3, FILL
| EXPAND
, SHRINK
);
312 attach (_vbox
, 0, 1, 0, 1, SHRINK
);
313 attach (_hbox
, 1, 2, 1, 2, FILL
| EXPAND
, SHRINK
);
315 set_col_spacing (0, 4);
316 set_row_spacing (0, 4);
322 _arrangement
= TOP_TO_RIGHT
;
323 _hlabel
.set_label (_("<b>Sources</b>"));
324 _vlabel
.set_label (_("<b>Destinations</b>"));
325 _vlabel
.set_angle (-90);
327 attach (*_body
, 0, 1, 1, 2);
328 attach (_vscroll
, 2, 3, 1, 2, SHRINK
);
329 attach (_hscroll
, 0, 1, 2, 3, FILL
| EXPAND
, SHRINK
);
330 attach (_vbox
, 1, 2, 1, 2, SHRINK
);
331 attach (_hbox
, 0, 1, 0, 1, FILL
| EXPAND
, SHRINK
);
333 set_col_spacing (1, 4);
334 set_row_spacing (1, 4);
338 /** @return columns list */
339 PortGroupList
const *
340 PortMatrix::columns () const
342 return &_ports
[_column_index
];
345 boost::shared_ptr
<const PortGroup
>
346 PortMatrix::visible_columns () const
348 return visible_ports (_column_index
);
351 /* @return rows list */
352 PortGroupList
const *
353 PortMatrix::rows () const
355 return &_ports
[_row_index
];
358 boost::shared_ptr
<const PortGroup
>
359 PortMatrix::visible_rows () const
361 return visible_ports (_row_index
);
365 PortMatrix::popup_menu (BundleChannel column
, BundleChannel row
, uint32_t t
)
367 using namespace Menu_Helpers
;
372 _menu
->set_name ("ArdourContextMenu");
374 MenuList
& items
= _menu
->items ();
377 bc
[_column_index
] = column
;
378 bc
[_row_index
] = row
;
381 bool need_separator
= false;
383 for (int dim
= 0; dim
< 2; ++dim
) {
385 if (bc
[dim
].bundle
) {
387 Menu
* m
= manage (new Menu
);
388 MenuList
& sub
= m
->items ();
390 boost::weak_ptr
<Bundle
> w (bc
[dim
].bundle
);
392 bool can_add_or_rename
= false;
394 if (can_add_channel (bc
[dim
].bundle
)) {
395 snprintf (buf
, sizeof (buf
), _("Add %s"), channel_noun().c_str());
396 sub
.push_back (MenuElem (buf
, sigc::bind (sigc::mem_fun (*this, &PortMatrix::add_channel_proxy
), w
)));
397 can_add_or_rename
= true;
401 if (can_rename_channels (bc
[dim
].bundle
)) {
403 buf
, sizeof (buf
), _("Rename '%s'..."),
404 escape_underscores (bc
[dim
].bundle
->channel_name (bc
[dim
].channel
)).c_str()
409 sigc::bind (sigc::mem_fun (*this, &PortMatrix::rename_channel_proxy
), w
, bc
[dim
].channel
)
412 can_add_or_rename
= true;
415 if (can_add_or_rename
) {
416 sub
.push_back (SeparatorElem ());
419 if (can_remove_channels (bc
[dim
].bundle
)) {
420 if (bc
[dim
].channel
!= -1) {
421 add_remove_option (sub
, w
, bc
[dim
].channel
);
424 snprintf (buf
, sizeof (buf
), _("Remove all"));
426 MenuElem (buf
, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_all_channels
), w
))
429 for (uint32_t i
= 0; i
< bc
[dim
].bundle
->nchannels(); ++i
) {
430 add_remove_option (sub
, w
, i
);
435 if (_show_only_bundles
|| bc
[dim
].bundle
->nchannels() <= 1) {
436 snprintf (buf
, sizeof (buf
), _("%s all"), disassociation_verb().c_str());
438 MenuElem (buf
, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel
), w
, bc
[dim
].channel
, dim
))
443 if (bc
[dim
].channel
!= -1) {
444 add_disassociate_option (sub
, w
, dim
, bc
[dim
].channel
);
446 snprintf (buf
, sizeof (buf
), _("%s all"), disassociation_verb().c_str());
448 MenuElem (buf
, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_bundle
), w
, dim
))
451 for (uint32_t i
= 0; i
< bc
[dim
].bundle
->nchannels(); ++i
) {
452 add_disassociate_option (sub
, w
, dim
, i
);
457 items
.push_back (MenuElem (escape_underscores (bc
[dim
].bundle
->name()).c_str(), *m
));
458 need_separator
= true;
463 if (need_separator
) {
464 items
.push_back (SeparatorElem ());
467 items
.push_back (MenuElem (_("Rescan"), sigc::mem_fun (*this, &PortMatrix::setup_all_ports
)));
468 items
.push_back (CheckMenuElem (_("Show individual ports"), sigc::mem_fun (*this, &PortMatrix::toggle_show_only_bundles
)));
469 CheckMenuItem
* i
= dynamic_cast<CheckMenuItem
*> (&items
.back());
470 _inhibit_toggle_show_only_bundles
= true;
471 i
->set_active (!_show_only_bundles
);
472 _inhibit_toggle_show_only_bundles
= false;
478 PortMatrix::remove_channel_proxy (boost::weak_ptr
<Bundle
> b
, uint32_t c
)
480 boost::shared_ptr
<Bundle
> sb
= b
.lock ();
485 remove_channel (BundleChannel (sb
, c
));
490 PortMatrix::rename_channel_proxy (boost::weak_ptr
<Bundle
> b
, uint32_t c
)
492 boost::shared_ptr
<Bundle
> sb
= b
.lock ();
497 rename_channel (BundleChannel (sb
, c
));
501 PortMatrix::disassociate_all_on_bundle (boost::weak_ptr
<Bundle
> bundle
, int dim
)
503 boost::shared_ptr
<Bundle
> sb
= bundle
.lock ();
508 for (uint32_t i
= 0; i
< sb
->nchannels(); ++i
) {
509 disassociate_all_on_channel (bundle
, i
, dim
);
514 PortMatrix::disassociate_all_on_channel (boost::weak_ptr
<Bundle
> bundle
, uint32_t channel
, int dim
)
516 boost::shared_ptr
<Bundle
> sb
= bundle
.lock ();
521 PortGroup::BundleList a
= _ports
[1-dim
].bundles ();
523 for (PortGroup::BundleList::iterator i
= a
.begin(); i
!= a
.end(); ++i
) {
524 for (uint32_t j
= 0; j
< (*i
)->bundle
->nchannels(); ++j
) {
527 c
[dim
] = BundleChannel (sb
, channel
);
528 c
[1-dim
] = BundleChannel ((*i
)->bundle
, j
);
530 if (get_state (c
) == PortMatrixNode::ASSOCIATED
) {
531 set_state (c
, false);
536 _body
->rebuild_and_draw_grid ();
540 PortMatrix::setup_global_ports ()
542 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_global_ports
)
544 for (int i
= 0; i
< 2; ++i
) {
545 if (list_is_global (i
)) {
552 PortMatrix::setup_all_ports ()
554 if (_session
->deletion_in_progress()) {
558 ENSURE_GUI_THREAD (*this, &PortMatrix::setup_all_ports
)
565 PortMatrix::toggle_show_only_bundles ()
567 if (_inhibit_toggle_show_only_bundles
) {
571 _show_only_bundles
= !_show_only_bundles
;
576 pair
<uint32_t, uint32_t>
577 PortMatrix::max_size () const
579 pair
<uint32_t, uint32_t> m
= _body
->max_size ();
581 m
.first
+= _vscroll
.get_width ();
582 m
.second
+= _hscroll
.get_height ();
588 PortMatrix::on_scroll_event (GdkEventScroll
* ev
)
590 double const h
= _hscroll
.get_value ();
591 double const v
= _vscroll
.get_value ();
593 switch (ev
->direction
) {
595 _vscroll
.set_value (v
- PortMatrixComponent::grid_spacing ());
597 case GDK_SCROLL_DOWN
:
598 _vscroll
.set_value (v
+ PortMatrixComponent::grid_spacing ());
600 case GDK_SCROLL_LEFT
:
601 _hscroll
.set_value (h
- PortMatrixComponent::grid_spacing ());
603 case GDK_SCROLL_RIGHT
:
604 _hscroll
.set_value (h
+ PortMatrixComponent::grid_spacing ());
611 boost::shared_ptr
<IO
>
612 PortMatrix::io_from_bundle (boost::shared_ptr
<Bundle
> b
) const
614 boost::shared_ptr
<IO
> io
= _ports
[0].io_from_bundle (b
);
616 io
= _ports
[1].io_from_bundle (b
);
623 PortMatrix::can_add_channel (boost::shared_ptr
<Bundle
> b
) const
625 return io_from_bundle (b
);
629 PortMatrix::add_channel (boost::shared_ptr
<Bundle
> b
)
631 boost::shared_ptr
<IO
> io
= io_from_bundle (b
);
634 io
->add_port ("", this, _type
);
639 PortMatrix::can_remove_channels (boost::shared_ptr
<Bundle
> b
) const
641 return io_from_bundle (b
);
645 PortMatrix::remove_channel (ARDOUR::BundleChannel b
)
647 boost::shared_ptr
<IO
> io
= io_from_bundle (b
.bundle
);
650 Port
* p
= io
->nth (b
.channel
);
652 io
->remove_port (p
, this);
658 PortMatrix::remove_all_channels (boost::weak_ptr
<Bundle
> w
)
660 boost::shared_ptr
<Bundle
> b
= w
.lock ();
665 for (uint32_t i
= 0; i
< b
->nchannels(); ++i
) {
666 remove_channel (ARDOUR::BundleChannel (b
, i
));
671 PortMatrix::add_channel_proxy (boost::weak_ptr
<Bundle
> w
)
673 boost::shared_ptr
<Bundle
> b
= w
.lock ();
682 PortMatrix::setup_notebooks ()
684 int const h_current_page
= _hnotebook
.get_current_page ();
685 int const v_current_page
= _vnotebook
.get_current_page ();
687 /* for some reason best known to GTK, erroneous switch_page signals seem to be generated
688 when adding or removing pages to or from notebooks, so ignore them */
690 _ignore_notebook_page_selected
= true;
692 remove_notebook_pages (_hnotebook
);
693 remove_notebook_pages (_vnotebook
);
695 for (PortGroupList::List::const_iterator i
= _ports
[_row_index
].begin(); i
!= _ports
[_row_index
].end(); ++i
) {
696 HBox
* dummy
= manage (new HBox
);
698 Label
* label
= manage (new Label ((*i
)->name
));
699 label
->set_angle (_arrangement
== LEFT_TO_BOTTOM
? 90 : -90);
701 _vnotebook
.prepend_page (*dummy
, *label
);
704 for (PortGroupList::List::const_iterator i
= _ports
[_column_index
].begin(); i
!= _ports
[_column_index
].end(); ++i
) {
705 HBox
* dummy
= manage (new HBox
);
707 _hnotebook
.append_page (*dummy
, (*i
)->name
);
710 _ignore_notebook_page_selected
= false;
712 _vnotebook
.set_tab_pos (POS_LEFT
);
713 _hnotebook
.set_tab_pos (POS_TOP
);
715 if (h_current_page
!= -1 && _hnotebook
.get_n_pages() > h_current_page
) {
716 _hnotebook
.set_current_page (h_current_page
);
718 _hnotebook
.set_current_page (0);
721 if (v_current_page
!= -1 && _vnotebook
.get_n_pages() > v_current_page
) {
722 _vnotebook
.set_current_page (v_current_page
);
724 _vnotebook
.set_current_page (0);
727 if (_hnotebook
.get_n_pages() <= 1) {
733 if (_vnotebook
.get_n_pages() <= 1) {
741 PortMatrix::remove_notebook_pages (Notebook
& n
)
743 int const N
= n
.get_n_pages ();
745 for (int i
= 0; i
< N
; ++i
) {
751 PortMatrix::notebook_page_selected (GtkNotebookPage
*, guint
)
753 if (_ignore_notebook_page_selected
) {
763 PortMatrix::session_going_away ()
769 PortMatrix::body_dimensions_changed ()
771 _hspacer
.set_size_request (_body
->column_labels_border_x (), -1);
772 if (_arrangement
== TOP_TO_RIGHT
) {
773 _vspacer
.set_size_request (-1, _body
->column_labels_height ());
782 boost::shared_ptr
<const PortGroup
>
783 PortMatrix::visible_ports (int d
) const
785 PortGroupList
const & p
= _ports
[d
];
786 PortGroupList::List::const_iterator j
= p
.begin ();
789 if (d
== _row_index
) {
790 n
= p
.size() - _vnotebook
.get_current_page () - 1;
792 n
= _hnotebook
.get_current_page ();
796 while (i
!= int (n
) && j
!= p
.end ()) {
802 return boost::shared_ptr
<const PortGroup
> ();
809 PortMatrix::add_remove_option (Menu_Helpers::MenuList
& m
, boost::weak_ptr
<Bundle
> w
, int c
)
811 using namespace Menu_Helpers
;
813 boost::shared_ptr
<Bundle
> b
= w
.lock ();
819 snprintf (buf
, sizeof (buf
), _("Remove '%s'"), escape_underscores (b
->channel_name (c
)).c_str());
820 m
.push_back (MenuElem (buf
, sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_channel_proxy
), w
, c
)));
824 PortMatrix::add_disassociate_option (Menu_Helpers::MenuList
& m
, boost::weak_ptr
<Bundle
> w
, int d
, int c
)
826 using namespace Menu_Helpers
;
828 boost::shared_ptr
<Bundle
> b
= w
.lock ();
834 snprintf (buf
, sizeof (buf
), _("%s all from '%s'"), disassociation_verb().c_str(), escape_underscores (b
->channel_name (c
)).c_str());
835 m
.push_back (MenuElem (buf
, sigc::bind (sigc::mem_fun (*this, &PortMatrix::disassociate_all_on_channel
), w
, c
, d
)));
839 PortMatrix::port_connected_or_disconnected ()
841 _body
->rebuild_and_draw_grid ();
845 PortMatrix::channel_noun () const