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