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