Fix a couple of crashes with empty matrices. Some small optimisations.
[ardour.git] / gtk2_ardour / port_matrix_row_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 <boost/weak_ptr.hpp>
22 #include <cairo/cairo.h>
23 #include "ardour/bundle.h"
24 #include "port_matrix_row_labels.h"
25 #include "port_matrix.h"
26 #include "port_matrix_body.h"
27 #include "i18n.h"
28 #include "utils.h"
29
30 using namespace std;
31
32 PortMatrixRowLabels::PortMatrixRowLabels (PortMatrix* m, PortMatrixBody* b)
33         : PortMatrixLabels (m, b)
34 {
35
36 }
37
38 void
39 PortMatrixRowLabels::compute_dimensions ()
40 {
41         GdkPixmap* pm = gdk_pixmap_new (NULL, 1, 1, 24);
42         gdk_drawable_set_colormap (pm, gdk_colormap_get_system());
43         cairo_t* cr = gdk_cairo_create (pm);
44
45         _longest_port_name = 0;
46         _longest_bundle_name = 0;
47
48         /* Compute maximum dimensions using all port groups, so that we allow for the largest and hence
49            we can change between visible groups without the size of the labels jumping around.
50         */
51
52         for (PortGroupList::List::const_iterator i = _matrix->rows()->begin(); i != _matrix->rows()->end(); ++i) {
53
54                 PortGroup::BundleList const r = (*i)->bundles ();
55                 for (PortGroup::BundleList::const_iterator j = r.begin(); j != r.end(); ++j) {
56
57                         for (uint32_t k = 0; k < j->bundle->nchannels(); ++k) {
58                                 cairo_text_extents_t ext;
59                                 cairo_text_extents (cr, j->bundle->channel_name(k).c_str(), &ext);
60                                 if (ext.width > _longest_port_name) {
61                                         _longest_port_name = ext.width;
62                                 }
63                         }
64
65                         cairo_text_extents_t ext;
66                         cairo_text_extents (cr, j->bundle->name().c_str(), &ext);
67                         if (ext.width > _longest_bundle_name) {
68                                 _longest_bundle_name = ext.width;
69                         }
70                 }
71         }
72
73
74         if (_matrix->visible_rows()) {
75                 _height = group_size (_matrix->visible_rows()) * grid_spacing ();
76         } else {
77                 _height = 0;
78         }
79
80         cairo_destroy (cr);
81         gdk_pixmap_unref (pm);
82
83         _width = _longest_bundle_name +
84                 name_pad() * 2;
85
86         if (!_matrix->show_only_bundles()) {
87                 _width += _longest_port_name;
88                 _width += name_pad() * 2;
89         }
90 }
91
92
93 void
94 PortMatrixRowLabels::render (cairo_t* cr)
95 {
96         /* BACKGROUND */
97
98         set_source_rgb (cr, background_colour());
99         cairo_rectangle (cr, 0, 0, _width, _height);
100         cairo_fill (cr);
101
102         /* BUNDLE AND PORT NAMES */
103
104         double y = 0;
105         int N = 0;
106         int M = 0;
107         
108         PortGroup::BundleList const & bundles = _matrix->visible_rows()->bundles ();
109         for (PortGroup::BundleList::const_iterator i = bundles.begin(); i != bundles.end(); ++i) {
110                 render_bundle_name (cr, background_colour (), i->has_colour ? i->colour : get_a_bundle_colour (N), 0, y, i->bundle);
111
112                 if (!_matrix->show_only_bundles()) {
113                         for (uint32_t j = 0; j < i->bundle->nchannels(); ++j) {
114                                 Gdk::Color c = i->has_colour ? i->colour : get_a_bundle_colour (M);
115                                 render_channel_name (cr, background_colour (), c, 0, y, ARDOUR::BundleChannel (i->bundle, j));
116                                 y += grid_spacing();
117                                 ++M;
118                         }
119                 } else {
120                         y += grid_spacing();
121                 }
122                 
123                 ++N;
124         }
125 }
126
127 void
128 PortMatrixRowLabels::button_press (double x, double y, int b, uint32_t t, guint)
129 {
130         ARDOUR::BundleChannel const w = position_to_channel (y, x, _matrix->visible_rows());
131
132         if (b == 3) {
133
134                 _matrix->popup_menu (
135                         ARDOUR::BundleChannel (),
136                         w,
137                         t
138                         );
139         }
140 }
141
142 double
143 PortMatrixRowLabels::component_to_parent_x (double x) const
144 {
145         /* Row labels don't scroll horizontally, so x conversion does not depend on xoffset */
146         return x + _parent_rectangle.get_x();
147 }
148
149 double
150 PortMatrixRowLabels::parent_to_component_x (double x) const
151 {
152         /* Row labels don't scroll horizontally, so x conversion does not depend on xoffset */
153         return x - _parent_rectangle.get_x();
154 }
155
156 double
157 PortMatrixRowLabels::component_to_parent_y (double y) const
158 {
159         return y - _body->yoffset() + _parent_rectangle.get_y();
160 }
161
162 double
163 PortMatrixRowLabels::parent_to_component_y (double y) const
164 {
165         return y + _body->yoffset() - _parent_rectangle.get_y();
166 }
167
168
169 double
170 PortMatrixRowLabels::bundle_name_x () const
171 {
172         double x = 0;
173
174         if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT && !_matrix->show_only_bundles ()) {
175                 x = _longest_port_name + name_pad() * 2;
176         }
177
178         return x;
179 }
180
181 double
182 PortMatrixRowLabels::port_name_x () const
183 {
184         if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
185                 return _longest_bundle_name + name_pad() * 2;
186         } else {
187                 return 0;
188         }
189
190         return 0;
191 }
192
193 void
194 PortMatrixRowLabels::render_bundle_name (
195         cairo_t* cr, Gdk::Color fg_colour, Gdk::Color bg_colour, double xoff, double yoff, boost::shared_ptr<ARDOUR::Bundle> b
196         )
197 {
198         double const x = bundle_name_x ();
199
200         int const n = _matrix->show_only_bundles() ? 1 : b->nchannels();
201         set_source_rgb (cr, bg_colour);
202         cairo_rectangle (cr, xoff + x, yoff, _longest_bundle_name + name_pad() * 2, grid_spacing() * n);
203         cairo_fill_preserve (cr);
204         set_source_rgb (cr, fg_colour);
205         cairo_set_line_width (cr, label_border_width ());
206         cairo_stroke (cr);
207
208         cairo_text_extents_t ext;
209         cairo_text_extents (cr, b->name().c_str(), &ext);
210         double const off = (grid_spacing() - ext.height) / 2;
211
212         set_source_rgb (cr, text_colour());
213         cairo_move_to (cr, xoff + x + name_pad(), yoff + name_pad() + off);
214         cairo_show_text (cr, b->name().c_str());
215 }
216
217 void
218 PortMatrixRowLabels::render_channel_name (
219         cairo_t* cr, Gdk::Color fg_colour, Gdk::Color bg_colour, double xoff, double yoff, ARDOUR::BundleChannel const& bc
220         )
221 {
222         set_source_rgb (cr, bg_colour);
223         cairo_rectangle (cr, port_name_x() + xoff, yoff, _longest_port_name + name_pad() * 2, grid_spacing());
224         cairo_fill_preserve (cr);
225         set_source_rgb (cr, fg_colour);
226         cairo_set_line_width (cr, label_border_width ());
227         cairo_stroke (cr);
228
229         cairo_text_extents_t ext;
230         cairo_text_extents (cr, bc.bundle->channel_name(bc.channel).c_str(), &ext);
231         double const off = (grid_spacing() - ext.height) / 2;
232
233         if (bc.bundle->nchannels() > 1) {
234
235                 /* only plot the name if the bundle has more than one channel;
236                    the name of a single channel is assumed to be redundant */
237                 
238                 set_source_rgb (cr, text_colour());
239                 cairo_move_to (cr, port_name_x() + xoff + name_pad(), yoff + name_pad() + off);
240                 cairo_show_text (cr, bc.bundle->channel_name(bc.channel).c_str());
241         }
242 }
243
244 double
245 PortMatrixRowLabels::channel_x (ARDOUR::BundleChannel const &) const
246 {
247         return 0;
248 }
249
250 double
251 PortMatrixRowLabels::channel_y (ARDOUR::BundleChannel const& bc) const
252 {
253         return channel_to_position (bc, _matrix->visible_rows()) * grid_spacing ();
254 }
255
256 void
257 PortMatrixRowLabels::queue_draw_for (ARDOUR::BundleChannel const & bc)
258 {
259         if (bc.bundle) {
260
261                 if (_matrix->show_only_bundles()) {
262                         _body->queue_draw_area (
263                                 component_to_parent_x (bundle_name_x()) - 1,
264                                 component_to_parent_y (channel_y (bc)) - 1,
265                                 _longest_bundle_name + name_pad() * 2 + 2,
266                                 grid_spacing() + 2
267                                 );
268                 } else {
269                         _body->queue_draw_area (
270                                 component_to_parent_x (port_name_x()) - 1,
271                                 component_to_parent_y (channel_y (bc)) - 1,
272                                 _longest_port_name + name_pad() * 2 + 2,
273                                 grid_spacing() + 2
274                                 );
275                 }
276         }
277
278 }
279
280 void
281 PortMatrixRowLabels::mouseover_changed (list<PortMatrixNode> const &)
282 {
283         list<PortMatrixNode> const m = _body->mouseover ();
284         for (list<PortMatrixNode>::const_iterator i = m.begin(); i != m.end(); ++i) {
285                 
286                 ARDOUR::BundleChannel c = i->column;
287                 ARDOUR::BundleChannel r = i->row;
288                 
289                 if (c.bundle && r.bundle) {
290                         add_channel_highlight (r);
291                 } else if (r.bundle) {
292                         _body->highlight_associated_channels (_matrix->row_index(), r);
293                 }
294         }
295 }
296
297 void
298 PortMatrixRowLabels::motion (double x, double y)
299 {
300         ARDOUR::BundleChannel const w = position_to_channel (y, x, _matrix->visible_rows());
301
302         uint32_t const bw = _longest_bundle_name + 2 * name_pad();
303
304         bool done = false;
305
306         if (w.bundle) {
307                 
308                 if (
309                         (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM && x < bw) ||
310                         (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT && x > (_width - bw) && x < _width)
311                         
312                         ) {
313
314                         /* if the mouse is over a bundle name, highlight all channels in the bundle */
315                         
316                         list<PortMatrixNode> n;
317                         
318                         for (uint32_t i = 0; i < w.bundle->nchannels(); ++i) {
319                                 ARDOUR::BundleChannel const bc (w.bundle, i);
320                                 n.push_back (PortMatrixNode (bc, ARDOUR::BundleChannel ()));
321                         }
322                         
323                         _body->set_mouseover (n);
324                         done = true;
325                         
326                 } else if (x < _width) {
327                         
328                         _body->set_mouseover (PortMatrixNode (w, ARDOUR::BundleChannel ()));
329                         done = true;
330                         
331                 }
332
333         }
334
335         if (!done) {
336                 /* not over any bundle */
337                 _body->set_mouseover (PortMatrixNode ());
338                 return;
339         }
340 }