Fix dragging grid lines when you move the mouse outside of the grid during the drag.
[ardour.git] / gtk2_ardour / port_matrix_body.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 "ardour/bundle.h"
22 #include "ardour/types.h"
23 #include "port_matrix_body.h"
24 #include "port_matrix.h"
25 #include "port_matrix_column_labels.h"
26 #include "port_matrix_row_labels.h"
27 #include "port_matrix_grid.h"
28
29 using namespace std;
30
31 PortMatrixBody::PortMatrixBody (PortMatrix* p)
32         : _matrix (p),
33           _alloc_width (0),
34           _alloc_height (0),
35           _xoffset (0),
36           _yoffset (0),
37           _ignore_component_size_changed (false)
38 {
39         _column_labels = new PortMatrixColumnLabels (p, this);
40         _row_labels = new PortMatrixRowLabels (p, this);
41         _grid = new PortMatrixGrid (p, this);
42
43         _components.push_back (_column_labels);
44         _components.push_back (_row_labels);
45         _components.push_back (_grid);
46
47         add_events (Gdk::LEAVE_NOTIFY_MASK | Gdk::POINTER_MOTION_MASK);
48 }
49
50
51 PortMatrixBody::~PortMatrixBody ()
52 {
53         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
54                 delete *i;
55         }
56 }
57
58 bool
59 PortMatrixBody::on_expose_event (GdkEventExpose* event)
60 {
61         if (_matrix->visible_columns()->bundles().empty() || _matrix->visible_rows()->bundles().empty()) {
62
63                 /* nothing to connect */
64
65                 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
66
67                 cairo_set_source_rgb (cr, 0, 0, 0);
68                 cairo_rectangle (cr, 0, 0, _alloc_width, _alloc_height);
69                 cairo_fill (cr);
70
71                 stringstream t;
72                 t << _("There are no ") << (_matrix->type() == ARDOUR::DataType::AUDIO ? _("audio") : _("MIDI")) << _(" ports to connect.");
73
74                 cairo_text_extents_t ext;
75                 cairo_text_extents (cr, t.str().c_str(), &ext);
76
77                 cairo_set_source_rgb (cr, 1, 1, 1);
78                 cairo_move_to (cr, (_alloc_width - ext.width) / 2, (_alloc_height + ext.height) / 2);
79                 cairo_show_text (cr, t.str().c_str ());
80
81                 cairo_destroy (cr);
82
83                 return true;
84         }
85
86         Gdk::Rectangle const exposure (
87                 event->area.x, event->area.y, event->area.width, event->area.height
88                 );
89
90         bool intersects;
91
92         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
93
94                 Gdk::Rectangle r = exposure;
95
96                 /* the get_pixmap call may cause things to be rerendered and sizes to change,
97                    so fetch the pixmap before calculating where to put it */
98                 GdkPixmap* p = (*i)->get_pixmap (get_window()->gobj());
99                 r.intersect ((*i)->parent_rectangle(), intersects);
100
101                 if (intersects) {
102                         
103                         gdk_draw_drawable (
104                                 get_window()->gobj(),
105                                 get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
106                                 p,
107                                 (*i)->parent_to_component_x (r.get_x()),
108                                 (*i)->parent_to_component_y (r.get_y()),
109                                 r.get_x(),
110                                 r.get_y(),
111                                 r.get_width(),
112                                 r.get_height()
113                                 );
114                 }
115
116         }
117
118         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
119
120         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
121                 cairo_save (cr);
122                 set_cairo_clip (cr, (*i)->parent_rectangle ());
123                 (*i)->draw_extra (cr);
124                 cairo_restore (cr);
125         }
126
127         cairo_destroy (cr);
128
129         return true;
130 }
131
132 void
133 PortMatrixBody::on_size_request (Gtk::Requisition *req)
134 {
135         pair<int, int> const col = _column_labels->dimensions ();
136         pair<int, int> const row = _row_labels->dimensions ();
137         pair<int, int> const grid = _grid->dimensions ();
138
139         if (grid.first == 0 && grid.second == 0) {
140                 /* nothing to display */
141                 req->width = 256;
142                 req->height = 64;
143                 return;
144         }
145
146         /* don't ask for the maximum size of our contents, otherwise GTK won't
147            let the containing window shrink below this size */
148
149         /* XXX these shouldn't be hard-coded */
150         int const min_width = 512;
151         int const min_height = 512;
152
153         req->width = min (min_width, max (col.first, grid.first + row.first));
154         req->height = min (min_height / _matrix->min_height_divisor(), col.second + grid.second);
155 }
156
157 void
158 PortMatrixBody::on_size_allocate (Gtk::Allocation& alloc)
159 {
160         Gtk::EventBox::on_size_allocate (alloc);
161
162         _alloc_width = alloc.get_width ();
163         _alloc_height = alloc.get_height ();
164
165         compute_rectangles ();
166         _matrix->setup_scrollbars ();
167 }
168
169 void
170 PortMatrixBody::compute_rectangles ()
171 {
172         /* full sizes of components */
173         pair<uint32_t, uint32_t> const col = _column_labels->dimensions ();
174         uint32_t col_overhang = _column_labels->overhang ();
175         pair<uint32_t, uint32_t> const row = _row_labels->dimensions ();
176         pair<uint32_t, uint32_t> const grid = _grid->dimensions ();
177
178         Gdk::Rectangle col_rect;
179         Gdk::Rectangle row_rect;
180         Gdk::Rectangle grid_rect;
181
182         if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
183
184                 col_rect.set_x (0);
185                 col_rect.set_y (0);
186                 grid_rect.set_x (0);
187
188                 col_rect.set_width (min (col.first, _alloc_width));
189
190                 uint32_t const y = min (_alloc_height, col.second);
191                 col_rect.set_height (y);
192                 row_rect.set_y (y);
193                 row_rect.set_height (_alloc_height - y);
194                 grid_rect.set_y (y);
195                 grid_rect.set_height (_alloc_height - y);
196
197                 uint32_t x = 0;
198                 if (_alloc_width > (grid.first + row.first)) {
199                         x = grid.first;
200                 } else if (_alloc_width > row.first) {
201                         x = _alloc_width - row.first;
202                 }
203
204                 grid_rect.set_width (x);
205                 row_rect.set_x (x);
206                 row_rect.set_width (_alloc_width - x);
207
208
209         } else if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
210
211                 col_rect.set_height (min (_alloc_height, col.second));
212
213                 row_rect.set_x (0);
214                 row_rect.set_y (0);
215                 row_rect.set_width (min (_alloc_width, row.first));
216                 row_rect.set_height (std::min (_alloc_height - col_rect.get_height(), row.second));
217
218                 grid_rect.set_x (row_rect.get_width());
219                 grid_rect.set_y (0);
220                 grid_rect.set_width (std::min (_alloc_width - row_rect.get_width(), grid.first));
221                 grid_rect.set_height (row_rect.get_height ());
222
223                 col_rect.set_width (grid_rect.get_width () + col_overhang);
224                 col_rect.set_x (row_rect.get_width() + grid_rect.get_width() - col_rect.get_width());
225                 col_rect.set_y (row_rect.get_height());
226
227         }
228
229         _row_labels->set_parent_rectangle (row_rect);
230         _column_labels->set_parent_rectangle (col_rect);
231         _grid->set_parent_rectangle (grid_rect);
232 }
233
234 void
235 PortMatrixBody::setup ()
236 {
237         /* Discard any old connections to bundles */
238
239         for (list<sigc::connection>::iterator i = _bundle_connections.begin(); i != _bundle_connections.end(); ++i) {
240                 i->disconnect ();
241         }
242         _bundle_connections.clear ();
243
244         /* Connect to bundles so that we find out when their names change */
245
246         PortGroup::BundleList r = _matrix->rows()->bundles ();
247         for (PortGroup::BundleList::iterator i = r.begin(); i != r.end(); ++i) {
248
249                 _bundle_connections.push_back (
250                         i->bundle->Changed.connect (sigc::hide (sigc::mem_fun (*this, &PortMatrixBody::rebuild_and_draw_row_labels)))
251                         );
252
253         }
254
255         PortGroup::BundleList c = _matrix->columns()->bundles ();
256         for (PortGroup::BundleList::iterator i = c.begin(); i != c.end(); ++i) {
257                 _bundle_connections.push_back (
258                         i->bundle->Changed.connect (sigc::hide (sigc::mem_fun (*this, &PortMatrixBody::rebuild_and_draw_column_labels)))
259                         );
260         }
261
262         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
263                 (*i)->setup ();
264         }
265
266         set_mouseover (PortMatrixNode ());
267         
268         _ignore_component_size_changed = true;
269         compute_rectangles ();
270         _ignore_component_size_changed = false;
271 }
272
273 uint32_t
274 PortMatrixBody::full_scroll_width ()
275 {
276         return _grid->dimensions().first;
277
278 }
279
280 uint32_t
281 PortMatrixBody::alloc_scroll_width ()
282 {
283         return _grid->parent_rectangle().get_width();
284 }
285
286 uint32_t
287 PortMatrixBody::full_scroll_height ()
288 {
289         return _grid->dimensions().second;
290 }
291
292 uint32_t
293 PortMatrixBody::alloc_scroll_height ()
294 {
295         return _grid->parent_rectangle().get_height();
296 }
297
298 void
299 PortMatrixBody::set_xoffset (uint32_t xo)
300 {
301         _xoffset = xo;
302         queue_draw ();
303 }
304
305 void
306 PortMatrixBody::set_yoffset (uint32_t yo)
307 {
308         _yoffset = yo;
309         queue_draw ();
310 }
311
312 bool
313 PortMatrixBody::on_button_press_event (GdkEventButton* ev)
314 {
315         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
316                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
317                         (*i)->button_press (
318                                 (*i)->parent_to_component_x (ev->x),
319                                 (*i)->parent_to_component_y (ev->y),
320                                 ev->button, ev->time
321                                 );
322                 }
323         }
324
325         return true;
326 }
327
328 bool
329 PortMatrixBody::on_button_release_event (GdkEventButton* ev)
330 {
331         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
332                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
333                         (*i)->button_release (
334                                 (*i)->parent_to_component_x (ev->x),
335                                 (*i)->parent_to_component_y (ev->y),
336                                 ev->button, ev->time
337                                 );
338                 } else {
339                         (*i)->button_release (
340                                 -1, -1,
341                                 ev->button, ev->time
342                                 );
343                 }
344         }
345
346         return true;
347 }
348
349 void
350 PortMatrixBody::rebuild_and_draw_grid ()
351 {
352         _grid->require_rebuild ();
353         queue_draw ();
354 }
355
356 void
357 PortMatrixBody::rebuild_and_draw_column_labels ()
358 {
359         _column_labels->require_rebuild ();
360         queue_draw ();
361 }
362
363 void
364 PortMatrixBody::rebuild_and_draw_row_labels ()
365 {
366         _row_labels->require_rebuild ();
367         queue_draw ();
368 }
369
370 bool
371 PortMatrixBody::on_leave_notify_event (GdkEventCrossing* ev)
372 {
373         if (ev->type == GDK_LEAVE_NOTIFY) {
374                 set_mouseover (PortMatrixNode ());
375         }
376
377         return true;
378 }
379
380 bool
381 PortMatrixBody::on_motion_notify_event (GdkEventMotion* ev)
382 {
383         bool done = false;
384         
385         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
386                 if (Gdk::Region ((*i)->parent_rectangle()).point_in (ev->x, ev->y)) {
387                         (*i)->motion (
388                                 (*i)->parent_to_component_x (ev->x),
389                                 (*i)->parent_to_component_y (ev->y)
390                                 );
391
392                         done = true;
393                 }
394         }
395                         
396
397         if (!done) {
398                 set_mouseover (PortMatrixNode ());
399         }
400
401         return true;
402 }
403
404 void
405 PortMatrixBody::set_mouseover (PortMatrixNode const & n)
406 {
407         list<PortMatrixNode> m;
408         m.push_back (n);
409         set_mouseover (m);
410 }
411
412 void
413 PortMatrixBody::set_mouseover (list<PortMatrixNode> const & n)
414 {
415         if (n == _mouseover) {
416                 return;
417         }
418
419         /* Channel highlights are set up only on mouseovers, so
420            it's reasonable to remove all channel highlights here.
421            We can't let individual components clear their own highlights
422            because of the case where, say, the row labels set up some column
423            highlights, and then we ask the column labels to set up their
424            own highlights and they clear them out before they start.
425         */
426
427         _row_labels->clear_channel_highlights ();
428         _column_labels->clear_channel_highlights ();
429
430         list<PortMatrixNode> old = _mouseover;
431         _mouseover = n;
432         
433         for (list<PortMatrixComponent*>::iterator i = _components.begin(); i != _components.end(); ++i) {
434                 (*i)->mouseover_changed (old);
435         }
436 }
437
438 void
439 PortMatrixBody::highlight_associated_channels (int dim, ARDOUR::BundleChannel h)
440 {
441         ARDOUR::BundleChannel bc[2];
442         bc[dim] = h;
443
444         if (!bc[dim].bundle) {
445                 return;
446         }
447
448         if (dim == _matrix->column_index()) {
449                 _column_labels->add_channel_highlight (bc[dim]);
450         } else {
451                 _row_labels->add_channel_highlight (bc[dim]);
452         }
453
454         PortGroup::BundleList const b = _matrix->visible_ports(1 - dim)->bundles ();
455
456         for (PortGroup::BundleList::const_iterator i = b.begin(); i != b.end(); ++i) {
457                 for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
458                         bc[1 - dim] = ARDOUR::BundleChannel (i->bundle, j);
459                         if (_matrix->get_state (bc) == PortMatrixNode::ASSOCIATED) {
460                                 if (dim == _matrix->column_index()) {
461                                         _row_labels->add_channel_highlight (bc[1 - dim]);
462                                 } else {
463                                         _column_labels->add_channel_highlight (bc[1 - dim]);
464                                 }
465                         }
466                 }
467         }
468 }
469
470 void
471 PortMatrixBody::set_cairo_clip (cairo_t* cr, Gdk::Rectangle const & r) const
472 {
473         cairo_rectangle (cr, r.get_x(), r.get_y(), r.get_width(), r.get_height());
474         cairo_clip (cr);
475 }
476
477 void
478 PortMatrixBody::component_size_changed ()
479 {
480         if (_ignore_component_size_changed) {
481                 return;
482         }
483         
484         compute_rectangles ();
485         _matrix->setup_scrollbars ();
486 }
487
488 pair<uint32_t, uint32_t>
489 PortMatrixBody::max_size () const
490 {
491         pair<uint32_t, uint32_t> const col = _column_labels->dimensions ();
492         pair<uint32_t, uint32_t> const row = _row_labels->dimensions ();
493         pair<uint32_t, uint32_t> const grid = _grid->dimensions ();
494
495         return make_pair (std::max (row.first, _column_labels->overhang()) + grid.first, col.second + grid.second);
496 }