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));
226 TimeAxisView::~TimeAxisView()
228 CatchDeletion (this);
230 in_destructor = true;
232 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
236 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
237 delete (*i)->rect; (*i)->rect=0;
238 delete (*i)->start_trim; (*i)->start_trim = 0;
239 delete (*i)->end_trim; (*i)->end_trim = 0;
243 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
244 delete (*i)->rect; (*i)->rect = 0;
245 delete (*i)->start_trim; (*i)->start_trim = 0;
246 delete (*i)->end_trim; (*i)->end_trim = 0;
249 delete selection_group;
252 delete _canvas_display;
262 TimeAxisView::hide ()
268 _canvas_display->hide ();
269 _canvas_separator->hide ();
271 if (control_parent) {
272 control_parent->remove (TOP_LEVEL_WIDGET);
279 /* now hide children */
281 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
285 /* if its hidden, it cannot be selected */
286 _editor.get_selection().remove (this);
287 /* and neither can its regions */
288 _editor.get_selection().remove_regions (this);
293 /** Display this TimeAxisView as the nth component of the parent box, at y.
295 * @param y y position.
296 * @param nth index for this TimeAxisView, increased if this view has children.
297 * @param parent parent component.
298 * @return height of this TimeAxisView.
301 TimeAxisView::show_at (double y, int& nth, VBox *parent)
303 if (control_parent) {
304 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
306 control_parent = parent;
307 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
308 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
313 if (_y_position != y) {
314 _canvas_display->set_y_position (y);
318 _canvas_display->raise_to_top ();
319 _canvas_display->show ();
323 _effective_height = current_height ();
325 /* now show relevant children */
327 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
328 if ((*i)->marked_for_display()) {
330 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
336 /* put separator at the bottom of this time axis view */
338 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
339 _canvas_separator->lower_to_bottom ();
340 _canvas_separator->show ();
342 return _effective_height;
346 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
348 switch (ev->direction) {
350 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
351 /* See Editor::_stepping_axis_view for notes on this hack */
352 Editor& e = dynamic_cast<Editor&> (_editor);
353 if (!e.stepping_axis_view ()) {
354 e.set_stepping_axis_view (this);
356 e.stepping_axis_view()->step_height (false);
361 case GDK_SCROLL_DOWN:
362 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
363 /* See Editor::_stepping_axis_view for notes on this hack */
364 Editor& e = dynamic_cast<Editor&> (_editor);
365 if (!e.stepping_axis_view ()) {
366 e.set_stepping_axis_view (this);
368 e.stepping_axis_view()->step_height (true);
374 /* no handling for left/right, yet */
378 /* Just forward to the normal canvas scroll method. The coordinate
379 systems are different but since the canvas is always larger than the
380 track headers, and aligned with the trackview area, this will work.
382 In the not too distant future this layout is going away anyway and
383 headers will be on the canvas.
385 return _editor.canvas_scroll_event (ev, false);
389 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
391 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
392 /* see if it is inside the name label */
393 if (name_label.is_ancestor (controls_ebox)) {
396 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
397 Gtk::Allocation a = name_label.get_allocation ();
398 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
400 _ebox_release_can_act = false;
407 _ebox_release_can_act = true;
409 if (maybe_set_cursor (event->y) > 0) {
410 _resize_drag_start = event->y_root;
417 TimeAxisView::idle_resize (int32_t h)
419 set_height (std::max(0, h));
424 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
426 if (_resize_drag_start >= 0) {
428 /* (ab)use the DragManager to do autoscrolling - basically we
429 * are pretending that the drag is taking place over the canvas
430 * (which perhaps in the glorious future, when track headers
431 * and the canvas are unified, will actually be true.)
434 _editor.maybe_autoscroll (false, true, true);
436 /* now schedule the actual TAV resize */
437 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
438 _editor.add_to_idle_resize (this, delta);
439 _resize_drag_start = ev->y_root;
442 /* not dragging but ... */
443 maybe_set_cursor (ev->y);
446 gdk_event_request_motions(ev);
451 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
453 if (_have_preresize_cursor) {
454 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
455 _have_preresize_cursor = false;
461 TimeAxisView::maybe_set_cursor (int y)
463 /* XXX no Gtkmm Gdk::Window::get_cursor() */
464 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
466 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
468 /* y-coordinate in lower 25% */
470 if (!_have_preresize_cursor) {
471 _preresize_cursor = gdk_window_get_cursor (win->gobj());
472 _have_preresize_cursor = true;
473 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
478 } else if (_have_preresize_cursor) {
479 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
480 _have_preresize_cursor = false;
489 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
491 if (_resize_drag_start >= 0) {
492 if (_have_preresize_cursor) {
493 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
494 _preresize_cursor = 0;
495 _have_preresize_cursor = false;
497 _editor.stop_canvas_autoscroll ();
498 _resize_drag_start = -1;
501 // don't change selection
506 if (!_ebox_release_can_act) {
510 switch (ev->button) {
512 selection_click (ev);
516 popup_display_menu (ev->time);
524 TimeAxisView::selection_click (GdkEventButton* ev)
526 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
527 _editor.set_selected_track (*this, op, false);
531 /** Steps through the defined heights for this TrackView.
532 * @param coarser true if stepping should decrease in size, otherwise false.
535 TimeAxisView::step_height (bool coarser)
537 static const uint32_t step = 25;
541 if (height <= preset_height (HeightSmall)) {
543 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
544 set_height_enum (HeightSmall);
546 set_height (height - step);
551 if (height <= preset_height(HeightSmall)) {
552 set_height_enum (HeightNormal);
554 set_height (height + step);
561 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
563 if (apply_to_selection) {
564 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
566 set_height (preset_height (h));
571 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
574 if (m == TotalHeight) {
575 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
576 if ( !(*i)->hidden()) ++lanes;
581 if (h < preset_height (HeightSmall)) {
582 h = preset_height (HeightSmall);
585 TOP_LEVEL_WIDGET.property_height_request () = h;
589 snprintf (buf, sizeof (buf), "%u", height);
590 set_gui_property ("height", buf);
592 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
596 if (selection_group->visible ()) {
597 /* resize the selection rect */
598 show_selection (_editor.get_selection().time);
602 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
603 (*i)->set_height(h, OnlySelf);
607 _editor.override_visible_track_count ();
611 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
613 /* steal escape, tabs from GTK */
615 switch (ev->keyval) {
617 case GDK_ISO_Left_Tab:
625 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
627 TrackViewList::iterator i;
629 switch (ev->keyval) {
631 end_name_edit (RESPONSE_CANCEL);
634 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
635 * generates a different ev->keyval, rather than setting
638 case GDK_ISO_Left_Tab:
639 end_name_edit (RESPONSE_APPLY);
643 end_name_edit (RESPONSE_ACCEPT);
653 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
655 end_name_edit (RESPONSE_OK);
660 TimeAxisView::begin_name_edit ()
666 if (can_edit_name()) {
668 name_entry = manage (new Gtkmm2ext::FocusEntry);
670 name_entry->set_width_chars(8); // min width, entry expands
672 name_entry->set_name ("EditorTrackNameDisplay");
673 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
674 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
675 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
676 name_entry->set_text (name_label.get_text());
677 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
679 if (name_label.is_ancestor (name_hbox)) {
680 name_hbox.remove (name_label);
683 name_hbox.pack_end (*name_entry, true, true);
686 name_entry->select_region (0, -1);
687 name_entry->set_state (STATE_SELECTED);
688 name_entry->grab_focus ();
689 name_entry->start_editing (0);
694 TimeAxisView::end_name_edit (int response)
700 if (ending_name_edit) {
701 /* already doing this, and focus out or other event has caused
702 us to re-enter this code.
707 PBD::Unwinder<bool> uw (ending_name_edit, true);
709 bool edit_next = false;
710 bool edit_prev = false;
713 case RESPONSE_CANCEL:
716 name_entry_changed ();
718 case RESPONSE_ACCEPT:
719 name_entry_changed ();
722 name_entry_changed ();
726 /* this will delete the name_entry. but it will also drop focus, which
727 * will cause another callback to this function, so set name_entry = 0
728 * first to ensure we don't double-remove etc. etc.
731 Gtk::Entry* tmp = name_entry;
733 name_hbox.remove (*tmp);
735 /* put the name label back */
737 name_hbox.pack_end (name_label);
742 TrackViewList const & allviews = _editor.get_track_views ();
743 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
745 if (i != allviews.end()) {
748 if (++i == allviews.end()) {
752 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
754 if (rtav && rtav->route()->record_enabled()) {
758 if (!(*i)->hidden()) {
765 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
766 _editor.ensure_time_axis_view_is_visible (**i, false);
767 (*i)->begin_name_edit ();
770 } else if (edit_prev) {
772 TrackViewList const & allviews = _editor.get_track_views ();
773 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
775 if (i != allviews.begin()) {
777 if (i == allviews.begin()) {
783 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
785 if (rtav && rtav->route()->record_enabled()) {
789 if (!(*i)->hidden()) {
796 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
797 _editor.ensure_time_axis_view_is_visible (**i, false);
798 (*i)->begin_name_edit ();
804 TimeAxisView::name_entry_changed ()
809 TimeAxisView::can_edit_name () const
815 TimeAxisView::conditionally_add_to_selection ()
817 Selection& s (_editor.get_selection ());
819 if (!s.selected (this)) {
820 _editor.set_selected_track (*this, Selection::Set);
825 TimeAxisView::popup_display_menu (guint32 when)
827 conditionally_add_to_selection ();
829 build_display_menu ();
830 display_menu->popup (1, when);
834 TimeAxisView::set_selected (bool yn)
836 if (can_edit_name() && name_entry && name_entry->get_visible()) {
837 end_name_edit (RESPONSE_CANCEL);
840 if (yn == _selected) {
844 Selectable::set_selected (yn);
847 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
848 time_axis_frame.set_name ("MixerStripSelectedFrame");
849 controls_ebox.set_name (controls_base_selected_name);
850 controls_vbox.set_name (controls_base_selected_name);
851 time_axis_vbox.set_name (controls_base_selected_name);
853 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
854 time_axis_frame.set_name (controls_base_unselected_name);
855 controls_ebox.set_name (controls_base_unselected_name);
856 controls_vbox.set_name (controls_base_unselected_name);
857 time_axis_vbox.set_name (controls_base_unselected_name);
861 /* children will be set for the yn=true case. but when deselecting
862 the editor only has a list of top-level trackviews, so we
863 have to do this here.
866 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
867 (*i)->set_selected (false);
871 time_axis_frame.show();
876 TimeAxisView::build_display_menu ()
878 using namespace Menu_Helpers;
882 display_menu = new Menu;
883 display_menu->set_name ("ArdourContextMenu");
885 // Just let implementing classes define what goes into the manu
889 TimeAxisView::set_samples_per_pixel (double fpp)
891 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
892 (*i)->set_samples_per_pixel (fpp);
897 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
899 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
900 (*i)->show_timestretch (start, end, layers, layer);
905 TimeAxisView::hide_timestretch ()
907 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
908 (*i)->hide_timestretch ();
913 TimeAxisView::show_selection (TimeSelection& ts)
918 SelectionRect *rect; time_axis_frame.show();
921 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
922 (*i)->show_selection (ts);
925 if (selection_group->visible ()) {
926 while (!used_selection_rects.empty()) {
927 free_selection_rects.push_front (used_selection_rects.front());
928 used_selection_rects.pop_front();
929 free_selection_rects.front()->rect->hide();
930 free_selection_rects.front()->start_trim->hide();
931 free_selection_rects.front()->end_trim->hide();
933 selection_group->hide();
936 selection_group->show();
937 selection_group->raise_to_top();
939 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
940 framepos_t start, end;
945 cnt = end - start + 1;
947 rect = get_selection_rect ((*i).id);
949 x1 = _editor.sample_to_pixel (start);
950 x2 = _editor.sample_to_pixel (start + cnt - 1);
951 y2 = current_height() - 1;
953 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
955 // trim boxes are at the top for selections
958 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
959 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
961 rect->start_trim->show();
962 rect->end_trim->show();
964 rect->start_trim->hide();
965 rect->end_trim->hide();
969 used_selection_rects.push_back (rect);
974 TimeAxisView::reshow_selection (TimeSelection& ts)
978 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
979 (*i)->show_selection (ts);
984 TimeAxisView::hide_selection ()
986 if (selection_group->visible ()) {
987 while (!used_selection_rects.empty()) {
988 free_selection_rects.push_front (used_selection_rects.front());
989 used_selection_rects.pop_front();
990 free_selection_rects.front()->rect->hide();
991 free_selection_rects.front()->start_trim->hide();
992 free_selection_rects.front()->end_trim->hide();
994 selection_group->hide();
997 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
998 (*i)->hide_selection ();
1003 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
1005 /* find the selection rect this is for. we have the item corresponding to one
1006 of the trim handles.
1009 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1010 if ((*i)->start_trim == item || (*i)->end_trim == item) {
1012 /* make one trim handle be "above" the other so that if they overlap,
1013 the top one is the one last used.
1016 (*i)->rect->raise_to_top ();
1017 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
1018 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
1026 TimeAxisView::get_selection_rect (uint32_t id)
1028 SelectionRect *rect;
1030 /* check to see if we already have a visible rect for this particular selection ID */
1032 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1033 if ((*i)->id == id) {
1038 /* ditto for the free rect list */
1040 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1041 if ((*i)->id == id) {
1042 SelectionRect* ret = (*i);
1043 free_selection_rects.erase (i);
1048 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1050 if (free_selection_rects.empty()) {
1052 rect = new SelectionRect;
1054 rect->rect = new ArdourCanvas::Rectangle (selection_group);
1055 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1056 rect->rect->set_outline (false);
1057 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1059 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1060 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1061 rect->start_trim->set_outline (false);
1062 rect->start_trim->set_fill (false);
1064 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1065 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1066 rect->end_trim->set_outline (false);
1067 rect->end_trim->set_fill (false);
1069 free_selection_rects.push_front (rect);
1071 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1072 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1073 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1076 rect = free_selection_rects.front();
1078 free_selection_rects.pop_front();
1082 struct null_deleter { void operator()(void const *) const {} };
1085 TimeAxisView::is_child (TimeAxisView* tav)
1087 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1091 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1093 children.push_back (child);
1097 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1099 Children::iterator i;
1101 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1106 /** Get selectable things within a given range.
1107 * @param start Start time in session frames.
1108 * @param end End time in session frames.
1109 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1110 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1111 * @param result Filled in with selectable things.
1114 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1120 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1126 TimeAxisView::add_ghost (RegionView* rv)
1128 GhostRegion* gr = rv->add_ghost (*this);
1131 ghosts.push_back(gr);
1136 TimeAxisView::remove_ghost (RegionView* rv)
1138 rv->remove_ghost_in (*this);
1142 TimeAxisView::erase_ghost (GhostRegion* gr)
1144 if (in_destructor) {
1148 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1157 TimeAxisView::touched (double top, double bot)
1159 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1160 y_position is the "origin" or "top" of the track.
1163 double mybot = _y_position + current_height();
1165 return ((_y_position <= bot && _y_position >= top) ||
1166 ((mybot <= bot) && (top < mybot)) ||
1167 (mybot >= bot && _y_position < top));
1171 TimeAxisView::set_parent (TimeAxisView& p)
1177 TimeAxisView::reset_height ()
1179 set_height (height);
1181 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1182 (*i)->set_height ((*i)->height);
1187 TimeAxisView::compute_heights ()
1189 // TODO this function should be re-evaluated when font-scaling changes (!)
1190 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1191 Gtk::Table one_row_table (1, 1);
1192 ArdourButton* test_button = manage (new ArdourButton);
1193 const int border_width = 2;
1194 const int frame_height = 2;
1195 extra_height = (2 * border_width) + frame_height;
1197 window.add (one_row_table);
1198 test_button->set_name ("mute button");
1199 test_button->set_text (S_("Mute|M"));
1200 test_button->set_tweaks (ArdourButton::TrackHeader);
1202 one_row_table.set_border_width (border_width);
1203 one_row_table.set_row_spacings (2);
1204 one_row_table.set_col_spacings (2);
1206 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1207 one_row_table.show_all ();
1209 Gtk::Requisition req(one_row_table.size_request ());
1210 button_height = req.height;
1214 TimeAxisView::color_handler ()
1216 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1220 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1222 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1223 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1225 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1226 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1228 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1229 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1232 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_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"));
1245 /** @return Pair: TimeAxisView, layer index.
1246 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1247 * does. @param y is an offset from the top of the trackview area.
1249 * If the covering object is a child axis, then the child is returned.
1250 * TimeAxisView is 0 otherwise.
1252 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1253 * and is in stacked or expanded * region display mode, otherwise 0.
1255 std::pair<TimeAxisView*, double>
1256 TimeAxisView::covers_y_position (double y) const
1259 return std::make_pair ((TimeAxisView *) 0, 0);
1262 if (_y_position <= y && y < (_y_position + height)) {
1264 /* work out the layer index if appropriate */
1266 switch (layer_display ()) {
1272 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1273 /* clamp to max layers to be on the safe side; sometimes the above calculation
1274 returns a too-high value */
1275 if (l >= view()->layers ()) {
1276 l = view()->layers() - 1;
1282 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1284 if (l >= (view()->layers() - 0.5)) {
1285 l = view()->layers() - 0.5;
1291 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1294 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1296 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1302 return std::make_pair ((TimeAxisView *) 0, 0);
1306 TimeAxisView::covered_by_y_range (double y0, double y1) const
1312 /* if either the top or bottom of the axisview is in the vertical
1313 * range, we cover it.
1316 if ((y0 < _y_position && y1 < _y_position) ||
1317 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1321 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1322 if ((*i)->covered_by_y_range (y0, y1)) {
1331 TimeAxisView::preset_height (Height h)
1335 return (button_height * 2) + extra_height + 260;
1337 return (button_height * 2) + extra_height + 160;
1339 return (button_height * 2) + extra_height + 60;
1341 return (button_height * 2) + extra_height + 10;
1343 return button_height + extra_height;
1346 abort(); /* NOTREACHED */
1350 /** @return Child time axis views that are not hidden */
1351 TimeAxisView::Children
1352 TimeAxisView::get_child_list ()
1356 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1357 if (!(*i)->hidden()) {
1366 TimeAxisView::build_size_menu ()
1368 if (_size_menu && _size_menu->gobj ()) {
1374 using namespace Menu_Helpers;
1376 _size_menu = new Menu;
1377 _size_menu->set_name ("ArdourContextMenu");
1378 MenuList& items = _size_menu->items();
1380 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1381 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1382 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1383 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1384 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1388 TimeAxisView::reset_visual_state ()
1390 /* this method is not required to trigger a global redraw */
1392 string str = gui_property ("height");
1395 set_height (atoi (str));
1397 set_height (preset_height (HeightNormal));
1402 TrackViewList::filter_to_unique_playlists ()
1404 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1407 for (iterator i = begin(); i != end(); ++i) {
1408 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1410 /* not a route: include it anyway */
1413 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1415 if (playlists.insert (t->playlist()).second) {
1416 /* playlist not seen yet */
1420 /* not a track: include it anyway */