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