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"
38 #include "canvas/utils.h"
39 #include "canvas/colors.h"
41 #include "ardour/profile.h"
43 #include "ardour_ui.h"
44 #include "ardour_dialog.h"
45 #include "global_signals.h"
46 #include "gui_thread.h"
47 #include "public_editor.h"
48 #include "time_axis_view.h"
49 #include "region_view.h"
50 #include "ghostregion.h"
51 #include "selection.h"
53 #include "rgb_macros.h"
55 #include "streamview.h"
56 #include "editor_drag.h"
64 using namespace ARDOUR;
65 using namespace ARDOUR_UI_UTILS;
67 using namespace Editing;
68 using namespace ArdourCanvas;
69 using Gtkmm2ext::Keyboard;
71 #define TOP_LEVEL_WIDGET controls_ebox
73 const double trim_handle_size = 6.0; /* pixels */
74 uint32_t TimeAxisView::button_height = 0;
75 uint32_t TimeAxisView::extra_height = 0;
76 int const TimeAxisView::_max_order = 512;
77 unsigned int TimeAxisView::name_width_px = 100; // TODO adjust with font-scaling on style-change
78 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
79 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
80 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
82 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
84 , controls_table (3, 3)
85 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
86 , _name_editing (false)
93 , in_destructor (false)
101 , _effective_height (0)
102 , _resize_drag_start (-1)
103 , _did_resize (false)
104 , _preresize_cursor (0)
105 , _have_preresize_cursor (false)
106 , _ebox_release_can_act (true)
108 if (!controls_meters_size_group) {
109 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
111 if (!midi_scroomer_size_group) {
112 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
114 if (extra_height == 0) {
118 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
119 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
120 _canvas_display->hide(); // reveal as needed
122 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
123 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
124 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
125 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
126 _canvas_separator->set_outline_width(1.0);
127 _canvas_separator->hide();
129 selection_group = new ArdourCanvas::Container (_canvas_display);
130 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
131 selection_group->set_data (X_("timeselection"), (void *) 1);
132 selection_group->hide();
134 _ghost_group = new ArdourCanvas::Container (_canvas_display);
135 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
136 _ghost_group->lower_to_bottom();
137 _ghost_group->show();
139 name_label.set_name ("TrackLabel");
140 name_label.set_alignment (0.0, 0.5);
141 name_label.set_width_chars (12);
142 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
144 Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
145 an_entry->set_name ("EditorTrackNameDisplay");
146 Gtk::Requisition req;
147 an_entry->size_request (req);
148 name_label.set_size_request (-1, req.height);
149 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
152 name_hbox.pack_end (name_label, true, true);
154 // set min. track-header width if fader is not visible
155 name_hbox.set_size_request(name_width_px, -1);
160 controls_table.set_row_spacings (2);
161 controls_table.set_col_spacings (2);
162 controls_table.set_border_width (2);
164 if (ARDOUR::Profile->get_mixbus() ) {
165 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
167 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
169 controls_table.show_all ();
170 controls_table.set_no_show_all ();
172 controls_vbox.pack_start (controls_table, false, false);
173 controls_vbox.show ();
175 top_hbox.pack_start (controls_vbox, true, true);
178 controls_ebox.add (time_axis_hbox);
179 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
180 Gdk::BUTTON_RELEASE_MASK|
181 Gdk::POINTER_MOTION_MASK|
182 Gdk::ENTER_NOTIFY_MASK|
183 Gdk::LEAVE_NOTIFY_MASK|
185 controls_ebox.set_flags (CAN_FOCUS);
187 /* note that this handler connects *before* the default handler */
188 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
189 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
190 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
191 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
192 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
193 controls_ebox.show ();
195 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
196 time_axis_frame.add(top_hbox);
197 time_axis_frame.show();
199 HSeparator* separator = manage (new HSeparator());
200 separator->set_name("TrackSeparator");
201 separator->set_size_request(-1, 1);
204 scroomer_placeholder.set_size_request (-1, -1);
205 scroomer_placeholder.show();
206 midi_scroomer_size_group->add_widget (scroomer_placeholder);
208 time_axis_vbox.pack_start (*separator, false, false);
209 time_axis_vbox.pack_start (time_axis_frame, true, true);
210 time_axis_vbox.show();
211 time_axis_hbox.pack_start (time_axis_vbox, true, true);
212 time_axis_hbox.show();
213 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
215 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
217 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
220 TimeAxisView::~TimeAxisView()
222 in_destructor = true;
224 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
228 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
230 delete (*i)->start_trim;
231 delete (*i)->end_trim;
235 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
237 delete (*i)->start_trim;
238 delete (*i)->end_trim;
241 delete selection_group;
244 delete _canvas_display;
254 TimeAxisView::hide ()
260 _canvas_display->hide ();
261 _canvas_separator->hide ();
263 if (control_parent) {
264 control_parent->remove (TOP_LEVEL_WIDGET);
271 /* now hide children */
273 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
277 /* if its hidden, it cannot be selected */
278 _editor.get_selection().remove (this);
279 /* and neither can its regions */
280 _editor.get_selection().remove_regions (this);
285 /** Display this TimeAxisView as the nth component of the parent box, at y.
287 * @param y y position.
288 * @param nth index for this TimeAxisView, increased if this view has children.
289 * @param parent parent component.
290 * @return height of this TimeAxisView.
293 TimeAxisView::show_at (double y, int& nth, VBox *parent)
295 if (control_parent) {
296 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
298 control_parent = parent;
299 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
300 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
305 if (_y_position != y) {
306 _canvas_display->set_y_position (y);
310 _canvas_display->raise_to_top ();
311 _canvas_display->show ();
315 _effective_height = current_height ();
317 /* now show relevant children */
319 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
320 if ((*i)->marked_for_display()) {
322 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
328 /* put separator at the bottom of this time axis view */
330 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
331 _canvas_separator->lower_to_bottom ();
332 _canvas_separator->show ();
334 return _effective_height;
338 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
340 switch (ev->direction) {
342 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
343 /* See Editor::_stepping_axis_view for notes on this hack */
344 Editor& e = dynamic_cast<Editor&> (_editor);
345 if (!e.stepping_axis_view ()) {
346 e.set_stepping_axis_view (this);
348 e.stepping_axis_view()->step_height (false);
353 case GDK_SCROLL_DOWN:
354 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
355 /* See Editor::_stepping_axis_view for notes on this hack */
356 Editor& e = dynamic_cast<Editor&> (_editor);
357 if (!e.stepping_axis_view ()) {
358 e.set_stepping_axis_view (this);
360 e.stepping_axis_view()->step_height (true);
366 /* no handling for left/right, yet */
370 /* Just forward to the normal canvas scroll method. The coordinate
371 systems are different but since the canvas is always larger than the
372 track headers, and aligned with the trackview area, this will work.
374 In the not too distant future this layout is going away anyway and
375 headers will be on the canvas.
377 return _editor.canvas_scroll_event (ev, false);
381 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
383 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
384 /* see if it is inside the name label */
385 if (name_label.is_ancestor (controls_ebox)) {
388 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
389 Gtk::Allocation a = name_label.get_allocation ();
390 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
392 _ebox_release_can_act = false;
399 _ebox_release_can_act = true;
401 if (maybe_set_cursor (event->y) > 0) {
402 _resize_drag_start = event->y_root;
409 TimeAxisView::idle_resize (uint32_t h)
416 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
418 if (_resize_drag_start >= 0) {
420 /* (ab)use the DragManager to do autoscrolling - basically we
421 * are pretending that the drag is taking place over the canvas
422 * (which perhaps in the glorious future, when track headers
423 * and the canvas are unified, will actually be true.)
426 _editor.maybe_autoscroll (false, true, true);
428 /* now schedule the actual TAV resize */
429 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
430 _editor.add_to_idle_resize (this, delta);
431 _resize_drag_start = ev->y_root;
434 /* not dragging but ... */
435 maybe_set_cursor (ev->y);
438 gdk_event_request_motions(ev);
443 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
445 if (_have_preresize_cursor) {
446 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
447 _have_preresize_cursor = false;
453 TimeAxisView::maybe_set_cursor (int y)
455 /* XXX no Gtkmm Gdk::Window::get_cursor() */
456 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
458 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
460 /* y-coordinate in lower 25% */
462 if (!_have_preresize_cursor) {
463 _preresize_cursor = gdk_window_get_cursor (win->gobj());
464 _have_preresize_cursor = true;
465 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
470 } else if (_have_preresize_cursor) {
471 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
472 _have_preresize_cursor = false;
481 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
483 if (_resize_drag_start >= 0) {
484 if (_have_preresize_cursor) {
485 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
486 _preresize_cursor = 0;
487 _have_preresize_cursor = false;
489 _editor.stop_canvas_autoscroll ();
490 _resize_drag_start = -1;
493 // don't change selection
498 if (!_ebox_release_can_act) {
502 switch (ev->button) {
504 selection_click (ev);
508 popup_display_menu (ev->time);
516 TimeAxisView::selection_click (GdkEventButton* ev)
518 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
519 _editor.set_selected_track (*this, op, false);
523 /** Steps through the defined heights for this TrackView.
524 * @param coarser true if stepping should decrease in size, otherwise false.
527 TimeAxisView::step_height (bool coarser)
529 static const uint32_t step = 25;
533 if (height <= preset_height (HeightSmall)) {
535 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
536 set_height_enum (HeightSmall);
538 set_height (height - step);
543 if (height <= preset_height(HeightSmall)) {
544 set_height_enum (HeightNormal);
546 set_height (height + step);
553 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
555 if (apply_to_selection) {
556 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
558 set_height (preset_height (h));
563 TimeAxisView::set_height (uint32_t h)
565 if (h < preset_height (HeightSmall)) {
566 h = preset_height (HeightSmall);
569 TOP_LEVEL_WIDGET.property_height_request () = h;
573 snprintf (buf, sizeof (buf), "%u", height);
574 set_gui_property ("height", buf);
576 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
580 if (selection_group->visible ()) {
581 /* resize the selection rect */
582 show_selection (_editor.get_selection().time);
585 _editor.override_visible_track_count ();
589 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
591 /* steal escape, tabs from GTK */
593 switch (ev->keyval) {
595 case GDK_ISO_Left_Tab:
603 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
605 TrackViewList::iterator i;
607 switch (ev->keyval) {
609 end_name_edit (RESPONSE_CANCEL);
612 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
613 * generates a different ev->keyval, rather than setting
616 case GDK_ISO_Left_Tab:
617 end_name_edit (RESPONSE_APPLY);
621 end_name_edit (RESPONSE_ACCEPT);
631 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
633 end_name_edit (RESPONSE_OK);
638 TimeAxisView::begin_name_edit ()
644 if (can_edit_name()) {
646 name_entry = manage (new Gtkmm2ext::FocusEntry);
648 name_entry->set_width_chars(8); // min width, entry expands
650 name_entry->set_name ("EditorTrackNameDisplay");
651 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
652 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
653 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
654 name_entry->set_text (name_label.get_text());
655 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
657 if (name_label.is_ancestor (name_hbox)) {
658 name_hbox.remove (name_label);
661 name_hbox.pack_end (*name_entry, true, true);
664 name_entry->select_region (0, -1);
665 name_entry->set_state (STATE_SELECTED);
666 name_entry->grab_focus ();
667 name_entry->start_editing (0);
672 TimeAxisView::end_name_edit (int response)
678 bool edit_next = false;
679 bool edit_prev = false;
682 case RESPONSE_CANCEL:
685 name_entry_changed ();
687 case RESPONSE_ACCEPT:
688 name_entry_changed ();
691 name_entry_changed ();
695 /* this will delete the name_entry. but it will also drop focus, which
696 * will cause another callback to this function, so set name_entry = 0
697 * first to ensure we don't double-remove etc. etc.
700 Gtk::Entry* tmp = name_entry;
702 name_hbox.remove (*tmp);
704 /* put the name label back */
706 name_hbox.pack_end (name_label);
711 TrackViewList const & allviews = _editor.get_track_views ();
712 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
714 if (i != allviews.end()) {
717 if (++i == allviews.end()) {
721 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
723 if (rtav && rtav->route()->record_enabled()) {
727 if (!(*i)->hidden()) {
734 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
735 _editor.ensure_time_axis_view_is_visible (**i, false);
736 (*i)->begin_name_edit ();
739 } else if (edit_prev) {
741 TrackViewList const & allviews = _editor.get_track_views ();
742 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
744 if (i != allviews.begin()) {
746 if (i == allviews.begin()) {
752 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
754 if (rtav && rtav->route()->record_enabled()) {
758 if (!(*i)->hidden()) {
765 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
766 _editor.ensure_time_axis_view_is_visible (**i, false);
767 (*i)->begin_name_edit ();
773 TimeAxisView::name_entry_changed ()
778 TimeAxisView::can_edit_name () const
784 TimeAxisView::conditionally_add_to_selection ()
786 Selection& s (_editor.get_selection ());
788 if (!s.selected (this)) {
789 _editor.set_selected_track (*this, Selection::Set);
794 TimeAxisView::popup_display_menu (guint32 when)
796 conditionally_add_to_selection ();
798 build_display_menu ();
799 display_menu->popup (1, when);
803 TimeAxisView::set_selected (bool yn)
805 if (can_edit_name() && name_entry && name_entry->get_visible()) {
806 end_name_edit (RESPONSE_CANCEL);
809 if (yn == _selected) {
813 Selectable::set_selected (yn);
816 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
817 time_axis_frame.set_name ("MixerStripSelectedFrame");
818 controls_ebox.set_name (controls_base_selected_name);
819 controls_vbox.set_name (controls_base_selected_name);
820 time_axis_vbox.set_name (controls_base_selected_name);
822 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
823 time_axis_frame.set_name (controls_base_unselected_name);
824 controls_ebox.set_name (controls_base_unselected_name);
825 controls_vbox.set_name (controls_base_unselected_name);
826 time_axis_vbox.set_name (controls_base_unselected_name);
830 /* children will be set for the yn=true case. but when deselecting
831 the editor only has a list of top-level trackviews, so we
832 have to do this here.
835 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
836 (*i)->set_selected (false);
840 time_axis_frame.show();
845 TimeAxisView::build_display_menu ()
847 using namespace Menu_Helpers;
851 display_menu = new Menu;
852 display_menu->set_name ("ArdourContextMenu");
854 // Just let implementing classes define what goes into the manu
858 TimeAxisView::set_samples_per_pixel (double fpp)
860 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
861 (*i)->set_samples_per_pixel (fpp);
866 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
868 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
869 (*i)->show_timestretch (start, end, layers, layer);
874 TimeAxisView::hide_timestretch ()
876 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
877 (*i)->hide_timestretch ();
882 TimeAxisView::show_selection (TimeSelection& ts)
887 SelectionRect *rect; time_axis_frame.show();
890 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
891 (*i)->show_selection (ts);
894 if (selection_group->visible ()) {
895 while (!used_selection_rects.empty()) {
896 free_selection_rects.push_front (used_selection_rects.front());
897 used_selection_rects.pop_front();
898 free_selection_rects.front()->rect->hide();
899 free_selection_rects.front()->start_trim->hide();
900 free_selection_rects.front()->end_trim->hide();
902 selection_group->hide();
905 selection_group->show();
906 selection_group->raise_to_top();
908 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
909 framepos_t start, end;
914 cnt = end - start + 1;
916 rect = get_selection_rect ((*i).id);
918 x1 = _editor.sample_to_pixel (start);
919 x2 = _editor.sample_to_pixel (start + cnt - 1);
920 y2 = current_height() - 1;
922 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
924 // trim boxes are at the top for selections
927 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
928 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
930 rect->start_trim->show();
931 rect->end_trim->show();
933 rect->start_trim->hide();
934 rect->end_trim->hide();
938 used_selection_rects.push_back (rect);
943 TimeAxisView::reshow_selection (TimeSelection& ts)
947 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
948 (*i)->show_selection (ts);
953 TimeAxisView::hide_selection ()
955 if (selection_group->visible ()) {
956 while (!used_selection_rects.empty()) {
957 free_selection_rects.push_front (used_selection_rects.front());
958 used_selection_rects.pop_front();
959 free_selection_rects.front()->rect->hide();
960 free_selection_rects.front()->start_trim->hide();
961 free_selection_rects.front()->end_trim->hide();
963 selection_group->hide();
966 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
967 (*i)->hide_selection ();
972 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
974 /* find the selection rect this is for. we have the item corresponding to one
978 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
979 if ((*i)->start_trim == item || (*i)->end_trim == item) {
981 /* make one trim handle be "above" the other so that if they overlap,
982 the top one is the one last used.
985 (*i)->rect->raise_to_top ();
986 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
987 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
995 TimeAxisView::get_selection_rect (uint32_t id)
999 /* check to see if we already have a visible rect for this particular selection ID */
1001 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1002 if ((*i)->id == id) {
1007 /* ditto for the free rect list */
1009 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1010 if ((*i)->id == id) {
1011 SelectionRect* ret = (*i);
1012 free_selection_rects.erase (i);
1017 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1019 if (free_selection_rects.empty()) {
1021 rect = new SelectionRect;
1023 rect->rect = new ArdourCanvas::TimeRectangle (selection_group);
1024 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1025 rect->rect->set_outline (false);
1026 rect->rect->set_fill_color (ARDOUR_UI::config()->get_SelectionRect());
1028 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1029 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1030 rect->start_trim->set_outline (false);
1031 rect->start_trim->set_fill (false);
1033 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1034 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1035 rect->end_trim->set_outline (false);
1036 rect->end_trim->set_fill (false);
1038 free_selection_rects.push_front (rect);
1040 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1041 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1042 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1045 rect = free_selection_rects.front();
1047 free_selection_rects.pop_front();
1051 struct null_deleter { void operator()(void const *) const {} };
1054 TimeAxisView::is_child (TimeAxisView* tav)
1056 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1060 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1062 children.push_back (child);
1066 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1068 Children::iterator i;
1070 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1075 /** Get selectable things within a given range.
1076 * @param start Start time in session frames.
1077 * @param end End time in session frames.
1078 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1079 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1080 * @param result Filled in with selectable things.
1083 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1089 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1095 TimeAxisView::add_ghost (RegionView* rv)
1097 GhostRegion* gr = rv->add_ghost (*this);
1100 ghosts.push_back(gr);
1105 TimeAxisView::remove_ghost (RegionView* rv)
1107 rv->remove_ghost_in (*this);
1111 TimeAxisView::erase_ghost (GhostRegion* gr)
1113 if (in_destructor) {
1117 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1126 TimeAxisView::touched (double top, double bot)
1128 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1129 y_position is the "origin" or "top" of the track.
1132 double mybot = _y_position + current_height();
1134 return ((_y_position <= bot && _y_position >= top) ||
1135 ((mybot <= bot) && (top < mybot)) ||
1136 (mybot >= bot && _y_position < top));
1140 TimeAxisView::set_parent (TimeAxisView& p)
1146 TimeAxisView::reset_height ()
1148 set_height (height);
1150 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1151 (*i)->set_height ((*i)->height);
1156 TimeAxisView::compute_heights ()
1158 // TODO this function should be re-evaluated when font-scaling changes (!)
1159 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1160 Gtk::Table one_row_table (1, 1);
1161 ArdourButton* test_button = manage (new ArdourButton);
1162 const int border_width = 2;
1163 const int frame_height = 2;
1164 extra_height = (2 * border_width) + frame_height;
1166 window.add (one_row_table);
1167 test_button->set_name ("mute button");
1168 test_button->set_text (_("M"));
1169 test_button->set_tweaks (ArdourButton::TrackHeader);
1171 one_row_table.set_border_width (border_width);
1172 one_row_table.set_row_spacings (2);
1173 one_row_table.set_col_spacings (2);
1175 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1176 one_row_table.show_all ();
1178 Gtk::Requisition req(one_row_table.size_request ());
1179 button_height = req.height;
1183 TimeAxisView::color_handler ()
1185 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1189 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1191 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_SelectionRect());
1192 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_Selection());
1194 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_Selection());
1195 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_Selection());
1197 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_Selection());
1198 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_Selection());
1201 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1203 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_SelectionRect());
1204 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_Selection());
1206 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_Selection());
1207 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_Selection());
1209 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_Selection());
1210 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_Selection());
1214 /** @return Pair: TimeAxisView, layer index.
1215 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1216 * does. @param y is an offset from the top of the trackview area.
1218 * If the covering object is a child axis, then the child is returned.
1219 * TimeAxisView is 0 otherwise.
1221 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1222 * and is in stacked or expanded * region display mode, otherwise 0.
1224 std::pair<TimeAxisView*, double>
1225 TimeAxisView::covers_y_position (double y) const
1228 return std::make_pair ((TimeAxisView *) 0, 0);
1231 if (_y_position <= y && y < (_y_position + height)) {
1233 /* work out the layer index if appropriate */
1235 switch (layer_display ()) {
1241 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1242 /* clamp to max layers to be on the safe side; sometimes the above calculation
1243 returns a too-high value */
1244 if (l >= view()->layers ()) {
1245 l = view()->layers() - 1;
1251 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1253 if (l >= (view()->layers() - 0.5)) {
1254 l = view()->layers() - 0.5;
1260 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1263 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1265 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1271 return std::make_pair ((TimeAxisView *) 0, 0);
1275 TimeAxisView::covered_by_y_range (double y0, double y1) const
1281 /* if either the top or bottom of the axisview is in the vertical
1282 * range, we cover it.
1285 if ((y0 < _y_position && y1 < _y_position) ||
1286 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1290 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1291 if ((*i)->covered_by_y_range (y0, y1)) {
1300 TimeAxisView::preset_height (Height h)
1304 return (button_height * 2) + extra_height + 260;
1306 return (button_height * 2) + extra_height + 160;
1308 return (button_height * 2) + extra_height + 60;
1310 return (button_height * 2) + extra_height + 10;
1312 return button_height + extra_height;
1315 abort(); /* NOTREACHED */
1319 /** @return Child time axis views that are not hidden */
1320 TimeAxisView::Children
1321 TimeAxisView::get_child_list ()
1325 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1326 if (!(*i)->hidden()) {
1335 TimeAxisView::build_size_menu ()
1337 if (_size_menu && _size_menu->gobj ()) {
1343 using namespace Menu_Helpers;
1345 _size_menu = new Menu;
1346 _size_menu->set_name ("ArdourContextMenu");
1347 MenuList& items = _size_menu->items();
1349 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1350 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1351 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1352 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1353 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1357 TimeAxisView::reset_visual_state ()
1359 /* this method is not required to trigger a global redraw */
1361 string str = gui_property ("height");
1364 set_height (atoi (str));
1366 set_height (preset_height (HeightNormal));
1371 TrackViewList::filter_to_unique_playlists ()
1373 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1376 for (iterator i = begin(); i != end(); ++i) {
1377 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1379 /* not a route: include it anyway */
1382 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1384 if (playlists.insert (t->playlist()).second) {
1385 /* playlist not seen yet */
1389 /* not a track: include it anyway */