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