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