Fix up mouseover handling in the port matrix.
[ardour.git] / gtk2_ardour / port_matrix_body.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_body.h"
24 #include "port_matrix.h"
25
26 PortMatrixBody::PortMatrixBody (PortMatrix* p)
27         : _matrix (p),
28           _column_labels (p, this),
29           _row_labels (p, this),
30           _grid (p, this),
31           _xoffset (0),
32           _yoffset (0)
33 {
34         modify_bg (Gtk::STATE_NORMAL, Gdk::Color ("#00000"));
35         add_events (Gdk::LEAVE_NOTIFY_MASK | Gdk::POINTER_MOTION_MASK);
36 }
37
38
39 bool
40 PortMatrixBody::on_expose_event (GdkEventExpose* event)
41 {
42         Gdk::Rectangle const exposure (
43                 event->area.x, event->area.y, event->area.width, event->area.height
44                 );
45
46         bool intersects;
47         Gdk::Rectangle r = exposure;
48         r.intersect (_column_labels.parent_rectangle(), intersects);
49
50         if (intersects) {
51                 gdk_draw_drawable (
52                         get_window()->gobj(),
53                         get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
54                         _column_labels.get_pixmap (get_window()->gobj()),
55                         _column_labels.parent_to_component_x (r.get_x()),
56                         _column_labels.parent_to_component_y (r.get_y()),
57                         r.get_x(),
58                         r.get_y(),
59                         r.get_width(),
60                         r.get_height()
61                         );
62         }
63
64         r = exposure;
65         r.intersect (_row_labels.parent_rectangle(), intersects);
66
67         if (intersects) {
68                 gdk_draw_drawable (
69                         get_window()->gobj(),
70                         get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
71                         _row_labels.get_pixmap (get_window()->gobj()),
72                         _row_labels.parent_to_component_x (r.get_x()),
73                         _row_labels.parent_to_component_y (r.get_y()),
74                         r.get_x(),
75                         r.get_y(),
76                         r.get_width(),
77                         r.get_height()
78                         );
79         }
80
81         r = exposure;
82         r.intersect (_grid.parent_rectangle(), intersects);
83
84         if (intersects) {
85                 gdk_draw_drawable (
86                         get_window()->gobj(),
87                         get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
88                         _grid.get_pixmap (get_window()->gobj()),
89                         _grid.parent_to_component_x (r.get_x()),
90                         _grid.parent_to_component_y (r.get_y()),
91                         r.get_x(),
92                         r.get_y(),
93                         r.get_width(),
94                         r.get_height()
95                         );
96         }
97
98         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
99         _grid.draw_extra (cr);
100         _row_labels.draw_extra (cr);
101         _column_labels.draw_extra (cr);
102         cairo_destroy (cr);
103
104         return true;
105 }
106
107 void
108 PortMatrixBody::on_size_request (Gtk::Requisition *req)
109 {
110         std::pair<int, int> const col = _column_labels.dimensions ();
111         std::pair<int, int> const row = _row_labels.dimensions ();
112         std::pair<int, int> const grid = _grid.dimensions ();
113
114         /* don't ask for the maximum size of our contents, otherwise GTK won't
115            let the containing window shrink below this size */
116
117         req->width = std::min (512, std::max (col.first, grid.first + row.first));
118         req->height = std::min (512, col.second + grid.second);
119 }
120
121 void
122 PortMatrixBody::on_size_allocate (Gtk::Allocation& alloc)
123 {
124         Gtk::EventBox::on_size_allocate (alloc);
125
126         _alloc_width = alloc.get_width ();
127         _alloc_height = alloc.get_height ();
128
129         compute_rectangles ();
130         _matrix->setup_scrollbars ();
131 }
132
133 void
134 PortMatrixBody::compute_rectangles ()
135 {
136         /* full sizes of components */
137         std::pair<uint32_t, uint32_t> const col = _column_labels.dimensions ();
138         std::pair<uint32_t, uint32_t> const row = _row_labels.dimensions ();
139         std::pair<uint32_t, uint32_t> const grid = _grid.dimensions ();
140
141         Gdk::Rectangle col_rect;
142         Gdk::Rectangle row_rect;
143         Gdk::Rectangle grid_rect;
144
145         if (_matrix->arrangement() == PortMatrix::TOP_TO_RIGHT) {
146
147                 /* build from top left */
148
149                 col_rect.set_x (0);
150                 col_rect.set_y (0);
151                 grid_rect.set_x (0);
152
153                 if (_alloc_width > col.first) {
154                         col_rect.set_width (col.first);
155                 } else {
156                         col_rect.set_width (_alloc_width);
157                 }
158
159                 /* move down to y division */
160                 
161                 uint32_t y = 0;
162                 if (_alloc_height > col.second) {
163                         y = col.second;
164                 } else {
165                         y = _alloc_height;
166                 }
167
168                 col_rect.set_height (y);
169                 row_rect.set_y (y);
170                 row_rect.set_height (_alloc_height - y);
171                 grid_rect.set_y (y);
172                 grid_rect.set_height (_alloc_height - y);
173
174                 /* move right to x division */
175
176                 uint32_t x = 0;
177                 if (_alloc_width > (grid.first + row.first)) {
178                         x = grid.first;
179                 } else if (_alloc_width > row.first) {
180                         x = _alloc_width - row.first;
181                 }
182
183                 grid_rect.set_width (x);
184                 row_rect.set_x (x);
185                 row_rect.set_width (_alloc_width - x);
186                         
187
188         } else if (_matrix->arrangement() == PortMatrix::LEFT_TO_BOTTOM) {
189
190                 /* build from bottom right */
191
192                 /* move left to x division */
193
194                 uint32_t x = 0;
195                 if (_alloc_width > (grid.first + row.first)) {
196                         x = grid.first;
197                 } else if (_alloc_width > row.first) {
198                         x = _alloc_width - row.first;
199                 }
200
201                 grid_rect.set_x (_alloc_width - x);
202                 grid_rect.set_width (x);
203                 col_rect.set_width (col.first - grid.first + x);
204                 col_rect.set_x (_alloc_width - col_rect.get_width());
205
206                 row_rect.set_width (std::min (_alloc_width - x, row.first));
207                 row_rect.set_x (_alloc_width - x - row_rect.get_width());
208
209                 /* move up to the y division */
210                 
211                 uint32_t y = 0;
212                 if (_alloc_height > col.second) {
213                         y = col.second;
214                 } else {
215                         y = _alloc_height;
216                 }
217
218                 col_rect.set_y (_alloc_height - y);
219                 col_rect.set_height (y);
220
221                 grid_rect.set_height (std::min (grid.second, _alloc_height - y));
222                 grid_rect.set_y (_alloc_height - y - grid_rect.get_height());
223
224                 row_rect.set_height (grid_rect.get_height());
225                 row_rect.set_y (grid_rect.get_y());
226         }
227
228         _row_labels.set_parent_rectangle (row_rect);
229         _column_labels.set_parent_rectangle (col_rect);
230         _grid.set_parent_rectangle (grid_rect);
231 }
232
233 void
234 PortMatrixBody::setup ()
235 {
236         /* Discard any old connections to bundles */
237         
238         for (std::list<sigc::connection>::iterator i = _bundle_connections.begin(); i != _bundle_connections.end(); ++i) {
239                 i->disconnect ();
240         }
241         _bundle_connections.clear ();
242
243         /* Connect to bundles so that we find out when their names change */
244         
245         ARDOUR::BundleList r = _matrix->rows()->bundles ();
246         for (ARDOUR::BundleList::iterator i = r.begin(); i != r.end(); ++i) {
247                 
248                 _bundle_connections.push_back (
249                         (*i)->NameChanged.connect (sigc::mem_fun (*this, &PortMatrixBody::rebuild_and_draw_row_labels))
250                         );
251                 
252         }
253
254         ARDOUR::BundleList c = _matrix->columns()->bundles ();
255         for (ARDOUR::BundleList::iterator i = c.begin(); i != c.end(); ++i) {
256                 _bundle_connections.push_back (
257                         (*i)->NameChanged.connect (sigc::mem_fun (*this, &PortMatrixBody::rebuild_and_draw_column_labels))
258                         );
259         }
260         
261         _column_labels.setup ();
262         _row_labels.setup ();
263         _grid.setup ();
264
265         set_mouseover (PortMatrixNode ());
266         compute_rectangles ();
267 }
268
269 uint32_t
270 PortMatrixBody::full_scroll_width ()
271 {
272         return _grid.dimensions().first;
273
274 }
275
276 uint32_t
277 PortMatrixBody::alloc_scroll_width ()
278 {
279         return _grid.parent_rectangle().get_width();
280 }
281
282 uint32_t
283 PortMatrixBody::full_scroll_height ()
284 {
285         return _grid.dimensions().second;
286 }
287
288 uint32_t
289 PortMatrixBody::alloc_scroll_height ()
290 {
291         return _grid.parent_rectangle().get_height();
292 }
293
294 void
295 PortMatrixBody::set_xoffset (uint32_t xo)
296 {
297         _xoffset = xo;
298         queue_draw ();
299 }
300
301 void
302 PortMatrixBody::set_yoffset (uint32_t yo)
303 {
304         _yoffset = yo;
305         queue_draw ();
306 }
307
308 bool
309 PortMatrixBody::on_button_press_event (GdkEventButton* ev)
310 {
311         if (Gdk::Region (_grid.parent_rectangle()).point_in (ev->x, ev->y)) {
312
313                 _grid.button_press (
314                         _grid.parent_to_component_x (ev->x),
315                         _grid.parent_to_component_y (ev->y),
316                         ev->button
317                         );
318
319         } else if (Gdk::Region (_row_labels.parent_rectangle()).point_in (ev->x, ev->y)) {
320
321                 _row_labels.button_press (
322                         _row_labels.parent_to_component_x (ev->x),
323                         _row_labels.parent_to_component_y (ev->y),
324                         ev->button, ev->time
325                         );
326         
327         } else if (Gdk::Region (_column_labels.parent_rectangle()).point_in (ev->x, ev->y)) {
328
329                 _column_labels.button_press (
330                         _column_labels.parent_to_component_x (ev->x),
331                         _column_labels.parent_to_component_y (ev->y),
332                         ev->button, ev->time
333                         );
334         }
335
336         return true;
337 }
338
339 void
340 PortMatrixBody::rebuild_and_draw_grid ()
341 {
342         _grid.require_rebuild ();
343         queue_draw ();
344 }
345
346 void
347 PortMatrixBody::rebuild_and_draw_column_labels ()
348 {
349         _column_labels.require_rebuild ();
350         queue_draw ();
351 }
352
353 void
354 PortMatrixBody::rebuild_and_draw_row_labels ()
355 {
356         _row_labels.require_rebuild ();
357         queue_draw ();
358 }
359
360 bool
361 PortMatrixBody::on_leave_notify_event (GdkEventCrossing* ev)
362 {
363         if (ev->type == GDK_LEAVE_NOTIFY) {
364                 set_mouseover (PortMatrixNode ());
365         }
366
367         return true;
368 }
369
370 bool
371 PortMatrixBody::on_motion_notify_event (GdkEventMotion* ev)
372 {
373         if (Gdk::Region (_grid.parent_rectangle()).point_in (ev->x, ev->y)) {
374                 _grid.mouseover_event (
375                         _grid.parent_to_component_x (ev->x),
376                         _grid.parent_to_component_y (ev->y)
377                         );
378         }
379
380         return true;
381 }
382
383 void
384 PortMatrixBody::set_mouseover (PortMatrixNode const & n)
385 {
386         if (n == _mouseover) {
387                 return;
388         }
389
390         PortMatrixNode old = _mouseover;
391         _mouseover = n;
392         
393         _grid.mouseover_changed (old);
394         _row_labels.mouseover_changed (old);
395         _column_labels.mouseover_changed (old);
396 }