fix background of ArdourButtons with no Body
[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 "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::show_selected ()
746 {
747         if (selected()) {
748                 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
749                 time_axis_frame.set_name ("MixerStripSelectedFrame");
750                 controls_ebox.set_name (controls_base_selected_name);
751                 controls_vbox.set_name (controls_base_selected_name);
752                 time_axis_vbox.set_name (controls_base_selected_name);
753         } else {
754                 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
755                 time_axis_frame.set_name (controls_base_unselected_name);
756                 controls_ebox.set_name (controls_base_unselected_name);
757                 controls_vbox.set_name (controls_base_unselected_name);
758                 time_axis_vbox.set_name (controls_base_unselected_name);
759
760                 hide_selection ();
761
762                 /* children will be set for the yn=true case. but when deselecting
763                    the editor only has a list of top-level trackviews, so we
764                    have to do this here.
765                 */
766
767                 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
768                         (*i)->set_selected (false);
769                 }
770         }
771
772         time_axis_frame.show();
773 }
774
775 void
776 TimeAxisView::build_display_menu ()
777 {
778         using namespace Menu_Helpers;
779
780         delete display_menu;
781
782         display_menu = new Menu;
783         display_menu->set_name ("ArdourContextMenu");
784
785         // Just let implementing classes define what goes into the manu
786 }
787
788 void
789 TimeAxisView::set_samples_per_pixel (double fpp)
790 {
791         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
792                 (*i)->set_samples_per_pixel (fpp);
793         }
794 }
795
796 void
797 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
798 {
799         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
800                 (*i)->show_timestretch (start, end, layers, layer);
801         }
802 }
803
804 void
805 TimeAxisView::hide_timestretch ()
806 {
807         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
808                 (*i)->hide_timestretch ();
809         }
810 }
811
812 void
813 TimeAxisView::show_selection (TimeSelection& ts)
814 {
815         double x1;
816         double x2;
817         double y2;
818         SelectionRect *rect;    time_axis_frame.show();
819
820
821         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
822                 (*i)->show_selection (ts);
823         }
824
825         if (selection_group->visible ()) {
826                 while (!used_selection_rects.empty()) {
827                         free_selection_rects.push_front (used_selection_rects.front());
828                         used_selection_rects.pop_front();
829                         free_selection_rects.front()->rect->hide();
830                         free_selection_rects.front()->start_trim->hide();
831                         free_selection_rects.front()->end_trim->hide();
832                 }
833                 selection_group->hide();
834         }
835
836         selection_group->show();
837         selection_group->raise_to_top();
838
839         for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
840                 framepos_t start, end;
841                 framecnt_t cnt;
842
843                 start = (*i).start;
844                 end = (*i).end;
845                 cnt = end - start + 1;
846
847                 rect = get_selection_rect ((*i).id);
848
849                 x1 = _editor.sample_to_pixel (start);
850                 x2 = _editor.sample_to_pixel (start + cnt - 1);
851                 y2 = current_height() - 1;
852
853                 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
854
855                 // trim boxes are at the top for selections
856
857                 if (x2 > x1) {
858                         rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
859                         rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
860
861                         rect->start_trim->show();
862                         rect->end_trim->show();
863                 } else {
864                         rect->start_trim->hide();
865                         rect->end_trim->hide();
866                 }
867
868                 rect->rect->show ();
869                 used_selection_rects.push_back (rect);
870         }
871 }
872
873 void
874 TimeAxisView::reshow_selection (TimeSelection& ts)
875 {
876         show_selection (ts);
877
878         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
879                 (*i)->show_selection (ts);
880         }
881 }
882
883 void
884 TimeAxisView::hide_selection ()
885 {
886         if (selection_group->visible ()) {
887                 while (!used_selection_rects.empty()) {
888                         free_selection_rects.push_front (used_selection_rects.front());
889                         used_selection_rects.pop_front();
890                         free_selection_rects.front()->rect->hide();
891                         free_selection_rects.front()->start_trim->hide();
892                         free_selection_rects.front()->end_trim->hide();
893                 }
894                 selection_group->hide();
895         }
896
897         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
898                 (*i)->hide_selection ();
899         }
900 }
901
902 void
903 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
904 {
905         /* find the selection rect this is for. we have the item corresponding to one
906            of the trim handles.
907          */
908
909         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
910                 if ((*i)->start_trim == item || (*i)->end_trim == item) {
911
912                         /* make one trim handle be "above" the other so that if they overlap,
913                            the top one is the one last used.
914                         */
915
916                         (*i)->rect->raise_to_top ();
917                         (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
918                         (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
919
920                         break;
921                 }
922         }
923 }
924
925 SelectionRect *
926 TimeAxisView::get_selection_rect (uint32_t id)
927 {
928         SelectionRect *rect;
929
930         /* check to see if we already have a visible rect for this particular selection ID */
931
932         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
933                 if ((*i)->id == id) {
934                         return (*i);
935                 }
936         }
937
938         /* ditto for the free rect list */
939
940         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
941                 if ((*i)->id == id) {
942                         SelectionRect* ret = (*i);
943                         free_selection_rects.erase (i);
944                         return ret;
945                 }
946         }
947
948         /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
949
950         if (free_selection_rects.empty()) {
951
952                 rect = new SelectionRect;
953
954                 rect->rect = new ArdourCanvas::Rectangle (selection_group);
955                 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
956                 rect->rect->set_outline (false);
957                 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
958
959                 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
960                 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
961                 rect->start_trim->set_outline (false);
962                 rect->start_trim->set_fill (false);
963
964                 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
965                 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
966                 rect->end_trim->set_outline (false);
967                 rect->end_trim->set_fill (false);
968
969                 free_selection_rects.push_front (rect);
970
971                 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
972                 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
973                 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
974         }
975
976         rect = free_selection_rects.front();
977         rect->id = id;
978         free_selection_rects.pop_front();
979         return rect;
980 }
981
982 struct null_deleter { void operator()(void const *) const {} };
983
984 bool
985 TimeAxisView::is_child (TimeAxisView* tav)
986 {
987         return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
988 }
989
990 void
991 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
992 {
993         children.push_back (child);
994 }
995
996 void
997 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
998 {
999         Children::iterator i;
1000
1001         if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1002                 children.erase (i);
1003         }
1004 }
1005
1006 /** Get selectable things within a given range.
1007  *  @param start Start time in session frames.
1008  *  @param end End time in session frames.
1009  *  @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1010  *  @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1011  *  @param result Filled in with selectable things.
1012  */
1013 void
1014 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1015 {
1016         return;
1017 }
1018
1019 void
1020 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1021 {
1022         return;
1023 }
1024
1025 void
1026 TimeAxisView::add_ghost (RegionView* rv)
1027 {
1028         GhostRegion* gr = rv->add_ghost (*this);
1029
1030         if (gr) {
1031                 ghosts.push_back(gr);
1032         }
1033 }
1034
1035 void
1036 TimeAxisView::remove_ghost (RegionView* rv)
1037 {
1038         rv->remove_ghost_in (*this);
1039 }
1040
1041 void
1042 TimeAxisView::erase_ghost (GhostRegion* gr)
1043 {
1044         if (in_destructor) {
1045                 return;
1046         }
1047
1048         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1049                 if ((*i) == gr) {
1050                         ghosts.erase (i);
1051                         break;
1052                 }
1053         }
1054 }
1055
1056 bool
1057 TimeAxisView::touched (double top, double bot)
1058 {
1059         /* remember: this is X Window - coordinate space starts in upper left and moves down.
1060           y_position is the "origin" or "top" of the track.
1061         */
1062
1063         double mybot = _y_position + current_height();
1064
1065         return ((_y_position <= bot && _y_position >= top) ||
1066                 ((mybot <= bot) && (top < mybot)) ||
1067                 (mybot >= bot && _y_position < top));
1068 }
1069
1070 void
1071 TimeAxisView::set_parent (TimeAxisView& p)
1072 {
1073         parent = &p;
1074 }
1075
1076 void
1077 TimeAxisView::reset_height ()
1078 {
1079         set_height (height);
1080
1081         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1082                 (*i)->set_height ((*i)->height);
1083         }
1084 }
1085
1086 void
1087 TimeAxisView::compute_heights ()
1088 {
1089         // TODO this function should be re-evaluated when font-scaling changes (!)
1090         Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1091         Gtk::Table one_row_table (1, 1);
1092         ArdourButton* test_button = manage (new ArdourButton);
1093         const int border_width = 2;
1094         const int frame_height = 2;
1095         extra_height = (2 * border_width) + frame_height;
1096
1097         window.add (one_row_table);
1098         test_button->set_name ("mute button");
1099         test_button->set_text (S_("Mute|M"));
1100         test_button->set_tweaks (ArdourButton::TrackHeader);
1101
1102         one_row_table.set_border_width (border_width);
1103         one_row_table.set_row_spacings (2);
1104         one_row_table.set_col_spacings (2);
1105
1106         one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1107         one_row_table.show_all ();
1108
1109         Gtk::Requisition req(one_row_table.size_request ());
1110         button_height = req.height;
1111 }
1112
1113 void
1114 TimeAxisView::color_handler ()
1115 {
1116         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1117                 (*i)->set_colors();
1118         }
1119
1120         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1121
1122                 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1123                 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1124
1125                 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1126                 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1127
1128                 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1129                 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1130         }
1131
1132         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1133
1134                 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1135                 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1136
1137                 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1138                 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1139
1140                 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1141                 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1142         }
1143 }
1144
1145 /** @return Pair: TimeAxisView, layer index.
1146  * TimeAxisView is non-0 if this object covers @param y, or one of its children
1147  * does. @param y is an offset from the top of the trackview area.
1148  *
1149  * If the covering object is a child axis, then the child is returned.
1150  * TimeAxisView is 0 otherwise.
1151  *
1152  * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1153  * and is in stacked or expanded * region display mode, otherwise 0.
1154  */
1155 std::pair<TimeAxisView*, double>
1156 TimeAxisView::covers_y_position (double y) const
1157 {
1158         if (hidden()) {
1159                 return std::make_pair ((TimeAxisView *) 0, 0);
1160         }
1161
1162         if (_y_position <= y && y < (_y_position + height)) {
1163
1164                 /* work out the layer index if appropriate */
1165                 double l = 0;
1166                 switch (layer_display ()) {
1167                 case Overlaid:
1168                         break;
1169                 case Stacked:
1170                         if (view ()) {
1171                                 /* compute layer */
1172                                 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1173                                 /* clamp to max layers to be on the safe side; sometimes the above calculation
1174                                    returns a too-high value */
1175                                 if (l >= view()->layers ()) {
1176                                         l = view()->layers() - 1;
1177                                 }
1178                         }
1179                         break;
1180                 case Expanded:
1181                         if (view ()) {
1182                                 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1183                                 l = n * 0.5 - 0.5;
1184                                 if (l >= (view()->layers() - 0.5)) {
1185                                         l = view()->layers() - 0.5;
1186                                 }
1187                         }
1188                         break;
1189                 }
1190
1191                 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1192         }
1193
1194         for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1195
1196                 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1197                 if (r.first) {
1198                         return r;
1199                 }
1200         }
1201
1202         return std::make_pair ((TimeAxisView *) 0, 0);
1203 }
1204
1205 bool
1206 TimeAxisView::covered_by_y_range (double y0, double y1) const
1207 {
1208         if (hidden()) {
1209                 return false;
1210         }
1211
1212         /* if either the top or bottom of the axisview is in the vertical
1213          * range, we cover it.
1214          */
1215
1216         if ((y0 < _y_position && y1 < _y_position) ||
1217             (y0 >= _y_position + height && y1 >= _y_position + height)) {
1218                 return false;
1219         }
1220
1221         for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1222                 if ((*i)->covered_by_y_range (y0, y1)) {
1223                         return true;
1224                 }
1225         }
1226
1227         return true;
1228 }
1229
1230 uint32_t
1231 TimeAxisView::preset_height (Height h)
1232 {
1233         switch (h) {
1234         case HeightLargest:
1235                 return (button_height * 2) + extra_height + 260;
1236         case HeightLarger:
1237                 return (button_height * 2) + extra_height + 160;
1238         case HeightLarge:
1239                 return (button_height * 2) + extra_height + 60;
1240         case HeightNormal:
1241                 return (button_height * 2) + extra_height + 10;
1242         case HeightSmall:
1243                 return button_height + extra_height;
1244         }
1245
1246         abort(); /* NOTREACHED */
1247         return 0;
1248 }
1249
1250 /** @return Child time axis views that are not hidden */
1251 TimeAxisView::Children
1252 TimeAxisView::get_child_list ()
1253 {
1254         Children c;
1255
1256         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1257                 if (!(*i)->hidden()) {
1258                         c.push_back(*i);
1259                 }
1260         }
1261
1262         return c;
1263 }
1264
1265 void
1266 TimeAxisView::build_size_menu ()
1267 {
1268         if (_size_menu && _size_menu->gobj ()) {
1269                 return;
1270         }
1271
1272         delete _size_menu;
1273
1274         using namespace Menu_Helpers;
1275
1276         _size_menu = new Menu;
1277         _size_menu->set_name ("ArdourContextMenu");
1278         MenuList& items = _size_menu->items();
1279
1280         items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1281         items.push_back (MenuElem (_("Larger"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1282         items.push_back (MenuElem (_("Large"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1283         items.push_back (MenuElem (_("Normal"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1284         items.push_back (MenuElem (_("Small"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1285 }
1286
1287 void
1288 TimeAxisView::reset_visual_state ()
1289 {
1290         /* this method is not required to trigger a global redraw */
1291
1292         string str = gui_property ("height");
1293
1294         if (!str.empty()) {
1295                 set_height (atoi (str));
1296         } else {
1297                 set_height (preset_height (HeightNormal));
1298         }
1299 }
1300
1301 TrackViewList
1302 TrackViewList::filter_to_unique_playlists ()
1303 {
1304         std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1305         TrackViewList ts;
1306
1307         for (iterator i = begin(); i != end(); ++i) {
1308                 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1309                 if (!rtav) {
1310                         /* not a route: include it anyway */
1311                         ts.push_back (*i);
1312                 } else {
1313                         boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1314                         if (t) {
1315                                 if (playlists.insert (t->playlist()).second) {
1316                                         /* playlist not seen yet */
1317                                         ts.push_back (*i);
1318                                 }
1319                         } else {
1320                                 /* not a track: include it anyway */
1321                                 ts.push_back (*i);
1322                         }
1323                 }
1324         }
1325         return ts;
1326 }