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