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