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"
30 #include "pbd/unwind.h"
32 #include <gtkmm2ext/doi.h>
33 #include <gtkmm2ext/utils.h>
34 #include <gtkmm2ext/selector.h>
36 #include "canvas/canvas.h"
37 #include "canvas/rectangle.h"
38 #include "canvas/debug.h"
39 #include "canvas/utils.h"
40 #include "canvas/colors.h"
42 #include "ardour/profile.h"
44 #include "ardour_dialog.h"
45 #include "gui_thread.h"
46 #include "public_editor.h"
47 #include "time_axis_view.h"
48 #include "region_view.h"
49 #include "ghostregion.h"
50 #include "selection.h"
52 #include "rgb_macros.h"
54 #include "streamview.h"
55 #include "editor_drag.h"
58 #include "ui_config.h"
65 using namespace ARDOUR;
66 using namespace ARDOUR_UI_UTILS;
68 using namespace Editing;
69 using namespace ArdourCanvas;
70 using Gtkmm2ext::Keyboard;
72 #define TOP_LEVEL_WIDGET controls_ebox
74 const double trim_handle_size = 6.0; /* pixels */
75 uint32_t TimeAxisView::button_height = 0;
76 uint32_t TimeAxisView::extra_height = 0;
77 int const TimeAxisView::_max_order = 512;
78 unsigned int TimeAxisView::name_width_px = 100;
79 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
80 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
81 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
84 TimeAxisView::setup_sizes()
86 name_width_px = ceilf (100.f * UIConfiguration::instance().get_ui_scale());
89 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
91 , controls_table (3, 3)
92 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
93 , _name_editing (false)
100 , in_destructor (false)
102 , _canvas_display (0)
106 , ending_name_edit (false)
109 , _effective_height (0)
110 , _resize_drag_start (-1)
111 , _did_resize (false)
112 , _preresize_cursor (0)
113 , _have_preresize_cursor (false)
114 , _ebox_release_can_act (true)
116 if (!controls_meters_size_group) {
117 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
119 if (!midi_scroomer_size_group) {
120 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
122 if (extra_height == 0) {
126 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
127 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
128 _canvas_display->hide(); // reveal as needed
130 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
131 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
132 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
133 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
134 _canvas_separator->set_outline_width(1.0);
135 _canvas_separator->hide();
137 selection_group = new ArdourCanvas::Container (_canvas_display);
138 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
139 selection_group->set_data (X_("timeselection"), (void *) 1);
140 selection_group->hide();
142 _ghost_group = new ArdourCanvas::Container (_canvas_display);
143 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
144 _ghost_group->lower_to_bottom();
145 _ghost_group->show();
147 name_label.set_name ("TrackLabel");
148 name_label.set_alignment (0.0, 0.5);
149 name_label.set_width_chars (12);
150 set_tooltip (name_label, _("Track/Bus name (double click to edit)"));
152 Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
153 an_entry->set_name ("EditorTrackNameDisplay");
154 Gtk::Requisition req;
155 an_entry->size_request (req);
156 name_label.set_size_request (-1, req.height);
157 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
160 name_hbox.pack_end (name_label, true, true);
162 // set min. track-header width if fader is not visible
163 name_hbox.set_size_request(name_width_px, -1);
168 controls_table.set_row_spacings (2);
169 controls_table.set_col_spacings (2);
170 controls_table.set_border_width (2);
172 if (ARDOUR::Profile->get_mixbus() ) {
173 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
175 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
177 controls_table.show_all ();
178 controls_table.set_no_show_all ();
180 controls_vbox.pack_start (controls_table, false, false);
181 controls_vbox.show ();
183 top_hbox.pack_start (controls_vbox, true, true);
186 controls_ebox.add (time_axis_hbox);
187 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
188 Gdk::BUTTON_RELEASE_MASK|
189 Gdk::POINTER_MOTION_MASK|
190 Gdk::ENTER_NOTIFY_MASK|
191 Gdk::LEAVE_NOTIFY_MASK|
193 controls_ebox.set_flags (CAN_FOCUS);
195 /* note that this handler connects *before* the default handler */
196 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
197 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
198 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
199 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
200 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
201 controls_ebox.show ();
203 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
204 time_axis_frame.add(top_hbox);
205 time_axis_frame.show();
207 HSeparator* separator = manage (new HSeparator());
208 separator->set_name("TrackSeparator");
209 separator->set_size_request(-1, 1);
212 scroomer_placeholder.set_size_request (-1, -1);
213 scroomer_placeholder.show();
214 midi_scroomer_size_group->add_widget (scroomer_placeholder);
216 time_axis_vbox.pack_start (*separator, false, false);
217 time_axis_vbox.pack_start (time_axis_frame, true, true);
218 time_axis_vbox.show();
219 time_axis_hbox.pack_start (time_axis_vbox, true, true);
220 time_axis_hbox.show();
221 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
223 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
225 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
228 TimeAxisView::~TimeAxisView()
230 CatchDeletion (this);
232 in_destructor = true;
234 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
238 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
239 delete (*i)->rect; (*i)->rect=0;
240 delete (*i)->start_trim; (*i)->start_trim = 0;
241 delete (*i)->end_trim; (*i)->end_trim = 0;
245 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
246 delete (*i)->rect; (*i)->rect = 0;
247 delete (*i)->start_trim; (*i)->start_trim = 0;
248 delete (*i)->end_trim; (*i)->end_trim = 0;
251 delete selection_group;
254 delete _canvas_display;
264 TimeAxisView::hide ()
270 _canvas_display->hide ();
271 _canvas_separator->hide ();
273 if (control_parent) {
274 control_parent->remove (TOP_LEVEL_WIDGET);
281 /* now hide children */
283 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
287 /* if its hidden, it cannot be selected */
288 _editor.get_selection().remove (this);
289 /* and neither can its regions */
290 _editor.get_selection().remove_regions (this);
295 /** Display this TimeAxisView as the nth component of the parent box, at y.
297 * @param y y position.
298 * @param nth index for this TimeAxisView, increased if this view has children.
299 * @param parent parent component.
300 * @return height of this TimeAxisView.
303 TimeAxisView::show_at (double y, int& nth, VBox *parent)
305 if (control_parent) {
306 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
308 control_parent = parent;
309 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
310 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
315 if (_y_position != y) {
316 _canvas_display->set_y_position (y);
320 _canvas_display->raise_to_top ();
321 _canvas_display->show ();
325 _effective_height = current_height ();
327 /* now show relevant children */
329 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
330 if ((*i)->marked_for_display()) {
332 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
338 /* put separator at the bottom of this time axis view */
340 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
341 _canvas_separator->lower_to_bottom ();
342 _canvas_separator->show ();
344 return _effective_height;
348 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
350 switch (ev->direction) {
352 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
353 /* See Editor::_stepping_axis_view for notes on this hack */
354 Editor& e = dynamic_cast<Editor&> (_editor);
355 if (!e.stepping_axis_view ()) {
356 e.set_stepping_axis_view (this);
358 e.stepping_axis_view()->step_height (false);
363 case GDK_SCROLL_DOWN:
364 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
365 /* See Editor::_stepping_axis_view for notes on this hack */
366 Editor& e = dynamic_cast<Editor&> (_editor);
367 if (!e.stepping_axis_view ()) {
368 e.set_stepping_axis_view (this);
370 e.stepping_axis_view()->step_height (true);
376 /* no handling for left/right, yet */
380 /* Just forward to the normal canvas scroll method. The coordinate
381 systems are different but since the canvas is always larger than the
382 track headers, and aligned with the trackview area, this will work.
384 In the not too distant future this layout is going away anyway and
385 headers will be on the canvas.
387 return _editor.canvas_scroll_event (ev, false);
391 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
393 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
394 /* see if it is inside the name label */
395 if (name_label.is_ancestor (controls_ebox)) {
398 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
399 Gtk::Allocation a = name_label.get_allocation ();
400 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
402 _ebox_release_can_act = false;
409 _ebox_release_can_act = true;
411 if (maybe_set_cursor (event->y) > 0) {
412 _resize_drag_start = event->y_root;
419 TimeAxisView::idle_resize (int32_t h)
421 set_height (std::max(0, h));
426 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
428 if (_resize_drag_start >= 0) {
430 /* (ab)use the DragManager to do autoscrolling - basically we
431 * are pretending that the drag is taking place over the canvas
432 * (which perhaps in the glorious future, when track headers
433 * and the canvas are unified, will actually be true.)
436 _editor.maybe_autoscroll (false, true, true);
438 /* now schedule the actual TAV resize */
439 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
440 _editor.add_to_idle_resize (this, delta);
441 _resize_drag_start = ev->y_root;
444 /* not dragging but ... */
445 maybe_set_cursor (ev->y);
448 gdk_event_request_motions(ev);
453 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
455 if (_have_preresize_cursor) {
456 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
457 _have_preresize_cursor = false;
463 TimeAxisView::maybe_set_cursor (int y)
465 /* XXX no Gtkmm Gdk::Window::get_cursor() */
466 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
468 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
470 /* y-coordinate in lower 25% */
472 if (!_have_preresize_cursor) {
473 _preresize_cursor = gdk_window_get_cursor (win->gobj());
474 _have_preresize_cursor = true;
475 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
480 } else if (_have_preresize_cursor) {
481 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
482 _have_preresize_cursor = false;
491 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
493 if (_resize_drag_start >= 0) {
494 if (_have_preresize_cursor) {
495 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
496 _preresize_cursor = 0;
497 _have_preresize_cursor = false;
499 _editor.stop_canvas_autoscroll ();
500 _resize_drag_start = -1;
503 // don't change selection
508 if (!_ebox_release_can_act) {
512 switch (ev->button) {
514 selection_click (ev);
518 popup_display_menu (ev->time);
526 TimeAxisView::selection_click (GdkEventButton* ev)
528 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
529 _editor.set_selected_track (*this, op, false);
533 /** Steps through the defined heights for this TrackView.
534 * @param coarser true if stepping should decrease in size, otherwise false.
537 TimeAxisView::step_height (bool coarser)
539 static const uint32_t step = 25;
543 if (height <= preset_height (HeightSmall)) {
545 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
546 set_height_enum (HeightSmall);
548 set_height (height - step);
553 if (height <= preset_height(HeightSmall)) {
554 set_height_enum (HeightNormal);
556 set_height (height + step);
563 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
565 if (apply_to_selection) {
566 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
568 set_height (preset_height (h));
573 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
576 if (m == TotalHeight) {
577 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
578 if ( !(*i)->hidden()) ++lanes;
583 if (h < preset_height (HeightSmall)) {
584 h = preset_height (HeightSmall);
587 TOP_LEVEL_WIDGET.property_height_request () = h;
591 snprintf (buf, sizeof (buf), "%u", height);
592 set_gui_property ("height", buf);
594 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
598 if (selection_group->visible ()) {
599 /* resize the selection rect */
600 show_selection (_editor.get_selection().time);
604 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
605 (*i)->set_height(h, OnlySelf);
609 _editor.override_visible_track_count ();
613 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
615 /* steal escape, tabs from GTK */
617 switch (ev->keyval) {
619 case GDK_ISO_Left_Tab:
627 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
629 TrackViewList::iterator i;
631 switch (ev->keyval) {
633 end_name_edit (RESPONSE_CANCEL);
636 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
637 * generates a different ev->keyval, rather than setting
640 case GDK_ISO_Left_Tab:
641 end_name_edit (RESPONSE_APPLY);
645 end_name_edit (RESPONSE_ACCEPT);
655 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
657 end_name_edit (RESPONSE_OK);
662 TimeAxisView::begin_name_edit ()
668 if (can_edit_name()) {
670 name_entry = manage (new Gtkmm2ext::FocusEntry);
672 name_entry->set_width_chars(8); // min width, entry expands
674 name_entry->set_name ("EditorTrackNameDisplay");
675 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
676 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
677 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
678 name_entry->set_text (name_label.get_text());
679 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
681 if (name_label.is_ancestor (name_hbox)) {
682 name_hbox.remove (name_label);
685 name_hbox.pack_end (*name_entry, true, true);
688 name_entry->select_region (0, -1);
689 name_entry->set_state (STATE_SELECTED);
690 name_entry->grab_focus ();
691 name_entry->start_editing (0);
696 TimeAxisView::end_name_edit (int response)
702 if (ending_name_edit) {
703 /* already doing this, and focus out or other event has caused
704 us to re-enter this code.
709 PBD::Unwinder<bool> uw (ending_name_edit, true);
711 bool edit_next = false;
712 bool edit_prev = false;
715 case RESPONSE_CANCEL:
718 name_entry_changed ();
720 case RESPONSE_ACCEPT:
721 name_entry_changed ();
724 name_entry_changed ();
728 /* this will delete the name_entry. but it will also drop focus, which
729 * will cause another callback to this function, so set name_entry = 0
730 * first to ensure we don't double-remove etc. etc.
733 Gtk::Entry* tmp = name_entry;
735 name_hbox.remove (*tmp);
737 /* put the name label back */
739 name_hbox.pack_end (name_label);
744 TrackViewList const & allviews = _editor.get_track_views ();
745 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
747 if (i != allviews.end()) {
750 if (++i == allviews.end()) {
754 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
756 if (rtav && rtav->route()->record_enabled()) {
760 if (!(*i)->hidden()) {
767 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
768 _editor.ensure_time_axis_view_is_visible (**i, false);
769 (*i)->begin_name_edit ();
772 } else if (edit_prev) {
774 TrackViewList const & allviews = _editor.get_track_views ();
775 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
777 if (i != allviews.begin()) {
779 if (i == allviews.begin()) {
785 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
787 if (rtav && rtav->route()->record_enabled()) {
791 if (!(*i)->hidden()) {
798 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
799 _editor.ensure_time_axis_view_is_visible (**i, false);
800 (*i)->begin_name_edit ();
806 TimeAxisView::name_entry_changed ()
811 TimeAxisView::can_edit_name () const
817 TimeAxisView::conditionally_add_to_selection ()
819 Selection& s (_editor.get_selection ());
821 if (!s.selected (this)) {
822 _editor.set_selected_track (*this, Selection::Set);
827 TimeAxisView::popup_display_menu (guint32 when)
829 conditionally_add_to_selection ();
831 build_display_menu ();
832 display_menu->popup (1, when);
836 TimeAxisView::set_selected (bool yn)
838 if (can_edit_name() && name_entry && name_entry->get_visible()) {
839 end_name_edit (RESPONSE_CANCEL);
842 if (yn == _selected) {
846 Selectable::set_selected (yn);
849 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
850 time_axis_frame.set_name ("MixerStripSelectedFrame");
851 controls_ebox.set_name (controls_base_selected_name);
852 controls_vbox.set_name (controls_base_selected_name);
853 time_axis_vbox.set_name (controls_base_selected_name);
855 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
856 time_axis_frame.set_name (controls_base_unselected_name);
857 controls_ebox.set_name (controls_base_unselected_name);
858 controls_vbox.set_name (controls_base_unselected_name);
859 time_axis_vbox.set_name (controls_base_unselected_name);
863 /* children will be set for the yn=true case. but when deselecting
864 the editor only has a list of top-level trackviews, so we
865 have to do this here.
868 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
869 (*i)->set_selected (false);
873 time_axis_frame.show();
878 TimeAxisView::build_display_menu ()
880 using namespace Menu_Helpers;
884 display_menu = new Menu;
885 display_menu->set_name ("ArdourContextMenu");
887 // Just let implementing classes define what goes into the manu
891 TimeAxisView::set_samples_per_pixel (double fpp)
893 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
894 (*i)->set_samples_per_pixel (fpp);
899 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
901 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
902 (*i)->show_timestretch (start, end, layers, layer);
907 TimeAxisView::hide_timestretch ()
909 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
910 (*i)->hide_timestretch ();
915 TimeAxisView::show_selection (TimeSelection& ts)
920 SelectionRect *rect; time_axis_frame.show();
923 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
924 (*i)->show_selection (ts);
927 if (selection_group->visible ()) {
928 while (!used_selection_rects.empty()) {
929 free_selection_rects.push_front (used_selection_rects.front());
930 used_selection_rects.pop_front();
931 free_selection_rects.front()->rect->hide();
932 free_selection_rects.front()->start_trim->hide();
933 free_selection_rects.front()->end_trim->hide();
935 selection_group->hide();
938 selection_group->show();
939 selection_group->raise_to_top();
941 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
942 framepos_t start, end;
947 cnt = end - start + 1;
949 rect = get_selection_rect ((*i).id);
951 x1 = _editor.sample_to_pixel (start);
952 x2 = _editor.sample_to_pixel (start + cnt - 1);
953 y2 = current_height() - 1;
955 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
957 // trim boxes are at the top for selections
960 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
961 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
963 rect->start_trim->show();
964 rect->end_trim->show();
966 rect->start_trim->hide();
967 rect->end_trim->hide();
971 used_selection_rects.push_back (rect);
976 TimeAxisView::reshow_selection (TimeSelection& ts)
980 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
981 (*i)->show_selection (ts);
986 TimeAxisView::hide_selection ()
988 if (selection_group->visible ()) {
989 while (!used_selection_rects.empty()) {
990 free_selection_rects.push_front (used_selection_rects.front());
991 used_selection_rects.pop_front();
992 free_selection_rects.front()->rect->hide();
993 free_selection_rects.front()->start_trim->hide();
994 free_selection_rects.front()->end_trim->hide();
996 selection_group->hide();
999 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1000 (*i)->hide_selection ();
1005 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
1007 /* find the selection rect this is for. we have the item corresponding to one
1008 of the trim handles.
1011 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1012 if ((*i)->start_trim == item || (*i)->end_trim == item) {
1014 /* make one trim handle be "above" the other so that if they overlap,
1015 the top one is the one last used.
1018 (*i)->rect->raise_to_top ();
1019 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
1020 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
1028 TimeAxisView::get_selection_rect (uint32_t id)
1030 SelectionRect *rect;
1032 /* check to see if we already have a visible rect for this particular selection ID */
1034 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1035 if ((*i)->id == id) {
1040 /* ditto for the free rect list */
1042 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1043 if ((*i)->id == id) {
1044 SelectionRect* ret = (*i);
1045 free_selection_rects.erase (i);
1050 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1052 if (free_selection_rects.empty()) {
1054 rect = new SelectionRect;
1056 rect->rect = new ArdourCanvas::Rectangle (selection_group);
1057 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1058 rect->rect->set_outline (false);
1059 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1061 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1062 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1063 rect->start_trim->set_outline (false);
1064 rect->start_trim->set_fill (false);
1066 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1067 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1068 rect->end_trim->set_outline (false);
1069 rect->end_trim->set_fill (false);
1071 free_selection_rects.push_front (rect);
1073 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1074 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1075 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1078 rect = free_selection_rects.front();
1080 free_selection_rects.pop_front();
1084 struct null_deleter { void operator()(void const *) const {} };
1087 TimeAxisView::is_child (TimeAxisView* tav)
1089 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1093 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1095 children.push_back (child);
1099 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1101 Children::iterator i;
1103 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1108 /** Get selectable things within a given range.
1109 * @param start Start time in session frames.
1110 * @param end End time in session frames.
1111 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1112 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1113 * @param result Filled in with selectable things.
1116 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1122 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1128 TimeAxisView::add_ghost (RegionView* rv)
1130 GhostRegion* gr = rv->add_ghost (*this);
1133 ghosts.push_back(gr);
1138 TimeAxisView::remove_ghost (RegionView* rv)
1140 rv->remove_ghost_in (*this);
1144 TimeAxisView::erase_ghost (GhostRegion* gr)
1146 if (in_destructor) {
1150 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1159 TimeAxisView::touched (double top, double bot)
1161 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1162 y_position is the "origin" or "top" of the track.
1165 double mybot = _y_position + current_height();
1167 return ((_y_position <= bot && _y_position >= top) ||
1168 ((mybot <= bot) && (top < mybot)) ||
1169 (mybot >= bot && _y_position < top));
1173 TimeAxisView::set_parent (TimeAxisView& p)
1179 TimeAxisView::reset_height ()
1181 set_height (height);
1183 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1184 (*i)->set_height ((*i)->height);
1189 TimeAxisView::compute_heights ()
1191 // TODO this function should be re-evaluated when font-scaling changes (!)
1192 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1193 Gtk::Table one_row_table (1, 1);
1194 ArdourButton* test_button = manage (new ArdourButton);
1195 const int border_width = 2;
1196 const int frame_height = 2;
1197 extra_height = (2 * border_width) + frame_height;
1199 window.add (one_row_table);
1200 test_button->set_name ("mute button");
1201 test_button->set_text (S_("Mute|M"));
1202 test_button->set_tweaks (ArdourButton::TrackHeader);
1204 one_row_table.set_border_width (border_width);
1205 one_row_table.set_row_spacings (2);
1206 one_row_table.set_col_spacings (2);
1208 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1209 one_row_table.show_all ();
1211 Gtk::Requisition req(one_row_table.size_request ());
1212 button_height = req.height;
1216 TimeAxisView::color_handler ()
1218 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1222 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1224 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1225 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1227 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1228 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1230 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1231 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1234 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1236 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1237 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1239 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1240 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1242 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1243 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1247 /** @return Pair: TimeAxisView, layer index.
1248 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1249 * does. @param y is an offset from the top of the trackview area.
1251 * If the covering object is a child axis, then the child is returned.
1252 * TimeAxisView is 0 otherwise.
1254 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1255 * and is in stacked or expanded * region display mode, otherwise 0.
1257 std::pair<TimeAxisView*, double>
1258 TimeAxisView::covers_y_position (double y) const
1261 return std::make_pair ((TimeAxisView *) 0, 0);
1264 if (_y_position <= y && y < (_y_position + height)) {
1266 /* work out the layer index if appropriate */
1268 switch (layer_display ()) {
1274 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1275 /* clamp to max layers to be on the safe side; sometimes the above calculation
1276 returns a too-high value */
1277 if (l >= view()->layers ()) {
1278 l = view()->layers() - 1;
1284 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1286 if (l >= (view()->layers() - 0.5)) {
1287 l = view()->layers() - 0.5;
1293 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1296 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1298 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1304 return std::make_pair ((TimeAxisView *) 0, 0);
1308 TimeAxisView::covered_by_y_range (double y0, double y1) const
1314 /* if either the top or bottom of the axisview is in the vertical
1315 * range, we cover it.
1318 if ((y0 < _y_position && y1 < _y_position) ||
1319 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1323 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1324 if ((*i)->covered_by_y_range (y0, y1)) {
1333 TimeAxisView::preset_height (Height h)
1337 return (button_height * 2) + extra_height + 260;
1339 return (button_height * 2) + extra_height + 160;
1341 return (button_height * 2) + extra_height + 60;
1343 return (button_height * 2) + extra_height + 10;
1345 return button_height + extra_height;
1348 abort(); /* NOTREACHED */
1352 /** @return Child time axis views that are not hidden */
1353 TimeAxisView::Children
1354 TimeAxisView::get_child_list ()
1358 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1359 if (!(*i)->hidden()) {
1368 TimeAxisView::build_size_menu ()
1370 if (_size_menu && _size_menu->gobj ()) {
1376 using namespace Menu_Helpers;
1378 _size_menu = new Menu;
1379 _size_menu->set_name ("ArdourContextMenu");
1380 MenuList& items = _size_menu->items();
1382 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1383 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1384 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1385 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1386 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1390 TimeAxisView::reset_visual_state ()
1392 /* this method is not required to trigger a global redraw */
1394 string str = gui_property ("height");
1397 set_height (atoi (str));
1399 set_height (preset_height (HeightNormal));
1404 TrackViewList::filter_to_unique_playlists ()
1406 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1409 for (iterator i = begin(); i != end(); ++i) {
1410 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1412 /* not a route: include it anyway */
1415 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1417 if (playlists.insert (t->playlist()).second) {
1418 /* playlist not seen yet */
1422 /* not a track: include it anyway */