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