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