Allow ardour to manipulate connections between two JACK ports that don't belong to us.
[ardour.git] / gtk2_ardour / port_matrix_grid.cc
1 /*
2     Copyright (C) 2002-2009 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <iostream>
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"
27
28 using namespace std;
29
30 PortMatrixGrid::PortMatrixGrid (PortMatrix* m, PortMatrixBody* b)
31         : PortMatrixComponent (m, b),
32           _dragging (false),
33           _drag_valid (false),
34           _moved (false)
35 {
36
37 }
38
39 void
40 PortMatrixGrid::compute_dimensions ()
41 {
42         _width = 0;
43
44         for (PortGroupList::List::const_iterator i = _matrix->columns()->begin(); i != _matrix->columns()->end(); ++i) {
45                 _width += group_size (*i) * grid_spacing ();
46         }
47
48         _height = 0;
49         for (PortGroupList::List::const_iterator i = _matrix->rows()->begin(); i != _matrix->rows()->end(); ++i) {
50                 _height += group_size (*i) * grid_spacing ();
51         }
52 }
53
54
55 void
56 PortMatrixGrid::render (cairo_t* cr)
57 {
58         set_source_rgb (cr, background_colour());
59         cairo_rectangle (cr, 0, 0, _width, _height);
60         cairo_fill (cr);
61
62         uint32_t x = 0;
63         for (PortGroupList::List::const_iterator c = _matrix->columns()->begin(); c != _matrix->columns()->end(); ++c) {
64
65                 uint32_t y = 0;
66                 for (PortGroupList::List::const_iterator r = _matrix->rows()->begin(); r != _matrix->rows()->end(); ++r) {
67
68                         if ((*c)->visible() && (*r)->visible()) {
69                                 render_group_pair (cr, *r, *c, x, y);
70                         }
71
72                         y += group_size (*r) * grid_spacing ();
73                 }
74
75                 x += group_size (*c) * grid_spacing ();
76         }
77 }
78
79 void
80 PortMatrixGrid::render_group_pair (cairo_t* cr, boost::shared_ptr<const PortGroup> row, boost::shared_ptr<const PortGroup> column, uint32_t const x, uint32_t const y)
81 {
82         PortGroup::BundleList const & row_bundles = row->bundles();
83         PortGroup::BundleList const & column_bundles = column->bundles();
84
85         /* unfortunately we need to compute the height of the row group here */
86         uint32_t height = group_size (row) * grid_spacing ();
87
88         uint32_t tx = x;
89
90         /* VERTICAL GRID LINES */
91
92         set_source_rgb (cr, grid_colour());
93         uint32_t N = 0;
94
95         for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
96
97                 cairo_set_line_width (cr, thick_grid_line_width());
98                 cairo_move_to (cr, tx, y);
99                 cairo_line_to (cr, tx, y + height);
100                 cairo_stroke (cr);
101
102                 if (!_matrix->show_only_bundles()) {
103                         cairo_set_line_width (cr, thin_grid_line_width());
104                         for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
105                                 tx += grid_spacing ();
106                                 cairo_move_to (cr, tx, y);
107                                 cairo_line_to (cr, tx, y + height);
108                                 cairo_stroke (cr);
109                         }
110
111                 } else {
112
113                         tx += grid_spacing ();
114
115                 }
116
117                 ++N;
118         }
119
120         uint32_t const width = tx - x;
121
122         uint32_t ty = y;
123
124         /* HORIZONTAL GRID LINES */
125
126         N = 0;
127         for (PortGroup::BundleList::const_iterator i = row_bundles.begin(); i != row_bundles.end(); ++i) {
128
129                 cairo_set_line_width (cr, thick_grid_line_width());
130                 cairo_move_to (cr, x, ty);
131                 cairo_line_to (cr, x + width, ty);
132                 cairo_stroke (cr);
133
134                 if (!_matrix->show_only_bundles()) {
135                         cairo_set_line_width (cr, thin_grid_line_width());
136                         for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
137                                 ty += grid_spacing ();
138                                 cairo_move_to (cr, x, ty);
139                                 cairo_line_to (cr, x + width, ty);
140                                 cairo_stroke (cr);
141                         }
142
143                 } else {
144
145                         ty += grid_spacing ();
146
147                 }
148
149                 ++N;
150         }
151
152         /* ASSOCIATION INDICATORS */
153
154         uint32_t bx = x;
155         uint32_t by = y;
156
157         if (_matrix->show_only_bundles()) {
158
159                 for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
160                         by = y;
161
162                         for (PortGroup::BundleList::const_iterator j = row_bundles.begin(); j != row_bundles.end(); ++j) {
163
164                                 PortMatrixNode::State s = get_association (PortMatrixNode (
165                                                                                    ARDOUR::BundleChannel (i->bundle, 0),
166                                                                                    ARDOUR::BundleChannel (j->bundle, 0)
167                                                                                    ));
168                                 switch (s) {
169                                 case PortMatrixNode::ASSOCIATED:
170                                         draw_association_indicator (cr, bx, by);
171                                         break;
172                                 case PortMatrixNode::PARTIAL:
173                                         draw_association_indicator (cr, bx, by, 0.5);
174                                         break;
175                                 default:
176                                         break;
177                                 }
178
179                                 by += grid_spacing();
180                         }
181
182                         bx += grid_spacing();
183
184                 }
185
186         } else {
187
188                 for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
189                         by = y;
190
191                         for (PortGroup::BundleList::const_iterator j = row_bundles.begin(); j != row_bundles.end(); ++j) {
192
193                                 tx = bx;
194                                 for (uint32_t k = 0; k < i->bundle->nchannels (); ++k) {
195
196                                         ty = by;
197                                         for (uint32_t l = 0; l < j->bundle->nchannels (); ++l) {
198
199                                                 ARDOUR::BundleChannel c[2];
200                                                 c[_matrix->column_index()] = ARDOUR::BundleChannel (i->bundle, k);
201                                                 c[_matrix->row_index()] = ARDOUR::BundleChannel (j->bundle, l);
202
203                                                 PortMatrixNode::State const s = _matrix->get_state (c);
204
205                                                 switch (s) {
206                                                 case PortMatrixNode::ASSOCIATED:
207                                                         draw_association_indicator (cr, tx, ty);
208                                                         break;
209
210                                                 case PortMatrixNode::NOT_ASSOCIATED:
211                                                         break;
212
213                                                 default:
214                                                         break;
215                                                 }
216
217                                                 ty += grid_spacing();
218                                         }
219
220                                         tx += grid_spacing();
221                                 }
222
223                                 by += j->bundle->nchannels () * grid_spacing();
224                         }
225
226                         bx += i->bundle->nchannels () * grid_spacing();
227                 }
228         }
229 }
230
231 void
232 PortMatrixGrid::draw_association_indicator (cairo_t* cr, uint32_t x, uint32_t y, double p)
233 {
234         set_source_rgba (cr, association_colour(), 0.5);
235
236         cairo_arc (
237                 cr,
238                 x + grid_spacing() / 2,
239                 y + grid_spacing() / 2,
240                 (grid_spacing() - (2 * connection_indicator_pad())) / 2,
241                 0,
242                 p * 2 * M_PI
243                 );
244
245         cairo_fill (cr);
246 }
247
248 void
249 PortMatrixGrid::draw_empty_square (cairo_t* cr, uint32_t x, uint32_t y)
250 {
251         set_source_rgb (cr, background_colour());
252         cairo_rectangle (
253                 cr,
254                 x + thick_grid_line_width(),
255                 y + thick_grid_line_width(),
256                 grid_spacing() - 2 * thick_grid_line_width(),
257                 grid_spacing() - 2 * thick_grid_line_width()
258                 );
259         cairo_fill (cr);
260 }
261
262 PortMatrixNode
263 PortMatrixGrid::position_to_node (uint32_t x, uint32_t y) const
264 {
265         return PortMatrixNode (
266                 position_to_group_and_channel (y, _matrix->rows()).second,
267                 position_to_group_and_channel (x, _matrix->columns()).second
268                 );
269 }
270
271 void
272 PortMatrixGrid::button_press (double x, double y, int b, uint32_t t)
273 {
274         pair<boost::shared_ptr<PortGroup>, ARDOUR::BundleChannel> px = position_to_group_and_channel (x / grid_spacing(), _matrix->columns());
275         pair<boost::shared_ptr<PortGroup>, ARDOUR::BundleChannel> py = position_to_group_and_channel (y / grid_spacing(), _matrix->rows());
276
277         if (b == 1) {
278
279                 _dragging = true;
280                 _drag_valid = (px.second.bundle && py.second.bundle);
281
282                 _moved = false;
283                 _drag_start_x = x / grid_spacing ();
284                 _drag_start_y = y / grid_spacing ();
285
286         } else if (b == 3) {
287
288                 _matrix->popup_menu (px, py, t);
289
290         }
291 }
292
293 PortMatrixNode::State
294 PortMatrixGrid::get_association (PortMatrixNode node) const
295 {
296         if (_matrix->show_only_bundles()) {
297
298                 bool have_unknown = false;
299                 bool have_off_diagonal_association = false;
300                 bool have_diagonal_association = false;
301                 bool have_diagonal_not_association = false;
302
303                 for (uint32_t i = 0; i < node.row.bundle->nchannels (); ++i) {
304
305                         for (uint32_t j = 0; j < node.column.bundle->nchannels (); ++j) {
306
307                                 ARDOUR::BundleChannel c[2];
308                                 c[_matrix->column_index()] = ARDOUR::BundleChannel (node.row.bundle, i);
309                                 c[_matrix->row_index()] = ARDOUR::BundleChannel (node.column.bundle, j);
310
311                                 PortMatrixNode::State const s = _matrix->get_state (c);
312
313                                 switch (s) {
314                                 case PortMatrixNode::ASSOCIATED:
315                                         if (i == j) {
316                                                 have_diagonal_association = true;
317                                         } else {
318                                                 have_off_diagonal_association = true;
319                                         }
320                                         break;
321
322                                 case PortMatrixNode::NOT_ASSOCIATED:
323                                         if (i == j) {
324                                                 have_diagonal_not_association = true;
325                                         }
326                                         break;
327
328                                 default:
329                                         break;
330                                 }
331                         }
332                 }
333
334                 if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
335                         return PortMatrixNode::ASSOCIATED;
336                 } else if (!have_diagonal_association && !have_off_diagonal_association) {
337                         return PortMatrixNode::NOT_ASSOCIATED;
338                 }
339
340                 return PortMatrixNode::PARTIAL;
341
342         } else {
343
344                 ARDOUR::BundleChannel c[2];
345                 c[_matrix->column_index()] = node.column;
346                 c[_matrix->row_index()] = node.row;
347                 return _matrix->get_state (c);
348
349         }
350
351         /* NOTREACHED */
352         return PortMatrixNode::NOT_ASSOCIATED;
353 }
354
355 void
356 PortMatrixGrid::set_association (PortMatrixNode node, bool s)
357 {
358         if (_matrix->show_only_bundles()) {
359
360                 for (uint32_t i = 0; i < node.column.bundle->nchannels(); ++i) {
361                         for (uint32_t j = 0; j < node.row.bundle->nchannels(); ++j) {
362
363                                 ARDOUR::BundleChannel c[2];
364                                 c[_matrix->column_index()] = ARDOUR::BundleChannel (node.column.bundle, i);
365                                 c[_matrix->row_index()] = ARDOUR::BundleChannel (node.row.bundle, j);
366                                 _matrix->set_state (c, s && (i == j));
367                         }
368                 }
369
370         } else {
371
372                 if (node.row.bundle && node.column.bundle) {
373
374                         ARDOUR::BundleChannel c[2];
375                         c[_matrix->row_index()] = node.row;
376                         c[_matrix->column_index()] = node.column;
377                         _matrix->set_state (c, s);
378                 }
379         }
380 }
381
382 void
383 PortMatrixGrid::button_release (double x, double y, int b, uint32_t /*t*/)
384 {
385         if (b == 1) {
386
387                 if (_dragging && _moved) {
388
389                         if (_drag_valid) {
390                                 list<PortMatrixNode> const p = nodes_on_line (_drag_start_x, _drag_start_y, _drag_x, _drag_y);
391
392                                 if (!p.empty()) {
393                                         PortMatrixNode::State const s = get_association (p.front());
394                                         for (list<PortMatrixNode>::const_iterator i = p.begin(); i != p.end(); ++i) {
395                                                 set_association (*i, toggle_state (s));
396                                         }
397                                 }
398                         }
399
400                 } else {
401
402                         PortMatrixNode const n = position_to_node (x / grid_spacing(), y / grid_spacing());
403                         if (n.row.bundle && n.column.bundle) {
404                                 PortMatrixNode::State const s = get_association (n);
405                                 set_association (n, toggle_state (s));
406                         }
407                 }
408
409                 require_render ();
410                 _body->queue_draw ();
411         }
412
413         _dragging = false;
414 }
415
416
417 void
418 PortMatrixGrid::draw_extra (cairo_t* cr)
419 {
420         set_source_rgba (cr, mouseover_line_colour(), 0.3);
421         cairo_set_line_width (cr, mouseover_line_width());
422
423         double const x = component_to_parent_x (channel_to_position (_body->mouseover().column, _matrix->columns()) * grid_spacing()) + grid_spacing() / 2;
424         double const y = component_to_parent_y (channel_to_position (_body->mouseover().row, _matrix->rows()) * grid_spacing()) + grid_spacing() / 2;
425
426         if (_body->mouseover().row.bundle && _body->mouseover().column.bundle) {
427
428                 cairo_move_to (cr, x, y);
429                 if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
430                         cairo_line_to (cr, component_to_parent_x (0), y);
431                 } else if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
432                         cairo_line_to (cr, _parent_rectangle.get_x() + _parent_rectangle.get_width(), y);
433                 }
434                 cairo_stroke (cr);
435
436                 cairo_move_to (cr, x, y);
437                 if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
438                         cairo_line_to (cr, x, _parent_rectangle.get_y() + _parent_rectangle.get_height());
439                 } else if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
440                         cairo_line_to (cr, x, component_to_parent_y (0));
441                 }
442                 cairo_stroke (cr);
443         }
444
445         if (_dragging && _drag_valid && _moved) {
446
447                 list<PortMatrixNode> const p = nodes_on_line (_drag_start_x, _drag_start_y, _drag_x, _drag_y);
448
449                 if (!p.empty()) {
450
451                         bool const s = toggle_state (get_association (p.front()));
452
453                         for (list<PortMatrixNode>::const_iterator i = p.begin(); i != p.end(); ++i) {
454                                 if (s) {
455                                         draw_association_indicator (
456                                                 cr,
457                                                 component_to_parent_x (channel_to_position (i->column, _matrix->columns()) * grid_spacing ()),
458                                                 component_to_parent_y (channel_to_position (i->row, _matrix->rows()) * grid_spacing ())
459                                                 );
460                                 } else {
461                                         draw_empty_square (
462                                                 cr,
463                                                 component_to_parent_x (channel_to_position (i->column, _matrix->columns()) * grid_spacing ()),
464                                                 component_to_parent_y (channel_to_position (i->row, _matrix->rows()) * grid_spacing ())
465                                                 );
466                                 }
467                         }
468                 }
469
470                 set_source_rgba (cr, association_colour (), 0.3);
471
472                 cairo_move_to (
473                         cr,
474                         component_to_parent_x (_drag_start_x * grid_spacing() + grid_spacing() / 2),
475                         component_to_parent_y (_drag_start_y * grid_spacing() + grid_spacing() / 2)
476                         );
477
478                 cairo_line_to (
479                         cr,
480                         component_to_parent_x (_drag_x * grid_spacing() + grid_spacing() / 2),
481                         component_to_parent_y (_drag_y * grid_spacing() + grid_spacing() / 2)
482                         );
483
484                 cairo_stroke (cr);
485
486         }
487 }
488
489 void
490 PortMatrixGrid::mouseover_changed (PortMatrixNode const& old)
491 {
492         queue_draw_for (old);
493         queue_draw_for (_body->mouseover());
494 }
495
496 void
497 PortMatrixGrid::motion (double x, double y)
498 {
499         _body->set_mouseover (position_to_node (x / grid_spacing(), y / grid_spacing()));
500
501         int const px = x / grid_spacing ();
502         int const py = y / grid_spacing ();
503
504         if (_dragging && !_moved && ( (px != _drag_start_x || py != _drag_start_x) )) {
505                 _moved = true;
506         }
507
508         if (_dragging && _drag_valid && _moved) {
509                 _drag_x = px;
510                 _drag_y = py;
511                 _body->queue_draw ();
512         }
513 }
514
515 void
516 PortMatrixGrid::queue_draw_for (PortMatrixNode const &n)
517 {
518         if (n.row.bundle) {
519
520                 double const y = channel_to_position (n.row, _matrix->rows()) * grid_spacing ();
521                 _body->queue_draw_area (
522                         _parent_rectangle.get_x(),
523                         component_to_parent_y (y),
524                         _parent_rectangle.get_width(),
525                         grid_spacing()
526                         );
527         }
528
529         if (n.column.bundle) {
530
531                 double const x = channel_to_position (n.column, _matrix->columns()) * grid_spacing ();
532
533                 _body->queue_draw_area (
534                         component_to_parent_x (x),
535                         _parent_rectangle.get_y(),
536                         grid_spacing(),
537                         _parent_rectangle.get_height()
538                         );
539         }
540 }
541
542 double
543 PortMatrixGrid::component_to_parent_x (double x) const
544 {
545         return x - _body->xoffset() + _parent_rectangle.get_x();
546 }
547
548 double
549 PortMatrixGrid::parent_to_component_x (double x) const
550 {
551         return x + _body->xoffset() - _parent_rectangle.get_x();
552 }
553
554 double
555 PortMatrixGrid::component_to_parent_y (double y) const
556 {
557         return y - _body->yoffset() + _parent_rectangle.get_y();
558 }
559
560 double
561 PortMatrixGrid::parent_to_component_y (double y) const
562 {
563         return y + _body->yoffset() - _parent_rectangle.get_y();
564 }
565
566 list<PortMatrixNode>
567 PortMatrixGrid::nodes_on_line (int x0, int y0, int x1, int y1) const
568 {
569         list<PortMatrixNode> p;
570
571         bool const steep = abs (y1 - y0) > abs (x1 - x0);
572         if (steep) {
573                 int tmp = x0;
574                 x0 = y0;
575                 y0 = tmp;
576
577                 tmp = y1;
578                 y1 = x1;
579                 x1 = tmp;
580         }
581
582         if (x0 > x1) {
583                 int tmp = x0;
584                 x0 = x1;
585                 x1 = tmp;
586
587                 tmp = y0;
588                 y0 = y1;
589                 y1 = tmp;
590         }
591
592         int dx = x1 - x0;
593         int dy = abs (y1 - y0);
594
595         double err = 0;
596         double derr = double (dy) / dx;
597
598         int y = y0;
599         int const ystep = y0 < y1 ? 1 : -1;
600
601         for (int x = x0; x <= x1; ++x) {
602                 if (steep) {
603                         PortMatrixNode n = position_to_node (y, x);
604                         if (n.row.bundle && n.column.bundle) {
605                                 p.push_back (n);
606                         }
607                 } else {
608                         PortMatrixNode n = position_to_node (x, y);
609                         if (n.row.bundle && n.column.bundle) {
610                                 p.push_back (n);
611                         }
612                 }
613
614                 err += derr;
615
616                 if (err >= 0.5) {
617                         y += ystep;
618                         err -= 1;
619                 }
620         }
621
622         return p;
623 }
624
625 bool
626 PortMatrixGrid::toggle_state (PortMatrixNode::State s) const
627 {
628         return (s == PortMatrixNode::NOT_ASSOCIATED || s == PortMatrixNode::PARTIAL);
629 }