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.
26 #include <gtkmm/separator.h>
28 #include "pbd/error.h"
29 #include "pbd/convert.h"
30 #include "pbd/stacktrace.h"
31 #include "pbd/unwind.h"
33 #include "ardour/profile.h"
35 #include "gtkmm2ext/colors.h"
36 #include "gtkmm2ext/doi.h"
37 #include "gtkmm2ext/utils.h"
39 #include "canvas/canvas.h"
40 #include "canvas/rectangle.h"
41 #include "canvas/debug.h"
42 #include "canvas/utils.h"
44 #include "widgets/tooltips.h"
46 #include "ardour_dialog.h"
47 #include "floating_text_entry.h"
48 #include "gui_thread.h"
49 #include "public_editor.h"
50 #include "time_axis_view.h"
51 #include "region_view.h"
52 #include "ghostregion.h"
53 #include "selection.h"
55 #include "rgb_macros.h"
57 #include "streamview.h"
58 #include "editor_drag.h"
60 #include "ui_config.h"
67 using namespace ARDOUR;
69 using namespace Editing;
70 using namespace ArdourCanvas;
71 using namespace ArdourWidgets;
72 using Gtkmm2ext::Keyboard;
74 #define TOP_LEVEL_WIDGET controls_ebox
76 const double trim_handle_size = 6.0; /* pixels */
77 uint32_t TimeAxisView::button_height = 0;
78 uint32_t TimeAxisView::extra_height = 0;
79 int const TimeAxisView::_max_order = 512;
80 unsigned int TimeAxisView::name_width_px = 100;
81 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
82 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
83 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
86 TimeAxisView::setup_sizes()
88 name_width_px = ceilf (100.f * UIConfiguration::instance().get_ui_scale());
91 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
92 : controls_table (5, 4)
93 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
94 , _name_editing (false)
101 , in_destructor (false)
103 , _canvas_display (0)
108 , _effective_height (0)
109 , _resize_drag_start (-1)
110 , _did_resize (false)
111 , _preresize_cursor (0)
112 , _have_preresize_cursor (false)
113 , _ebox_release_can_act (true)
115 if (!controls_meters_size_group) {
116 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
118 if (!midi_scroomer_size_group) {
119 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
121 if (extra_height == 0) {
125 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
126 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
127 _canvas_display->hide(); // reveal as needed
129 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
130 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
131 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
132 _canvas_separator->set_outline_color(Gtkmm2ext::rgba_to_color (0, 0, 0, 1.0));
133 _canvas_separator->set_outline_width(1.0);
134 _canvas_separator->hide();
136 selection_group = new ArdourCanvas::Container (_canvas_display);
137 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
138 selection_group->set_data (X_("timeselection"), (void *) 1);
139 selection_group->hide();
141 _ghost_group = new ArdourCanvas::Container (_canvas_display);
142 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
143 _ghost_group->lower_to_bottom();
144 _ghost_group->show();
146 name_label.set_name (X_("TrackNameEditor"));
147 name_label.set_alignment (0.0, 0.5);
148 name_label.set_width_chars (12);
149 set_tooltip (name_label, _("Track/Bus name (double click to edit)"));
152 std::auto_ptr<Gtk::Entry> an_entry (new FocusEntry);
153 an_entry->set_name (X_("TrackNameEditor"));
154 Gtk::Requisition req;
155 an_entry->size_request (req);
157 name_label.set_size_request (-1, req.height);
158 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
161 // set min. track-header width if fader is not visible
162 name_label.set_size_request(name_width_px, -1);
166 controls_table.set_row_spacings (2);
167 controls_table.set_col_spacings (2);
168 controls_table.set_border_width (2);
170 if (ARDOUR::Profile->get_mixbus() ) {
171 controls_table.attach (name_label, 4, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
173 controls_table.attach (name_label, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
176 controls_table.show_all ();
177 controls_table.set_no_show_all ();
179 controls_vbox.pack_start (controls_table, false, false);
180 controls_vbox.show ();
182 top_hbox.pack_start (controls_vbox, true, true);
185 controls_ebox.add (time_axis_hbox);
186 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
187 Gdk::BUTTON_RELEASE_MASK|
188 Gdk::POINTER_MOTION_MASK|
189 Gdk::ENTER_NOTIFY_MASK|
190 Gdk::LEAVE_NOTIFY_MASK|
192 controls_ebox.set_flags (CAN_FOCUS);
194 /* note that this handler connects *before* the default handler */
195 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
196 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
197 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
198 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
199 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
200 controls_ebox.show ();
202 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
203 time_axis_frame.add(top_hbox);
204 time_axis_frame.show();
206 HSeparator* separator = manage (new HSeparator());
207 separator->set_name("TrackSeparator");
208 separator->set_size_request(-1, 1);
211 scroomer_placeholder.set_size_request (-1, -1);
212 scroomer_placeholder.show();
213 midi_scroomer_size_group->add_widget (scroomer_placeholder);
215 time_axis_vbox.pack_start (*separator, false, false);
216 time_axis_vbox.pack_start (time_axis_frame, true, true);
217 time_axis_vbox.show();
218 time_axis_hbox.pack_start (time_axis_vbox, true, true);
219 time_axis_hbox.show();
220 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
222 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
225 TimeAxisView::~TimeAxisView()
227 CatchDeletion (this);
229 in_destructor = true;
231 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
235 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
236 delete (*i)->rect; (*i)->rect=0;
237 delete (*i)->start_trim; (*i)->start_trim = 0;
238 delete (*i)->end_trim; (*i)->end_trim = 0;
242 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
243 delete (*i)->rect; (*i)->rect = 0;
244 delete (*i)->start_trim; (*i)->start_trim = 0;
245 delete (*i)->end_trim; (*i)->end_trim = 0;
248 delete selection_group;
251 delete _canvas_display;
261 TimeAxisView::hide ()
267 _canvas_display->hide ();
268 _canvas_separator->hide ();
270 if (control_parent) {
271 control_parent->remove (TOP_LEVEL_WIDGET);
278 /* now hide children */
280 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
284 /* if its hidden, it cannot be selected */
285 _editor.get_selection().remove (this);
286 /* and neither can its regions */
287 _editor.get_selection().remove_regions (this);
292 /** Display this TimeAxisView as the nth component of the parent box, at y.
294 * @param y y position.
295 * @param nth index for this TimeAxisView, increased if this view has children.
296 * @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 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
340 /* put separator at the bottom of this time axis view */
342 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
343 _canvas_separator->lower_to_bottom ();
344 _canvas_separator->show ();
346 return _effective_height;
350 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
352 switch (ev->direction) {
354 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
355 /* See Editor::_stepping_axis_view for notes on this hack */
356 Editor& e = dynamic_cast<Editor&> (_editor);
357 if (!e.stepping_axis_view ()) {
358 e.set_stepping_axis_view (this);
360 e.stepping_axis_view()->step_height (false);
365 case GDK_SCROLL_DOWN:
366 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
367 /* See Editor::_stepping_axis_view for notes on this hack */
368 Editor& e = dynamic_cast<Editor&> (_editor);
369 if (!e.stepping_axis_view ()) {
370 e.set_stepping_axis_view (this);
372 e.stepping_axis_view()->step_height (true);
378 /* no handling for left/right, yet */
382 /* Just forward to the normal canvas scroll method. The coordinate
383 systems are different but since the canvas is always larger than the
384 track headers, and aligned with the trackview area, this will work.
386 In the not too distant future this layout is going away anyway and
387 headers will be on the canvas.
389 return _editor.canvas_scroll_event (ev, false);
393 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
395 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
396 /* see if it is inside the name label */
397 if (name_label.is_ancestor (controls_ebox)) {
400 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
401 Gtk::Allocation a = name_label.get_allocation ();
402 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
404 _ebox_release_can_act = false;
411 _ebox_release_can_act = true;
413 if (maybe_set_cursor (event->y) > 0) {
414 _resize_drag_start = event->y_root;
421 TimeAxisView::idle_resize (int32_t h)
423 set_height (std::max(0, h));
428 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
430 if (_resize_drag_start >= 0) {
432 /* (ab)use the DragManager to do autoscrolling - basically we
433 * are pretending that the drag is taking place over the canvas
434 * (which perhaps in the glorious future, when track headers
435 * and the canvas are unified, will actually be true.)
438 _editor.maybe_autoscroll (false, true, true);
440 /* now schedule the actual TAV resize */
441 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
442 _editor.add_to_idle_resize (this, delta);
443 _resize_drag_start = ev->y_root;
446 /* not dragging but ... */
447 maybe_set_cursor (ev->y);
450 gdk_event_request_motions(ev);
455 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
457 if (_have_preresize_cursor) {
458 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
459 _have_preresize_cursor = false;
465 TimeAxisView::maybe_set_cursor (int y)
467 /* XXX no Gtkmm Gdk::Window::get_cursor() */
468 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
470 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
472 /* y-coordinate in lower 25% */
474 if (!_have_preresize_cursor) {
475 _preresize_cursor = gdk_window_get_cursor (win->gobj());
476 _have_preresize_cursor = true;
477 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
482 } else if (_have_preresize_cursor) {
483 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
484 _have_preresize_cursor = false;
493 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
495 if (_resize_drag_start >= 0) {
496 if (_have_preresize_cursor) {
497 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
498 _preresize_cursor = 0;
499 _have_preresize_cursor = false;
501 _editor.stop_canvas_autoscroll ();
502 _resize_drag_start = -1;
505 // don't change selection
510 if (!_ebox_release_can_act) {
514 switch (ev->button) {
517 selection_click (ev);
522 popup_display_menu (ev->time);
530 TimeAxisView::selection_click (GdkEventButton* ev)
532 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
533 _editor.set_selected_track (*this, op, false);
537 /** Steps through the defined heights for this TrackView.
538 * @param coarser true if stepping should decrease in size, otherwise false.
541 TimeAxisView::step_height (bool coarser)
543 static const uint32_t step = 25;
547 if (height <= preset_height (HeightSmall)) {
549 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
550 set_height_enum (HeightSmall);
552 set_height (height - step);
557 if (height <= preset_height(HeightSmall)) {
558 set_height_enum (HeightNormal);
560 set_height (height + step);
567 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
569 if (apply_to_selection) {
570 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
572 set_height (preset_height (h));
577 TimeAxisView::set_height (uint32_t h, TrackHeightMode m)
580 if (m == TotalHeight) {
581 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
582 if ( !(*i)->hidden()) ++lanes;
587 if (h < preset_height (HeightSmall)) {
588 h = preset_height (HeightSmall);
591 TOP_LEVEL_WIDGET.property_height_request () = h;
594 set_gui_property ("height", height);
596 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
600 if (selection_group->visible ()) {
601 /* resize the selection rect */
602 show_selection (_editor.get_selection().time);
606 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
607 (*i)->set_height(h, OnlySelf);
611 _editor.override_visible_track_count ();
615 TimeAxisView::begin_name_edit ()
617 if (!can_edit_name()) {
621 Gtk::Window* toplevel = (Gtk::Window*) control_parent->get_toplevel();
622 FloatingTextEntry* fte = new FloatingTextEntry (toplevel, name ());
624 fte->set_name ("TrackNameEditor");
625 fte->use_text.connect (sigc::mem_fun (*this, &TimeAxisView::end_name_edit));
627 /* We want to new toplevel window to overlay the name label, so
628 * translate the coordinates of the upper left corner of the name label
629 * into the coordinate space of the top level window.
635 name_label.translate_coordinates (*toplevel, 0, 0, x, y);
636 toplevel->get_window()->get_origin (wx, wy);
638 fte->move (wx + x, wy + y);
643 TimeAxisView::end_name_edit (std::string str, int next_dir)
645 if (!name_entry_changed (str)) {
651 TrackViewList const & allviews = _editor.get_track_views ();
652 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
654 if (i != allviews.end()) {
657 if (++i == allviews.end()) {
661 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
663 if (rtav && (!rtav->is_track() || rtav->track()->rec_enable_control()->get_value())) {
667 if (!(*i)->hidden()) {
674 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
675 _editor.ensure_time_axis_view_is_visible (**i, false);
676 (*i)->begin_name_edit ();
679 } else if (next_dir < 0) {
681 TrackViewList const & allviews = _editor.get_track_views ();
682 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
684 if (i != allviews.begin()) {
686 if (i == allviews.begin()) {
692 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
694 if (rtav && (!rtav->is_track() || rtav->track()->rec_enable_control()->get_value())) {
698 if (!(*i)->hidden()) {
705 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
706 _editor.ensure_time_axis_view_is_visible (**i, false);
707 (*i)->begin_name_edit ();
713 TimeAxisView::name_entry_changed (string const&)
719 TimeAxisView::can_edit_name () const
725 TimeAxisView::conditionally_add_to_selection ()
731 Selection& s (_editor.get_selection ());
733 if (!s.selected (this)) {
734 _editor.set_selected_track (*this, Selection::Set);
739 TimeAxisView::popup_display_menu (guint32 when)
741 conditionally_add_to_selection ();
743 build_display_menu ();
745 if (!display_menu->items().empty()) {
746 display_menu->popup (1, when);
751 TimeAxisView::set_selected (bool yn)
753 if (yn == selected()) {
757 AxisView::set_selected (yn);
760 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
761 time_axis_frame.set_name ("MixerStripSelectedFrame");
762 controls_ebox.set_name (controls_base_selected_name);
763 controls_vbox.set_name (controls_base_selected_name);
764 time_axis_vbox.set_name (controls_base_selected_name);
766 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
767 time_axis_frame.set_name (controls_base_unselected_name);
768 controls_ebox.set_name (controls_base_unselected_name);
769 controls_vbox.set_name (controls_base_unselected_name);
770 time_axis_vbox.set_name (controls_base_unselected_name);
775 time_axis_frame.show();
779 TimeAxisView::build_display_menu ()
781 using namespace Menu_Helpers;
785 display_menu = new Menu;
786 display_menu->set_name ("ArdourContextMenu");
788 // Just let implementing classes define what goes into the manu
792 TimeAxisView::set_samples_per_pixel (double fpp)
794 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
795 (*i)->set_samples_per_pixel (fpp);
800 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
802 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
803 (*i)->show_timestretch (start, end, layers, layer);
808 TimeAxisView::hide_timestretch ()
810 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
811 (*i)->hide_timestretch ();
816 TimeAxisView::show_selection (TimeSelection& ts)
821 SelectionRect *rect; time_axis_frame.show();
824 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
825 if (!(*i)->selected () && !(*i)->propagate_time_selection ()) {
828 (*i)->show_selection (ts);
831 if (selection_group->visible ()) {
832 while (!used_selection_rects.empty()) {
833 free_selection_rects.push_front (used_selection_rects.front());
834 used_selection_rects.pop_front();
835 free_selection_rects.front()->rect->hide();
836 free_selection_rects.front()->start_trim->hide();
837 free_selection_rects.front()->end_trim->hide();
839 selection_group->hide();
842 selection_group->show();
843 selection_group->raise_to_top();
845 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
846 framepos_t start, end;
851 cnt = end - start + 1;
853 rect = get_selection_rect ((*i).id);
855 x1 = _editor.sample_to_pixel (start);
856 x2 = _editor.sample_to_pixel (start + cnt - 1);
857 y2 = current_height() - 1;
859 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
861 // trim boxes are at the top for selections
864 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
865 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
867 rect->start_trim->show();
868 rect->end_trim->show();
870 rect->start_trim->hide();
871 rect->end_trim->hide();
875 used_selection_rects.push_back (rect);
880 TimeAxisView::reshow_selection (TimeSelection& ts)
884 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
885 if (!(*i)->selected () && !(*i)->propagate_time_selection ()) {
888 (*i)->show_selection (ts);
893 TimeAxisView::hide_selection ()
895 if (selection_group->visible ()) {
896 while (!used_selection_rects.empty()) {
897 free_selection_rects.push_front (used_selection_rects.front());
898 used_selection_rects.pop_front();
899 free_selection_rects.front()->rect->hide();
900 free_selection_rects.front()->start_trim->hide();
901 free_selection_rects.front()->end_trim->hide();
903 selection_group->hide();
906 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
907 (*i)->hide_selection ();
912 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
914 /* find the selection rect this is for. we have the item corresponding to one
918 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
919 if ((*i)->start_trim == item || (*i)->end_trim == item) {
921 /* make one trim handle be "above" the other so that if they overlap,
922 the top one is the one last used.
925 (*i)->rect->raise_to_top ();
926 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
927 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
934 // retuned rect is pushed back into the used_selection_rects list
935 // in TimeAxisView::show_selection() which is the only caller.
937 TimeAxisView::get_selection_rect (uint32_t id)
941 /* check to see if we already have a visible rect for this particular selection ID */
943 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
944 if ((*i)->id == id) {
945 SelectionRect* ret = (*i);
946 used_selection_rects.erase (i);
951 /* ditto for the free rect list */
953 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
954 if ((*i)->id == id) {
955 SelectionRect* ret = (*i);
956 free_selection_rects.erase (i);
961 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
963 if (free_selection_rects.empty()) {
965 rect = new SelectionRect;
967 rect->rect = new ArdourCanvas::Rectangle (selection_group);
968 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
969 rect->rect->set_outline (false);
970 rect->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
972 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
973 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
974 rect->start_trim->set_outline (false);
975 rect->start_trim->set_fill (false);
977 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
978 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
979 rect->end_trim->set_outline (false);
980 rect->end_trim->set_fill (false);
982 free_selection_rects.push_front (rect);
984 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
985 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
986 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
989 rect = free_selection_rects.front();
991 free_selection_rects.pop_front();
995 struct null_deleter { void operator()(void const *) const {} };
998 TimeAxisView::is_child (TimeAxisView* tav)
1000 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1004 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1006 children.push_back (child);
1010 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1012 Children::iterator i;
1014 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1019 /** Get selectable things within a given range.
1020 * @param start Start time in session frames.
1021 * @param end End time in session frames.
1022 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1023 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1024 * @param result Filled in with selectable things.
1027 TimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results, bool within)
1029 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1030 if (!(*i)->hidden()) {
1031 (*i)->get_selectables (start, end, top, bot, results, within);
1037 TimeAxisView::set_selected_points (PointSelection& points)
1039 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1040 (*i)->set_selected_points (points);
1045 TimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& results)
1047 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1048 if (!(*i)->hidden()) {
1049 (*i)->get_inverted_selectables (sel, results);
1055 TimeAxisView::add_ghost (RegionView* rv)
1057 GhostRegion* gr = rv->add_ghost (*this);
1060 ghosts.push_back(gr);
1065 TimeAxisView::remove_ghost (RegionView* rv)
1067 rv->remove_ghost_in (*this);
1071 TimeAxisView::erase_ghost (GhostRegion* gr)
1073 if (in_destructor) {
1077 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1086 TimeAxisView::touched (double top, double bot)
1088 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1089 y_position is the "origin" or "top" of the track.
1092 double mybot = _y_position + current_height();
1094 return ((_y_position <= bot && _y_position >= top) ||
1095 ((mybot <= bot) && (top < mybot)) ||
1096 (mybot >= bot && _y_position < top));
1100 TimeAxisView::set_parent (TimeAxisView& p)
1106 TimeAxisView::reset_height ()
1108 set_height (height);
1110 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1111 (*i)->set_height ((*i)->height);
1116 TimeAxisView::compute_heights ()
1118 // TODO this function should be re-evaluated when font-scaling changes (!)
1119 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1120 Gtk::Table one_row_table (1, 1);
1121 ArdourButton* test_button = manage (new ArdourButton);
1122 const int border_width = 2;
1123 const int frame_height = 2;
1124 extra_height = (2 * border_width) + frame_height;
1126 window.add (one_row_table);
1127 test_button->set_name ("mute button");
1128 test_button->set_text (S_("Mute|M"));
1129 test_button->set_tweaks (ArdourButton::TrackHeader);
1131 one_row_table.set_border_width (border_width);
1132 one_row_table.set_row_spacings (2);
1133 one_row_table.set_col_spacings (2);
1135 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1136 one_row_table.show_all ();
1138 Gtk::Requisition req(one_row_table.size_request ());
1139 button_height = req.height;
1143 TimeAxisView::color_handler ()
1145 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1149 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1151 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1152 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1154 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1155 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1157 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1158 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1161 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1163 (*i)->rect->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
1164 (*i)->rect->set_outline_color (UIConfiguration::instance().color ("selection"));
1166 (*i)->start_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1167 (*i)->start_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1169 (*i)->end_trim->set_fill_color (UIConfiguration::instance().color ("selection"));
1170 (*i)->end_trim->set_outline_color (UIConfiguration::instance().color ("selection"));
1174 /** @return Pair: TimeAxisView, layer index.
1175 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1176 * does. @param y is an offset from the top of the trackview area.
1178 * If the covering object is a child axis, then the child is returned.
1179 * TimeAxisView is 0 otherwise.
1181 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1182 * and is in stacked or expanded * region display mode, otherwise 0.
1184 std::pair<TimeAxisView*, double>
1185 TimeAxisView::covers_y_position (double y) const
1188 return std::make_pair ((TimeAxisView *) 0, 0);
1191 if (_y_position <= y && y < (_y_position + height)) {
1193 /* work out the layer index if appropriate */
1195 switch (layer_display ()) {
1201 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1202 /* clamp to max layers to be on the safe side; sometimes the above calculation
1203 returns a too-high value */
1204 if (l >= view()->layers ()) {
1205 l = view()->layers() - 1;
1211 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1213 if (l >= (view()->layers() - 0.5)) {
1214 l = view()->layers() - 0.5;
1220 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1223 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1225 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1231 return std::make_pair ((TimeAxisView *) 0, 0);
1235 TimeAxisView::covered_by_y_range (double y0, double y1) const
1241 /* if either the top or bottom of the axisview is in the vertical
1242 * range, we cover it.
1245 if ((y0 < _y_position && y1 < _y_position) ||
1246 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1250 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1251 if ((*i)->covered_by_y_range (y0, y1)) {
1260 TimeAxisView::preset_height (Height h)
1264 return (button_height * 2) + extra_height + 260;
1266 return (button_height * 2) + extra_height + 160;
1268 return (button_height * 2) + extra_height + 60;
1270 return (button_height * 2) + extra_height + 10;
1272 return button_height + extra_height;
1275 abort(); /* NOTREACHED */
1279 /** @return Child time axis views that are not hidden */
1280 TimeAxisView::Children
1281 TimeAxisView::get_child_list () const
1285 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1286 if (!(*i)->hidden()) {
1295 TimeAxisView::build_size_menu ()
1297 if (_size_menu && _size_menu->gobj ()) {
1303 using namespace Menu_Helpers;
1305 _size_menu = new Menu;
1306 _size_menu->set_name ("ArdourContextMenu");
1307 MenuList& items = _size_menu->items();
1309 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1310 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1311 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1312 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1313 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1317 TimeAxisView::reset_visual_state ()
1319 /* this method is not required to trigger a global redraw */
1322 if (get_gui_property ("height", height)) {
1323 set_height (height);
1325 set_height (preset_height (HeightNormal));
1330 TrackViewList::filter_to_unique_playlists ()
1332 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1335 for (iterator i = begin(); i != end(); ++i) {
1336 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1338 /* not a route: include it anyway */
1341 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1343 if (playlists.insert (t->playlist()).second) {
1344 /* playlist not seen yet */
1348 /* not a track: include it anyway */