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