simplify time-axis packing: consistent table layout
[ardour.git] / gtk2_ardour / time_axis_view.cc
1 /*
2     Copyright (C) 2000 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 <cstdlib>
21 #include <cmath>
22 #include <algorithm>
23 #include <string>
24 #include <list>
25
26
27 #include "pbd/error.h"
28 #include "pbd/convert.h"
29 #include "pbd/stacktrace.h"
30
31 #include <gtkmm2ext/doi.h>
32 #include <gtkmm2ext/utils.h>
33 #include <gtkmm2ext/selector.h>
34
35 #include "canvas/canvas.h"
36 #include "canvas/rectangle.h"
37 #include "canvas/debug.h"
38
39 #include "ardour_ui.h"
40 #include "ardour_dialog.h"
41 #include "global_signals.h"
42 #include "gui_thread.h"
43 #include "public_editor.h"
44 #include "time_axis_view.h"
45 #include "region_view.h"
46 #include "ghostregion.h"
47 #include "selection.h"
48 #include "keyboard.h"
49 #include "rgb_macros.h"
50 #include "utils.h"
51 #include "streamview.h"
52 #include "editor_drag.h"
53 #include "editor.h"
54
55 #include "i18n.h"
56
57 using namespace std;
58 using namespace Gtk;
59 using namespace Gdk;
60 using namespace ARDOUR;
61 using namespace ARDOUR_UI_UTILS;
62 using namespace PBD;
63 using namespace Editing;
64 using namespace ArdourCanvas;
65 using Gtkmm2ext::Keyboard;
66
67 const double trim_handle_size = 6.0; /* pixels */
68 uint32_t TimeAxisView::button_height = 0;
69 uint32_t TimeAxisView::extra_height = 0;
70 int const TimeAxisView::_max_order = 512;
71 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
72
73 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
74         : AxisView (sess)
75         , controls_table (4, 4)
76         , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
77         , _name_editing (false)
78         , height (0)
79         , display_menu (0)
80         , parent (rent)
81         , selection_group (0)
82         , _ghost_group (0)
83         , _hidden (false)
84         , in_destructor (false)
85         , _size_menu (0)
86         , _canvas_display (0)
87         , _y_position (0)
88         , _editor (ed)
89         , name_entry (0)
90         , control_parent (0)
91         , _order (0)
92         , _effective_height (0)
93         , _resize_drag_start (-1)
94         , _preresize_cursor (0)
95         , _have_preresize_cursor (false)
96         , _ebox_release_can_act (true)
97 {
98         if (extra_height == 0) {
99                 compute_heights ();
100         }
101
102         _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group (), ArdourCanvas::Duple (0.0, 0.0));
103         CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
104         _canvas_display->hide(); // reveal as needed
105
106         selection_group = new ArdourCanvas::Container (_canvas_display);
107         CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
108         selection_group->set_data (X_("timeselection"), (void *) 1);
109         selection_group->hide();
110         
111         _ghost_group = new ArdourCanvas::Container (_canvas_display);
112         CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
113         _ghost_group->lower_to_bottom();
114         _ghost_group->show();
115
116         name_label.set_name ("TrackLabel");
117         name_label.set_alignment (0.0, 0.5);
118         name_label.set_width_chars (12);
119         ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
120
121         Gtk::Entry* an_entry = new Gtk::Entry;
122         Gtk::Requisition req;
123         an_entry->size_request (req);
124         name_label.set_size_request (-1, req.height);
125         name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
126         delete an_entry;
127
128         name_hbox.pack_end (name_label, true, true);
129         name_hbox.set_size_request(100, 0); // XXX min header width (if fader is not visible)
130         name_hbox.show ();
131         name_label.show ();
132
133         controls_table.set_row_spacings (2);
134         controls_table.set_col_spacings (2);
135         controls_table.set_border_width (2);
136
137         controls_table.attach (name_hbox, 4, 5, 0, 2,  Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
138         controls_table.show_all ();
139         controls_table.set_no_show_all ();
140
141         HSeparator* separator = manage (new HSeparator());
142         separator->set_name("TrackSeparator");
143         separator->set_size_request(-1, 1);
144         separator->show();
145
146         controls_vbox.pack_start (controls_table, false, false);
147         controls_vbox.show ();
148
149         top_hbox.pack_start (controls_vbox, true, true);
150         top_hbox.show ();
151
152         //controls_ebox.set_name ("TimeAxisViewControlsBaseUnselected");
153         controls_ebox.add (top_hbox);
154         controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
155                                   Gdk::BUTTON_RELEASE_MASK|
156                                   Gdk::POINTER_MOTION_MASK|
157                                   Gdk::ENTER_NOTIFY_MASK|
158                                   Gdk::LEAVE_NOTIFY_MASK|
159                                   Gdk::SCROLL_MASK);
160         controls_ebox.set_flags (CAN_FOCUS);
161
162         /* note that this handler connects *before* the default handler */
163         controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
164         controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
165         controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
166         controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
167         controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
168         controls_ebox.show ();
169
170         time_axis_frame.add(controls_ebox);
171         time_axis_frame.show();
172
173         ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
174
175         GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
176 }
177
178 TimeAxisView::~TimeAxisView()
179 {
180         in_destructor = true;
181
182         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
183                 delete *i;
184         }
185
186         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
187                 delete (*i)->rect;
188                 delete (*i)->start_trim;
189                 delete (*i)->end_trim;
190
191         }
192
193         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
194                 delete (*i)->rect;
195                 delete (*i)->start_trim;
196                 delete (*i)->end_trim;
197         }
198
199         delete selection_group;
200         selection_group = 0;
201
202         delete _canvas_display;
203         _canvas_display = 0;
204
205         delete display_menu;
206         display_menu = 0;
207
208         delete _size_menu;
209 }
210
211 void
212 TimeAxisView::hide ()
213 {
214         if (_hidden) {
215                 return;
216         }
217
218         _canvas_display->hide ();
219
220         if (control_parent) {
221                 control_parent->remove (time_axis_frame);
222                 control_parent = 0;
223         }
224
225         _y_position = -1;
226         _hidden = true;
227
228         /* now hide children */
229
230         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
231                 (*i)->hide ();
232         }
233
234         /* if its hidden, it cannot be selected */
235         _editor.get_selection().remove (this);
236         /* and neither can its regions */
237         _editor.get_selection().remove_regions (this);
238
239         Hiding ();
240 }
241
242 /** Display this TimeAxisView as the nth component of the parent box, at y.
243 *
244 * @param y y position.
245 * @param nth index for this TimeAxisView, increased if this view has children.
246 * @param parent parent component.
247 * @return height of this TimeAxisView.
248 */
249 guint32
250 TimeAxisView::show_at (double y, int& nth, VBox *parent)
251 {
252         if (control_parent) {
253                 control_parent->reorder_child (time_axis_frame, nth);
254         } else {
255                 control_parent = parent;
256                 parent->pack_start (time_axis_frame, false, false);
257                 parent->reorder_child (time_axis_frame, nth);
258         }
259
260         _order = nth;
261
262         if (_y_position != y) {
263                 _canvas_display->set_y_position (y);
264                 _y_position = y;
265
266         }
267
268         _canvas_display->raise_to_top ();
269         _canvas_display->show ();
270
271         _hidden = false;
272
273         _effective_height = current_height ();
274
275         /* now show relevant children */
276
277         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
278                 if ((*i)->marked_for_display()) {
279                         ++nth;
280                         _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
281                 } else {
282                         (*i)->hide ();
283                 }
284         }
285
286         return _effective_height;
287 }
288
289 bool
290 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
291 {
292         switch (ev->direction) {
293         case GDK_SCROLL_UP:
294                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
295                         /* See Editor::_stepping_axis_view for notes on this hack */
296                         Editor& e = dynamic_cast<Editor&> (_editor);
297                         if (!e.stepping_axis_view ()) {
298                                 e.set_stepping_axis_view (this);
299                         }
300                         e.stepping_axis_view()->step_height (false);
301                         return true;
302                 } 
303                 break;
304
305         case GDK_SCROLL_DOWN:
306                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
307                         /* See Editor::_stepping_axis_view for notes on this hack */
308                         Editor& e = dynamic_cast<Editor&> (_editor);
309                         if (!e.stepping_axis_view ()) {
310                                 e.set_stepping_axis_view (this);
311                         }
312                         e.stepping_axis_view()->step_height (true);
313                         return true;
314                 } 
315                 break;
316
317         default:
318                 /* no handling for left/right, yet */
319                 break;
320         }
321
322         /* Just forward to the normal canvas scroll method. The coordinate
323            systems are different but since the canvas is always larger than the
324            track headers, and aligned with the trackview area, this will work.
325
326            In the not too distant future this layout is going away anyway and
327            headers will be on the canvas.
328         */
329         return _editor.canvas_scroll_event (ev, false);
330 }
331
332 bool
333 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
334 {
335         if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
336                 /* see if it is inside the name label */
337                 if (name_label.is_ancestor (controls_ebox)) {
338                         int nlx;
339                         int nly;
340                         controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
341                         Gtk::Allocation a = name_label.get_allocation ();
342                         if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
343                                 begin_name_edit ();
344                                 _ebox_release_can_act = false;
345                                 return true;
346                         }
347                 }
348
349         }
350
351         _ebox_release_can_act = true;
352                         
353         if (maybe_set_cursor (event->y) > 0) {
354                 _resize_drag_start = event->y_root;
355         }
356
357         return true;
358 }
359
360 void
361 TimeAxisView::idle_resize (uint32_t h)
362 {
363         set_height (h);
364 }
365
366
367 bool
368 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
369 {
370         if (_resize_drag_start >= 0) {
371
372                 /* (ab)use the DragManager to do autoscrolling - basically we
373                  * are pretending that the drag is taking place over the canvas
374                  * (which perhaps in the glorious future, when track headers
375                  * and the canvas are unified, will actually be true.)
376                 */
377
378                 _editor.maybe_autoscroll (false, true, true);
379
380                 /* now schedule the actual TAV resize */
381                 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
382                 _editor.add_to_idle_resize (this, delta);
383                 _resize_drag_start = ev->y_root;
384         } else {
385                 /* not dragging but ... */
386                 maybe_set_cursor (ev->y);
387         }
388
389         gdk_event_request_motions(ev);
390         return true;
391 }
392
393 bool
394 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
395 {
396         if (_have_preresize_cursor) {
397                 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
398                 _have_preresize_cursor = false;
399         }
400         return true;
401 }
402
403 bool
404 TimeAxisView::maybe_set_cursor (int y)
405 {
406         /* XXX no Gtkmm Gdk::Window::get_cursor() */
407         Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
408
409         if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
410
411                 /* y-coordinate in lower 25% */
412
413                 if (!_have_preresize_cursor) {
414                         _preresize_cursor = gdk_window_get_cursor (win->gobj());
415                         _have_preresize_cursor = true;
416                         win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
417                 }
418
419                 return 1;
420
421         } else if (_have_preresize_cursor) {
422                 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
423                 _have_preresize_cursor = false;
424
425                 return -1;
426         }
427
428         return 0;
429 }
430
431 bool
432 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
433 {
434         if (_resize_drag_start >= 0) {
435                 if (_have_preresize_cursor) {
436                         gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
437                         _preresize_cursor = 0;
438                         _have_preresize_cursor = false;
439                 }
440                 _editor.stop_canvas_autoscroll ();
441                 _resize_drag_start = -1;
442         }
443
444         if (!_ebox_release_can_act) {
445                 return true;
446         }
447
448         switch (ev->button) {
449         case 1:
450                 selection_click (ev);
451                 break;
452
453         case 3:
454                 popup_display_menu (ev->time);
455                 break;
456         }
457
458         return true;
459 }
460
461 void
462 TimeAxisView::selection_click (GdkEventButton* ev)
463 {
464         Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
465         _editor.set_selected_track (*this, op, false);
466 }
467
468
469 /** Steps through the defined heights for this TrackView.
470  *  @param coarser true if stepping should decrease in size, otherwise false.
471  */
472 void
473 TimeAxisView::step_height (bool coarser)
474 {
475         static const uint32_t step = 25;
476
477         if (coarser) {
478
479                 if (height <= preset_height (HeightSmall)) {
480                         return;
481                 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
482                         set_height_enum (HeightSmall);
483                 } else {
484                         set_height (height - step);
485                 }
486
487         } else {
488
489                 if (height <= preset_height(HeightSmall)) {
490                         set_height_enum (HeightNormal);
491                 } else {
492                         set_height (height + step);
493                 }
494
495         }
496 }
497
498 void
499 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
500 {
501         if (apply_to_selection) {
502                 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
503         } else {
504                 set_height (preset_height (h));
505         }
506 }
507
508 void
509 TimeAxisView::set_height (uint32_t h)
510 {
511         if (h < preset_height (HeightSmall)) {
512                 h = preset_height (HeightSmall);
513         }
514
515         time_axis_frame.property_height_request () = h;
516         height = h;
517
518         char buf[32];
519         snprintf (buf, sizeof (buf), "%u", height);
520         set_gui_property ("height", buf);
521
522         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
523                 (*i)->set_height ();
524         }
525
526         if (selection_group->visible ()) {
527                 /* resize the selection rect */
528                 show_selection (_editor.get_selection().time);
529         }
530
531         _editor.override_visible_track_count ();
532 }
533
534 bool
535 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
536 {
537         /* steal escape, tabs from GTK */
538
539         switch (ev->keyval) {
540         case GDK_Escape:
541         case GDK_ISO_Left_Tab:
542         case GDK_Tab:
543                 return true;
544         }
545         return false;
546 }
547
548 bool
549 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
550 {
551         TrackViewList::iterator i;
552
553         switch (ev->keyval) {
554         case GDK_Escape:
555                 end_name_edit (RESPONSE_CANCEL);
556                 return true;
557
558         /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
559          * generates a different ev->keyval, rather than setting
560          * ev->state.
561          */
562         case GDK_ISO_Left_Tab:
563                 end_name_edit (RESPONSE_APPLY);
564                 return true;
565
566         case GDK_Tab:
567                 end_name_edit (RESPONSE_ACCEPT);
568                 return true;
569         default:
570                 break;
571         }
572
573         return false;
574 }
575
576 bool
577 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
578 {
579         end_name_edit (RESPONSE_OK);
580         return false;
581 }
582
583 void
584 TimeAxisView::begin_name_edit ()
585 {
586         if (name_entry) {
587                 return;
588         }
589
590         if (can_edit_name()) {
591
592                 name_entry = manage (new Gtkmm2ext::FocusEntry);
593                 
594                 name_entry->set_width_chars(8); // min width, entry expands
595
596                 name_entry->set_name ("EditorTrackNameDisplay");
597                 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
598                 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
599                 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
600                 name_entry->set_text (name_label.get_text());
601                 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
602
603                 if (name_label.is_ancestor (name_hbox)) {
604                         name_hbox.remove (name_label);
605                 }
606                 
607                 name_hbox.pack_end (*name_entry, true, true);
608                 name_entry->show ();
609
610                 name_entry->select_region (0, -1);
611                 name_entry->set_state (STATE_SELECTED);
612                 name_entry->grab_focus ();
613                 name_entry->start_editing (0);
614         }
615 }
616
617 void
618 TimeAxisView::end_name_edit (int response)
619 {
620         if (!name_entry) {
621                 return;
622         }
623         
624         bool edit_next = false;
625         bool edit_prev = false;
626
627         switch (response) {
628         case RESPONSE_CANCEL:
629                 break;
630         case RESPONSE_OK:
631                 name_entry_changed ();
632                 break;
633         case RESPONSE_ACCEPT:
634                 name_entry_changed ();
635                 edit_next = true;
636         case RESPONSE_APPLY:
637                 name_entry_changed ();
638                 edit_prev = true;
639         }
640
641         /* this will delete the name_entry. but it will also drop focus, which
642          * will cause another callback to this function, so set name_entry = 0
643          * first to ensure we don't double-remove etc. etc.
644          */
645
646         Gtk::Entry* tmp = name_entry;
647         name_entry = 0;
648         name_hbox.remove (*tmp);
649
650         /* put the name label back */
651
652         name_hbox.pack_end (name_label);
653         name_label.show ();
654
655         if (edit_next) {
656
657                 TrackViewList const & allviews = _editor.get_track_views ();
658                 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
659                 
660                 if (i != allviews.end()) {
661                         
662                         do {
663                                 if (++i == allviews.end()) {
664                                         return;
665                                 }
666                                 
667                                 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
668                         
669                                 if (rtav && rtav->route()->record_enabled()) {
670                                         continue;
671                                 }
672                                 
673                                 if (!(*i)->hidden()) {
674                                         break;
675                                 }
676                                 
677                         } while (true);
678                 }
679
680                 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
681                         _editor.ensure_time_axis_view_is_visible (**i, false);
682                         (*i)->begin_name_edit ();
683                 } 
684
685         } else if (edit_prev) {
686
687                 TrackViewList const & allviews = _editor.get_track_views ();
688                 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
689                 
690                 if (i != allviews.begin()) {
691                         do {
692                                 if (i == allviews.begin()) {
693                                         return;
694                                 }
695                                 
696                                 --i;
697                                 
698                                 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
699                                 
700                                 if (rtav && rtav->route()->record_enabled()) {
701                                         continue;
702                                 }
703                                 
704                                 if (!(*i)->hidden()) {
705                                         break;
706                                 }
707                                 
708                         } while (true);
709                 }
710                 
711                 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
712                         _editor.ensure_time_axis_view_is_visible (**i, false);
713                         (*i)->begin_name_edit ();
714                 } 
715         }
716 }
717
718 void
719 TimeAxisView::name_entry_changed ()
720 {
721 }
722
723 bool
724 TimeAxisView::can_edit_name () const
725 {
726         return true;
727 }
728
729 void
730 TimeAxisView::conditionally_add_to_selection ()
731 {
732         Selection& s (_editor.get_selection ());
733
734         if (!s.selected (this)) {
735                 _editor.set_selected_track (*this, Selection::Set);
736         }
737 }
738
739 void
740 TimeAxisView::popup_display_menu (guint32 when)
741 {
742         conditionally_add_to_selection ();
743
744         build_display_menu ();
745         display_menu->popup (1, when);
746 }
747
748 void
749 TimeAxisView::set_selected (bool yn)
750 {
751         if (yn == _selected) {
752                 return;
753         }
754
755         Selectable::set_selected (yn);
756
757         if (_selected) {
758                 time_axis_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
759                 time_axis_frame.set_name ("MixerStripSelectedFrame");
760
761                 //time_axis_frame.set_name (controls_base_selected_name);
762                 controls_ebox.set_name (controls_base_selected_name);
763                 controls_vbox.set_name (controls_base_selected_name);
764         } else {
765                 time_axis_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
766                 time_axis_frame.set_name (controls_base_unselected_name);
767
768                 //time_axis_frame.set_name (controls_base_unselected_name);
769                 controls_ebox.set_name (controls_base_unselected_name);
770                 controls_vbox.set_name (controls_base_unselected_name);
771
772                 hide_selection ();
773
774                 /* children will be set for the yn=true case. but when deselecting
775                    the editor only has a list of top-level trackviews, so we
776                    have to do this here.
777                 */
778
779                 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
780                         (*i)->set_selected (false);
781                 }
782         }
783
784         time_axis_frame.show();
785
786 }
787
788 void
789 TimeAxisView::build_display_menu ()
790 {
791         using namespace Menu_Helpers;
792
793         delete display_menu;
794
795         display_menu = new Menu;
796         display_menu->set_name ("ArdourContextMenu");
797
798         // Just let implementing classes define what goes into the manu
799 }
800
801 void
802 TimeAxisView::set_samples_per_pixel (double fpp)
803 {
804         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
805                 (*i)->set_samples_per_pixel (fpp);
806         }
807 }
808
809 void
810 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
811 {
812         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
813                 (*i)->show_timestretch (start, end, layers, layer);
814         }
815 }
816
817 void
818 TimeAxisView::hide_timestretch ()
819 {
820         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
821                 (*i)->hide_timestretch ();
822         }
823 }
824
825 void
826 TimeAxisView::show_selection (TimeSelection& ts)
827 {
828         double x1;
829         double x2;
830         double y2;
831         SelectionRect *rect;    time_axis_frame.show();
832
833
834         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
835                 (*i)->show_selection (ts);
836         }
837
838         if (selection_group->visible ()) {
839                 while (!used_selection_rects.empty()) {
840                         free_selection_rects.push_front (used_selection_rects.front());
841                         used_selection_rects.pop_front();
842                         free_selection_rects.front()->rect->hide();
843                         free_selection_rects.front()->start_trim->hide();
844                         free_selection_rects.front()->end_trim->hide();
845                 }
846                 selection_group->hide();
847         }
848
849         selection_group->show();
850         selection_group->raise_to_top();
851
852         for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
853                 framepos_t start, end;
854                 framecnt_t cnt;
855
856                 start = (*i).start;
857                 end = (*i).end;
858                 cnt = end - start + 1;
859
860                 rect = get_selection_rect ((*i).id);
861
862                 x1 = _editor.sample_to_pixel (start);
863                 x2 = _editor.sample_to_pixel (start + cnt - 1);
864                 y2 = current_height() - 1;
865
866                 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
867
868                 // trim boxes are at the top for selections
869
870                 if (x2 > x1) {
871                         rect->start_trim->set (ArdourCanvas::Rect (x1, 1, x1 + trim_handle_size, y2));
872                         rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
873
874                         rect->start_trim->show();
875                         rect->end_trim->show();
876                 } else {
877                         rect->start_trim->hide();
878                         rect->end_trim->hide();
879                 }
880
881                 rect->rect->show ();
882                 used_selection_rects.push_back (rect);
883         }
884 }
885
886 void
887 TimeAxisView::reshow_selection (TimeSelection& ts)
888 {
889         show_selection (ts);
890
891         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
892                 (*i)->show_selection (ts);
893         }
894 }
895
896 void
897 TimeAxisView::hide_selection ()
898 {
899         if (selection_group->visible ()) {
900                 while (!used_selection_rects.empty()) {
901                         free_selection_rects.push_front (used_selection_rects.front());
902                         used_selection_rects.pop_front();
903                         free_selection_rects.front()->rect->hide();
904                         free_selection_rects.front()->start_trim->hide();
905                         free_selection_rects.front()->end_trim->hide();
906                 }
907                 selection_group->hide();
908         }
909
910         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
911                 (*i)->hide_selection ();
912         }
913 }
914
915 void
916 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
917 {
918         /* find the selection rect this is for. we have the item corresponding to one
919            of the trim handles.
920          */
921
922         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
923                 if ((*i)->start_trim == item || (*i)->end_trim == item) {
924
925                         /* make one trim handle be "above" the other so that if they overlap,
926                            the top one is the one last used.
927                         */
928
929                         (*i)->rect->raise_to_top ();
930                         (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
931                         (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
932
933                         break;
934                 }
935         }
936 }
937
938 SelectionRect *
939 TimeAxisView::get_selection_rect (uint32_t id)
940 {
941         SelectionRect *rect;
942
943         /* check to see if we already have a visible rect for this particular selection ID */
944
945         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
946                 if ((*i)->id == id) {
947                         return (*i);
948                 }
949         }
950
951         /* ditto for the free rect list */
952
953         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
954                 if ((*i)->id == id) {
955                         SelectionRect* ret = (*i);
956                         free_selection_rects.erase (i);
957                         return ret;
958                 }
959         }
960
961         /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
962
963         if (free_selection_rects.empty()) {
964
965                 rect = new SelectionRect;
966
967                 rect->rect = new ArdourCanvas::Rectangle (selection_group);
968                 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
969                 rect->rect->set_outline (false);
970                 rect->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
971
972                 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
973                 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
974                 rect->start_trim->set_outline (false);
975                 rect->start_trim->set_fill (false);
976
977                 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
978                 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
979                 rect->end_trim->set_outline (false);
980                 rect->end_trim->set_fill (false);
981
982                 free_selection_rects.push_front (rect);
983
984                 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
985                 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
986                 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
987         }
988
989         rect = free_selection_rects.front();
990         rect->id = id;
991         free_selection_rects.pop_front();
992         return rect;
993 }
994
995 struct null_deleter { void operator()(void const *) const {} };
996
997 bool
998 TimeAxisView::is_child (TimeAxisView* tav)
999 {
1000         return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1001 }
1002
1003 void
1004 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1005 {
1006         children.push_back (child);
1007 }
1008
1009 void
1010 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1011 {
1012         Children::iterator i;
1013
1014         if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1015                 children.erase (i);
1016         }
1017 }
1018
1019 /** Get selectable things within a given range.
1020  *  @param start Start time in session frames.
1021  *  @param end End time in session frames.
1022  *  @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1023  *  @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1024  *  @param result Filled in with selectable things.
1025  */
1026 void
1027 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1028 {
1029         return;
1030 }
1031
1032 void
1033 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1034 {
1035         return;
1036 }
1037
1038 void
1039 TimeAxisView::add_ghost (RegionView* rv)
1040 {
1041         GhostRegion* gr = rv->add_ghost (*this);
1042
1043         if (gr) {
1044                 ghosts.push_back(gr);
1045         }
1046 }
1047
1048 void
1049 TimeAxisView::remove_ghost (RegionView* rv)
1050 {
1051         rv->remove_ghost_in (*this);
1052 }
1053
1054 void
1055 TimeAxisView::erase_ghost (GhostRegion* gr)
1056 {
1057         if (in_destructor) {
1058                 return;
1059         }
1060
1061         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1062                 if ((*i) == gr) {
1063                         ghosts.erase (i);
1064                         break;
1065                 }
1066         }
1067 }
1068
1069 bool
1070 TimeAxisView::touched (double top, double bot)
1071 {
1072         /* remember: this is X Window - coordinate space starts in upper left and moves down.
1073           y_position is the "origin" or "top" of the track.
1074         */
1075
1076         double mybot = _y_position + current_height();
1077
1078         return ((_y_position <= bot && _y_position >= top) ||
1079                 ((mybot <= bot) && (top < mybot)) ||
1080                 (mybot >= bot && _y_position < top));
1081 }
1082
1083 void
1084 TimeAxisView::set_parent (TimeAxisView& p)
1085 {
1086         parent = &p;
1087 }
1088
1089 void
1090 TimeAxisView::reset_height ()
1091 {
1092         set_height (height);
1093
1094         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1095                 (*i)->set_height ((*i)->height);
1096         }
1097 }
1098
1099 void
1100 TimeAxisView::compute_heights ()
1101 {
1102         Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1103         Gtk::Table two_row_table (2, 8);
1104         Gtk::Table one_row_table (1, 8);
1105         Button* buttons[5];
1106         const int border_width = 2;
1107
1108         const int separator_height = 2;
1109         extra_height = (2 * border_width) + separator_height;
1110
1111         window.add (one_row_table);
1112
1113         one_row_table.set_border_width (border_width);
1114         one_row_table.set_row_spacings (0);
1115         one_row_table.set_col_spacings (0);
1116         one_row_table.set_homogeneous (true);
1117
1118         two_row_table.set_border_width (border_width);
1119         two_row_table.set_row_spacings (0);
1120         two_row_table.set_col_spacings (0);
1121         two_row_table.set_homogeneous (true);
1122
1123         for (int i = 0; i < 5; ++i) {
1124                 buttons[i] = manage (new Button (X_("f")));
1125                 buttons[i]->set_name ("TrackMuteButton");
1126         }
1127
1128         one_row_table.attach (*buttons[0], 6, 7, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
1129
1130         one_row_table.show_all ();
1131         Gtk::Requisition req(one_row_table.size_request ());
1132
1133         // height required to show 1 row of buttons
1134         button_height = req.height;
1135 }
1136
1137 void
1138 TimeAxisView::color_handler ()
1139 {
1140         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1141                 (*i)->set_colors();
1142         }
1143
1144         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1145
1146                 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1147                 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1148
1149                 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1150                 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1151                 
1152                 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1153                 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1154         }
1155         
1156         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1157                 
1158                 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1159                 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1160                 
1161                 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1162                 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1163                 
1164                 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1165                 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1166         }
1167 }
1168
1169 /** @return Pair: TimeAxisView, layer index.
1170  * TimeAxisView is non-0 if this object covers @param y, or one of its children
1171  * does. @param y is an offset from the top of the trackview area.
1172  *
1173  * If the covering object is a child axis, then the child is returned.
1174  * TimeAxisView is 0 otherwise.
1175  *
1176  * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1177  * and is in stacked or expanded * region display mode, otherwise 0.
1178  */
1179 std::pair<TimeAxisView*, double>
1180 TimeAxisView::covers_y_position (double y) const
1181 {
1182         if (hidden()) {
1183                 return std::make_pair ((TimeAxisView *) 0, 0);
1184         }
1185
1186         if (_y_position <= y && y < (_y_position + height)) {
1187
1188                 /* work out the layer index if appropriate */
1189                 double l = 0;
1190                 switch (layer_display ()) {
1191                 case Overlaid:
1192                         break;
1193                 case Stacked:
1194                         if (view ()) {
1195                                 /* compute layer */
1196                                 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1197                                 /* clamp to max layers to be on the safe side; sometimes the above calculation
1198                                    returns a too-high value */
1199                                 if (l >= view()->layers ()) {
1200                                         l = view()->layers() - 1;
1201                                 }
1202                         }
1203                         break;
1204                 case Expanded:
1205                         if (view ()) {
1206                                 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1207                                 l = n * 0.5 - 0.5;
1208                                 if (l >= (view()->layers() - 0.5)) {
1209                                         l = view()->layers() - 0.5;
1210                                 }
1211                         }
1212                         break;
1213                 }
1214
1215                 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1216         }
1217
1218         for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1219
1220                 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1221                 if (r.first) {
1222                         return r;
1223                 }
1224         }
1225
1226         return std::make_pair ((TimeAxisView *) 0, 0);
1227 }
1228
1229 bool
1230 TimeAxisView::covered_by_y_range (double y0, double y1) const
1231 {
1232         if (hidden()) {
1233                 return false;
1234         }
1235
1236         /* if either the top or bottom of the axisview is in the vertical
1237          * range, we cover it.
1238          */
1239         
1240         if ((y0 < _y_position && y1 < _y_position) ||
1241             (y0 >= _y_position + height && y1 >= _y_position + height)) {
1242                 return false;
1243         }
1244
1245         for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1246                 if ((*i)->covered_by_y_range (y0, y1)) {
1247                         return true;
1248                 }
1249         }
1250
1251         return true;
1252 }
1253
1254 uint32_t
1255 TimeAxisView::preset_height (Height h)
1256 {
1257         switch (h) {
1258         case HeightLargest:
1259                 return (button_height * 2) + extra_height + 260;
1260         case HeightLarger:
1261                 return (button_height * 2) + extra_height + 160;
1262         case HeightLarge:
1263                 return (button_height * 2) + extra_height + 60;
1264         case HeightNormal:
1265                 return (button_height * 2) + extra_height + 10;
1266         case HeightSmall:
1267                 return button_height + extra_height;
1268         }
1269
1270         /* NOTREACHED */
1271         return 0;
1272 }
1273
1274 /** @return Child time axis views that are not hidden */
1275 TimeAxisView::Children
1276 TimeAxisView::get_child_list ()
1277 {
1278         Children c;
1279
1280         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1281                 if (!(*i)->hidden()) {
1282                         c.push_back(*i);
1283                 }
1284         }
1285
1286         return c;
1287 }
1288
1289 void
1290 TimeAxisView::build_size_menu ()
1291 {
1292         if (_size_menu && _size_menu->gobj ()) {
1293                 return;
1294         }
1295
1296         delete _size_menu;
1297
1298         using namespace Menu_Helpers;
1299
1300         _size_menu = new Menu;
1301         _size_menu->set_name ("ArdourContextMenu");
1302         MenuList& items = _size_menu->items();
1303
1304         items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1305         items.push_back (MenuElem (_("Larger"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1306         items.push_back (MenuElem (_("Large"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1307         items.push_back (MenuElem (_("Normal"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1308         items.push_back (MenuElem (_("Small"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1309 }
1310
1311 void
1312 TimeAxisView::reset_visual_state ()
1313 {
1314         /* this method is not required to trigger a global redraw */
1315
1316         string str = gui_property ("height");
1317         
1318         if (!str.empty()) {
1319                 set_height (atoi (str));
1320         } else {
1321                 set_height (preset_height (HeightNormal));
1322         }
1323 }
1324
1325 TrackViewList
1326 TrackViewList::filter_to_unique_playlists ()
1327 {
1328         std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1329         TrackViewList ts;
1330
1331         for (iterator i = begin(); i != end(); ++i) {
1332                 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1333                 if (!rtav) {
1334                         /* not a route: include it anyway */
1335                         ts.push_back (*i);
1336                 } else {
1337                         boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1338                         if (t) {
1339                                 if (playlists.insert (t->playlist()).second) {
1340                                         /* playlist not seen yet */
1341                                         ts.push_back (*i);
1342                                 }
1343                         } else {
1344                                 /* not a track: include it anyway */
1345                                 ts.push_back (*i);
1346                         }
1347                 }
1348         }
1349         return ts;
1350 }