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