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