Various tweaks to the port matrix: open to full size; remove buttons and move their...
[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 {
33         
34 }
35
36 void
37 PortMatrixGrid::compute_dimensions ()
38 {
39         _width = 0;
40         for (PortGroupList::List::const_iterator i = _matrix->columns()->begin(); i != _matrix->columns()->end(); ++i) {
41                 _width += group_width (*i);
42         }
43
44         _height = 0;
45         for (PortGroupList::List::const_iterator i = _matrix->rows()->begin(); i != _matrix->rows()->end(); ++i) {
46                 _height += group_height (*i);
47         }
48 }
49
50
51 void
52 PortMatrixGrid::render (cairo_t* cr)
53 {
54         set_source_rgb (cr, background_colour());
55         cairo_rectangle (cr, 0, 0, _width, _height);
56         cairo_fill (cr);
57
58         uint32_t x = 0;
59         for (PortGroupList::List::const_iterator c = _matrix->columns()->begin(); c != _matrix->columns()->end(); ++c) {
60
61                 uint32_t y = 0;
62                 for (PortGroupList::List::const_iterator r = _matrix->rows()->begin(); r != _matrix->rows()->end(); ++r) {
63
64                         if ((*c)->visible() && (*r)->visible()) {
65                                 render_group_pair (cr, *r, *c, x, y);
66                         }
67
68                         y += group_height (*r);
69                 }
70
71                 x += group_width (*c);
72         }
73 }
74
75 void
76 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)
77 {
78         PortGroup::BundleList const & row_bundles = row->bundles();
79         PortGroup::BundleList const & column_bundles = column->bundles();
80
81         /* unfortunately we need to compute the height of the row group here */
82         uint32_t height = group_height (row);
83         
84         uint32_t tx = x;
85
86         /* VERTICAL GRID LINES */
87         
88         set_source_rgb (cr, grid_colour());
89         uint32_t N = 0;
90         
91         for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
92
93                 cairo_set_line_width (cr, thick_grid_line_width());
94                 cairo_move_to (cr, tx, y);
95                 cairo_line_to (cr, tx, y + height);
96                 cairo_stroke (cr);
97                 
98                 if (!_matrix->show_only_bundles()) {
99                         cairo_set_line_width (cr, thin_grid_line_width());
100                         for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
101                                 tx += column_width ();
102                                 cairo_move_to (cr, tx, y);
103                                 cairo_line_to (cr, tx, y + height);
104                                 cairo_stroke (cr);
105                         }
106                         
107                 } else {
108                         
109                         tx += column_width ();
110                         
111                 }
112                 
113                 ++N;
114         }
115
116         uint32_t const width = tx - x;
117
118         uint32_t ty = y;
119         
120         /* HORIZONTAL GRID LINES */
121         
122         N = 0;
123         for (PortGroup::BundleList::const_iterator i = row_bundles.begin(); i != row_bundles.end(); ++i) {
124                 
125                 cairo_set_line_width (cr, thick_grid_line_width());
126                 cairo_move_to (cr, x, ty);
127                 cairo_line_to (cr, x + width, ty);
128                 cairo_stroke (cr);
129                 
130                 if (!_matrix->show_only_bundles()) {
131                         cairo_set_line_width (cr, thin_grid_line_width());
132                         for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
133                                 ty += row_height ();
134                                 cairo_move_to (cr, x, ty);
135                                 cairo_line_to (cr, x + width, ty);
136                                 cairo_stroke (cr);
137                         }
138
139                 } else {
140
141                         ty += row_height ();
142
143                 }
144                 
145                 ++N;
146         }
147         
148         /* ASSOCIATION INDICATORS */
149
150         uint32_t bx = x;
151         uint32_t by = y;
152
153         if (_matrix->show_only_bundles()) {
154
155                 for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
156                         by = y;
157                         
158                         for (PortGroup::BundleList::const_iterator j = row_bundles.begin(); j != row_bundles.end(); ++j) {
159                                 
160                                 PortMatrixNode::State s = bundle_to_bundle_state (i->bundle, j->bundle);
161                                 switch (s) {
162                                 case PortMatrixNode::UNKNOWN:
163                                         draw_unknown_indicator (cr, bx, by);
164                                         break;
165                                 case PortMatrixNode::ASSOCIATED:
166                                         draw_association_indicator (cr, bx, by);
167                                         break;
168                                 case PortMatrixNode::PARTIAL:
169                                         draw_association_indicator (cr, bx, by, 0.5);
170                                         break;
171                                 default:
172                                         break;
173                                 }
174                                 
175                                 by += row_height();
176                         }
177                         
178                         bx += column_width();
179                         
180                 }
181
182         } else {
183
184                 for (PortGroup::BundleList::const_iterator i = column_bundles.begin(); i != column_bundles.end(); ++i) {
185                         by = y;
186                         
187                         for (PortGroup::BundleList::const_iterator j = row_bundles.begin(); j != row_bundles.end(); ++j) {
188                                 
189                                 tx = bx;
190                                 for (uint32_t k = 0; k < i->bundle->nchannels (); ++k) {
191                                         
192                                         ty = by;
193                                         for (uint32_t l = 0; l < j->bundle->nchannels (); ++l) {
194                                                 
195                                                 ARDOUR::BundleChannel c[2];
196                                                 c[_matrix->column_index()] = ARDOUR::BundleChannel (i->bundle, k);
197                                                 c[_matrix->row_index()] = ARDOUR::BundleChannel (j->bundle, l);
198                                                 
199                                                 PortMatrixNode::State const s = _matrix->get_state (c);
200                                                 
201                                                 switch (s) {
202                                                 case PortMatrixNode::ASSOCIATED:
203                                                         draw_association_indicator (cr, tx, ty);
204                                                         break;
205                                                         
206                                                 case PortMatrixNode::UNKNOWN:
207                                                         draw_unknown_indicator (cr, tx, ty);
208                                                         break;
209                                                         
210                                                 case PortMatrixNode::NOT_ASSOCIATED:
211                                                         break;
212                                                         
213                                                 default:
214                                                         break;
215                                                 }
216                                                 
217                                                 ty += row_height();
218                                         }
219                                         
220                                         tx += column_width();
221                                 }
222                                 
223                                 by += j->bundle->nchannels () * row_height();
224                         }
225                         
226                         bx += i->bundle->nchannels () * column_width();
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         cairo_arc (
236                 cr,
237                 x + column_width() / 2,
238                 y + column_width() / 2,
239                 (column_width() - (2 * connection_indicator_pad())) / 2,
240                 0,
241                 p * 2 * M_PI
242                 );
243         
244         cairo_fill (cr);
245 }
246
247 void
248 PortMatrixGrid::draw_unknown_indicator (cairo_t* cr, uint32_t x, uint32_t y)
249 {
250         set_source_rgba (cr, unknown_colour(), 0.5);
251         cairo_rectangle (
252                 cr,
253                 x + thick_grid_line_width(),
254                 y + thick_grid_line_width(),
255                 column_width() - 2 * thick_grid_line_width(),
256                 row_height() - 2 * thick_grid_line_width()
257                 );
258         cairo_fill (cr);
259 }
260
261 PortMatrixNode
262 PortMatrixGrid::position_to_node (double x, double y) const
263 {
264         return PortMatrixNode (
265                 y_position_to_group_and_channel (y).second,
266                 x_position_to_group_and_channel (x).second
267                 );
268 }
269
270
271 pair<boost::shared_ptr<PortGroup>, ARDOUR::BundleChannel>
272 PortMatrixGrid::x_position_to_group_and_channel (double x) const
273 {
274         PortGroupList::List::const_iterator i = _matrix->columns()->begin();
275
276         while (i != _matrix->columns()->end()) {
277
278                 uint32_t const gw = group_width (*i);
279
280                 if (x < gw) {
281
282                         /* it's in this group */
283
284                         PortGroup::BundleList const & bundles = (*i)->bundles ();
285                         for (PortGroup::BundleList::const_iterator j = bundles.begin(); j != bundles.end(); ++j) {
286
287                                 if (_matrix->show_only_bundles()) {
288                                         
289                                         if (x < column_width()) {
290                                                 return make_pair (*i, ARDOUR::BundleChannel (j->bundle, 0));
291                                         } else {
292                                                 x -= column_width ();
293                                         }
294                                         
295                                 } else {
296
297                                         uint32_t const w = j->bundle->nchannels () * column_width ();
298                                         if (x < w) {
299                                                 return make_pair (*i, ARDOUR::BundleChannel (j->bundle, x / column_width()));
300                                         } else {
301                                                 x -= w;
302                                         }
303
304                                 }
305
306                         }
307
308                 } else {
309
310                         x -= gw;
311
312                 }
313
314                 ++i;
315         }
316
317         return make_pair (boost::shared_ptr<PortGroup> (), ARDOUR::BundleChannel (boost::shared_ptr<ARDOUR::Bundle> (), 0));
318 }
319
320
321
322 double
323 PortMatrixGrid::channel_position (
324         ARDOUR::BundleChannel bc,
325         PortGroup::BundleList const& bundles,
326         double inc) const
327 {
328         double p = 0;
329         
330         PortGroup::BundleList::const_iterator i = bundles.begin ();
331         while (i != bundles.end() && i->bundle != bc.bundle) {
332
333                 if (_matrix->show_only_bundles()) {
334                         p += inc;
335                 } else {
336                         p += inc * i->bundle->nchannels();
337                 }
338                 
339                 ++i;
340         }
341
342         if (i == bundles.end()) {
343                 return 0;
344         }
345
346         p += inc * bc.channel;
347
348         return p;
349 }
350
351 void
352 PortMatrixGrid::button_press (double x, double y, int b, uint32_t t)
353 {
354         if (b == 1) {
355                 
356                 PortMatrixNode const node = position_to_node (x, y);
357                 
358                 if (_matrix->show_only_bundles()) {
359                         
360                         PortMatrixNode::State const s = bundle_to_bundle_state (node.column.bundle, node.row.bundle);
361                         
362                         for (uint32_t i = 0; i < node.column.bundle->nchannels(); ++i) {
363                                 for (uint32_t j = 0; j < node.row.bundle->nchannels(); ++j) {
364                                         
365                                         ARDOUR::BundleChannel c[2];
366                                         c[_matrix->column_index()] = ARDOUR::BundleChannel (node.column.bundle, i);
367                                         c[_matrix->row_index()] = ARDOUR::BundleChannel (node.row.bundle, j);
368                                         if (s == PortMatrixNode::NOT_ASSOCIATED || s == PortMatrixNode::PARTIAL) {
369                                                 _matrix->set_state (c, i == j);
370                                         } else {
371                                                 _matrix->set_state (c, false);
372                                         }
373                                 }
374                         }
375                         
376                 } else {
377                         
378                         if (node.row.bundle && node.column.bundle) {
379                                 
380                                 ARDOUR::BundleChannel c[2];
381                                 c[_matrix->row_index()] = node.row;
382                                 c[_matrix->column_index()] = node.column;
383                                 
384                                 PortMatrixNode::State const s = _matrix->get_state (c);
385                                 
386                                 if (s == PortMatrixNode::ASSOCIATED || s == PortMatrixNode::NOT_ASSOCIATED) {
387                                         
388                                         bool const n = !(s == PortMatrixNode::ASSOCIATED);
389                                         
390                                         ARDOUR::BundleChannel c[2];
391                                         c[_matrix->row_index()] = node.row;
392                                         c[_matrix->column_index()] = node.column;
393                                         
394                                         _matrix->set_state (c, n);
395                                 }
396                                 
397                         }
398                 }
399                 
400                 require_render ();
401                 _body->queue_draw ();
402
403         } else if (b == 3) {
404
405                 _matrix->popup_menu (x_position_to_group_and_channel (x), y_position_to_group_and_channel (y), t);
406                 
407         }
408 }
409
410 void
411 PortMatrixGrid::draw_extra (cairo_t* cr)
412 {
413         set_source_rgba (cr, mouseover_line_colour(), 0.3);
414         cairo_set_line_width (cr, mouseover_line_width());
415
416         double const x = component_to_parent_x (
417                 channel_position (_body->mouseover().column, _matrix->columns()->bundles(), column_width()) + column_width() / 2
418                 );
419         
420         double const y = component_to_parent_y (
421                 channel_position (_body->mouseover().row, _matrix->rows()->bundles(), row_height()) + row_height() / 2
422                 );
423         
424         if (_body->mouseover().row.bundle) {
425
426                 cairo_move_to (cr, x, y);
427                 if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
428                         cairo_line_to (cr, component_to_parent_x (0), y);
429                 } else if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
430                         cairo_line_to (cr, _parent_rectangle.get_x() + _parent_rectangle.get_width(), y);
431                 }
432                 cairo_stroke (cr);
433         }
434
435         if (_body->mouseover().column.bundle) {
436
437                 cairo_move_to (cr, x, y);
438                 if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
439                         cairo_line_to (cr, x, _parent_rectangle.get_y() + _parent_rectangle.get_height());
440                 } else if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
441                         cairo_line_to (cr, x, component_to_parent_y (0));
442                 }
443                 cairo_stroke (cr);
444         }
445 }
446
447 void
448 PortMatrixGrid::mouseover_changed (PortMatrixNode const& old)
449 {
450         queue_draw_for (old);
451         queue_draw_for (_body->mouseover());
452 }
453
454 void
455 PortMatrixGrid::mouseover_event (double x, double y)
456 {
457         _body->set_mouseover (position_to_node (x, y));
458 }
459
460 void
461 PortMatrixGrid::queue_draw_for (PortMatrixNode const &n)
462 {
463         if (n.row.bundle) {
464
465                 double const y = channel_position (n.row, _matrix->rows()->bundles(), row_height());
466                 _body->queue_draw_area (
467                         _parent_rectangle.get_x(),
468                         component_to_parent_y (y),
469                         _parent_rectangle.get_width(),
470                         row_height()
471                         );
472         }
473
474         if (n.column.bundle) {
475
476                 double const x = channel_position (n.column, _matrix->columns()->bundles(), column_width());
477                 
478                 _body->queue_draw_area (
479                         component_to_parent_x (x),
480                         _parent_rectangle.get_y(),
481                         column_width(),
482                         _parent_rectangle.get_height()
483                         );
484         }
485 }
486
487 double
488 PortMatrixGrid::component_to_parent_x (double x) const
489 {
490         return x - _body->xoffset() + _parent_rectangle.get_x();
491 }
492
493 double
494 PortMatrixGrid::parent_to_component_x (double x) const
495 {
496         return x + _body->xoffset() - _parent_rectangle.get_x();
497 }
498
499 double
500 PortMatrixGrid::component_to_parent_y (double y) const
501 {
502         return y - _body->yoffset() + _parent_rectangle.get_y();
503 }
504
505 double
506 PortMatrixGrid::parent_to_component_y (double y) const
507 {
508         return y + _body->yoffset() - _parent_rectangle.get_y();
509 }
510
511 PortMatrixNode::State
512 PortMatrixGrid::bundle_to_bundle_state (boost::shared_ptr<ARDOUR::Bundle> a, boost::shared_ptr<ARDOUR::Bundle> b) const
513 {
514         bool have_unknown = false;
515         bool have_off_diagonal_association = false;
516         bool have_diagonal_association = false;
517         bool have_diagonal_not_association = false;
518                                 
519         for (uint32_t i = 0; i < a->nchannels (); ++i) {
520                                         
521                 for (uint32_t j = 0; j < b->nchannels (); ++j) {
522                                                 
523                         ARDOUR::BundleChannel c[2];
524                         c[_matrix->column_index()] = ARDOUR::BundleChannel (a, i);
525                         c[_matrix->row_index()] = ARDOUR::BundleChannel (b, j);
526                         
527                         PortMatrixNode::State const s = _matrix->get_state (c);
528
529                         switch (s) {
530                         case PortMatrixNode::ASSOCIATED:
531                                 if (i == j) {
532                                         have_diagonal_association = true;
533                                 } else {
534                                         have_off_diagonal_association = true;
535                                 }
536                                 break;
537                                 
538                         case PortMatrixNode::UNKNOWN:
539                                 have_unknown = true;
540                                 break;
541                                 
542                         case PortMatrixNode::NOT_ASSOCIATED:
543                                 if (i == j) {
544                                         have_diagonal_not_association = true;
545                                 }
546                                 break;
547
548                         default:
549                                 break;
550                         }
551                 }
552         }
553         
554         if (have_unknown) {
555                 return PortMatrixNode::UNKNOWN;
556         } else if (have_diagonal_association && !have_off_diagonal_association && !have_diagonal_not_association) {
557                 return PortMatrixNode::ASSOCIATED;
558         } else if (!have_diagonal_association && !have_off_diagonal_association) {
559                 return PortMatrixNode::NOT_ASSOCIATED;
560         }
561
562         return PortMatrixNode::PARTIAL;
563 }
564
565