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