8ddc2e51ca6faa11757bfd09924b29ef13831a80
[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 SelectionRect *
933 TimeAxisView::get_selection_rect (uint32_t id)
934 {
935         SelectionRect *rect;
936
937         /* check to see if we already have a visible rect for this particular selection ID */
938
939         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
940                 if ((*i)->id == id) {
941                         return (*i);
942                 }
943         }
944
945         /* ditto for the free rect list */
946
947         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
948                 if ((*i)->id == id) {
949                         SelectionRect* ret = (*i);
950                         free_selection_rects.erase (i);
951                         return ret;
952                 }
953         }
954
955         /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
956
957         if (free_selection_rects.empty()) {
958
959                 rect = new SelectionRect;
960
961                 rect->rect = new ArdourCanvas::Rectangle (selection_group);
962                 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
963                 rect->rect->set_outline (false);
964                 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
965
966                 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
967                 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
968                 rect->start_trim->set_outline (false);
969                 rect->start_trim->set_fill (false);
970
971                 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
972                 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
973                 rect->end_trim->set_outline (false);
974                 rect->end_trim->set_fill (false);
975
976                 free_selection_rects.push_front (rect);
977
978                 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
979                 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
980                 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
981         }
982
983         rect = free_selection_rects.front();
984         rect->id = id;
985         free_selection_rects.pop_front();
986         return rect;
987 }
988
989 struct null_deleter { void operator()(void const *) const {} };
990
991 bool
992 TimeAxisView::is_child (TimeAxisView* tav)
993 {
994         return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
995 }
996
997 void
998 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
999 {
1000         children.push_back (child);
1001 }
1002
1003 void
1004 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1005 {
1006         Children::iterator i;
1007
1008         if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1009                 children.erase (i);
1010         }
1011 }
1012
1013 /** Get selectable things within a given range.
1014  *  @param start Start time in session frames.
1015  *  @param end End time in session frames.
1016  *  @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1017  *  @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1018  *  @param result Filled in with selectable things.
1019  */
1020 void
1021 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1022 {
1023         return;
1024 }
1025
1026 void
1027 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1028 {
1029         return;
1030 }
1031
1032 void
1033 TimeAxisView::add_ghost (RegionView* rv)
1034 {
1035         GhostRegion* gr = rv->add_ghost (*this);
1036
1037         if (gr) {
1038                 ghosts.push_back(gr);
1039         }
1040 }
1041
1042 void
1043 TimeAxisView::remove_ghost (RegionView* rv)
1044 {
1045         rv->remove_ghost_in (*this);
1046 }
1047
1048 void
1049 TimeAxisView::erase_ghost (GhostRegion* gr)
1050 {
1051         if (in_destructor) {
1052                 return;
1053         }
1054
1055         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1056                 if ((*i) == gr) {
1057                         ghosts.erase (i);
1058                         break;
1059                 }
1060         }
1061 }
1062
1063 bool
1064 TimeAxisView::touched (double top, double bot)
1065 {
1066         /* remember: this is X Window - coordinate space starts in upper left and moves down.
1067           y_position is the "origin" or "top" of the track.
1068         */
1069
1070         double mybot = _y_position + current_height();
1071
1072         return ((_y_position <= bot && _y_position >= top) ||
1073                 ((mybot <= bot) && (top < mybot)) ||
1074                 (mybot >= bot && _y_position < top));
1075 }
1076
1077 void
1078 TimeAxisView::set_parent (TimeAxisView& p)
1079 {
1080         parent = &p;
1081 }
1082
1083 void
1084 TimeAxisView::reset_height ()
1085 {
1086         set_height (height);
1087
1088         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1089                 (*i)->set_height ((*i)->height);
1090         }
1091 }
1092
1093 void
1094 TimeAxisView::compute_heights ()
1095 {
1096         // TODO this function should be re-evaluated when font-scaling changes (!)
1097         Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1098         Gtk::Table one_row_table (1, 1);
1099         ArdourButton* test_button = manage (new ArdourButton);
1100         const int border_width = 2;
1101         const int frame_height = 2;
1102         extra_height = (2 * border_width) + frame_height;
1103
1104         window.add (one_row_table);
1105         test_button->set_name ("mute button");
1106         test_button->set_text (S_("Mute|M"));
1107         test_button->set_tweaks (ArdourButton::TrackHeader);
1108
1109         one_row_table.set_border_width (border_width);
1110         one_row_table.set_row_spacings (2);
1111         one_row_table.set_col_spacings (2);
1112
1113         one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1114         one_row_table.show_all ();
1115
1116         Gtk::Requisition req(one_row_table.size_request ());
1117         button_height = req.height;
1118 }
1119
1120 void
1121 TimeAxisView::color_handler ()
1122 {
1123         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1124                 (*i)->set_colors();
1125         }
1126
1127         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1128
1129                 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1130                 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1131
1132                 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1133                 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1134
1135                 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1136                 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1137         }
1138
1139         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1140
1141                 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1142                 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1143
1144                 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1145                 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1146
1147                 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1148                 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1149         }
1150 }
1151
1152 /** @return Pair: TimeAxisView, layer index.
1153  * TimeAxisView is non-0 if this object covers @param y, or one of its children
1154  * does. @param y is an offset from the top of the trackview area.
1155  *
1156  * If the covering object is a child axis, then the child is returned.
1157  * TimeAxisView is 0 otherwise.
1158  *
1159  * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1160  * and is in stacked or expanded * region display mode, otherwise 0.
1161  */
1162 std::pair<TimeAxisView*, double>
1163 TimeAxisView::covers_y_position (double y) const
1164 {
1165         if (hidden()) {
1166                 return std::make_pair ((TimeAxisView *) 0, 0);
1167         }
1168
1169         if (_y_position <= y && y < (_y_position + height)) {
1170
1171                 /* work out the layer index if appropriate */
1172                 double l = 0;
1173                 switch (layer_display ()) {
1174                 case Overlaid:
1175                         break;
1176                 case Stacked:
1177                         if (view ()) {
1178                                 /* compute layer */
1179                                 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1180                                 /* clamp to max layers to be on the safe side; sometimes the above calculation
1181                                    returns a too-high value */
1182                                 if (l >= view()->layers ()) {
1183                                         l = view()->layers() - 1;
1184                                 }
1185                         }
1186                         break;
1187                 case Expanded:
1188                         if (view ()) {
1189                                 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1190                                 l = n * 0.5 - 0.5;
1191                                 if (l >= (view()->layers() - 0.5)) {
1192                                         l = view()->layers() - 0.5;
1193                                 }
1194                         }
1195                         break;
1196                 }
1197
1198                 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1199         }
1200
1201         for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1202
1203                 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1204                 if (r.first) {
1205                         return r;
1206                 }
1207         }
1208
1209         return std::make_pair ((TimeAxisView *) 0, 0);
1210 }
1211
1212 bool
1213 TimeAxisView::covered_by_y_range (double y0, double y1) const
1214 {
1215         if (hidden()) {
1216                 return false;
1217         }
1218
1219         /* if either the top or bottom of the axisview is in the vertical
1220          * range, we cover it.
1221          */
1222
1223         if ((y0 < _y_position && y1 < _y_position) ||
1224             (y0 >= _y_position + height && y1 >= _y_position + height)) {
1225                 return false;
1226         }
1227
1228         for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1229                 if ((*i)->covered_by_y_range (y0, y1)) {
1230                         return true;
1231                 }
1232         }
1233
1234         return true;
1235 }
1236
1237 uint32_t
1238 TimeAxisView::preset_height (Height h)
1239 {
1240         switch (h) {
1241         case HeightLargest:
1242                 return (button_height * 2) + extra_height + 260;
1243         case HeightLarger:
1244                 return (button_height * 2) + extra_height + 160;
1245         case HeightLarge:
1246                 return (button_height * 2) + extra_height + 60;
1247         case HeightNormal:
1248                 return (button_height * 2) + extra_height + 10;
1249         case HeightSmall:
1250                 return button_height + extra_height;
1251         }
1252
1253         abort(); /* NOTREACHED */
1254         return 0;
1255 }
1256
1257 /** @return Child time axis views that are not hidden */
1258 TimeAxisView::Children
1259 TimeAxisView::get_child_list ()
1260 {
1261         Children c;
1262
1263         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1264                 if (!(*i)->hidden()) {
1265                         c.push_back(*i);
1266                 }
1267         }
1268
1269         return c;
1270 }
1271
1272 void
1273 TimeAxisView::build_size_menu ()
1274 {
1275         if (_size_menu && _size_menu->gobj ()) {
1276                 return;
1277         }
1278
1279         delete _size_menu;
1280
1281         using namespace Menu_Helpers;
1282
1283         _size_menu = new Menu;
1284         _size_menu->set_name ("ArdourContextMenu");
1285         MenuList& items = _size_menu->items();
1286
1287         items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1288         items.push_back (MenuElem (_("Larger"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1289         items.push_back (MenuElem (_("Large"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1290         items.push_back (MenuElem (_("Normal"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1291         items.push_back (MenuElem (_("Small"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1292 }
1293
1294 void
1295 TimeAxisView::reset_visual_state ()
1296 {
1297         /* this method is not required to trigger a global redraw */
1298
1299         string str = gui_property ("height");
1300
1301         if (!str.empty()) {
1302                 set_height (atoi (str));
1303         } else {
1304                 set_height (preset_height (HeightNormal));
1305         }
1306 }
1307
1308 TrackViewList
1309 TrackViewList::filter_to_unique_playlists ()
1310 {
1311         std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1312         TrackViewList ts;
1313
1314         for (iterator i = begin(); i != end(); ++i) {
1315                 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1316                 if (!rtav) {
1317                         /* not a route: include it anyway */
1318                         ts.push_back (*i);
1319                 } else {
1320                         boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1321                         if (t) {
1322                                 if (playlists.insert (t->playlist()).second) {
1323                                         /* playlist not seen yet */
1324                                         ts.push_back (*i);
1325                                 }
1326                         } else {
1327                                 /* not a track: include it anyway */
1328                                 ts.push_back (*i);
1329                         }
1330                 }
1331         }
1332         return ts;
1333 }