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)
107 , by_popup_menu (false)
110 , _effective_height (0)
111 , _resize_drag_start (-1)
112 , _did_resize (false)
113 , _preresize_cursor (0)
114 , _have_preresize_cursor (false)
115 , _ebox_release_can_act (true)
117 if (!controls_meters_size_group) {
118 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
120 if (!midi_scroomer_size_group) {
121 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
123 if (extra_height == 0) {
127 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
128 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
129 _canvas_display->hide(); // reveal as needed
131 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
132 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
133 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
134 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
135 _canvas_separator->set_outline_width(1.0);
136 _canvas_separator->hide();
138 selection_group = new ArdourCanvas::Container (_canvas_display);
139 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
140 selection_group->set_data (X_("timeselection"), (void *) 1);
141 selection_group->hide();
143 _ghost_group = new ArdourCanvas::Container (_canvas_display);
144 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
145 _ghost_group->lower_to_bottom();
146 _ghost_group->show();
148 name_label.set_name ("TrackLabel");
149 name_label.set_alignment (0.0, 0.5);
150 name_label.set_width_chars (12);
151 set_tooltip (name_label, _("Track/Bus name (double click to edit)"));
153 Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
154 an_entry->set_name ("EditorTrackNameDisplay");
155 Gtk::Requisition req;
156 an_entry->size_request (req);
157 name_label.set_size_request (-1, req.height);
158 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
161 name_hbox.pack_end (name_label, true, true);
163 // set min. track-header width if fader is not visible
164 name_hbox.set_size_request(name_width_px, -1);
169 controls_table.set_row_spacings (2);
170 controls_table.set_col_spacings (2);
171 controls_table.set_border_width (2);
173 if (ARDOUR::Profile->get_mixbus() ) {
174 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
176 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
178 controls_table.show_all ();
179 controls_table.set_no_show_all ();
181 controls_vbox.pack_start (controls_table, false, false);
182 controls_vbox.show ();
184 top_hbox.pack_start (controls_vbox, true, true);
187 controls_ebox.add (time_axis_hbox);
188 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
189 Gdk::BUTTON_RELEASE_MASK|
190 Gdk::POINTER_MOTION_MASK|
191 Gdk::ENTER_NOTIFY_MASK|
192 Gdk::LEAVE_NOTIFY_MASK|
194 controls_ebox.set_flags (CAN_FOCUS);
196 /* note that this handler connects *before* the default handler */
197 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
198 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
199 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
200 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
201 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
202 controls_ebox.show ();
204 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
205 time_axis_frame.add(top_hbox);
206 time_axis_frame.show();
208 HSeparator* separator = manage (new HSeparator());
209 separator->set_name("TrackSeparator");
210 separator->set_size_request(-1, 1);
213 scroomer_placeholder.set_size_request (-1, -1);
214 scroomer_placeholder.show();
215 midi_scroomer_size_group->add_widget (scroomer_placeholder);
217 time_axis_vbox.pack_start (*separator, false, false);
218 time_axis_vbox.pack_start (time_axis_frame, true, true);
219 time_axis_vbox.show();
220 time_axis_hbox.pack_start (time_axis_vbox, true, true);
221 time_axis_hbox.show();
222 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
224 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
227 TimeAxisView::~TimeAxisView()
229 CatchDeletion (this);
231 in_destructor = true;
233 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
237 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
238 delete (*i)->rect; (*i)->rect=0;
239 delete (*i)->start_trim; (*i)->start_trim = 0;
240 delete (*i)->end_trim; (*i)->end_trim = 0;
244 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
245 delete (*i)->rect; (*i)->rect = 0;
246 delete (*i)->start_trim; (*i)->start_trim = 0;
247 delete (*i)->end_trim; (*i)->end_trim = 0;
250 delete selection_group;
253 delete _canvas_display;
263 TimeAxisView::hide ()
269 _canvas_display->hide ();
270 _canvas_separator->hide ();
272 if (control_parent) {
273 control_parent->remove (TOP_LEVEL_WIDGET);
280 /* now hide children */
282 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
286 /* if its hidden, it cannot be selected */
287 _editor.get_selection().remove (this);
288 /* and neither can its regions */
289 _editor.get_selection().remove_regions (this);
294 /** Display this TimeAxisView as the nth component of the parent box, at y.
296 * @param y y position.
297 * @param nth index for this TimeAxisView, increased if this view has children.
298 * @param parent parent component.
299 * @return height of this TimeAxisView.
302 TimeAxisView::show_at (double y, int& nth, VBox *parent)
304 if (control_parent) {
305 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
307 control_parent = parent;
308 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
309 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
314 if (_y_position != y) {
315 _canvas_display->set_y_position (y);
319 _canvas_display->raise_to_top ();
320 _canvas_display->show ();
324 _effective_height = current_height ();
326 /* now show relevant children */
328 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
329 if ((*i)->marked_for_display()) {
331 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
337 /* put separator at the bottom of this time axis view */
339 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
340 _canvas_separator->lower_to_bottom ();
341 _canvas_separator->show ();
343 return _effective_height;
347 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
349 switch (ev->direction) {
351 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
352 /* See Editor::_stepping_axis_view for notes on this hack */
353 Editor& e = dynamic_cast<Editor&> (_editor);
354 if (!e.stepping_axis_view ()) {
355 e.set_stepping_axis_view (this);
357 e.stepping_axis_view()->step_height (false);
362 case GDK_SCROLL_DOWN:
363 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
364 /* See Editor::_stepping_axis_view for notes on this hack */
365 Editor& e = dynamic_cast<Editor&> (_editor);
366 if (!e.stepping_axis_view ()) {
367 e.set_stepping_axis_view (this);
369 e.stepping_axis_view()->step_height (true);
375 /* no handling for left/right, yet */
379 /* Just forward to the normal canvas scroll method. The coordinate
380 systems are different but since the canvas is always larger than the
381 track headers, and aligned with the trackview area, this will work.
383 In the not too distant future this layout is going away anyway and
384 headers will be on the canvas.
386 return _editor.canvas_scroll_event (ev, false);
390 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
392 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
393 /* see if it is inside the name label */
394 if (name_label.is_ancestor (controls_ebox)) {
397 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
398 Gtk::Allocation a = name_label.get_allocation ();
399 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
401 _ebox_release_can_act = false;
408 _ebox_release_can_act = true;
410 if (maybe_set_cursor (event->y) > 0) {
411 _resize_drag_start = event->y_root;
418 TimeAxisView::idle_resize (int32_t h)
420 set_height (std::max(0, h));
425 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
427 if (_resize_drag_start >= 0) {
429 /* (ab)use the DragManager to do autoscrolling - basically we
430 * are pretending that the drag is taking place over the canvas
431 * (which perhaps in the glorious future, when track headers
432 * and the canvas are unified, will actually be true.)
435 _editor.maybe_autoscroll (false, true, true);
437 /* now schedule the actual TAV resize */
438 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
439 _editor.add_to_idle_resize (this, delta);
440 _resize_drag_start = ev->y_root;
443 /* not dragging but ... */
444 maybe_set_cursor (ev->y);
447 gdk_event_request_motions(ev);
452 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
454 if (_have_preresize_cursor) {
455 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
456 _have_preresize_cursor = false;
462 TimeAxisView::maybe_set_cursor (int y)
464 /* XXX no Gtkmm Gdk::Window::get_cursor() */
465 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
467 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
469 /* y-coordinate in lower 25% */
471 if (!_have_preresize_cursor) {
472 _preresize_cursor = gdk_window_get_cursor (win->gobj());
473 _have_preresize_cursor = true;
474 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
479 } else if (_have_preresize_cursor) {
480 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
481 _have_preresize_cursor = false;
490 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
492 if (_resize_drag_start >= 0) {
493 if (_have_preresize_cursor) {
494 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
495 _preresize_cursor = 0;
496 _have_preresize_cursor = false;
498 _editor.stop_canvas_autoscroll ();
499 _resize_drag_start = -1;
502 // don't change selection
507 if (!_ebox_release_can_act) {
511 switch (ev->button) {
513 selection_click (ev);
517 popup_display_menu (ev->time);
525 TimeAxisView::selection_click (GdkEventButton* ev)
527 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
528 _editor.set_selected_track (*this, op, false);
532 /** Steps through the defined heights for this TrackView.
533 * @param coarser true if stepping should decrease in size, otherwise false.
536 TimeAxisView::step_height (bool coarser)
538 static const uint32_t step = 25;
542 if (height <= preset_height (HeightSmall)) {
544 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
545 set_height_enum (HeightSmall);
547 set_height (height - step);
552 if (height <= preset_height(HeightSmall)) {
553 set_height_enum (HeightNormal);
555 set_height (height + step);
562 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
564 if (apply_to_selection) {
565 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
567 set_height (preset_height (h));
572 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
575 if (m == TotalHeight) {
576 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
577 if ( !(*i)->hidden()) ++lanes;
582 if (h < preset_height (HeightSmall)) {
583 h = preset_height (HeightSmall);
586 TOP_LEVEL_WIDGET.property_height_request () = h;
590 snprintf (buf, sizeof (buf), "%u", height);
591 set_gui_property ("height", buf);
593 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
597 if (selection_group->visible ()) {
598 /* resize the selection rect */
599 show_selection (_editor.get_selection().time);
603 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
604 (*i)->set_height(h, OnlySelf);
608 _editor.override_visible_track_count ();
612 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
614 /* steal escape, tabs from GTK */
616 switch (ev->keyval) {
618 case GDK_ISO_Left_Tab:
626 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
628 TrackViewList::iterator i;
630 switch (ev->keyval) {
632 end_name_edit (RESPONSE_CANCEL);
635 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
636 * generates a different ev->keyval, rather than setting
639 case GDK_ISO_Left_Tab:
640 end_name_edit (RESPONSE_APPLY);
644 end_name_edit (RESPONSE_ACCEPT);
654 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
657 by_popup_menu = false;
660 end_name_edit (RESPONSE_OK);
665 TimeAxisView::name_entry_populate_popup (Gtk::Menu *)
667 by_popup_menu = true;
671 TimeAxisView::begin_name_edit ()
677 if (can_edit_name()) {
679 name_entry = manage (new Gtkmm2ext::FocusEntry);
681 name_entry->set_width_chars(8); // min width, entry expands
683 name_entry->set_name ("EditorTrackNameDisplay");
684 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
685 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
686 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
687 name_entry->set_text (name_label.get_text());
688 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
689 name_entry->signal_populate_popup().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_populate_popup));
691 if (name_label.is_ancestor (name_hbox)) {
692 name_hbox.remove (name_label);
695 name_hbox.pack_end (*name_entry, true, true);
698 name_entry->select_region (0, -1);
699 name_entry->set_state (STATE_SELECTED);
700 name_entry->grab_focus ();
701 name_entry->start_editing (0);
706 TimeAxisView::end_name_edit (int response)
712 if (ending_name_edit) {
713 /* already doing this, and focus out or other event has caused
714 us to re-enter this code.
719 PBD::Unwinder<bool> uw (ending_name_edit, true);
721 bool edit_next = false;
722 bool edit_prev = false;
725 case RESPONSE_CANCEL:
728 name_entry_changed ();
730 case RESPONSE_ACCEPT:
731 name_entry_changed ();
734 name_entry_changed ();
738 /* this will delete the name_entry. but it will also drop focus, which
739 * will cause another callback to this function, so set name_entry = 0
740 * first to ensure we don't double-remove etc. etc.
743 Gtk::Entry* tmp = name_entry;
745 name_hbox.remove (*tmp);
747 /* put the name label back */
749 name_hbox.pack_end (name_label);
754 TrackViewList const & allviews = _editor.get_track_views ();
755 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
757 if (i != allviews.end()) {
760 if (++i == allviews.end()) {
764 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
766 if (rtav && rtav->route()->record_enabled()) {
770 if (!(*i)->hidden()) {
777 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
778 _editor.ensure_time_axis_view_is_visible (**i, false);
779 (*i)->begin_name_edit ();
782 } else if (edit_prev) {
784 TrackViewList const & allviews = _editor.get_track_views ();
785 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
787 if (i != allviews.begin()) {
789 if (i == allviews.begin()) {
795 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
797 if (rtav && rtav->route()->record_enabled()) {
801 if (!(*i)->hidden()) {
808 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
809 _editor.ensure_time_axis_view_is_visible (**i, false);
810 (*i)->begin_name_edit ();
816 TimeAxisView::name_entry_changed ()
821 TimeAxisView::can_edit_name () const
827 TimeAxisView::conditionally_add_to_selection ()
829 Selection& s (_editor.get_selection ());
831 if (!s.selected (this)) {
832 _editor.set_selected_track (*this, Selection::Set);
837 TimeAxisView::popup_display_menu (guint32 when)
839 conditionally_add_to_selection ();
841 build_display_menu ();
842 display_menu->popup (1, when);
846 TimeAxisView::set_selected (bool yn)
848 if (can_edit_name() && name_entry && name_entry->get_visible()) {
849 end_name_edit (RESPONSE_CANCEL);
852 if (yn == _selected) {
856 Selectable::set_selected (yn);
859 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
860 time_axis_frame.set_name ("MixerStripSelectedFrame");
861 controls_ebox.set_name (controls_base_selected_name);
862 controls_vbox.set_name (controls_base_selected_name);
863 time_axis_vbox.set_name (controls_base_selected_name);
865 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
866 time_axis_frame.set_name (controls_base_unselected_name);
867 controls_ebox.set_name (controls_base_unselected_name);
868 controls_vbox.set_name (controls_base_unselected_name);
869 time_axis_vbox.set_name (controls_base_unselected_name);
873 /* children will be set for the yn=true case. but when deselecting
874 the editor only has a list of top-level trackviews, so we
875 have to do this here.
878 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
879 (*i)->set_selected (false);
883 time_axis_frame.show();
888 TimeAxisView::build_display_menu ()
890 using namespace Menu_Helpers;
894 display_menu = new Menu;
895 display_menu->set_name ("ArdourContextMenu");
897 // Just let implementing classes define what goes into the manu
901 TimeAxisView::set_samples_per_pixel (double fpp)
903 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
904 (*i)->set_samples_per_pixel (fpp);
909 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
911 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
912 (*i)->show_timestretch (start, end, layers, layer);
917 TimeAxisView::hide_timestretch ()
919 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
920 (*i)->hide_timestretch ();
925 TimeAxisView::show_selection (TimeSelection& ts)
930 SelectionRect *rect; time_axis_frame.show();
933 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
934 (*i)->show_selection (ts);
937 if (selection_group->visible ()) {
938 while (!used_selection_rects.empty()) {
939 free_selection_rects.push_front (used_selection_rects.front());
940 used_selection_rects.pop_front();
941 free_selection_rects.front()->rect->hide();
942 free_selection_rects.front()->start_trim->hide();
943 free_selection_rects.front()->end_trim->hide();
945 selection_group->hide();
948 selection_group->show();
949 selection_group->raise_to_top();
951 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
952 framepos_t start, end;
957 cnt = end - start + 1;
959 rect = get_selection_rect ((*i).id);
961 x1 = _editor.sample_to_pixel (start);
962 x2 = _editor.sample_to_pixel (start + cnt - 1);
963 y2 = current_height() - 1;
965 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
967 // trim boxes are at the top for selections
970 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
971 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
973 rect->start_trim->show();
974 rect->end_trim->show();
976 rect->start_trim->hide();
977 rect->end_trim->hide();
981 used_selection_rects.push_back (rect);
986 TimeAxisView::reshow_selection (TimeSelection& ts)
990 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
991 (*i)->show_selection (ts);
996 TimeAxisView::hide_selection ()
998 if (selection_group->visible ()) {
999 while (!used_selection_rects.empty()) {
1000 free_selection_rects.push_front (used_selection_rects.front());
1001 used_selection_rects.pop_front();
1002 free_selection_rects.front()->rect->hide();
1003 free_selection_rects.front()->start_trim->hide();
1004 free_selection_rects.front()->end_trim->hide();
1006 selection_group->hide();
1009 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1010 (*i)->hide_selection ();
1015 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
1017 /* find the selection rect this is for. we have the item corresponding to one
1018 of the trim handles.
1021 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1022 if ((*i)->start_trim == item || (*i)->end_trim == item) {
1024 /* make one trim handle be "above" the other so that if they overlap,
1025 the top one is the one last used.
1028 (*i)->rect->raise_to_top ();
1029 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
1030 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
1038 TimeAxisView::get_selection_rect (uint32_t id)
1040 SelectionRect *rect;
1042 /* check to see if we already have a visible rect for this particular selection ID */
1044 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1045 if ((*i)->id == id) {
1050 /* ditto for the free rect list */
1052 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1053 if ((*i)->id == id) {
1054 SelectionRect* ret = (*i);
1055 free_selection_rects.erase (i);
1060 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1062 if (free_selection_rects.empty()) {
1064 rect = new SelectionRect;
1066 rect->rect = new ArdourCanvas::Rectangle (selection_group);
1067 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1068 rect->rect->set_outline (false);
1069 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1071 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1072 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1073 rect->start_trim->set_outline (false);
1074 rect->start_trim->set_fill (false);
1076 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1077 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1078 rect->end_trim->set_outline (false);
1079 rect->end_trim->set_fill (false);
1081 free_selection_rects.push_front (rect);
1083 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1084 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1085 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1088 rect = free_selection_rects.front();
1090 free_selection_rects.pop_front();
1094 struct null_deleter { void operator()(void const *) const {} };
1097 TimeAxisView::is_child (TimeAxisView* tav)
1099 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1103 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1105 children.push_back (child);
1109 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1111 Children::iterator i;
1113 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1118 /** Get selectable things within a given range.
1119 * @param start Start time in session frames.
1120 * @param end End time in session frames.
1121 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1122 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1123 * @param result Filled in with selectable things.
1126 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1132 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1138 TimeAxisView::add_ghost (RegionView* rv)
1140 GhostRegion* gr = rv->add_ghost (*this);
1143 ghosts.push_back(gr);
1148 TimeAxisView::remove_ghost (RegionView* rv)
1150 rv->remove_ghost_in (*this);
1154 TimeAxisView::erase_ghost (GhostRegion* gr)
1156 if (in_destructor) {
1160 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1169 TimeAxisView::touched (double top, double bot)
1171 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1172 y_position is the "origin" or "top" of the track.
1175 double mybot = _y_position + current_height();
1177 return ((_y_position <= bot && _y_position >= top) ||
1178 ((mybot <= bot) && (top < mybot)) ||
1179 (mybot >= bot && _y_position < top));
1183 TimeAxisView::set_parent (TimeAxisView& p)
1189 TimeAxisView::reset_height ()
1191 set_height (height);
1193 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1194 (*i)->set_height ((*i)->height);
1199 TimeAxisView::compute_heights ()
1201 // TODO this function should be re-evaluated when font-scaling changes (!)
1202 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1203 Gtk::Table one_row_table (1, 1);
1204 ArdourButton* test_button = manage (new ArdourButton);
1205 const int border_width = 2;
1206 const int frame_height = 2;
1207 extra_height = (2 * border_width) + frame_height;
1209 window.add (one_row_table);
1210 test_button->set_name ("mute button");
1211 test_button->set_text (S_("Mute|M"));
1212 test_button->set_tweaks (ArdourButton::TrackHeader);
1214 one_row_table.set_border_width (border_width);
1215 one_row_table.set_row_spacings (2);
1216 one_row_table.set_col_spacings (2);
1218 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1219 one_row_table.show_all ();
1221 Gtk::Requisition req(one_row_table.size_request ());
1222 button_height = req.height;
1226 TimeAxisView::color_handler ()
1228 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1232 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1234 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1235 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1237 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1238 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1240 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1241 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1244 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1246 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1247 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1249 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1250 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1252 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1253 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1257 /** @return Pair: TimeAxisView, layer index.
1258 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1259 * does. @param y is an offset from the top of the trackview area.
1261 * If the covering object is a child axis, then the child is returned.
1262 * TimeAxisView is 0 otherwise.
1264 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1265 * and is in stacked or expanded * region display mode, otherwise 0.
1267 std::pair<TimeAxisView*, double>
1268 TimeAxisView::covers_y_position (double y) const
1271 return std::make_pair ((TimeAxisView *) 0, 0);
1274 if (_y_position <= y && y < (_y_position + height)) {
1276 /* work out the layer index if appropriate */
1278 switch (layer_display ()) {
1284 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1285 /* clamp to max layers to be on the safe side; sometimes the above calculation
1286 returns a too-high value */
1287 if (l >= view()->layers ()) {
1288 l = view()->layers() - 1;
1294 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1296 if (l >= (view()->layers() - 0.5)) {
1297 l = view()->layers() - 0.5;
1303 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1306 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1308 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1314 return std::make_pair ((TimeAxisView *) 0, 0);
1318 TimeAxisView::covered_by_y_range (double y0, double y1) const
1324 /* if either the top or bottom of the axisview is in the vertical
1325 * range, we cover it.
1328 if ((y0 < _y_position && y1 < _y_position) ||
1329 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1333 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1334 if ((*i)->covered_by_y_range (y0, y1)) {
1343 TimeAxisView::preset_height (Height h)
1347 return (button_height * 2) + extra_height + 260;
1349 return (button_height * 2) + extra_height + 160;
1351 return (button_height * 2) + extra_height + 60;
1353 return (button_height * 2) + extra_height + 10;
1355 return button_height + extra_height;
1358 abort(); /* NOTREACHED */
1362 /** @return Child time axis views that are not hidden */
1363 TimeAxisView::Children
1364 TimeAxisView::get_child_list ()
1368 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1369 if (!(*i)->hidden()) {
1378 TimeAxisView::build_size_menu ()
1380 if (_size_menu && _size_menu->gobj ()) {
1386 using namespace Menu_Helpers;
1388 _size_menu = new Menu;
1389 _size_menu->set_name ("ArdourContextMenu");
1390 MenuList& items = _size_menu->items();
1392 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1393 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1394 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1395 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1396 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1400 TimeAxisView::reset_visual_state ()
1402 /* this method is not required to trigger a global redraw */
1404 string str = gui_property ("height");
1407 set_height (atoi (str));
1409 set_height (preset_height (HeightNormal));
1414 TrackViewList::filter_to_unique_playlists ()
1416 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1419 for (iterator i = begin(); i != end(); ++i) {
1420 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1422 /* not a route: include it anyway */
1425 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1427 if (playlists.insert (t->playlist()).second) {
1428 /* playlist not seen yet */
1432 /* not a track: include it anyway */