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