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 <cairo/cairo.h>
22 #include "ardour/bundle.h"
23 #include "ardour/types.h"
24 #include "port_matrix_grid.h"
25 #include "port_matrix.h"
26 #include "port_matrix_body.h"
30 using Gtkmm2ext::Keyboard;
32 PortMatrixGrid::PortMatrixGrid (PortMatrix* m, PortMatrixBody* b)
33 : PortMatrixComponent (m, b),
42 PortMatrixGrid::compute_dimensions ()
44 if (_matrix->visible_columns()) {
45 _width = group_size (_matrix->visible_columns()) * grid_spacing ();
50 if (_matrix->visible_rows()) {
51 _height = group_size (_matrix->visible_rows()) * grid_spacing ();
59 PortMatrixGrid::render (cairo_t* cr)
61 set_source_rgb (cr, background_colour());
62 cairo_rectangle (cr, 0, 0, _width, _height);
65 PortGroup::BundleList const & row_bundles = _matrix->visible_rows()->bundles();
66 PortGroup::BundleList const & column_bundles = _matrix->visible_columns()->bundles();
70 /* VERTICAL GRID LINES */
72 set_source_rgb (cr, grid_colour());
75 for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
77 cairo_set_line_width (cr, thick_grid_line_width());
78 cairo_move_to (cr, x, 0);
79 cairo_line_to (cr, x, _height);
82 if (!_matrix->show_only_bundles()) {
83 cairo_set_line_width (cr, thin_grid_line_width());
84 for (uint32_t j = 0; j < _matrix->count_of_our_type_min_1 ((*i)->bundle->nchannels()); ++j) {
86 cairo_move_to (cr, x, 0);
87 cairo_line_to (cr, x, _height);
100 if (_matrix->show_only_bundles ()) {
101 cairo_move_to (cr, x, 0);
102 cairo_line_to (cr, x, _height);
108 /* HORIZONTAL GRID LINES */
111 for (PortGroup::BundleList::const_iterator i = row_bundles.begin(); i != row_bundles.end(); ++i) {
113 cairo_set_line_width (cr, thick_grid_line_width());
114 cairo_move_to (cr, 0, y);
115 cairo_line_to (cr, _width, y);
118 if (!_matrix->show_only_bundles()) {
119 cairo_set_line_width (cr, thin_grid_line_width());
120 for (uint32_t j = 0; j < _matrix->count_of_our_type_min_1 ((*i)->bundle->nchannels()); ++j) {
121 y += grid_spacing ();
122 cairo_move_to (cr, 0, y);
123 cairo_line_to (cr, _width, y);
129 y += grid_spacing ();
136 if (_matrix->show_only_bundles ()) {
137 cairo_move_to (cr, 0, y);
138 cairo_line_to (cr, _width, y);
142 /* ASSOCIATION INDICATORS and NON-CONNECTABLE INDICATORS */
144 /* we draw a grey square in a matrix box if the two ports that intersect at that box
145 cannot be connected because they are of different types (MIDI vs. audio)
151 if (_matrix->show_only_bundles()) {
153 for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
156 for (PortGroup::BundleList::const_iterator j = row_bundles.begin(); j != row_bundles.end(); ++j) {
158 PortMatrixNode::State s = _matrix->get_association (PortMatrixNode (
159 ARDOUR::BundleChannel ((*j)->bundle, 0),
160 ARDOUR::BundleChannel ((*i)->bundle, 0)
163 case PortMatrixNode::ASSOCIATED:
164 draw_association_indicator (cr, bx, by);
166 case PortMatrixNode::PARTIAL:
167 draw_association_indicator (cr, bx, by, 0.5);
173 by += grid_spacing();
176 bx += grid_spacing();
182 for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
185 for (PortGroup::BundleList::const_iterator j = row_bundles.begin(); j != row_bundles.end(); ++j) {
188 for (uint32_t k = 0; k < _matrix->count_of_our_type ((*i)->bundle->nchannels()); ++k) {
191 for (uint32_t l = 0; l < _matrix->count_of_our_type ((*j)->bundle->nchannels()); ++l) {
193 ARDOUR::BundleChannel c[2];
194 c[_matrix->column_index()] = ARDOUR::BundleChannel ((*i)->bundle, k);
195 c[_matrix->row_index()] = ARDOUR::BundleChannel ((*j)->bundle, l);
197 if (c[0].bundle->channel_type (c[0].channel) != c[1].bundle->channel_type (c[1].channel)) {
198 /* these two channels are of different types */
199 draw_non_connectable_indicator (cr, x, y);
201 /* these two channels might be associated */
202 PortMatrixNode::State const s = _matrix->get_state (c);
205 case PortMatrixNode::ASSOCIATED:
206 draw_association_indicator (cr, x, y);
209 case PortMatrixNode::NOT_ASSOCIATED:
220 if (_matrix->count_of_our_type ((*j)->bundle->nchannels()) == 0) {
221 /* the *j bundle has no channels of our type, so it will have a dummy
222 one which needs to be marked non-connectable.
224 draw_non_connectable_indicator (cr, x, y);
230 if (_matrix->count_of_our_type ((*i)->bundle->nchannels()) == 0) {
231 /* draw non-connectable indicators for the case where the *i bundle
232 has no channels of our type (and hence has 1 dummy channel)
235 for (uint32_t l = 0; l < _matrix->count_of_our_type_min_1 ((*j)->bundle->nchannels()); ++l) {
236 draw_non_connectable_indicator (cr, x, y);
237 y += grid_spacing ();
241 by += _matrix->count_of_our_type_min_1 ((*j)->bundle->nchannels()) * grid_spacing();
244 bx += _matrix->count_of_our_type_min_1 ((*i)->bundle->nchannels()) * grid_spacing();
250 PortMatrixGrid::draw_association_indicator (cairo_t* cr, uint32_t x, uint32_t y, double p)
252 set_source_rgba (cr, association_colour(), 0.5);
256 x + grid_spacing() / 2,
257 y + grid_spacing() / 2,
258 (grid_spacing() - (2 * connection_indicator_pad())) / 2,
267 PortMatrixGrid::draw_empty_square (cairo_t* cr, uint32_t x, uint32_t y)
269 set_source_rgb (cr, background_colour());
272 x + thick_grid_line_width(),
273 y + thick_grid_line_width(),
274 grid_spacing() - 2 * thick_grid_line_width(),
275 grid_spacing() - 2 * thick_grid_line_width()
280 /** Draw a square to indicate that two channels in a matrix cannot be associated
284 PortMatrixGrid::draw_non_connectable_indicator (cairo_t* cr, uint32_t x, uint32_t y)
286 set_source_rgb (cr, non_connectable_colour ());
289 x + thick_grid_line_width(),
290 y + thick_grid_line_width(),
291 grid_spacing() - 2 * thick_grid_line_width(),
292 grid_spacing() - 2 * thick_grid_line_width()
298 PortMatrixGrid::position_to_node (double x, double y) const
300 return PortMatrixNode (
301 position_to_channel (y, x, _matrix->visible_rows()),
302 position_to_channel (x, y, _matrix->visible_columns())
307 PortMatrixGrid::button_press (double x, double y, int b, uint32_t t, guint)
309 ARDOUR::BundleChannel const px = position_to_channel (x, y, _matrix->visible_columns());
310 ARDOUR::BundleChannel const py = position_to_channel (y, x, _matrix->visible_rows());
315 _drag_valid = (px.bundle && py.bundle);
318 _drag_start_x = x / grid_spacing ();
319 _drag_start_y = y / grid_spacing ();
323 _matrix->popup_menu (px, py, t);
329 PortMatrixGrid::set_association (PortMatrixNode node, bool s)
331 if (node.row.bundle->nchannels().n_total() == 0 || node.column.bundle->nchannels().n_total() == 0) {
332 /* One of the bundles has no channels, which means that it has none of the appropriate type,
333 and is only being displayed to look pretty. So we don't need to do anything.
338 if (_matrix->show_only_bundles()) {
340 for (uint32_t i = 0; i < node.column.bundle->nchannels().n_total(); ++i) {
341 for (uint32_t j = 0; j < node.row.bundle->nchannels().n_total(); ++j) {
343 if (!_matrix->should_show (node.column.bundle->channel_type(i)) || !_matrix->should_show (node.row.bundle->channel_type(j))) {
347 ARDOUR::BundleChannel c[2];
348 c[_matrix->column_index()] = ARDOUR::BundleChannel (node.column.bundle, i);
349 c[_matrix->row_index()] = ARDOUR::BundleChannel (node.row.bundle, j);
350 _matrix->set_state (c, s && (i == j));
356 if (node.row.bundle && node.column.bundle) {
358 ARDOUR::BundleChannel c[2];
359 c[_matrix->row_index()] = node.row;
360 c[_matrix->column_index()] = node.column;
361 _matrix->set_state (c, s);
367 PortMatrixGrid::button_release (double x, double y, int b, uint32_t /*t*/, guint s)
373 if (_dragging && _moved) {
376 list<PortMatrixNode> const p = nodes_on_line (_drag_start_x, _drag_start_y, _drag_x, _drag_y);
379 PortMatrixNode::State const s = _matrix->get_association (p.front());
380 for (list<PortMatrixNode>::const_iterator i = p.begin(); i != p.end(); ++i) {
381 set_association (*i, toggle_state (s));
388 if (Keyboard::modifier_state_equals (s, Keyboard::PrimaryModifier)) {
389 /* associate/disassociate things diagonally down and right until we run out */
390 PortMatrixNode::State s = (PortMatrixNode::State) 0;
392 PortMatrixNode const n = position_to_node (x, y);
393 if (n.row.bundle && n.column.bundle) {
394 if (s == (PortMatrixNode::State) 0) {
395 s = _matrix->get_association (n);
397 set_association (n, toggle_state (s));
401 x += grid_spacing ();
402 y += grid_spacing ();
407 PortMatrixNode const n = position_to_node (x, y);
408 if (n.row.bundle && n.column.bundle) {
409 PortMatrixNode::State const s = _matrix->get_association (n);
410 set_association (n, toggle_state (s));
418 _body->queue_draw ();
426 PortMatrixGrid::draw_extra (cairo_t* cr)
428 set_source_rgba (cr, mouseover_line_colour(), 0.3);
429 cairo_set_line_width (cr, mouseover_line_width());
431 list<PortMatrixNode> const m = _body->mouseover ();
433 for (list<PortMatrixNode>::const_iterator i = m.begin(); i != m.end(); ++i) {
435 double const x = component_to_parent_x (channel_to_position (i->column, _matrix->visible_columns()) * grid_spacing()) + grid_spacing() / 2;
436 double const y = component_to_parent_y (channel_to_position (i->row, _matrix->visible_rows()) * grid_spacing()) + grid_spacing() / 2;
438 if (PortMatrix::bundle_with_channels (i->row.bundle) && PortMatrix::bundle_with_channels (i->column.bundle)) {
440 cairo_move_to (cr, x, y);
441 if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
442 cairo_line_to (cr, component_to_parent_x (0), y);
443 } else if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
444 cairo_line_to (cr, _parent_rectangle.get_x() + _parent_rectangle.get_width(), y);
448 cairo_move_to (cr, x, y);
449 if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
450 cairo_line_to (cr, x, _parent_rectangle.get_y() + _parent_rectangle.get_height());
451 } else if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
452 cairo_line_to (cr, x, component_to_parent_y (0));
458 if (_dragging && _drag_valid && _moved) {
460 list<PortMatrixNode> const p = nodes_on_line (_drag_start_x, _drag_start_y, _drag_x, _drag_y);
464 bool const s = toggle_state (_matrix->get_association (p.front()));
466 for (list<PortMatrixNode>::const_iterator i = p.begin(); i != p.end(); ++i) {
468 draw_association_indicator (
470 component_to_parent_x (channel_to_position (i->column, _matrix->visible_columns()) * grid_spacing ()),
471 component_to_parent_y (channel_to_position (i->row, _matrix->visible_rows()) * grid_spacing ())
476 component_to_parent_x (channel_to_position (i->column, _matrix->visible_columns()) * grid_spacing ()),
477 component_to_parent_y (channel_to_position (i->row, _matrix->visible_rows()) * grid_spacing ())
483 set_source_rgba (cr, association_colour (), 0.3);
487 component_to_parent_x (_drag_start_x * grid_spacing() + grid_spacing() / 2),
488 component_to_parent_y (_drag_start_y * grid_spacing() + grid_spacing() / 2)
493 component_to_parent_x (_drag_x * grid_spacing() + grid_spacing() / 2),
494 component_to_parent_y (_drag_y * grid_spacing() + grid_spacing() / 2)
503 PortMatrixGrid::mouseover_changed (list<PortMatrixNode> const & old)
505 queue_draw_for (old);
506 queue_draw_for (_body->mouseover());
510 PortMatrixGrid::motion (double x, double y)
512 _body->set_mouseover (position_to_node (x, y));
514 int const px = x / grid_spacing ();
515 int const py = y / grid_spacing ();
517 if (_dragging && !_moved && ( (px != _drag_start_x || py != _drag_start_x) )) {
521 if (_dragging && _drag_valid && _moved) {
524 _body->queue_draw ();
529 PortMatrixGrid::queue_draw_for (list<PortMatrixNode> const &n)
531 for (list<PortMatrixNode>::const_iterator i = n.begin(); i != n.end(); ++i) {
535 double const y = channel_to_position (i->row, _matrix->visible_rows()) * grid_spacing ();
536 _body->queue_draw_area (
537 _parent_rectangle.get_x(),
538 component_to_parent_y (y),
539 _parent_rectangle.get_width(),
544 if (i->column.bundle) {
546 double const x = channel_to_position (i->column, _matrix->visible_columns()) * grid_spacing ();
548 _body->queue_draw_area (
549 component_to_parent_x (x),
550 _parent_rectangle.get_y(),
552 _parent_rectangle.get_height()
559 PortMatrixGrid::component_to_parent_x (double x) const
561 return x - _body->xoffset() + _parent_rectangle.get_x();
565 PortMatrixGrid::parent_to_component_x (double x) const
567 return x + _body->xoffset() - _parent_rectangle.get_x();
571 PortMatrixGrid::component_to_parent_y (double y) const
573 return y - _body->yoffset() + _parent_rectangle.get_y();
577 PortMatrixGrid::parent_to_component_y (double y) const
579 return y + _body->yoffset() - _parent_rectangle.get_y();
583 PortMatrixGrid::nodes_on_line (int x0, int y0, int x1, int y1) const
585 list<PortMatrixNode> p;
587 bool const steep = abs (y1 - y0) > abs (x1 - x0);
609 int dy = abs (y1 - y0);
612 double derr = double (dy) / dx;
615 int const ystep = y0 < y1 ? 1 : -1;
617 for (int x = x0; x <= x1; ++x) {
619 PortMatrixNode n = position_to_node (y * grid_spacing (), x * grid_spacing ());
620 if (n.row.bundle && n.column.bundle) {
624 PortMatrixNode n = position_to_node (x * grid_spacing (), y * grid_spacing ());
625 if (n.row.bundle && n.column.bundle) {
642 PortMatrixGrid::toggle_state (PortMatrixNode::State s) const
644 return (s == PortMatrixNode::NOT_ASSOCIATED || s == PortMatrixNode::PARTIAL);