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