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