Enable Menu > Quit to work again after startup on macOS
[ardour.git] / gtk2_ardour / port_matrix_column_labels.cc
1 /*
2  * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2009-2012 David Robillard <d@drobilla.net>
4  * Copyright (C) 2009-2016 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2014-2017 Robin Gareus <robin@gareus.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <iostream>
23 #include "gtkmm2ext/keyboard.h"
24 #include "ardour/bundle.h"
25 #include "gtkmm2ext/colors.h"
26 #include "utils.h"
27 #include "port_matrix_column_labels.h"
28 #include "port_matrix.h"
29 #include "port_matrix_body.h"
30
31 #include "pbd/i18n.h"
32
33 using namespace std;
34
35 PortMatrixColumnLabels::PortMatrixColumnLabels (PortMatrix* m, PortMatrixBody* b)
36         : PortMatrixLabels (m, b),
37           _overhang (0)
38 {
39
40 }
41
42 void
43 PortMatrixColumnLabels::compute_dimensions ()
44 {
45         cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 200, 200);
46         cairo_t* cr = cairo_create (surface);
47
48         /* width of the longest bundle name */
49         _longest_bundle_name = 0;
50         /* width of the longest channel name */
51         _longest_channel_name = 0;
52
53         /* Compute dimensions using all port groups, so that we allow for the largest and hence
54            we can change between visible groups without the size of the labels jumping around.
55         */
56
57         for (PortGroupList::List::const_iterator i = _matrix->columns()->begin(); i != _matrix->columns()->end(); ++i) {
58                 PortGroup::BundleList const c = _matrix->columns()->bundles();
59                 for (PortGroup::BundleList::const_iterator j = c.begin (); j != c.end(); ++j) {
60
61                         cairo_text_extents_t ext;
62                         cairo_text_extents (cr, (*j)->bundle->name().c_str(), &ext);
63                         if (ext.width > _longest_bundle_name) {
64                                 _longest_bundle_name = ext.width;
65                         }
66
67                         for (uint32_t k = 0; k < (*j)->bundle->nchannels().n_total(); ++k) {
68
69                                 if (!_matrix->should_show ((*j)->bundle->channel_type(k))) {
70                                         continue;
71                                 }
72
73                                 cairo_text_extents (
74                                         cr,
75                                         (*j)->bundle->channel_name (k).c_str(),
76                                         &ext
77                                         );
78
79                                 if (ext.width > _longest_channel_name) {
80                                         _longest_channel_name = ext.width;
81                                 }
82                         }
83                 }
84         }
85
86         /* height metrics */
87         cairo_text_extents_t ext;
88         cairo_text_extents (cr, X_("AQRjpy"), &ext);
89         _text_height = ext.height;
90         _descender_height = ext.height + ext.y_bearing;
91
92         /* width of the whole thing */
93         if (_matrix->visible_columns()) {
94                 _width = group_size (_matrix->visible_columns()) * grid_spacing ();
95         } else {
96                 _width = 0;
97         }
98
99         cairo_destroy (cr);
100         cairo_surface_destroy (surface);
101
102         /* height of the whole thing */
103
104         int a = _longest_bundle_name + 4 * name_pad();
105         if (!_matrix->show_only_bundles()) {
106                 a += _longest_channel_name;
107         }
108
109         _height =  a * sin (angle()) + _text_height * cos (angle());
110         _overhang = _height / tan (angle ());
111         _width += _overhang;
112 }
113
114 double
115 PortMatrixColumnLabels::basic_text_x_pos (int) const
116 {
117         return grid_spacing() / 2 +
118                 _text_height / (2 * sin (angle ()));
119 }
120
121 void
122 PortMatrixColumnLabels::render (cairo_t* cr)
123 {
124         /* BACKGROUND */
125
126         set_source_rgb (cr, background_colour());
127         cairo_rectangle (cr, 0, 0, _width, _height);
128         cairo_fill (cr);
129
130         /* BUNDLE PARALLELOGRAM-TYPE-THING AND NAME */
131
132         double x = 0;
133         int N = 0;
134
135         PortGroup::BundleList const & bundles = _matrix->visible_columns()->bundles ();
136         for (PortGroup::BundleList::const_iterator i = bundles.begin (); i != bundles.end(); ++i) {
137
138                 Gdk::Color c = (*i)->has_colour ? (*i)->colour : get_a_bundle_colour (N);
139                 render_bundle_name (cr, background_colour (), c, x, 0, (*i)->bundle);
140
141                 if (_matrix->show_only_bundles()) {
142                         x += grid_spacing();
143                 } else {
144                         x += _matrix->count_of_our_type_min_1 ((*i)->bundle->nchannels()) * grid_spacing();
145                 }
146
147                 ++N;
148         }
149
150         /* PORT NAMES */
151
152         if (!_matrix->show_only_bundles()) {
153                 x = 0;
154                 N = 0;
155
156                 for (PortGroup::BundleList::const_iterator i = bundles.begin (); i != bundles.end(); ++i) {
157
158                         uint32_t const C = _matrix->count_of_our_type ((*i)->bundle->nchannels ());
159
160                         for (uint32_t j = 0; j < C; ++j) {
161                                 Gdk::Color c = (*i)->has_colour ? (*i)->colour : get_a_bundle_colour (N);
162
163                                 ARDOUR::BundleChannel bc (
164                                         (*i)->bundle,
165                                         (*i)->bundle->type_channel_to_overall (_matrix->type (), j)
166                                         );
167
168                                 render_channel_name (cr, background_colour (), c, x, 0, bc);
169                                 x += grid_spacing();
170                         }
171
172                         if (C == 0) {
173                                 x += grid_spacing ();
174                         }
175
176                         ++N;
177                 }
178         }
179 }
180
181 double
182 PortMatrixColumnLabels::component_to_parent_x (double x) const
183 {
184         return x - _body->xoffset() + _parent_rectangle.get_x();
185 }
186
187 double
188 PortMatrixColumnLabels::parent_to_component_x (double x) const
189 {
190         return x + _body->xoffset() - _parent_rectangle.get_x();
191 }
192
193 double
194 PortMatrixColumnLabels::component_to_parent_y (double y) const
195 {
196         /* Column labels don't scroll vertically, so y conversion does not depend on yoffset */
197         return y + _parent_rectangle.get_y();
198 }
199
200 double
201 PortMatrixColumnLabels::parent_to_component_y (double y) const
202 {
203         /* Column labels don't scroll vertically, so y conversion does not depend on yoffset */
204         return y - _parent_rectangle.get_y();
205 }
206
207 void
208 PortMatrixColumnLabels::mouseover_changed (list<PortMatrixNode> const &)
209 {
210         list<PortMatrixNode> const m = _body->mouseover ();
211         for (list<PortMatrixNode>::const_iterator i = m.begin(); i != m.end(); ++i) {
212
213                 ARDOUR::BundleChannel c = i->column;
214                 ARDOUR::BundleChannel r = i->row;
215
216                 if (PortMatrix::bundle_with_channels (c.bundle) && PortMatrix::bundle_with_channels (r.bundle)) {
217                         add_channel_highlight (c);
218                 } else if (c.bundle) {
219                         _body->highlight_associated_channels (_matrix->column_index(), c);
220                 }
221         }
222 }
223
224 vector<pair<double, double> >
225 PortMatrixColumnLabels::port_name_shape (double xoff, double yoff) const
226 {
227         vector<pair<double, double> > shape;
228
229         double const lc = _longest_channel_name + name_pad();
230         double const w = grid_spacing();
231
232         if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
233
234                 double x_ = xoff + _height / tan (angle()) + w;
235                 double y_ = yoff;
236                 shape.push_back (make_pair (x_, y_));
237                 x_ -= w;
238                 shape.push_back (make_pair (x_, y_));
239                 x_ -= lc * cos (angle());
240                 y_ += lc * sin (angle());
241                 shape.push_back (make_pair (x_, y_));
242                 x_ += w * pow (sin (angle()), 2);
243                 y_ += w * sin (angle()) * cos (angle());
244                 shape.push_back (make_pair (x_, y_));
245
246         } else {
247
248                 double x_ = xoff;
249                 double y_ = yoff + _height;
250                 shape.push_back (make_pair (x_, y_));
251                 x_ += w;
252                 shape.push_back (make_pair (x_, y_));
253                 x_ += lc * cos (angle());
254                 y_ -= lc * sin (angle());
255                 shape.push_back (make_pair (x_, y_));
256                 x_ -= grid_spacing() * pow (sin (angle()), 2);
257                 y_ -= grid_spacing() * sin (angle()) * cos (angle());
258                 shape.push_back (make_pair (x_, y_));
259         }
260
261         return shape;
262 }
263
264 void
265 PortMatrixColumnLabels::render_bundle_name (
266         cairo_t* cr, Gdk::Color fg_colour, Gdk::Color bg_colour, double xoff, double yoff, boost::shared_ptr<ARDOUR::Bundle> b
267         )
268 {
269         set_source_rgb (cr, bg_colour);
270
271         double w = 0;
272         if (_matrix->show_only_bundles()) {
273                 w = grid_spacing ();
274         } else {
275                 w = _matrix->count_of_our_type_min_1 (b->nchannels()) * grid_spacing();
276         }
277
278         double x_ = xoff;
279
280         uint32_t y = yoff;
281         y += _height;
282
283         double y_ = y;
284         cairo_move_to (cr, x_, y_);
285         x_ += w;
286         cairo_line_to (cr, x_, y_);
287         x_ += _height / tan (angle ());
288         y_ -= _height;
289         cairo_line_to (cr, x_, y_);
290         x_ -= w;
291         cairo_line_to (cr, x_, y_);
292         cairo_line_to (cr, xoff, y);
293         cairo_fill_preserve (cr);
294         set_source_rgb (cr, fg_colour);
295         cairo_set_line_width (cr, label_border_width());
296         cairo_stroke (cr);
297
298         Gdk::Color textcolor;
299         ARDOUR_UI_UTILS::set_color_from_rgba(textcolor, Gtkmm2ext::contrasting_text_color(ARDOUR_UI_UTILS::gdk_color_to_rgba(bg_colour)));
300         set_source_rgb (cr, textcolor);
301
302         double const q = ((grid_spacing() * sin (angle())) - _text_height) / 2 + _descender_height;
303
304         if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
305
306                 double rl = 0;
307                 if (_matrix->show_only_bundles()) {
308                         rl = name_pad();
309                 } else {
310                         rl = 3 * name_pad() + _longest_channel_name;
311                 }
312                 cairo_move_to (
313                         cr,
314                         xoff + grid_spacing() - q * sin (angle ()) + rl * cos (angle()),
315                         yoff + _height - q * cos (angle ()) - rl * sin (angle())
316                         );
317
318         } else {
319
320                 cairo_move_to (
321                         cr,
322                         xoff + grid_spacing() - q * sin (angle ()),
323                         yoff + _height - q * cos (angle ())
324                         );
325         }
326
327         cairo_save (cr);
328         cairo_rotate (cr, -angle());
329         cairo_show_text (cr, b->name().c_str());
330         cairo_restore (cr);
331 }
332
333 void
334 PortMatrixColumnLabels::render_channel_name (
335         cairo_t* cr, Gdk::Color fg_colour, Gdk::Color bg_colour, double xoff, double yoff, ARDOUR::BundleChannel const &bc
336         )
337 {
338         vector<pair<double, double> > const shape = port_name_shape (xoff, yoff);
339
340         cairo_move_to (cr, shape[0].first, shape[0].second);
341         for (uint32_t i = 1; i < 4; ++i) {
342                 cairo_line_to (cr, shape[i].first, shape[i].second);
343         }
344         cairo_line_to (cr, shape[0].first, shape[0].second);
345
346         set_source_rgb (cr, bg_colour);
347         cairo_fill_preserve (cr);
348         set_source_rgb (cr, fg_colour);
349         cairo_set_line_width (cr, label_border_width());
350         cairo_stroke (cr);
351
352         Gdk::Color textcolor;
353         ARDOUR_UI_UTILS::set_color_from_rgba(textcolor, Gtkmm2ext::contrasting_text_color(ARDOUR_UI_UTILS::gdk_color_to_rgba(bg_colour)));
354         set_source_rgb (cr, textcolor);
355
356         double const q = ((grid_spacing() * sin (angle())) - _text_height) / 2 + _descender_height;
357
358         if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
359
360                 cairo_move_to (
361                         cr,
362                         xoff + grid_spacing() - q * sin (angle ()),
363                         yoff + _height - q * cos (angle ())
364                         );
365
366
367         } else {
368
369                 double const rl = 3 * name_pad() + _longest_bundle_name;
370                 cairo_move_to (
371                         cr,
372                         xoff + grid_spacing() - q * sin (angle ()) + rl * cos (angle ()),
373                         yoff + _height - q * cos (angle ()) - rl * sin (angle())
374                         );
375         }
376
377         if (_matrix->count_of_our_type (bc.bundle->nchannels()) > 1) {
378
379                 /* only plot the name if the bundle has more than one channel;
380                    the name of a single channel is assumed to be redundant */
381
382                 cairo_save (cr);
383                 cairo_rotate (cr, -angle());
384
385                 cairo_show_text (
386                         cr,
387                         bc.bundle->channel_name(bc.channel).c_str()
388                         );
389
390                 cairo_restore (cr);
391         }
392 }
393
394 double
395 PortMatrixColumnLabels::channel_x (ARDOUR::BundleChannel const &bc) const
396 {
397         return channel_to_position (bc, _matrix->visible_columns()) * grid_spacing ();
398 }
399
400 double
401 PortMatrixColumnLabels::channel_y (ARDOUR::BundleChannel const &) const
402 {
403         return 0;
404 }
405
406 void
407 PortMatrixColumnLabels::queue_draw_for (ARDOUR::BundleChannel const & bc)
408 {
409         if (!bc.bundle) {
410                 return;
411         }
412
413         if (_matrix->show_only_bundles()) {
414
415                 _body->queue_draw_area (
416                         component_to_parent_x (channel_x (bc)) - 1,
417                         component_to_parent_y (0) - 1,
418                         grid_spacing() + _height * tan (angle()) + 2,
419                         _height + 2
420                         );
421
422         } else {
423
424                 double const x = channel_x (bc);
425                 double const lc = _longest_channel_name + name_pad();
426                 double const h = lc * sin (angle ()) + grid_spacing() * sin (angle()) * cos (angle());
427
428                 if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
429
430                         _body->queue_draw_area (
431                                 component_to_parent_x (x) - 1,
432                                 component_to_parent_y (_height - h) - 1,
433                                 grid_spacing() + lc * cos (angle()) + 2,
434                                 h + 2
435                                 );
436
437                 } else if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
438
439                         double const x_ = x + _height / tan (angle()) - lc * cos (angle());
440
441                         _body->queue_draw_area (
442                                 component_to_parent_x (x_) - 1,
443                                 component_to_parent_y (0) - 1,
444                                 grid_spacing() + lc * cos (angle()) + 2,
445                                 h + 2
446                                 );
447
448                 }
449
450         }
451 }
452
453 ARDOUR::BundleChannel
454 PortMatrixColumnLabels::position_to_channel (double p, double o, boost::shared_ptr<const PortGroup> group) const
455 {
456         uint32_t const cx = p - (_height - o) * tan (angle ());
457         return PortMatrixComponent::position_to_channel (cx, o, group);
458 }
459
460 void
461 PortMatrixColumnLabels::button_press (double x, double y, GdkEventButton* ev)
462 {
463         ARDOUR::BundleChannel w = position_to_channel (x, y, _matrix->visible_columns());
464
465         if (
466                 (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM && y > (_height - _longest_bundle_name * sin (angle ()))) ||
467                 (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT && y < (_longest_bundle_name * sin (angle ())))
468                 ) {
469
470                 w.channel = -1;
471         }
472
473         if (Gtkmm2ext::Keyboard::is_delete_event (ev) && w.channel != -1) {
474                 _matrix->remove_channel (w);
475         } else if (ev->button == 3) {
476                 _matrix->popup_menu (
477                         w,
478                         ARDOUR::BundleChannel (),
479                         ev->time
480                         );
481         }
482 }
483
484 void
485 PortMatrixColumnLabels::motion (double x, double y)
486 {
487         ARDOUR::BundleChannel const w = position_to_channel (x, y, _matrix->visible_columns());
488
489         if (w.bundle == 0) {
490                 _body->set_mouseover (PortMatrixNode ());
491                 return;
492         }
493
494         uint32_t const bh = _longest_channel_name * sin (angle ()) + _text_height / cos (angle ());
495
496         if (
497                 (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM && y > bh) ||
498                 (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT && y < (_height - bh))
499                 ) {
500
501                 /* if the mouse is over a bundle name, highlight all channels in the bundle */
502
503                 list<PortMatrixNode> n;
504
505                 for (uint32_t i = 0; i < w.bundle->nchannels().n_total(); ++i) {
506                         if (!_matrix->should_show (w.bundle->channel_type (i))) {
507                                 continue;
508                         }
509
510                         ARDOUR::BundleChannel const bc (w.bundle, i);
511                         n.push_back (PortMatrixNode (ARDOUR::BundleChannel (), bc));
512                 }
513
514                 _body->set_mouseover (n);
515
516         } else {
517
518                 _body->set_mouseover (PortMatrixNode (ARDOUR::BundleChannel (), w));
519         }
520 }