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