Offer all of a bundle's ports for disconnection / removal when opening a menu over...
[ardour.git] / gtk2_ardour / port_matrix_column_labels.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_column_labels.h"
24 #include "port_matrix.h"
25 #include "port_matrix_body.h"
26 #include "utils.h"
27
28 using namespace std;
29
30 PortMatrixColumnLabels::PortMatrixColumnLabels (PortMatrix* m, PortMatrixBody* b)
31         : PortMatrixLabels (m, b),
32           _overhang (0)
33 {
34
35 }
36
37 void
38 PortMatrixColumnLabels::compute_dimensions ()
39 {
40         GdkPixmap* pm = gdk_pixmap_new (NULL, 1, 1, 24);
41         gdk_drawable_set_colormap (pm, gdk_colormap_get_system());
42         cairo_t* cr = gdk_cairo_create (pm);
43
44         /* width of the longest bundle name */
45         _longest_bundle_name = 0;
46         /* width of the longest channel name */
47         _longest_channel_name = 0;
48
49         /* Compute dimensions using all port groups, so that we allow for the largest and hence
50            we can change between visible groups without the size of the labels jumping around.
51         */
52
53         for (PortGroupList::List::const_iterator i = _matrix->columns()->begin(); i != _matrix->columns()->end(); ++i) {
54                 PortGroup::BundleList const c = _matrix->columns()->bundles();
55                 for (PortGroup::BundleList::const_iterator j = c.begin (); j != c.end(); ++j) {
56
57                         cairo_text_extents_t ext;
58                         cairo_text_extents (cr, j->bundle->name().c_str(), &ext);
59                         if (ext.width > _longest_bundle_name) {
60                                 _longest_bundle_name = ext.width;
61                         }
62
63                         for (uint32_t k = 0; k < j->bundle->nchannels (); ++k) {
64
65                                 cairo_text_extents (
66                                         cr,
67                                         j->bundle->channel_name (k).c_str(),
68                                         &ext
69                                         );
70
71                                 if (ext.width > _longest_channel_name) {
72                                         _longest_channel_name = ext.width;
73                                 }
74                         }
75                 }
76         }
77
78         /* height metrics */
79         cairo_text_extents_t ext;
80         cairo_text_extents (cr, X_("AQRjpy"), &ext);
81         _text_height = ext.height;
82         _descender_height = ext.height + ext.y_bearing;
83
84         /* width of the whole thing */
85         if (_matrix->visible_columns()) {
86                 _width = group_size (_matrix->visible_columns()) * grid_spacing ();
87         } else {
88                 _width = 0;
89         }
90
91         cairo_destroy (cr);
92         gdk_pixmap_unref (pm);
93
94         /* height of the whole thing */
95
96         int a = _longest_bundle_name + 4 * name_pad();
97         if (!_matrix->show_only_bundles()) {
98                 a += _longest_channel_name;
99         }
100
101         _height =  a * sin (angle()) + _text_height * cos (angle());
102         _overhang = _height / tan (angle ());
103         _width += _overhang;
104 }
105
106 double
107 PortMatrixColumnLabels::basic_text_x_pos (int) const
108 {
109         return grid_spacing() / 2 +
110                 _text_height / (2 * sin (angle ()));
111 }
112
113 void
114 PortMatrixColumnLabels::render (cairo_t* cr)
115 {
116         /* BACKGROUND */
117
118         set_source_rgb (cr, background_colour());
119         cairo_rectangle (cr, 0, 0, _width, _height);
120         cairo_fill (cr);
121
122         /* BUNDLE PARALLELOGRAM-TYPE-THING AND NAME */
123
124         double x = 0;
125         int N = 0;
126
127         PortGroup::BundleList const & bundles = _matrix->visible_columns()->bundles ();
128         for (PortGroup::BundleList::const_iterator i = bundles.begin (); i != bundles.end(); ++i) {
129
130                 Gdk::Color c = i->has_colour ? i->colour : get_a_bundle_colour (N);
131                 render_bundle_name (cr, background_colour (), c, x, 0, i->bundle);
132
133                 if (_matrix->show_only_bundles()) {
134                         x += grid_spacing();
135                 } else {
136                         x += i->bundle->nchannels () * grid_spacing();
137                 }
138                 
139                 ++N;
140         }
141
142         /* PORT NAMES */
143
144         if (!_matrix->show_only_bundles()) {
145                 x = 0;
146                 N = 0;
147
148                 for (PortGroup::BundleList::const_iterator i = bundles.begin (); i != bundles.end(); ++i) {
149
150                         for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
151                                 Gdk::Color c = i->has_colour ? i->colour : get_a_bundle_colour (N);
152                                 render_channel_name (cr, background_colour (), c, x, 0, ARDOUR::BundleChannel (i->bundle, j));
153                                 x += grid_spacing();
154                         }
155                         
156                         ++N;
157                 }
158         }
159 }
160
161 double
162 PortMatrixColumnLabels::component_to_parent_x (double x) const
163 {
164         return x - _body->xoffset() + _parent_rectangle.get_x();
165 }
166
167 double
168 PortMatrixColumnLabels::parent_to_component_x (double x) const
169 {
170         return x + _body->xoffset() - _parent_rectangle.get_x();
171 }
172
173 double
174 PortMatrixColumnLabels::component_to_parent_y (double y) const
175 {
176         /* Column labels don't scroll vertically, so y conversion does not depend on yoffset */
177         return y + _parent_rectangle.get_y();
178 }
179
180 double
181 PortMatrixColumnLabels::parent_to_component_y (double y) const
182 {
183         /* Column labels don't scroll vertically, so y conversion does not depend on yoffset */
184         return y - _parent_rectangle.get_y();
185 }
186
187 void
188 PortMatrixColumnLabels::mouseover_changed (list<PortMatrixNode> const &)
189 {
190         list<PortMatrixNode> const m = _body->mouseover ();
191         for (list<PortMatrixNode>::const_iterator i = m.begin(); i != m.end(); ++i) {
192
193                 ARDOUR::BundleChannel c = i->column;
194                 ARDOUR::BundleChannel r = i->row;
195                 
196                 if (c.bundle && r.bundle) {
197                         add_channel_highlight (c);
198                 } else if (c.bundle) {
199                         _body->highlight_associated_channels (_matrix->column_index(), c);
200                 }
201         }
202 }
203
204 vector<pair<double, double> >
205 PortMatrixColumnLabels::port_name_shape (double xoff, double yoff) const
206 {
207         vector<pair<double, double> > shape;
208
209         double const lc = _longest_channel_name + name_pad();
210         double const w = grid_spacing();
211
212         if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
213
214                 double x_ = xoff + _height / tan (angle()) + w;
215                 double y_ = yoff;
216                 shape.push_back (make_pair (x_, y_));
217                 x_ -= w;
218                 shape.push_back (make_pair (x_, y_));
219                 x_ -= lc * cos (angle());
220                 y_ += lc * sin (angle());
221                 shape.push_back (make_pair (x_, y_));
222                 x_ += w * pow (sin (angle()), 2);
223                 y_ += w * sin (angle()) * cos (angle());
224                 shape.push_back (make_pair (x_, y_));
225
226         } else {
227
228                 double x_ = xoff;
229                 double y_ = yoff + _height;
230                 shape.push_back (make_pair (x_, y_));
231                 x_ += w;
232                 shape.push_back (make_pair (x_, y_));
233                 x_ += lc * cos (angle());
234                 y_ -= lc * sin (angle());
235                 shape.push_back (make_pair (x_, y_));
236                 x_ -= grid_spacing() * pow (sin (angle()), 2);
237                 y_ -= grid_spacing() * sin (angle()) * cos (angle());
238                 shape.push_back (make_pair (x_, y_));
239         }
240
241         return shape;
242 }
243
244 void
245 PortMatrixColumnLabels::render_bundle_name (
246         cairo_t* cr, Gdk::Color fg_colour, Gdk::Color bg_colour, double xoff, double yoff, boost::shared_ptr<ARDOUR::Bundle> b
247         )
248 {
249         set_source_rgb (cr, bg_colour);
250
251         double w = 0;
252         if (_matrix->show_only_bundles()) {
253                 w = grid_spacing ();
254         } else {
255                 w = b->nchannels() * grid_spacing();
256         }
257
258         double x_ = xoff;
259
260         uint32_t y = yoff;
261         y += _height;
262
263         double y_ = y;
264         cairo_move_to (cr, x_, y_);
265         x_ += w;
266         cairo_line_to (cr, x_, y_);
267         x_ += _height / tan (angle ());
268         y_ -= _height;
269         cairo_line_to (cr, x_, y_);
270         x_ -= w;
271         cairo_line_to (cr, x_, y_);
272         cairo_line_to (cr, xoff, y);
273         cairo_fill_preserve (cr);
274         set_source_rgb (cr, fg_colour);
275         cairo_set_line_width (cr, label_border_width());
276         cairo_stroke (cr);
277
278         set_source_rgb (cr, text_colour());
279
280         double const q = ((grid_spacing() * sin (angle())) - _text_height) / 2 + _descender_height;
281
282         if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
283
284                 double rl = 0;
285                 if (_matrix->show_only_bundles()) {
286                         rl = name_pad();
287                 } else {
288                         rl = 3 * name_pad() + _longest_channel_name;
289                 }
290                 cairo_move_to (
291                         cr,
292                         xoff + grid_spacing() - q * sin (angle ()) + rl * cos (angle()),
293                         yoff + _height - q * cos (angle ()) - rl * sin (angle())
294                         );
295
296         } else {
297
298                 cairo_move_to (
299                         cr,
300                         xoff + grid_spacing() - q * sin (angle ()),
301                         yoff + _height - q * cos (angle ())
302                         );
303         }
304
305         cairo_save (cr);
306         cairo_rotate (cr, -angle());
307         cairo_show_text (cr, b->name().c_str());
308         cairo_restore (cr);
309 }
310
311 void
312 PortMatrixColumnLabels::render_channel_name (
313         cairo_t* cr, Gdk::Color fg_colour, Gdk::Color bg_colour, double xoff, double yoff, ARDOUR::BundleChannel const &bc
314         )
315 {
316         vector<pair<double, double> > const shape = port_name_shape (xoff, yoff);
317
318         cairo_move_to (cr, shape[0].first, shape[0].second);
319         for (uint32_t i = 1; i < 4; ++i) {
320                 cairo_line_to (cr, shape[i].first, shape[i].second);
321         }
322         cairo_line_to (cr, shape[0].first, shape[0].second);
323
324         set_source_rgb (cr, bg_colour);
325         cairo_fill_preserve (cr);
326         set_source_rgb (cr, fg_colour);
327         cairo_set_line_width (cr, label_border_width());
328         cairo_stroke (cr);
329
330         set_source_rgb (cr, text_colour());
331
332         double const q = ((grid_spacing() * sin (angle())) - _text_height) / 2 + _descender_height;
333
334         if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
335
336                 cairo_move_to (
337                         cr,
338                         xoff + grid_spacing() - q * sin (angle ()),
339                         yoff + _height - q * cos (angle ())
340                         );
341                                 
342
343         } else {
344
345                 double const rl = 3 * name_pad() + _longest_bundle_name;
346                 cairo_move_to (
347                         cr,
348                         xoff + grid_spacing() - q * sin (angle ()) + rl * cos (angle ()),
349                         yoff + _height - q * cos (angle ()) - rl * sin (angle())
350                         );
351         }
352
353         if (bc.bundle->nchannels() > 1) {
354
355                 /* only plot the name if the bundle has more than one channel;
356                    the name of a single channel is assumed to be redundant */
357                 
358                 cairo_save (cr);
359                 cairo_rotate (cr, -angle());
360                 
361                 cairo_show_text (
362                         cr,
363                         bc.bundle->channel_name(bc.channel).c_str()
364                         );
365                 
366                 cairo_restore (cr);
367         }
368 }
369
370 double
371 PortMatrixColumnLabels::channel_x (ARDOUR::BundleChannel const &bc) const
372 {
373         return channel_to_position (bc, _matrix->visible_columns()) * grid_spacing ();
374 }
375
376 double
377 PortMatrixColumnLabels::channel_y (ARDOUR::BundleChannel const &) const
378 {
379         return 0;
380 }
381
382 void
383 PortMatrixColumnLabels::queue_draw_for (ARDOUR::BundleChannel const & bc)
384 {
385         if (!bc.bundle) {
386                 return;
387         }
388
389         if (_matrix->show_only_bundles()) {
390
391                 _body->queue_draw_area (
392                         component_to_parent_x (channel_x (bc)) - 1,
393                         component_to_parent_y (0) - 1,
394                         grid_spacing() + _height * tan (angle()) + 2,
395                         _height + 2
396                         );
397
398         } else {
399
400                 double const x = channel_x (bc);
401                 double const lc = _longest_channel_name + name_pad();
402                 double const h = lc * sin (angle ()) + grid_spacing() * sin (angle()) * cos (angle());
403
404                 if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
405
406                         _body->queue_draw_area (
407                                 component_to_parent_x (x) - 1,
408                                 component_to_parent_y (_height - h) - 1,
409                                 grid_spacing() + lc * cos (angle()) + 2,
410                                 h + 2
411                                 );
412
413                 } else if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
414
415                         double const x_ = x + _height / tan (angle()) - lc * cos (angle());
416
417                         _body->queue_draw_area (
418                                 component_to_parent_x (x_) - 1,
419                                 component_to_parent_y (0) - 1,
420                                 grid_spacing() + lc * cos (angle()) + 2,
421                                 h + 2
422                                 );
423
424                 }
425
426         }
427 }
428
429 ARDOUR::BundleChannel
430 PortMatrixColumnLabels::position_to_channel (double p, double o, boost::shared_ptr<const PortGroup> group) const
431 {
432         uint32_t const cx = p - (_height - o) * tan (angle ());
433         return PortMatrixComponent::position_to_channel (cx, o, group);
434 }
435
436 void
437 PortMatrixColumnLabels::button_press (double x, double y, int b, uint32_t t, guint)
438 {
439         ARDOUR::BundleChannel w = position_to_channel (x, y, _matrix->visible_columns());
440
441         if (
442                 (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM && y > (_height - _longest_bundle_name * sin (angle ()))) ||
443                 (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT && y < (_longest_bundle_name * sin (angle ())))
444                 ) {
445
446                 w.channel = -1;
447         }
448
449         if (b == 3) {
450                 _matrix->popup_menu (
451                         w,
452                         ARDOUR::BundleChannel (),
453                         t
454                         );
455         }
456 }
457
458 void
459 PortMatrixColumnLabels::motion (double x, double y)
460 {
461         ARDOUR::BundleChannel const w = position_to_channel (x, y, _matrix->visible_columns());
462
463         if (w.bundle == 0) {
464                 _body->set_mouseover (PortMatrixNode ());
465                 return;
466         }
467
468         uint32_t const bh = _longest_channel_name * sin (angle ()) + _text_height / cos (angle ());
469
470         if (
471                 (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM && y > bh) ||
472                 (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT && y < (_height - bh))
473                 ) {
474
475                 /* if the mouse is over a bundle name, highlight all channels in the bundle */
476                 
477                 list<PortMatrixNode> n;
478
479                 for (uint32_t i = 0; i < w.bundle->nchannels(); ++i) {
480                         ARDOUR::BundleChannel const bc (w.bundle, i);
481                         n.push_back (PortMatrixNode (ARDOUR::BundleChannel (), bc));
482                 }
483
484                 _body->set_mouseover (n);
485
486         } else {
487         
488                 _body->set_mouseover (PortMatrixNode (ARDOUR::BundleChannel (), w));
489         }
490 }