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/profile.h"
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"
51 #include "rgb_macros.h"
53 #include "streamview.h"
54 #include "editor_drag.h"
62 using namespace ARDOUR;
63 using namespace ARDOUR_UI_UTILS;
65 using namespace Editing;
66 using namespace ArdourCanvas;
67 using Gtkmm2ext::Keyboard;
69 #define TOP_LEVEL_WIDGET controls_ebox
71 const double trim_handle_size = 6.0; /* pixels */
72 uint32_t TimeAxisView::button_height = 0;
73 uint32_t TimeAxisView::extra_height = 0;
74 int const TimeAxisView::_max_order = 512;
75 unsigned int TimeAxisView::name_width_px = 100; // TODO adjust with font-scaling on style-change
76 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
77 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
78 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_button_size_group = Glib::RefPtr<Gtk::SizeGroup>();
79 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::track_number_v_size_group = Glib::RefPtr<Gtk::SizeGroup>();
81 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
83 , controls_table (3, 3)
84 , _name_editing (false)
91 , in_destructor (false)
99 , _effective_height (0)
100 , _resize_drag_start (-1)
101 , _preresize_cursor (0)
102 , _have_preresize_cursor (false)
103 , _ebox_release_can_act (true)
105 if (!controls_meters_size_group) {
106 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
108 if (!track_number_v_size_group) {
109 track_number_v_size_group = SizeGroup::create (SIZE_GROUP_VERTICAL);
111 if (!controls_button_size_group) {
112 controls_button_size_group = SizeGroup::create (Gtk::SIZE_GROUP_BOTH);
114 if (extra_height == 0) {
118 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group (), ArdourCanvas::Duple (1.0, 0.0));
119 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
120 _canvas_display->hide(); // reveal as needed
122 _canvas_separator = new ArdourCanvas::Line(ed.get_trackview_group ());
123 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
124 _canvas_separator->set_outline_color(RGBA_TO_UINT (0, 0, 0, 255));
125 _canvas_separator->set_outline_width(1.0);
126 _canvas_separator->hide();
128 selection_group = new ArdourCanvas::Container (_canvas_display);
129 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
130 selection_group->set_data (X_("timeselection"), (void *) 1);
131 selection_group->hide();
133 _ghost_group = new ArdourCanvas::Container (_canvas_display);
134 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
135 _ghost_group->lower_to_bottom();
136 _ghost_group->show();
138 name_label.set_name ("TrackLabel");
139 name_label.set_alignment (0.0, 0.5);
140 name_label.set_width_chars (12);
141 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
143 Gtk::Entry* an_entry = new Gtk::Entry;
144 Gtk::Requisition req;
145 an_entry->size_request (req);
146 name_label.set_size_request (-1, req.height);
147 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
150 name_hbox.pack_end (name_label, true, true);
152 // set min. track-header width if fader is not visible
153 name_hbox.set_size_request(name_width_px, -1);
158 controls_table.set_row_spacings (2);
159 controls_table.set_col_spacings (2);
160 controls_table.set_border_width (2);
162 if (ARDOUR::Profile->get_mixbus() ) {
163 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
165 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
167 controls_table.show_all ();
168 controls_table.set_no_show_all ();
170 controls_vbox.pack_start (controls_table, false, false);
171 controls_vbox.show ();
173 top_hbox.pack_start (controls_vbox, true, true);
176 controls_ebox.add (time_axis_hbox);
177 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
178 Gdk::BUTTON_RELEASE_MASK|
179 Gdk::POINTER_MOTION_MASK|
180 Gdk::ENTER_NOTIFY_MASK|
181 Gdk::LEAVE_NOTIFY_MASK|
183 controls_ebox.set_flags (CAN_FOCUS);
185 /* note that this handler connects *before* the default handler */
186 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
187 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
188 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
189 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
190 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
191 controls_ebox.show ();
193 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
194 time_axis_frame.add(top_hbox);
195 time_axis_frame.show();
197 HSeparator* separator = manage (new HSeparator());
198 separator->set_name("TrackSeparator");
199 separator->set_size_request(-1, 1);
202 time_axis_vbox.pack_start (*separator, false, false);
203 time_axis_vbox.pack_start (time_axis_frame, true, true);
204 time_axis_vbox.show();
205 time_axis_hbox.pack_start (time_axis_vbox, true, true);
206 time_axis_hbox.show();
208 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
210 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
213 TimeAxisView::~TimeAxisView()
215 in_destructor = true;
217 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
221 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
223 delete (*i)->start_trim;
224 delete (*i)->end_trim;
228 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
230 delete (*i)->start_trim;
231 delete (*i)->end_trim;
234 delete selection_group;
237 delete _canvas_display;
240 delete _canvas_separator;
241 _canvas_separator = 0;
250 TimeAxisView::hide ()
256 _canvas_display->hide ();
257 _canvas_separator->hide ();
259 if (control_parent) {
260 control_parent->remove (TOP_LEVEL_WIDGET);
267 /* now hide children */
269 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
273 /* if its hidden, it cannot be selected */
274 _editor.get_selection().remove (this);
275 /* and neither can its regions */
276 _editor.get_selection().remove_regions (this);
281 /** Display this TimeAxisView as the nth component of the parent box, at y.
283 * @param y y position.
284 * @param nth index for this TimeAxisView, increased if this view has children.
285 * @param parent parent component.
286 * @return height of this TimeAxisView.
289 TimeAxisView::show_at (double y, int& nth, VBox *parent)
291 if (control_parent) {
292 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
294 control_parent = parent;
295 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
296 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
301 if (_y_position != y) {
302 _canvas_separator->set (ArdourCanvas::Duple(0, y), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, y));
303 _canvas_display->set_y_position (y + 1);
307 _canvas_display->raise_to_top ();
308 _canvas_display->show ();
310 _canvas_separator->raise_to_top ();
311 _canvas_separator->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 return _effective_height;
332 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
334 switch (ev->direction) {
336 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
337 /* See Editor::_stepping_axis_view for notes on this hack */
338 Editor& e = dynamic_cast<Editor&> (_editor);
339 if (!e.stepping_axis_view ()) {
340 e.set_stepping_axis_view (this);
342 e.stepping_axis_view()->step_height (false);
347 case GDK_SCROLL_DOWN:
348 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
349 /* See Editor::_stepping_axis_view for notes on this hack */
350 Editor& e = dynamic_cast<Editor&> (_editor);
351 if (!e.stepping_axis_view ()) {
352 e.set_stepping_axis_view (this);
354 e.stepping_axis_view()->step_height (true);
360 /* no handling for left/right, yet */
364 /* Just forward to the normal canvas scroll method. The coordinate
365 systems are different but since the canvas is always larger than the
366 track headers, and aligned with the trackview area, this will work.
368 In the not too distant future this layout is going away anyway and
369 headers will be on the canvas.
371 return _editor.canvas_scroll_event (ev, false);
375 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
377 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
378 /* see if it is inside the name label */
379 if (name_label.is_ancestor (controls_ebox)) {
382 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
383 Gtk::Allocation a = name_label.get_allocation ();
384 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
386 _ebox_release_can_act = false;
393 _ebox_release_can_act = true;
395 if (maybe_set_cursor (event->y) > 0) {
396 _resize_drag_start = event->y_root;
403 TimeAxisView::idle_resize (uint32_t h)
410 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
412 if (_resize_drag_start >= 0) {
414 /* (ab)use the DragManager to do autoscrolling - basically we
415 * are pretending that the drag is taking place over the canvas
416 * (which perhaps in the glorious future, when track headers
417 * and the canvas are unified, will actually be true.)
420 _editor.maybe_autoscroll (false, true, true);
422 /* now schedule the actual TAV resize */
423 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
424 _editor.add_to_idle_resize (this, delta);
425 _resize_drag_start = ev->y_root;
427 /* not dragging but ... */
428 maybe_set_cursor (ev->y);
431 gdk_event_request_motions(ev);
436 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
438 if (_have_preresize_cursor) {
439 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
440 _have_preresize_cursor = false;
446 TimeAxisView::maybe_set_cursor (int y)
448 /* XXX no Gtkmm Gdk::Window::get_cursor() */
449 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
451 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
453 /* y-coordinate in lower 25% */
455 if (!_have_preresize_cursor) {
456 _preresize_cursor = gdk_window_get_cursor (win->gobj());
457 _have_preresize_cursor = true;
458 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
463 } else if (_have_preresize_cursor) {
464 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
465 _have_preresize_cursor = false;
474 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
476 if (_resize_drag_start >= 0) {
477 if (_have_preresize_cursor) {
478 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
479 _preresize_cursor = 0;
480 _have_preresize_cursor = false;
482 _editor.stop_canvas_autoscroll ();
483 _resize_drag_start = -1;
486 if (!_ebox_release_can_act) {
490 switch (ev->button) {
492 selection_click (ev);
496 popup_display_menu (ev->time);
504 TimeAxisView::selection_click (GdkEventButton* ev)
506 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
507 _editor.set_selected_track (*this, op, false);
511 /** Steps through the defined heights for this TrackView.
512 * @param coarser true if stepping should decrease in size, otherwise false.
515 TimeAxisView::step_height (bool coarser)
517 static const uint32_t step = 25;
521 if (height <= preset_height (HeightSmall)) {
523 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
524 set_height_enum (HeightSmall);
526 set_height (height - step);
531 if (height <= preset_height(HeightSmall)) {
532 set_height_enum (HeightNormal);
534 set_height (height + step);
541 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
543 if (apply_to_selection) {
544 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
546 set_height (preset_height (h));
551 TimeAxisView::set_height (uint32_t h)
553 if (h < preset_height (HeightSmall)) {
554 h = preset_height (HeightSmall);
557 TOP_LEVEL_WIDGET.property_height_request () = h;
561 snprintf (buf, sizeof (buf), "%u", height);
562 set_gui_property ("height", buf);
564 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
568 if (selection_group->visible ()) {
569 /* resize the selection rect */
570 show_selection (_editor.get_selection().time);
573 _editor.override_visible_track_count ();
577 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
579 /* steal escape, tabs from GTK */
581 switch (ev->keyval) {
583 case GDK_ISO_Left_Tab:
591 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
593 TrackViewList::iterator i;
595 switch (ev->keyval) {
597 end_name_edit (RESPONSE_CANCEL);
600 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
601 * generates a different ev->keyval, rather than setting
604 case GDK_ISO_Left_Tab:
605 end_name_edit (RESPONSE_APPLY);
609 end_name_edit (RESPONSE_ACCEPT);
619 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
621 end_name_edit (RESPONSE_OK);
626 TimeAxisView::begin_name_edit ()
632 if (can_edit_name()) {
634 name_entry = manage (new Gtkmm2ext::FocusEntry);
636 name_entry->set_width_chars(8); // min width, entry expands
638 name_entry->set_name ("EditorTrackNameDisplay");
639 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
640 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
641 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
642 name_entry->set_text (name_label.get_text());
643 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
645 if (name_label.is_ancestor (name_hbox)) {
646 name_hbox.remove (name_label);
649 name_hbox.pack_end (*name_entry, true, true);
652 name_entry->select_region (0, -1);
653 name_entry->set_state (STATE_SELECTED);
654 name_entry->grab_focus ();
655 name_entry->start_editing (0);
660 TimeAxisView::end_name_edit (int response)
666 bool edit_next = false;
667 bool edit_prev = false;
670 case RESPONSE_CANCEL:
673 name_entry_changed ();
675 case RESPONSE_ACCEPT:
676 name_entry_changed ();
679 name_entry_changed ();
683 /* this will delete the name_entry. but it will also drop focus, which
684 * will cause another callback to this function, so set name_entry = 0
685 * first to ensure we don't double-remove etc. etc.
688 Gtk::Entry* tmp = name_entry;
690 name_hbox.remove (*tmp);
692 /* put the name label back */
694 name_hbox.pack_end (name_label);
699 TrackViewList const & allviews = _editor.get_track_views ();
700 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
702 if (i != allviews.end()) {
705 if (++i == allviews.end()) {
709 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
711 if (rtav && rtav->route()->record_enabled()) {
715 if (!(*i)->hidden()) {
722 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
723 _editor.ensure_time_axis_view_is_visible (**i, false);
724 (*i)->begin_name_edit ();
727 } else if (edit_prev) {
729 TrackViewList const & allviews = _editor.get_track_views ();
730 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
732 if (i != allviews.begin()) {
734 if (i == allviews.begin()) {
740 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
742 if (rtav && rtav->route()->record_enabled()) {
746 if (!(*i)->hidden()) {
753 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
754 _editor.ensure_time_axis_view_is_visible (**i, false);
755 (*i)->begin_name_edit ();
761 TimeAxisView::name_entry_changed ()
766 TimeAxisView::can_edit_name () const
772 TimeAxisView::conditionally_add_to_selection ()
774 Selection& s (_editor.get_selection ());
776 if (!s.selected (this)) {
777 _editor.set_selected_track (*this, Selection::Set);
782 TimeAxisView::popup_display_menu (guint32 when)
784 conditionally_add_to_selection ();
786 build_display_menu ();
787 display_menu->popup (1, when);
791 TimeAxisView::set_selected (bool yn)
793 if (can_edit_name() && name_entry && name_entry->get_visible()) {
794 end_name_edit (RESPONSE_CANCEL);
797 if (yn == _selected) {
801 Selectable::set_selected (yn);
804 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
805 time_axis_frame.set_name ("MixerStripSelectedFrame");
806 controls_ebox.set_name (controls_base_selected_name);
807 controls_vbox.set_name (controls_base_selected_name);
808 time_axis_vbox.set_name (controls_base_selected_name);
810 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
811 time_axis_frame.set_name (controls_base_unselected_name);
812 controls_ebox.set_name (controls_base_unselected_name);
813 controls_vbox.set_name (controls_base_unselected_name);
814 time_axis_vbox.set_name (controls_base_unselected_name);
818 /* children will be set for the yn=true case. but when deselecting
819 the editor only has a list of top-level trackviews, so we
820 have to do this here.
823 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
824 (*i)->set_selected (false);
828 time_axis_frame.show();
833 TimeAxisView::build_display_menu ()
835 using namespace Menu_Helpers;
839 display_menu = new Menu;
840 display_menu->set_name ("ArdourContextMenu");
842 // Just let implementing classes define what goes into the manu
846 TimeAxisView::set_samples_per_pixel (double fpp)
848 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
849 (*i)->set_samples_per_pixel (fpp);
854 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
856 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
857 (*i)->show_timestretch (start, end, layers, layer);
862 TimeAxisView::hide_timestretch ()
864 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
865 (*i)->hide_timestretch ();
870 TimeAxisView::show_selection (TimeSelection& ts)
875 SelectionRect *rect; time_axis_frame.show();
878 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
879 (*i)->show_selection (ts);
882 if (selection_group->visible ()) {
883 while (!used_selection_rects.empty()) {
884 free_selection_rects.push_front (used_selection_rects.front());
885 used_selection_rects.pop_front();
886 free_selection_rects.front()->rect->hide();
887 free_selection_rects.front()->start_trim->hide();
888 free_selection_rects.front()->end_trim->hide();
890 selection_group->hide();
893 selection_group->show();
894 selection_group->raise_to_top();
896 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
897 framepos_t start, end;
902 cnt = end - start + 1;
904 rect = get_selection_rect ((*i).id);
906 x1 = _editor.sample_to_pixel (start);
907 x2 = _editor.sample_to_pixel (start + cnt - 1);
908 y2 = current_height() - 1;
910 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
912 // trim boxes are at the top for selections
915 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
916 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
918 rect->start_trim->show();
919 rect->end_trim->show();
921 rect->start_trim->hide();
922 rect->end_trim->hide();
926 used_selection_rects.push_back (rect);
931 TimeAxisView::reshow_selection (TimeSelection& ts)
935 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
936 (*i)->show_selection (ts);
941 TimeAxisView::hide_selection ()
943 if (selection_group->visible ()) {
944 while (!used_selection_rects.empty()) {
945 free_selection_rects.push_front (used_selection_rects.front());
946 used_selection_rects.pop_front();
947 free_selection_rects.front()->rect->hide();
948 free_selection_rects.front()->start_trim->hide();
949 free_selection_rects.front()->end_trim->hide();
951 selection_group->hide();
954 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
955 (*i)->hide_selection ();
960 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
962 /* find the selection rect this is for. we have the item corresponding to one
966 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
967 if ((*i)->start_trim == item || (*i)->end_trim == item) {
969 /* make one trim handle be "above" the other so that if they overlap,
970 the top one is the one last used.
973 (*i)->rect->raise_to_top ();
974 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
975 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
983 TimeAxisView::get_selection_rect (uint32_t id)
987 /* check to see if we already have a visible rect for this particular selection ID */
989 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
990 if ((*i)->id == id) {
995 /* ditto for the free rect list */
997 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
998 if ((*i)->id == id) {
999 SelectionRect* ret = (*i);
1000 free_selection_rects.erase (i);
1005 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1007 if (free_selection_rects.empty()) {
1009 rect = new SelectionRect;
1011 rect->rect = new ArdourCanvas::Rectangle (selection_group);
1012 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1013 rect->rect->set_outline (false);
1014 rect->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1016 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1017 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1018 rect->start_trim->set_outline (false);
1019 rect->start_trim->set_fill (false);
1021 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1022 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1023 rect->end_trim->set_outline (false);
1024 rect->end_trim->set_fill (false);
1026 free_selection_rects.push_front (rect);
1028 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1029 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1030 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1033 rect = free_selection_rects.front();
1035 free_selection_rects.pop_front();
1039 struct null_deleter { void operator()(void const *) const {} };
1042 TimeAxisView::is_child (TimeAxisView* tav)
1044 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1048 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1050 children.push_back (child);
1054 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1056 Children::iterator i;
1058 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1063 /** Get selectable things within a given range.
1064 * @param start Start time in session frames.
1065 * @param end End time in session frames.
1066 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1067 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1068 * @param result Filled in with selectable things.
1071 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1077 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1083 TimeAxisView::add_ghost (RegionView* rv)
1085 GhostRegion* gr = rv->add_ghost (*this);
1088 ghosts.push_back(gr);
1093 TimeAxisView::remove_ghost (RegionView* rv)
1095 rv->remove_ghost_in (*this);
1099 TimeAxisView::erase_ghost (GhostRegion* gr)
1101 if (in_destructor) {
1105 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1114 TimeAxisView::touched (double top, double bot)
1116 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1117 y_position is the "origin" or "top" of the track.
1120 double mybot = _y_position + current_height();
1122 return ((_y_position <= bot && _y_position >= top) ||
1123 ((mybot <= bot) && (top < mybot)) ||
1124 (mybot >= bot && _y_position < top));
1128 TimeAxisView::set_parent (TimeAxisView& p)
1134 TimeAxisView::reset_height ()
1136 set_height (height);
1138 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1139 (*i)->set_height ((*i)->height);
1144 TimeAxisView::compute_heights ()
1146 // TODO this function should be re-evaluated when font-scaling changes (!)
1147 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1148 Gtk::Table one_row_table (1, 1);
1149 ArdourButton* test_button = manage (new ArdourButton);
1150 const int border_width = 2;
1151 const int frame_height = 2;
1152 extra_height = (2 * border_width) + frame_height;
1154 window.add (one_row_table);
1155 test_button->set_name ("mute button");
1156 test_button->set_text (_("M"));
1158 one_row_table.set_border_width (border_width);
1159 one_row_table.set_row_spacings (2);
1160 one_row_table.set_col_spacings (2);
1162 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1163 one_row_table.show_all ();
1165 Gtk::Requisition req(one_row_table.size_request ());
1166 button_height = req.height;
1170 TimeAxisView::color_handler ()
1172 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1176 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1178 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1179 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1181 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1182 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1184 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1185 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1188 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1190 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1191 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1193 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1194 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1196 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1197 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1201 /** @return Pair: TimeAxisView, layer index.
1202 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1203 * does. @param y is an offset from the top of the trackview area.
1205 * If the covering object is a child axis, then the child is returned.
1206 * TimeAxisView is 0 otherwise.
1208 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1209 * and is in stacked or expanded * region display mode, otherwise 0.
1211 std::pair<TimeAxisView*, double>
1212 TimeAxisView::covers_y_position (double y) const
1215 return std::make_pair ((TimeAxisView *) 0, 0);
1218 if (_y_position <= y && y < (_y_position + height)) {
1220 /* work out the layer index if appropriate */
1222 switch (layer_display ()) {
1228 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1229 /* clamp to max layers to be on the safe side; sometimes the above calculation
1230 returns a too-high value */
1231 if (l >= view()->layers ()) {
1232 l = view()->layers() - 1;
1238 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1240 if (l >= (view()->layers() - 0.5)) {
1241 l = view()->layers() - 0.5;
1247 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1250 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1252 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1258 return std::make_pair ((TimeAxisView *) 0, 0);
1262 TimeAxisView::covered_by_y_range (double y0, double y1) const
1268 /* if either the top or bottom of the axisview is in the vertical
1269 * range, we cover it.
1272 if ((y0 < _y_position && y1 < _y_position) ||
1273 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1277 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1278 if ((*i)->covered_by_y_range (y0, y1)) {
1287 TimeAxisView::preset_height (Height h)
1291 return (button_height * 2) + extra_height + 260;
1293 return (button_height * 2) + extra_height + 160;
1295 return (button_height * 2) + extra_height + 60;
1297 return (button_height * 2) + extra_height + 10;
1299 return button_height + extra_height;
1306 /** @return Child time axis views that are not hidden */
1307 TimeAxisView::Children
1308 TimeAxisView::get_child_list ()
1312 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1313 if (!(*i)->hidden()) {
1322 TimeAxisView::build_size_menu ()
1324 if (_size_menu && _size_menu->gobj ()) {
1330 using namespace Menu_Helpers;
1332 _size_menu = new Menu;
1333 _size_menu->set_name ("ArdourContextMenu");
1334 MenuList& items = _size_menu->items();
1336 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1337 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1338 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1339 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1340 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1344 TimeAxisView::reset_visual_state ()
1346 /* this method is not required to trigger a global redraw */
1348 string str = gui_property ("height");
1351 set_height (atoi (str));
1353 set_height (preset_height (HeightNormal));
1358 TrackViewList::filter_to_unique_playlists ()
1360 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1363 for (iterator i = begin(); i != end(); ++i) {
1364 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1366 /* not a route: include it anyway */
1369 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1371 if (playlists.insert (t->playlist()).second) {
1372 /* playlist not seen yet */
1376 /* not a track: include it anyway */