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