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 "floating_text_entry.h"
46 #include "gui_thread.h"
47 #include "public_editor.h"
48 #include "time_axis_view.h"
49 #include "region_view.h"
50 #include "ghostregion.h"
51 #include "selection.h"
53 #include "rgb_macros.h"
55 #include "streamview.h"
56 #include "editor_drag.h"
59 #include "ui_config.h"
66 using namespace ARDOUR;
67 using namespace ARDOUR_UI_UTILS;
69 using namespace Editing;
70 using namespace ArdourCanvas;
71 using Gtkmm2ext::Keyboard;
73 #define TOP_LEVEL_WIDGET controls_ebox
75 const double trim_handle_size = 6.0; /* pixels */
76 uint32_t TimeAxisView::button_height = 0;
77 uint32_t TimeAxisView::extra_height = 0;
78 int const TimeAxisView::_max_order = 512;
79 unsigned int TimeAxisView::name_width_px = 100;
80 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
81 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
82 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
85 TimeAxisView::setup_sizes()
87 name_width_px = ceilf (100.f * UIConfiguration::instance().get_ui_scale());
90 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
91 : controls_table (5, 4)
92 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
93 , _name_editing (false)
100 , in_destructor (false)
102 , _canvas_display (0)
107 , _effective_height (0)
108 , _resize_drag_start (-1)
109 , _did_resize (false)
110 , _preresize_cursor (0)
111 , _have_preresize_cursor (false)
112 , _ebox_release_can_act (true)
114 if (!controls_meters_size_group) {
115 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
117 if (!midi_scroomer_size_group) {
118 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
120 if (extra_height == 0) {
124 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
125 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
126 _canvas_display->hide(); // reveal as needed
128 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
129 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
130 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
131 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
132 _canvas_separator->set_outline_width(1.0);
133 _canvas_separator->hide();
135 selection_group = new ArdourCanvas::Container (_canvas_display);
136 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
137 selection_group->set_data (X_("timeselection"), (void *) 1);
138 selection_group->hide();
140 _ghost_group = new ArdourCanvas::Container (_canvas_display);
141 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
142 _ghost_group->lower_to_bottom();
143 _ghost_group->show();
145 name_label.set_name (X_("TrackNameEditor"));
146 name_label.set_alignment (0.0, 0.5);
147 name_label.set_width_chars (12);
148 set_tooltip (name_label, _("Track/Bus name (double click to edit)"));
151 std::auto_ptr<Gtk::Entry> an_entry (new Gtkmm2ext::FocusEntry);
152 an_entry->set_name (X_("TrackNameEditor"));
153 Gtk::Requisition req;
154 an_entry->size_request (req);
156 name_label.set_size_request (-1, req.height);
157 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
160 // set min. track-header width if fader is not visible
161 name_label.set_size_request(name_width_px, -1);
165 controls_table.set_row_spacings (2);
166 controls_table.set_col_spacings (2);
167 controls_table.set_border_width (2);
169 if (ARDOUR::Profile->get_mixbus() ) {
170 controls_table.attach (name_label, 4, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
172 controls_table.attach (name_label, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
175 controls_table.show_all ();
176 controls_table.set_no_show_all ();
178 controls_vbox.pack_start (controls_table, false, false);
179 controls_vbox.show ();
181 top_hbox.pack_start (controls_vbox, true, true);
184 controls_ebox.add (time_axis_hbox);
185 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
186 Gdk::BUTTON_RELEASE_MASK|
187 Gdk::POINTER_MOTION_MASK|
188 Gdk::ENTER_NOTIFY_MASK|
189 Gdk::LEAVE_NOTIFY_MASK|
191 controls_ebox.set_flags (CAN_FOCUS);
193 /* note that this handler connects *before* the default handler */
194 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
195 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
196 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
197 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
198 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
199 controls_ebox.show ();
201 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
202 time_axis_frame.add(top_hbox);
203 time_axis_frame.show();
205 HSeparator* separator = manage (new HSeparator());
206 separator->set_name("TrackSeparator");
207 separator->set_size_request(-1, 1);
210 scroomer_placeholder.set_size_request (-1, -1);
211 scroomer_placeholder.show();
212 midi_scroomer_size_group->add_widget (scroomer_placeholder);
214 time_axis_vbox.pack_start (*separator, false, false);
215 time_axis_vbox.pack_start (time_axis_frame, true, true);
216 time_axis_vbox.show();
217 time_axis_hbox.pack_start (time_axis_vbox, true, true);
218 time_axis_hbox.show();
219 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
221 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
224 TimeAxisView::~TimeAxisView()
226 CatchDeletion (this);
228 in_destructor = true;
230 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
234 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
235 delete (*i)->rect; (*i)->rect=0;
236 delete (*i)->start_trim; (*i)->start_trim = 0;
237 delete (*i)->end_trim; (*i)->end_trim = 0;
241 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
242 delete (*i)->rect; (*i)->rect = 0;
243 delete (*i)->start_trim; (*i)->start_trim = 0;
244 delete (*i)->end_trim; (*i)->end_trim = 0;
247 delete selection_group;
250 delete _canvas_display;
260 TimeAxisView::hide ()
266 _canvas_display->hide ();
267 _canvas_separator->hide ();
269 if (control_parent) {
270 control_parent->remove (TOP_LEVEL_WIDGET);
277 /* now hide children */
279 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
283 /* if its hidden, it cannot be selected */
284 _editor.get_selection().remove (this);
285 /* and neither can its regions */
286 _editor.get_selection().remove_regions (this);
291 /** Display this TimeAxisView as the nth component of the parent box, at y.
293 * @param y y position.
294 * @param nth index for this TimeAxisView, increased if this view has children.
295 * @param parent parent component.
297 * @return height of this TimeAxisView.
300 TimeAxisView::show_at (double y, int& nth, VBox *parent)
302 if (control_parent) {
303 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
305 control_parent = parent;
306 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
307 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
312 if (_y_position != y) {
313 _canvas_display->set_y_position (y);
317 _canvas_display->raise_to_top ();
318 _canvas_display->show ();
322 _effective_height = current_height ();
324 /* now show relevant children */
326 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
327 if ((*i)->marked_for_display()) {
329 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
335 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
339 /* put separator at the bottom of this time axis view */
341 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
342 _canvas_separator->lower_to_bottom ();
343 _canvas_separator->show ();
345 return _effective_height;
349 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
351 switch (ev->direction) {
353 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
354 /* See Editor::_stepping_axis_view for notes on this hack */
355 Editor& e = dynamic_cast<Editor&> (_editor);
356 if (!e.stepping_axis_view ()) {
357 e.set_stepping_axis_view (this);
359 e.stepping_axis_view()->step_height (false);
364 case GDK_SCROLL_DOWN:
365 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
366 /* See Editor::_stepping_axis_view for notes on this hack */
367 Editor& e = dynamic_cast<Editor&> (_editor);
368 if (!e.stepping_axis_view ()) {
369 e.set_stepping_axis_view (this);
371 e.stepping_axis_view()->step_height (true);
377 /* no handling for left/right, yet */
381 /* Just forward to the normal canvas scroll method. The coordinate
382 systems are different but since the canvas is always larger than the
383 track headers, and aligned with the trackview area, this will work.
385 In the not too distant future this layout is going away anyway and
386 headers will be on the canvas.
388 return _editor.canvas_scroll_event (ev, false);
392 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
394 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
395 /* see if it is inside the name label */
396 if (name_label.is_ancestor (controls_ebox)) {
399 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
400 Gtk::Allocation a = name_label.get_allocation ();
401 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
403 _ebox_release_can_act = false;
410 _ebox_release_can_act = true;
412 if (maybe_set_cursor (event->y) > 0) {
413 _resize_drag_start = event->y_root;
420 TimeAxisView::idle_resize (int32_t h)
422 set_height (std::max(0, h));
427 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
429 if (_resize_drag_start >= 0) {
431 /* (ab)use the DragManager to do autoscrolling - basically we
432 * are pretending that the drag is taking place over the canvas
433 * (which perhaps in the glorious future, when track headers
434 * and the canvas are unified, will actually be true.)
437 _editor.maybe_autoscroll (false, true, true);
439 /* now schedule the actual TAV resize */
440 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
441 _editor.add_to_idle_resize (this, delta);
442 _resize_drag_start = ev->y_root;
445 /* not dragging but ... */
446 maybe_set_cursor (ev->y);
449 gdk_event_request_motions(ev);
454 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
456 if (_have_preresize_cursor) {
457 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
458 _have_preresize_cursor = false;
464 TimeAxisView::maybe_set_cursor (int y)
466 /* XXX no Gtkmm Gdk::Window::get_cursor() */
467 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
469 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
471 /* y-coordinate in lower 25% */
473 if (!_have_preresize_cursor) {
474 _preresize_cursor = gdk_window_get_cursor (win->gobj());
475 _have_preresize_cursor = true;
476 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
481 } else if (_have_preresize_cursor) {
482 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
483 _have_preresize_cursor = false;
492 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
494 if (_resize_drag_start >= 0) {
495 if (_have_preresize_cursor) {
496 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
497 _preresize_cursor = 0;
498 _have_preresize_cursor = false;
500 _editor.stop_canvas_autoscroll ();
501 _resize_drag_start = -1;
504 // don't change selection
509 if (!_ebox_release_can_act) {
513 switch (ev->button) {
516 selection_click (ev);
521 popup_display_menu (ev->time);
529 TimeAxisView::selection_click (GdkEventButton* ev)
531 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
532 _editor.set_selected_track (*this, op, false);
536 /** Steps through the defined heights for this TrackView.
537 * @param coarser true if stepping should decrease in size, otherwise false.
540 TimeAxisView::step_height (bool coarser)
542 static const uint32_t step = 25;
546 if (height <= preset_height (HeightSmall)) {
548 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
549 set_height_enum (HeightSmall);
551 set_height (height - step);
556 if (height <= preset_height(HeightSmall)) {
557 set_height_enum (HeightNormal);
559 set_height (height + step);
566 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
568 if (apply_to_selection) {
569 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
571 set_height (preset_height (h));
576 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
579 if (m == TotalHeight) {
580 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
581 if ( !(*i)->hidden()) ++lanes;
586 if (h < preset_height (HeightSmall)) {
587 h = preset_height (HeightSmall);
590 TOP_LEVEL_WIDGET.property_height_request () = h;
594 snprintf (buf, sizeof (buf), "%u", height);
595 set_gui_property ("height", buf);
597 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
601 if (selection_group->visible ()) {
602 /* resize the selection rect */
603 show_selection (_editor.get_selection().time);
607 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
608 (*i)->set_height(h, OnlySelf);
612 _editor.override_visible_track_count ();
616 TimeAxisView::begin_name_edit ()
618 if (!can_edit_name()) {
622 Gtk::Window* toplevel = (Gtk::Window*) control_parent->get_toplevel();
623 FloatingTextEntry* fte = new FloatingTextEntry (toplevel, name_label.get_text ());
625 fte->set_name ("TrackNameEditor");
626 fte->use_text.connect (sigc::mem_fun (*this, &TimeAxisView::end_name_edit));
628 /* We want to new toplevel window to overlay the name label, so
629 * translate the coordinates of the upper left corner of the name label
630 * into the coordinate space of the top level window.
636 name_label.translate_coordinates (*toplevel, 0, 0, x, y);
637 toplevel->get_window()->get_origin (wx, wy);
639 fte->move (wx + x, wy + y);
644 TimeAxisView::end_name_edit (std::string str, int next_dir)
646 if (!name_entry_changed (str)) {
652 TrackViewList const & allviews = _editor.get_track_views ();
653 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
655 if (i != allviews.end()) {
658 if (++i == allviews.end()) {
662 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
664 if (rtav && (!rtav->is_track() || rtav->track()->rec_enable_control()->get_value())) {
668 if (!(*i)->hidden()) {
675 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
676 _editor.ensure_time_axis_view_is_visible (**i, false);
677 (*i)->begin_name_edit ();
680 } else if (next_dir < 0) {
682 TrackViewList const & allviews = _editor.get_track_views ();
683 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
685 if (i != allviews.begin()) {
687 if (i == allviews.begin()) {
693 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
695 if (rtav && (!rtav->is_track() || rtav->track()->rec_enable_control()->get_value())) {
699 if (!(*i)->hidden()) {
706 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
707 _editor.ensure_time_axis_view_is_visible (**i, false);
708 (*i)->begin_name_edit ();
714 TimeAxisView::name_entry_changed (string const&)
720 TimeAxisView::can_edit_name () const
726 TimeAxisView::conditionally_add_to_selection ()
732 Selection& s (_editor.get_selection ());
734 if (!s.selected (this)) {
735 _editor.set_selected_track (*this, Selection::Set);
740 TimeAxisView::popup_display_menu (guint32 when)
742 conditionally_add_to_selection ();
744 build_display_menu ();
746 if (!display_menu->items().empty()) {
747 display_menu->popup (1, when);
752 TimeAxisView::set_selected (bool yn)
754 if (yn == selected()) {
758 AxisView::set_selected (yn);
761 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
762 time_axis_frame.set_name ("MixerStripSelectedFrame");
763 controls_ebox.set_name (controls_base_selected_name);
764 controls_vbox.set_name (controls_base_selected_name);
765 time_axis_vbox.set_name (controls_base_selected_name);
767 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
768 time_axis_frame.set_name (controls_base_unselected_name);
769 controls_ebox.set_name (controls_base_unselected_name);
770 controls_vbox.set_name (controls_base_unselected_name);
771 time_axis_vbox.set_name (controls_base_unselected_name);
775 /* children will be set for the yn=true case. but when deselecting
776 the editor only has a list of top-level trackviews, so we
777 have to do this here.
780 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
781 (*i)->set_selected (false);
785 time_axis_frame.show();
790 TimeAxisView::build_display_menu ()
792 using namespace Menu_Helpers;
796 display_menu = new Menu;
797 display_menu->set_name ("ArdourContextMenu");
799 // Just let implementing classes define what goes into the manu
803 TimeAxisView::set_samples_per_pixel (double fpp)
805 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
806 (*i)->set_samples_per_pixel (fpp);
811 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
813 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
814 (*i)->show_timestretch (start, end, layers, layer);
819 TimeAxisView::hide_timestretch ()
821 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
822 (*i)->hide_timestretch ();
827 TimeAxisView::show_selection (TimeSelection& ts)
832 SelectionRect *rect; time_axis_frame.show();
835 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
836 (*i)->show_selection (ts);
839 if (selection_group->visible ()) {
840 while (!used_selection_rects.empty()) {
841 free_selection_rects.push_front (used_selection_rects.front());
842 used_selection_rects.pop_front();
843 free_selection_rects.front()->rect->hide();
844 free_selection_rects.front()->start_trim->hide();
845 free_selection_rects.front()->end_trim->hide();
847 selection_group->hide();
850 selection_group->show();
851 selection_group->raise_to_top();
853 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
854 framepos_t start, end;
859 cnt = end - start + 1;
861 rect = get_selection_rect ((*i).id);
863 x1 = _editor.sample_to_pixel (start);
864 x2 = _editor.sample_to_pixel (start + cnt - 1);
865 y2 = current_height() - 1;
867 /* rect must be visible for the change in its bounding box to
872 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
874 // trim boxes are at the top for selections
877 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
878 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
880 rect->start_trim->show();
881 rect->end_trim->show();
883 rect->start_trim->hide();
884 rect->end_trim->hide();
887 used_selection_rects.push_back (rect);
892 TimeAxisView::reshow_selection (TimeSelection& ts)
896 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
897 (*i)->show_selection (ts);
902 TimeAxisView::hide_selection ()
904 if (selection_group->visible ()) {
905 while (!used_selection_rects.empty()) {
906 free_selection_rects.push_front (used_selection_rects.front());
907 used_selection_rects.pop_front();
908 free_selection_rects.front()->rect->hide();
909 free_selection_rects.front()->start_trim->hide();
910 free_selection_rects.front()->end_trim->hide();
912 selection_group->hide();
915 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
916 (*i)->hide_selection ();
921 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
923 /* find the selection rect this is for. we have the item corresponding to one
927 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
928 if ((*i)->start_trim == item || (*i)->end_trim == item) {
930 /* make one trim handle be "above" the other so that if they overlap,
931 the top one is the one last used.
934 (*i)->rect->raise_to_top ();
935 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
936 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
943 // retuned rect is pushed back into the used_selection_rects list
944 // in TimeAxisView::show_selection() which is the only caller.
946 TimeAxisView::get_selection_rect (uint32_t id)
950 /* check to see if we already have a visible rect for this particular selection ID */
952 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
953 if ((*i)->id == id) {
954 SelectionRect* ret = (*i);
955 used_selection_rects.erase (i);
960 /* ditto for the free rect list */
962 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
963 if ((*i)->id == id) {
964 SelectionRect* ret = (*i);
965 free_selection_rects.erase (i);
970 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
972 if (free_selection_rects.empty()) {
974 rect = new SelectionRect;
976 rect->rect = new ArdourCanvas::Rectangle (selection_group);
977 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
978 rect->rect->set_outline (false);
979 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
981 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
982 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
983 rect->start_trim->set_outline (false);
984 rect->start_trim->set_fill (false);
986 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
987 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
988 rect->end_trim->set_outline (false);
989 rect->end_trim->set_fill (false);
991 free_selection_rects.push_front (rect);
993 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
994 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
995 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
998 rect = free_selection_rects.front();
1000 free_selection_rects.pop_front();
1004 struct null_deleter { void operator()(void const *) const {} };
1007 TimeAxisView::is_child (TimeAxisView* tav)
1009 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1013 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1015 children.push_back (child);
1019 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1021 Children::iterator i;
1023 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1028 /** Get selectable things within a given range.
1029 * @param start Start time in session frames.
1030 * @param end End time in session frames.
1031 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1032 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1033 * @param result Filled in with selectable things.
1036 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/, bool /*within*/)
1042 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1048 TimeAxisView::add_ghost (RegionView* rv)
1050 GhostRegion* gr = rv->add_ghost (*this);
1053 ghosts.push_back(gr);
1058 TimeAxisView::remove_ghost (RegionView* rv)
1060 rv->remove_ghost_in (*this);
1064 TimeAxisView::erase_ghost (GhostRegion* gr)
1066 if (in_destructor) {
1070 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1079 TimeAxisView::touched (double top, double bot)
1081 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1082 y_position is the "origin" or "top" of the track.
1085 double mybot = _y_position + current_height();
1087 return ((_y_position <= bot && _y_position >= top) ||
1088 ((mybot <= bot) && (top < mybot)) ||
1089 (mybot >= bot && _y_position < top));
1093 TimeAxisView::set_parent (TimeAxisView& p)
1099 TimeAxisView::reset_height ()
1101 set_height (height);
1103 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1104 (*i)->set_height ((*i)->height);
1109 TimeAxisView::compute_heights ()
1111 // TODO this function should be re-evaluated when font-scaling changes (!)
1112 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1113 Gtk::Table one_row_table (1, 1);
1114 ArdourButton* test_button = manage (new ArdourButton);
1115 const int border_width = 2;
1116 const int frame_height = 2;
1117 extra_height = (2 * border_width) + frame_height;
1119 window.add (one_row_table);
1120 test_button->set_name ("mute button");
1121 test_button->set_text (S_("Mute|M"));
1122 test_button->set_tweaks (ArdourButton::TrackHeader);
1124 one_row_table.set_border_width (border_width);
1125 one_row_table.set_row_spacings (2);
1126 one_row_table.set_col_spacings (2);
1128 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1129 one_row_table.show_all ();
1131 Gtk::Requisition req(one_row_table.size_request ());
1132 button_height = req.height;
1136 TimeAxisView::color_handler ()
1138 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1142 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1144 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1145 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1147 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1148 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1150 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1151 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1154 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1156 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1157 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1159 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1160 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1162 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1163 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1167 /** @return Pair: TimeAxisView, layer index.
1168 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1169 * does. @param y is an offset from the top of the trackview area.
1171 * If the covering object is a child axis, then the child is returned.
1172 * TimeAxisView is 0 otherwise.
1174 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1175 * and is in stacked or expanded * region display mode, otherwise 0.
1177 std::pair<TimeAxisView*, double>
1178 TimeAxisView::covers_y_position (double y) const
1181 return std::make_pair ((TimeAxisView *) 0, 0);
1184 if (_y_position <= y && y < (_y_position + height)) {
1186 /* work out the layer index if appropriate */
1188 switch (layer_display ()) {
1194 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1195 /* clamp to max layers to be on the safe side; sometimes the above calculation
1196 returns a too-high value */
1197 if (l >= view()->layers ()) {
1198 l = view()->layers() - 1;
1204 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1206 if (l >= (view()->layers() - 0.5)) {
1207 l = view()->layers() - 0.5;
1213 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1216 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1218 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1224 return std::make_pair ((TimeAxisView *) 0, 0);
1228 TimeAxisView::covered_by_y_range (double y0, double y1) const
1234 /* if either the top or bottom of the axisview is in the vertical
1235 * range, we cover it.
1238 if ((y0 < _y_position && y1 < _y_position) ||
1239 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1243 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1244 if ((*i)->covered_by_y_range (y0, y1)) {
1253 TimeAxisView::preset_height (Height h)
1257 return (button_height * 2) + extra_height + 260;
1259 return (button_height * 2) + extra_height + 160;
1261 return (button_height * 2) + extra_height + 60;
1263 return (button_height * 2) + extra_height + 10;
1265 return button_height + extra_height;
1268 abort(); /* NOTREACHED */
1272 /** @return Child time axis views that are not hidden */
1273 TimeAxisView::Children
1274 TimeAxisView::get_child_list ()
1278 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1279 if (!(*i)->hidden()) {
1288 TimeAxisView::build_size_menu ()
1290 if (_size_menu && _size_menu->gobj ()) {
1296 using namespace Menu_Helpers;
1298 _size_menu = new Menu;
1299 _size_menu->set_name ("ArdourContextMenu");
1300 MenuList& items = _size_menu->items();
1302 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1303 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1304 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1305 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1306 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1310 TimeAxisView::reset_visual_state ()
1312 /* this method is not required to trigger a global redraw */
1314 string str = gui_property ("height");
1317 set_height (atoi (str));
1319 set_height (preset_height (HeightNormal));
1324 TrackViewList::filter_to_unique_playlists ()
1326 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1329 for (iterator i = begin(); i != end(); ++i) {
1330 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1332 /* not a route: include it anyway */
1335 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1337 if (playlists.insert (t->playlist()).second) {
1338 /* playlist not seen yet */
1342 /* not a track: include it anyway */