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