2 Copyright (C) 2000 Paul Davis
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.
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.
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.
27 #include "pbd/error.h"
28 #include "pbd/convert.h"
29 #include "pbd/stacktrace.h"
31 #include <gtkmm2ext/doi.h>
32 #include <gtkmm2ext/utils.h>
33 #include <gtkmm2ext/selector.h>
35 #include "canvas/canvas.h"
36 #include "canvas/rectangle.h"
37 #include "canvas/debug.h"
39 #include "ardour_ui.h"
40 #include "ardour_dialog.h"
41 #include "global_signals.h"
42 #include "gui_thread.h"
43 #include "public_editor.h"
44 #include "time_axis_view.h"
45 #include "region_view.h"
46 #include "ghostregion.h"
47 #include "selection.h"
49 #include "rgb_macros.h"
51 #include "streamview.h"
52 #include "editor_drag.h"
60 using namespace ARDOUR;
61 using namespace ARDOUR_UI_UTILS;
63 using namespace Editing;
64 using namespace ArdourCanvas;
65 using Gtkmm2ext::Keyboard;
67 const double trim_handle_size = 6.0; /* pixels */
68 uint32_t TimeAxisView::button_height = 0;
69 uint32_t TimeAxisView::extra_height = 0;
70 int const TimeAxisView::_max_order = 512;
71 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
73 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
75 , controls_table (2, 3)
77 , _name_editing (false)
84 , in_destructor (false)
92 , _effective_height (0)
93 , _resize_drag_start (-1)
94 , _preresize_cursor (0)
95 , _have_preresize_cursor (false)
96 , _ebox_release_can_act (true)
98 if (extra_height == 0) {
102 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group (), ArdourCanvas::Duple (0.0, 0.0));
103 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
104 _canvas_display->hide(); // reveal as needed
106 selection_group = new ArdourCanvas::Container (_canvas_display);
107 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
108 selection_group->set_data (X_("timeselection"), (void *) 1);
109 selection_group->hide();
111 _ghost_group = new ArdourCanvas::Container (_canvas_display);
112 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
113 _ghost_group->lower_to_bottom();
114 _ghost_group->show();
116 name_label.set_name ("TrackLabel");
117 name_label.set_alignment (0.0, 0.5);
118 name_label.set_width_chars (12);
119 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
121 Gtk::Entry* an_entry = new Gtk::Entry;
122 Gtk::Requisition req;
123 an_entry->size_request (req);
124 name_label.set_size_request (-1, req.height);
125 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
128 name_hbox.pack_end (name_label, true, true);
132 controls_table.set_row_spacings (2);
133 controls_table.set_col_spacings (2);
134 controls_table.set_border_width (2);
135 controls_table.set_homogeneous (true);
137 controls_table.show_all ();
138 controls_table.set_no_show_all ();
140 name_table.attach (name_hbox, 0, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 3, 0);
141 name_table.show_all ();
142 name_table.set_no_show_all ();
144 HSeparator* separator = manage (new HSeparator());
145 separator->set_name("TrackSeparator");
146 separator->set_size_request(-1, 1);
149 name_vbox.pack_start (name_table, false, false);
152 controls_hbox.pack_start (controls_table, false, false);
153 controls_hbox.show ();
155 controls_hbox.pack_start (name_vbox, true, true);
156 controls_hbox.show ();
158 controls_vbox.pack_start (controls_hbox, false, false);
159 controls_vbox.show ();
161 top_hbox.pack_start (controls_vbox, true, true);
164 //controls_ebox.set_name ("TimeAxisViewControlsBaseUnselected");
165 controls_ebox.add (top_hbox);
166 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
167 Gdk::BUTTON_RELEASE_MASK|
168 Gdk::POINTER_MOTION_MASK|
169 Gdk::ENTER_NOTIFY_MASK|
170 Gdk::LEAVE_NOTIFY_MASK|
172 controls_ebox.set_flags (CAN_FOCUS);
174 /* note that this handler connects *before* the default handler */
175 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
176 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
177 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
178 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
179 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
180 controls_ebox.show ();
182 time_axis_vbox.pack_start (controls_ebox, true, true, 0);
183 time_axis_vbox.pack_end (*separator, false, false);
184 time_axis_vbox.show();
186 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
188 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
191 TimeAxisView::~TimeAxisView()
193 in_destructor = true;
195 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
199 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
201 delete (*i)->start_trim;
202 delete (*i)->end_trim;
206 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
208 delete (*i)->start_trim;
209 delete (*i)->end_trim;
212 delete selection_group;
215 delete _canvas_display;
225 TimeAxisView::hide ()
231 _canvas_display->hide ();
233 if (control_parent) {
234 control_parent->remove (time_axis_vbox);
241 /* now hide children */
243 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
247 /* if its hidden, it cannot be selected */
248 _editor.get_selection().remove (this);
249 /* and neither can its regions */
250 _editor.get_selection().remove_regions (this);
255 /** Display this TimeAxisView as the nth component of the parent box, at y.
257 * @param y y position.
258 * @param nth index for this TimeAxisView, increased if this view has children.
259 * @param parent parent component.
260 * @return height of this TimeAxisView.
263 TimeAxisView::show_at (double y, int& nth, VBox *parent)
265 if (control_parent) {
266 control_parent->reorder_child (time_axis_vbox, nth);
268 control_parent = parent;
269 parent->pack_start (time_axis_vbox, false, false);
270 parent->reorder_child (time_axis_vbox, nth);
275 if (_y_position != y) {
276 _canvas_display->set_y_position (y);
281 _canvas_display->raise_to_top ();
282 _canvas_display->show ();
286 _effective_height = current_height ();
288 /* now show relevant children */
290 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
291 if ((*i)->marked_for_display()) {
293 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
299 return _effective_height;
303 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
305 switch (ev->direction) {
307 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
308 /* See Editor::_stepping_axis_view for notes on this hack */
309 Editor& e = dynamic_cast<Editor&> (_editor);
310 if (!e.stepping_axis_view ()) {
311 e.set_stepping_axis_view (this);
313 e.stepping_axis_view()->step_height (false);
318 case GDK_SCROLL_DOWN:
319 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
320 /* See Editor::_stepping_axis_view for notes on this hack */
321 Editor& e = dynamic_cast<Editor&> (_editor);
322 if (!e.stepping_axis_view ()) {
323 e.set_stepping_axis_view (this);
325 e.stepping_axis_view()->step_height (true);
331 /* no handling for left/right, yet */
335 /* Just forward to the normal canvas scroll method. The coordinate
336 systems are different but since the canvas is always larger than the
337 track headers, and aligned with the trackview area, this will work.
339 In the not too distant future this layout is going away anyway and
340 headers will be on the canvas.
342 return _editor.canvas_scroll_event (ev, false);
346 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
348 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
349 /* see if it is inside the name label */
350 if (name_label.is_ancestor (controls_ebox)) {
353 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
354 Gtk::Allocation a = name_label.get_allocation ();
355 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
357 _ebox_release_can_act = false;
364 _ebox_release_can_act = true;
366 if (maybe_set_cursor (event->y) > 0) {
367 _resize_drag_start = event->y_root;
374 TimeAxisView::idle_resize (uint32_t h)
381 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
383 if (_resize_drag_start >= 0) {
385 /* (ab)use the DragManager to do autoscrolling - basically we
386 * are pretending that the drag is taking place over the canvas
387 * (which perhaps in the glorious future, when track headers
388 * and the canvas are unified, will actually be true.)
391 _editor.maybe_autoscroll (false, true, true);
393 /* now schedule the actual TAV resize */
394 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
395 _editor.add_to_idle_resize (this, delta);
396 _resize_drag_start = ev->y_root;
398 /* not dragging but ... */
399 maybe_set_cursor (ev->y);
402 gdk_event_request_motions(ev);
407 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
409 if (_have_preresize_cursor) {
410 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
411 _have_preresize_cursor = false;
417 TimeAxisView::maybe_set_cursor (int y)
419 /* XXX no Gtkmm Gdk::Window::get_cursor() */
420 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
422 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
424 /* y-coordinate in lower 25% */
426 if (!_have_preresize_cursor) {
427 _preresize_cursor = gdk_window_get_cursor (win->gobj());
428 _have_preresize_cursor = true;
429 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
434 } else if (_have_preresize_cursor) {
435 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
436 _have_preresize_cursor = false;
445 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
447 if (_resize_drag_start >= 0) {
448 if (_have_preresize_cursor) {
449 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
450 _preresize_cursor = 0;
451 _have_preresize_cursor = false;
453 _editor.stop_canvas_autoscroll ();
454 _resize_drag_start = -1;
457 if (!_ebox_release_can_act) {
461 switch (ev->button) {
463 selection_click (ev);
467 popup_display_menu (ev->time);
475 TimeAxisView::selection_click (GdkEventButton* ev)
477 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
478 _editor.set_selected_track (*this, op, false);
482 /** Steps through the defined heights for this TrackView.
483 * @param coarser true if stepping should decrease in size, otherwise false.
486 TimeAxisView::step_height (bool coarser)
488 static const uint32_t step = 25;
492 if (height <= preset_height (HeightSmall)) {
494 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
495 set_height_enum (HeightSmall);
497 set_height (height - step);
502 if (height <= preset_height(HeightSmall)) {
503 set_height_enum (HeightNormal);
505 set_height (height + step);
512 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
514 if (apply_to_selection) {
515 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
517 set_height (preset_height (h));
522 TimeAxisView::set_height (uint32_t h)
524 if (h < preset_height (HeightSmall)) {
525 h = preset_height (HeightSmall);
528 time_axis_vbox.property_height_request () = h;
532 snprintf (buf, sizeof (buf), "%u", height);
533 set_gui_property ("height", buf);
535 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
539 if (selection_group->visible ()) {
540 /* resize the selection rect */
541 show_selection (_editor.get_selection().time);
544 _editor.override_visible_track_count ();
548 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
550 /* steal escape, tabs from GTK */
552 switch (ev->keyval) {
554 case GDK_ISO_Left_Tab:
562 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
564 TrackViewList::iterator i;
566 switch (ev->keyval) {
568 end_name_edit (RESPONSE_CANCEL);
571 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
572 * generates a different ev->keyval, rather than setting
575 case GDK_ISO_Left_Tab:
576 end_name_edit (RESPONSE_APPLY);
580 end_name_edit (RESPONSE_ACCEPT);
590 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
592 end_name_edit (RESPONSE_OK);
597 TimeAxisView::begin_name_edit ()
603 if (can_edit_name()) {
605 name_entry = manage (new Gtkmm2ext::FocusEntry);
607 name_entry->set_width_chars(15);
609 name_entry->set_name ("EditorTrackNameDisplay");
610 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
611 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
612 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
613 name_entry->set_text (name_label.get_text());
614 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
616 if (name_label.is_ancestor (name_hbox)) {
617 name_hbox.remove (name_label);
620 name_hbox.pack_end (*name_entry, false, false);
623 name_entry->select_region (0, -1);
624 name_entry->set_state (STATE_SELECTED);
625 name_entry->grab_focus ();
626 name_entry->start_editing (0);
631 TimeAxisView::end_name_edit (int response)
637 bool edit_next = false;
638 bool edit_prev = false;
641 case RESPONSE_CANCEL:
644 name_entry_changed ();
646 case RESPONSE_ACCEPT:
647 name_entry_changed ();
650 name_entry_changed ();
654 /* this will delete the name_entry. but it will also drop focus, which
655 * will cause another callback to this function, so set name_entry = 0
656 * first to ensure we don't double-remove etc. etc.
659 Gtk::Entry* tmp = name_entry;
661 name_hbox.remove (*tmp);
663 /* put the name label back */
665 name_hbox.pack_end (name_label);
670 TrackViewList const & allviews = _editor.get_track_views ();
671 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
673 if (i != allviews.end()) {
676 if (++i == allviews.end()) {
680 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
682 if (rtav && rtav->route()->record_enabled()) {
686 if (!(*i)->hidden()) {
693 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
694 _editor.ensure_time_axis_view_is_visible (**i, false);
695 (*i)->begin_name_edit ();
698 } else if (edit_prev) {
700 TrackViewList const & allviews = _editor.get_track_views ();
701 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
703 if (i != allviews.begin()) {
705 if (i == allviews.begin()) {
711 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
713 if (rtav && rtav->route()->record_enabled()) {
717 if (!(*i)->hidden()) {
724 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
725 _editor.ensure_time_axis_view_is_visible (**i, false);
726 (*i)->begin_name_edit ();
732 TimeAxisView::name_entry_changed ()
737 TimeAxisView::can_edit_name () const
743 TimeAxisView::conditionally_add_to_selection ()
745 Selection& s (_editor.get_selection ());
747 if (!s.selected (this)) {
748 _editor.set_selected_track (*this, Selection::Set);
753 TimeAxisView::popup_display_menu (guint32 when)
755 conditionally_add_to_selection ();
757 build_display_menu ();
758 display_menu->popup (1, when);
762 TimeAxisView::set_selected (bool yn)
764 if (yn == _selected) {
768 Selectable::set_selected (yn);
771 controls_ebox.set_name (controls_base_selected_name);
772 time_axis_vbox.set_name (controls_base_selected_name);
773 controls_vbox.set_name (controls_base_selected_name);
775 controls_ebox.set_name (controls_base_unselected_name);
776 time_axis_vbox.set_name (controls_base_unselected_name);
777 controls_vbox.set_name (controls_base_unselected_name);
780 /* children will be set for the yn=true case. but when deselecting
781 the editor only has a list of top-level trackviews, so we
782 have to do this here.
785 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
786 (*i)->set_selected (false);
792 TimeAxisView::build_display_menu ()
794 using namespace Menu_Helpers;
798 display_menu = new Menu;
799 display_menu->set_name ("ArdourContextMenu");
801 // Just let implementing classes define what goes into the manu
805 TimeAxisView::set_samples_per_pixel (double fpp)
807 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
808 (*i)->set_samples_per_pixel (fpp);
813 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
815 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
816 (*i)->show_timestretch (start, end, layers, layer);
821 TimeAxisView::hide_timestretch ()
823 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
824 (*i)->hide_timestretch ();
829 TimeAxisView::show_selection (TimeSelection& ts)
836 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
837 (*i)->show_selection (ts);
840 if (selection_group->visible ()) {
841 while (!used_selection_rects.empty()) {
842 free_selection_rects.push_front (used_selection_rects.front());
843 used_selection_rects.pop_front();
844 free_selection_rects.front()->rect->hide();
845 free_selection_rects.front()->start_trim->hide();
846 free_selection_rects.front()->end_trim->hide();
848 selection_group->hide();
851 selection_group->show();
852 selection_group->raise_to_top();
854 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
855 framepos_t start, end;
860 cnt = end - start + 1;
862 rect = get_selection_rect ((*i).id);
864 x1 = _editor.sample_to_pixel (start);
865 x2 = _editor.sample_to_pixel (start + cnt - 1);
866 y2 = current_height() - 1;
868 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
870 // trim boxes are at the top for selections
873 rect->start_trim->set (ArdourCanvas::Rect (x1, 1, x1 + trim_handle_size, y2));
874 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
876 rect->start_trim->show();
877 rect->end_trim->show();
879 rect->start_trim->hide();
880 rect->end_trim->hide();
884 used_selection_rects.push_back (rect);
889 TimeAxisView::reshow_selection (TimeSelection& ts)
893 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
894 (*i)->show_selection (ts);
899 TimeAxisView::hide_selection ()
901 if (selection_group->visible ()) {
902 while (!used_selection_rects.empty()) {
903 free_selection_rects.push_front (used_selection_rects.front());
904 used_selection_rects.pop_front();
905 free_selection_rects.front()->rect->hide();
906 free_selection_rects.front()->start_trim->hide();
907 free_selection_rects.front()->end_trim->hide();
909 selection_group->hide();
912 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
913 (*i)->hide_selection ();
918 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
920 /* find the selection rect this is for. we have the item corresponding to one
924 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
925 if ((*i)->start_trim == item || (*i)->end_trim == item) {
927 /* make one trim handle be "above" the other so that if they overlap,
928 the top one is the one last used.
931 (*i)->rect->raise_to_top ();
932 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
933 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
941 TimeAxisView::get_selection_rect (uint32_t id)
945 /* check to see if we already have a visible rect for this particular selection ID */
947 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
948 if ((*i)->id == id) {
953 /* ditto for the free rect list */
955 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
956 if ((*i)->id == id) {
957 SelectionRect* ret = (*i);
958 free_selection_rects.erase (i);
963 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
965 if (free_selection_rects.empty()) {
967 rect = new SelectionRect;
969 rect->rect = new ArdourCanvas::Rectangle (selection_group);
970 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
971 rect->rect->set_outline (false);
972 rect->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
974 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
975 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
976 rect->start_trim->set_outline (false);
977 rect->start_trim->set_fill (false);
979 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
980 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
981 rect->end_trim->set_outline (false);
982 rect->end_trim->set_fill (false);
984 free_selection_rects.push_front (rect);
986 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
987 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
988 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
991 rect = free_selection_rects.front();
993 free_selection_rects.pop_front();
997 struct null_deleter { void operator()(void const *) const {} };
1000 TimeAxisView::is_child (TimeAxisView* tav)
1002 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1006 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1008 children.push_back (child);
1012 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1014 Children::iterator i;
1016 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1021 /** Get selectable things within a given range.
1022 * @param start Start time in session frames.
1023 * @param end End time in session frames.
1024 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1025 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1026 * @param result Filled in with selectable things.
1029 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1035 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1041 TimeAxisView::add_ghost (RegionView* rv)
1043 GhostRegion* gr = rv->add_ghost (*this);
1046 ghosts.push_back(gr);
1051 TimeAxisView::remove_ghost (RegionView* rv)
1053 rv->remove_ghost_in (*this);
1057 TimeAxisView::erase_ghost (GhostRegion* gr)
1059 if (in_destructor) {
1063 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1072 TimeAxisView::touched (double top, double bot)
1074 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1075 y_position is the "origin" or "top" of the track.
1078 double mybot = _y_position + current_height();
1080 return ((_y_position <= bot && _y_position >= top) ||
1081 ((mybot <= bot) && (top < mybot)) ||
1082 (mybot >= bot && _y_position < top));
1086 TimeAxisView::set_parent (TimeAxisView& p)
1092 TimeAxisView::reset_height ()
1094 set_height (height);
1096 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1097 (*i)->set_height ((*i)->height);
1102 TimeAxisView::compute_heights ()
1104 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1105 Gtk::Table two_row_table (2, 8);
1106 Gtk::Table one_row_table (1, 8);
1108 const int border_width = 2;
1110 const int separator_height = 2;
1111 extra_height = (2 * border_width) + separator_height;
1113 window.add (one_row_table);
1115 one_row_table.set_border_width (border_width);
1116 one_row_table.set_row_spacings (0);
1117 one_row_table.set_col_spacings (0);
1118 one_row_table.set_homogeneous (true);
1120 two_row_table.set_border_width (border_width);
1121 two_row_table.set_row_spacings (0);
1122 two_row_table.set_col_spacings (0);
1123 two_row_table.set_homogeneous (true);
1125 for (int i = 0; i < 5; ++i) {
1126 buttons[i] = manage (new Button (X_("f")));
1127 buttons[i]->set_name ("TrackMuteButton");
1130 one_row_table.attach (*buttons[0], 6, 7, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
1132 one_row_table.show_all ();
1133 Gtk::Requisition req(one_row_table.size_request ());
1135 // height required to show 1 row of buttons
1136 button_height = req.height;
1140 TimeAxisView::color_handler ()
1142 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1146 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1148 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1149 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1151 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1152 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1154 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1155 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1158 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1160 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1161 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1163 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1164 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1166 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1167 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1171 /** @return Pair: TimeAxisView, layer index.
1172 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1173 * does. @param y is an offset from the top of the trackview area.
1175 * If the covering object is a child axis, then the child is returned.
1176 * TimeAxisView is 0 otherwise.
1178 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1179 * and is in stacked or expanded * region display mode, otherwise 0.
1181 std::pair<TimeAxisView*, double>
1182 TimeAxisView::covers_y_position (double y) const
1185 return std::make_pair ((TimeAxisView *) 0, 0);
1188 if (_y_position <= y && y < (_y_position + height)) {
1190 /* work out the layer index if appropriate */
1192 switch (layer_display ()) {
1198 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1199 /* clamp to max layers to be on the safe side; sometimes the above calculation
1200 returns a too-high value */
1201 if (l >= view()->layers ()) {
1202 l = view()->layers() - 1;
1208 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1210 if (l >= (view()->layers() - 0.5)) {
1211 l = view()->layers() - 0.5;
1217 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1220 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1222 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1228 return std::make_pair ((TimeAxisView *) 0, 0);
1232 TimeAxisView::covered_by_y_range (double y0, double y1) const
1238 /* if either the top or bottom of the axisview is in the vertical
1239 * range, we cover it.
1242 if ((y0 < _y_position && y1 < _y_position) ||
1243 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1247 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1248 if ((*i)->covered_by_y_range (y0, y1)) {
1257 TimeAxisView::preset_height (Height h)
1261 return (button_height * 2) + extra_height + 260;
1263 return (button_height * 2) + extra_height + 160;
1265 return (button_height * 2) + extra_height + 60;
1267 return (button_height * 2) + extra_height + 10;
1269 return button_height + extra_height;
1276 /** @return Child time axis views that are not hidden */
1277 TimeAxisView::Children
1278 TimeAxisView::get_child_list ()
1282 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1283 if (!(*i)->hidden()) {
1292 TimeAxisView::build_size_menu ()
1294 if (_size_menu && _size_menu->gobj ()) {
1300 using namespace Menu_Helpers;
1302 _size_menu = new Menu;
1303 _size_menu->set_name ("ArdourContextMenu");
1304 MenuList& items = _size_menu->items();
1306 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1307 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1308 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1309 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1310 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1314 TimeAxisView::reset_visual_state ()
1316 /* this method is not required to trigger a global redraw */
1318 string str = gui_property ("height");
1321 set_height (atoi (str));
1323 set_height (preset_height (HeightNormal));
1328 TrackViewList::filter_to_unique_playlists ()
1330 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1333 for (iterator i = begin(); i != end(); ++i) {
1334 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1336 /* not a route: include it anyway */
1339 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1341 if (playlists.insert (t->playlist()).second) {
1342 /* playlist not seen yet */
1346 /* not a track: include it anyway */