Make MIDI selection actually show up.
[ardour.git] / gtk2_ardour / matrix.cc
1 #include <gtkmm.h>
2 #include <cairo/cairo.h>
3 #include <stdlib.h>
4 #include <iostream>
5 #include <algorithm>
6 #include <stdint.h>
7 #include <cmath>
8 #include <map>
9 #include <vector>
10
11 #include "matrix.h"
12
13 using namespace std;
14 using namespace Gtk;
15 using namespace ARDOUR;
16
17 Matrix::Matrix ()
18 {
19         alloc_width = 0;
20         alloc_height = 0;
21         line_width = 0;
22         line_height = 0;
23         labels_y_shift = 0;
24         labels_x_shift = 0;
25         arc_radius = 0;
26         xstep = 0;
27         ystep = 0;
28         pixmap = 0;
29         drawn = false;
30         angle_radians = M_PI/4.0;
31         motion_x = -1;
32         motion_y = -1;
33
34         add_events (Gdk::POINTER_MOTION_MASK|Gdk::LEAVE_NOTIFY_MASK);
35 }
36
37 void 
38 Matrix::set_ports (const list<string>& ports)
39 {
40         ours = ports;
41         reset_size ();
42 }
43
44 void 
45 Matrix::add_group (PortGroup& pg)
46 {
47         for (vector<string>::const_iterator s = pg.ports.begin(); s != pg.ports.end(); ++s) {
48                 others.push_back (OtherPort (*s, pg));
49         }
50         reset_size ();
51 }
52
53
54 void
55 Matrix::clear ()
56 {
57         others.clear ();
58         reset_size ();
59 }
60
61 void
62 Matrix::remove_group (PortGroup& pg)
63 {
64         for (list<OtherPort>::iterator o = others.begin(); o != others.end(); ) {
65                 if (&(*o).group() == &pg) {
66                         o = others.erase (o);
67                 } else {
68                         ++o;
69                 }
70         }
71         reset_size ();
72 }
73
74 void
75 Matrix::hide_group (PortGroup& pg)
76 {
77         reset_size();
78 }
79
80 void
81 Matrix::show_group (PortGroup& pg)
82 {
83         reset_size ();
84 }
85
86 void
87 Matrix::setup_nodes ()
88 {
89         int n, x, y;
90         list<string>::iterator m;
91         list<OtherPort>::iterator s;
92
93         for (vector<MatrixNode*>::iterator p = nodes.begin(); p != nodes.end(); ++p) {
94                 delete *p;
95         }
96         nodes.clear ();
97
98         list<OtherPort>::size_type visible_others = 0;
99         
100         for (list<OtherPort>::iterator s = others.begin(); s != others.end(); ++s) {
101                 if ((*s).visible()) {
102                         ++visible_others;
103                 }
104         }
105         
106         nodes.assign (ours.size() * visible_others, 0);
107
108         for (n = 0, y = 0, m = ours.begin(); m != ours.end(); ++m, ++y) {
109                 for (x = 0, s = others.begin(); s != others.end(); ++s) {
110                         if ((*s).visible()) {
111                                 nodes[n] = new MatrixNode (*m, *s, x, y);
112                                 n++;
113                                 x++;
114                         }
115                 }
116         }
117 }
118
119 void
120 Matrix::reset_size ()
121 {
122         list<OtherPort>::size_type visible_others = 0;
123         
124         for (list<OtherPort>::iterator s = others.begin(); s != others.end(); ++s) {
125                 if ((*s).visible()) {
126                         ++visible_others;
127                 }
128         }
129
130         if (!visible_others) {
131                 cerr << "There are no visible others!\n";
132                 xstep = 1;
133                 ystep = 1;
134                 line_width = 1;
135                 line_height = 1;
136                 border = 10;
137                 arc_radius = 3;
138                 labels_x_shift = 0;
139                 labels_y_shift = 0;
140                 return;
141         }
142
143         border = 10;
144
145         if (alloc_width > line_width) {
146
147                 xstep = (alloc_width - labels_x_shift - (2 * border) - (2 * arc_radius)) / visible_others;
148                 line_width = xstep * (others.size() - 1);
149
150                 ystep = (alloc_height - labels_y_shift - (2 * border) - (2 * arc_radius)) / (ours.size() - 1);
151                 line_height = ystep * (ours.size() - 1);
152
153         } else {
154
155                 xstep = 20;
156                 ystep = 20;
157                 
158                 line_height = (ours.size() - 1) * ystep;
159                 line_width = visible_others * xstep;
160         }
161
162         int half_step = min (ystep/2,xstep/2);
163         if (half_step > 3) {
164                 arc_radius = half_step - 5;
165         } else {
166                 arc_radius = 3;
167         }
168
169         arc_radius = min (arc_radius, 10);
170
171         /* scan all the port names that will be rotated, and compute
172            how much space we need for them
173         */
174         
175         float w = 0;
176         float h = 0;
177         cairo_text_extents_t extents;
178         cairo_t* cr;
179         GdkPixmap* pm;
180
181         pm = gdk_pixmap_new (NULL, 1, 1, 24);
182         gdk_drawable_set_colormap (pm, gdk_colormap_get_system());
183
184         cr = gdk_cairo_create (pm);
185
186         for (list<OtherPort>::iterator s = others.begin(); s != others.end(); ++s) {
187                 if ((*s).visible()) {
188                         cairo_text_extents (cr, (*s).name().c_str(), &extents);
189                         w = max ((float) extents.width, w);
190                         h = max ((float) extents.height, h);
191                 }
192         }
193
194         cairo_destroy (cr);
195         gdk_pixmap_unref (pm);
196
197         /* transform */
198
199         w = fabs (w * cos (angle_radians) + h * sin (angle_radians));
200         h = fabs (w * sin (angle_radians) + h * cos (angle_radians));
201
202         labels_y_shift = (int) ceil (h) + 10;
203         labels_x_shift = (int) ceil (w);
204
205         setup_nodes ();
206
207
208         cerr << "Based on ours = " << ours.size() << " others = " << others.size()
209              << " dimens = "
210              << " xstep " << xstep << endl
211              << " ystep " << ystep << endl
212              << " line_width " << line_width << endl
213              << " line_height " << line_height << endl
214              << " border " << border << endl
215              << " arc_radius " << arc_radius << endl
216              << " labels_x_shift " << labels_x_shift << endl
217              << " labels_y_shift " << labels_y_shift << endl;
218 }
219
220 bool
221 Matrix::on_motion_notify_event (GdkEventMotion* ev)
222 {
223         motion_x = ev->x;
224         motion_y = ev->y;
225         queue_draw ();
226         return false;
227 }
228
229 bool
230 Matrix::on_leave_notify_event (GdkEventCrossing *ev)
231 {
232         motion_x = -1;
233         motion_y = -1;
234         queue_draw ();
235         return false;
236 }
237
238 void
239 Matrix::on_size_request (Requisition* req)
240 {
241         req->width = labels_x_shift + line_width + (2*border) + (2*arc_radius);
242         req->height = labels_y_shift + line_height + (2*border) + (2*arc_radius);
243 }
244
245 MatrixNode*
246 Matrix::get_node (int32_t x, int32_t y)
247 {
248         int half_xstep = xstep / 2;
249         int half_ystep = ystep / 2;
250
251         x -= labels_x_shift - border;
252         if (x < half_xstep) {
253                 return 0;
254         }
255
256         y -= labels_y_shift - border;
257         if (y < half_ystep) {
258                 return 0;
259         }
260
261         x = (x - half_xstep) / xstep;
262         y = (y - half_ystep) / ystep;
263
264         x = y*ours.size() + x;
265
266         if (x >= nodes.size()) {
267                 return 0;
268         }
269
270         return nodes[x];
271 }
272
273 bool
274 Matrix::on_button_press_event (GdkEventButton* ev)
275 {
276         MatrixNode* node;
277         
278         if ((node = get_node (ev->x, ev->y)) != 0) {
279                 cerr << "Event in node " << node->our_name() << " x " << node->their_name () << endl;
280                 node->set_connected (!node->connected());
281                 drawn = false;
282                 queue_draw();
283         } 
284 }
285
286 void
287 Matrix::alloc_pixmap ()
288 {
289         if (pixmap) {
290                 gdk_pixmap_unref (pixmap);
291         }
292
293         pixmap = gdk_pixmap_new (get_window()->gobj(),
294                                  alloc_width,
295                                  alloc_height,
296                                  -1);
297
298         drawn = false;
299 }
300
301 void
302 Matrix::on_size_allocate (Allocation& alloc)
303 {
304         EventBox::on_size_allocate (alloc);
305
306         alloc_width = alloc.get_width();
307         alloc_height = alloc.get_height();
308
309         if (is_realized()) {
310                 alloc_pixmap ();
311                 reset_size ();
312 #ifdef MATRIX_USE_BACKING_PIXMAP
313                 redraw (pixmap, 0, 0, alloc_width, alloc_height);
314 #endif
315         }
316 }
317
318 void
319 Matrix::on_realize ()
320 {
321         EventBox::on_realize ();
322         alloc_pixmap ();
323 }
324
325 void
326 Matrix::redraw (GdkDrawable* drawable, GdkRectangle* rect)
327 {
328         list<string>::iterator o;
329         list<OtherPort>::iterator t;
330         int x, y;
331         uint32_t top_shift, bottom_shift, left_shift, right_shift;
332         cairo_t* cr;
333
334         cr = gdk_cairo_create (drawable);
335
336         cairo_set_source_rgb (cr, 0.83, 0.83, 0.83);
337         cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
338         cairo_fill (cr);
339
340         cairo_set_line_width (cr, 0.5);
341         
342         top_shift = labels_y_shift + border;
343         left_shift = labels_x_shift + border;
344         bottom_shift = 0;
345         right_shift = 0;
346
347         /* horizontal grid lines and side labels */
348
349         for (y = top_shift, o = ours.begin(); o != ours.end(); ++o, y += ystep) {
350                 
351                 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
352                 cairo_move_to (cr, left_shift, y);
353                 cairo_line_to (cr, left_shift+line_width, y);
354                 cairo_stroke (cr);
355 #if 0
356                 
357                 cairo_text_extents_t extents;
358                 cairo_text_extents (cr, (*o).c_str(),&extents);
359                 cairo_move_to (cr, border, y+extents.height/2);
360                 cairo_show_text (cr, (*o).c_str());
361 #endif
362
363         }
364
365         /* vertical grid lines and rotated labels*/
366
367         for (x = left_shift, t = others.begin(); t != others.end(); ++t, x += xstep) {
368
369                 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
370                 cairo_move_to (cr, x, top_shift);
371                 cairo_line_to (cr, x, top_shift+line_height);
372                 cairo_stroke (cr);
373
374                 cairo_move_to (cr, x-left_shift+12, border);
375                 cairo_set_source_rgb (cr, 0, 0, 1.0);
376                 
377                 cairo_save (cr);
378                 cairo_rotate (cr, angle_radians);
379                 cairo_show_text (cr, (*t).name().c_str());
380                 cairo_restore (cr);
381
382         }
383
384         /* nodes */
385
386         for (vector<MatrixNode*>::iterator n = nodes.begin(); n != nodes.end(); ++n) {
387
388                 x = (*n)->x() * xstep;
389                 y = (*n)->y() * ystep;
390
391                 cairo_new_path (cr);
392
393                 if (arc_radius) {
394                         cairo_arc (cr, left_shift+x, top_shift+y, arc_radius, 0, 2.0 * M_PI);
395                         if ((*n)->connected()) {
396                                 cairo_set_source_rgba (cr, 1.0, 0, 0, 1.0);
397                                 cairo_stroke (cr);
398                         } else {
399                                 cairo_set_source_rgba (cr, 1.0, 0, 0, 0.7);
400                                 cairo_fill (cr);
401                         }
402                 }
403         }
404
405         /* motion indicators */
406
407         if (motion_x >= left_shift && motion_y >= top_shift) {
408                 
409                 int col_left = left_shift + ((motion_x - left_shift) / xstep) * xstep;
410                 int row_top = top_shift + ((motion_y - top_shift) / ystep) * ystep;
411
412                 cairo_set_line_width (cr, 5);
413                 cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 0.3);
414                 
415                 /* horizontal (row) */
416
417                 cairo_line_to (cr, left_shift, row_top);
418                 cairo_line_to (cr, left_shift + line_width, row_top);
419                 cairo_stroke (cr);
420
421                 /* vertical (col) */
422                 
423                 cairo_move_to (cr, col_left, top_shift);
424                 cairo_line_to (cr, col_left, top_shift + line_height);
425                 cairo_stroke (cr);
426         }
427
428         cairo_destroy (cr);
429
430 #ifdef MATRIX_USE_BACKING_PIXMAP
431         drawn = true;
432 #endif
433 }
434
435 bool
436 Matrix::on_expose_event (GdkEventExpose* event)
437 {
438 #ifdef MATRIX_USE_BACKING_PIXMAP
439         if (!drawn) {
440                 redraw (pixmap, 0, 0, alloc_width, alloc_height);
441         }
442
443         gdk_draw_drawable (get_window()->gobj(),
444                            get_style()->get_fg_gc (STATE_NORMAL)->gobj(),
445                            pixmap,
446                            event->area.x,
447                            event->area.y,
448                            event->area.x,
449                            event->area.y,
450                            event->area.width,
451                            event->area.height);
452 #else
453         redraw (get_window()->gobj(), &event->area);
454 #endif
455         
456
457
458         return true;
459 }