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"
31 #include <gtkmm2ext/doi.h>
32 #include <gtkmm2ext/utils.h>
33 #include <gtkmm2ext/selector.h>
35 #include "canvas/canvas.h"
36 #include "canvas/rectangle.h"
37 #include "canvas/debug.h"
39 #include "ardour/profile.h"
41 #include "ardour_ui.h"
42 #include "ardour_dialog.h"
43 #include "global_signals.h"
44 #include "gui_thread.h"
45 #include "public_editor.h"
46 #include "time_axis_view.h"
47 #include "region_view.h"
48 #include "ghostregion.h"
49 #include "selection.h"
51 #include "rgb_macros.h"
53 #include "streamview.h"
54 #include "editor_drag.h"
62 using namespace ARDOUR;
63 using namespace ARDOUR_UI_UTILS;
65 using namespace Editing;
66 using namespace ArdourCanvas;
67 using Gtkmm2ext::Keyboard;
69 const double trim_handle_size = 6.0; /* pixels */
70 uint32_t TimeAxisView::button_height = 0;
71 uint32_t TimeAxisView::extra_height = 0;
72 int const TimeAxisView::_max_order = 512;
73 unsigned int TimeAxisView::name_width_px = 100; // TODO adjust with font-scaling on style-change
74 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
75 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
77 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
79 , controls_table (3, 3)
80 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
81 , _name_editing (false)
88 , in_destructor (false)
96 , _effective_height (0)
97 , _resize_drag_start (-1)
98 , _preresize_cursor (0)
99 , _have_preresize_cursor (false)
100 , _ebox_release_can_act (true)
102 if (!controls_meters_size_group) {
103 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
105 if (extra_height == 0) {
109 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group (), ArdourCanvas::Duple (0.0, 0.0));
110 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
111 _canvas_display->hide(); // reveal as needed
113 selection_group = new ArdourCanvas::Container (_canvas_display);
114 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
115 selection_group->set_data (X_("timeselection"), (void *) 1);
116 selection_group->hide();
118 _ghost_group = new ArdourCanvas::Container (_canvas_display);
119 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
120 _ghost_group->lower_to_bottom();
121 _ghost_group->show();
123 name_label.set_name ("TrackLabel");
124 name_label.set_alignment (0.0, 0.5);
125 name_label.set_width_chars (12);
126 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
128 Gtk::Entry* an_entry = new Gtk::Entry;
129 Gtk::Requisition req;
130 an_entry->size_request (req);
131 name_label.set_size_request (-1, req.height);
132 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
135 name_hbox.pack_end (name_label, true, true);
137 // set min. track-header width if fader is not visible
138 name_hbox.set_size_request(name_width_px, 0);
143 controls_table.set_row_spacings (2);
144 controls_table.set_col_spacings (2);
145 controls_table.set_border_width (2);
147 if (ARDOUR::Profile->get_mixbus() ) {
148 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
150 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
152 controls_table.show_all ();
153 controls_table.set_no_show_all ();
155 controls_vbox.pack_start (controls_table, false, false);
156 controls_vbox.show ();
158 top_hbox.pack_start (controls_vbox, true, true);
161 controls_ebox.add (top_hbox);
162 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
163 Gdk::BUTTON_RELEASE_MASK|
164 Gdk::POINTER_MOTION_MASK|
165 Gdk::ENTER_NOTIFY_MASK|
166 Gdk::LEAVE_NOTIFY_MASK|
168 controls_ebox.set_flags (CAN_FOCUS);
170 /* note that this handler connects *before* the default handler */
171 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
172 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
173 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
174 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
175 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
176 controls_ebox.show ();
178 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
179 time_axis_frame.add(controls_ebox);
180 time_axis_frame.show();
182 HSeparator* separator = manage (new HSeparator());
183 separator->set_name("TrackSeparator");
184 separator->set_size_request(-1, 1);
187 time_axis_vbox.pack_start (time_axis_frame, true, true);
188 time_axis_vbox.pack_end (*separator, false, false);
189 time_axis_vbox.show();
191 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
193 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
196 TimeAxisView::~TimeAxisView()
198 in_destructor = true;
200 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
204 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
206 delete (*i)->start_trim;
207 delete (*i)->end_trim;
211 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
213 delete (*i)->start_trim;
214 delete (*i)->end_trim;
217 delete selection_group;
220 delete _canvas_display;
230 TimeAxisView::hide ()
236 _canvas_display->hide ();
238 if (control_parent) {
239 control_parent->remove (time_axis_vbox);
246 /* now hide children */
248 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
252 /* if its hidden, it cannot be selected */
253 _editor.get_selection().remove (this);
254 /* and neither can its regions */
255 _editor.get_selection().remove_regions (this);
260 /** Display this TimeAxisView as the nth component of the parent box, at y.
262 * @param y y position.
263 * @param nth index for this TimeAxisView, increased if this view has children.
264 * @param parent parent component.
265 * @return height of this TimeAxisView.
268 TimeAxisView::show_at (double y, int& nth, VBox *parent)
270 if (control_parent) {
271 control_parent->reorder_child (time_axis_vbox, nth);
273 control_parent = parent;
274 parent->pack_start (time_axis_vbox, false, false);
275 parent->reorder_child (time_axis_vbox, nth);
280 if (_y_position != y) {
281 _canvas_display->set_y_position (y);
286 _canvas_display->raise_to_top ();
287 _canvas_display->show ();
291 _effective_height = current_height ();
293 /* now show relevant children */
295 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
296 if ((*i)->marked_for_display()) {
298 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
304 return _effective_height;
308 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
310 switch (ev->direction) {
312 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
313 /* See Editor::_stepping_axis_view for notes on this hack */
314 Editor& e = dynamic_cast<Editor&> (_editor);
315 if (!e.stepping_axis_view ()) {
316 e.set_stepping_axis_view (this);
318 e.stepping_axis_view()->step_height (false);
323 case GDK_SCROLL_DOWN:
324 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
325 /* See Editor::_stepping_axis_view for notes on this hack */
326 Editor& e = dynamic_cast<Editor&> (_editor);
327 if (!e.stepping_axis_view ()) {
328 e.set_stepping_axis_view (this);
330 e.stepping_axis_view()->step_height (true);
336 /* no handling for left/right, yet */
340 /* Just forward to the normal canvas scroll method. The coordinate
341 systems are different but since the canvas is always larger than the
342 track headers, and aligned with the trackview area, this will work.
344 In the not too distant future this layout is going away anyway and
345 headers will be on the canvas.
347 return _editor.canvas_scroll_event (ev, false);
351 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
353 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
354 /* see if it is inside the name label */
355 if (name_label.is_ancestor (controls_ebox)) {
358 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
359 Gtk::Allocation a = name_label.get_allocation ();
360 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
362 _ebox_release_can_act = false;
369 _ebox_release_can_act = true;
371 if (maybe_set_cursor (event->y) > 0) {
372 _resize_drag_start = event->y_root;
379 TimeAxisView::idle_resize (uint32_t h)
386 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
388 if (_resize_drag_start >= 0) {
390 /* (ab)use the DragManager to do autoscrolling - basically we
391 * are pretending that the drag is taking place over the canvas
392 * (which perhaps in the glorious future, when track headers
393 * and the canvas are unified, will actually be true.)
396 _editor.maybe_autoscroll (false, true, true);
398 /* now schedule the actual TAV resize */
399 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
400 _editor.add_to_idle_resize (this, delta);
401 _resize_drag_start = ev->y_root;
403 /* not dragging but ... */
404 maybe_set_cursor (ev->y);
407 gdk_event_request_motions(ev);
412 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
414 if (_have_preresize_cursor) {
415 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
416 _have_preresize_cursor = false;
422 TimeAxisView::maybe_set_cursor (int y)
424 /* XXX no Gtkmm Gdk::Window::get_cursor() */
425 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
427 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
429 /* y-coordinate in lower 25% */
431 if (!_have_preresize_cursor) {
432 _preresize_cursor = gdk_window_get_cursor (win->gobj());
433 _have_preresize_cursor = true;
434 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
439 } else if (_have_preresize_cursor) {
440 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
441 _have_preresize_cursor = false;
450 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
452 if (_resize_drag_start >= 0) {
453 if (_have_preresize_cursor) {
454 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
455 _preresize_cursor = 0;
456 _have_preresize_cursor = false;
458 _editor.stop_canvas_autoscroll ();
459 _resize_drag_start = -1;
462 if (!_ebox_release_can_act) {
466 switch (ev->button) {
468 selection_click (ev);
472 popup_display_menu (ev->time);
480 TimeAxisView::selection_click (GdkEventButton* ev)
482 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
483 _editor.set_selected_track (*this, op, false);
487 /** Steps through the defined heights for this TrackView.
488 * @param coarser true if stepping should decrease in size, otherwise false.
491 TimeAxisView::step_height (bool coarser)
493 static const uint32_t step = 25;
497 if (height <= preset_height (HeightSmall)) {
499 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
500 set_height_enum (HeightSmall);
502 set_height (height - step);
507 if (height <= preset_height(HeightSmall)) {
508 set_height_enum (HeightNormal);
510 set_height (height + step);
517 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
519 if (apply_to_selection) {
520 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
522 set_height (preset_height (h));
527 TimeAxisView::set_height (uint32_t h)
529 if (h < preset_height (HeightSmall)) {
530 h = preset_height (HeightSmall);
533 time_axis_vbox.property_height_request () = h;
537 snprintf (buf, sizeof (buf), "%u", height);
538 set_gui_property ("height", buf);
540 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
544 if (selection_group->visible ()) {
545 /* resize the selection rect */
546 show_selection (_editor.get_selection().time);
549 _editor.override_visible_track_count ();
553 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
555 /* steal escape, tabs from GTK */
557 switch (ev->keyval) {
559 case GDK_ISO_Left_Tab:
567 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
569 TrackViewList::iterator i;
571 switch (ev->keyval) {
573 end_name_edit (RESPONSE_CANCEL);
576 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
577 * generates a different ev->keyval, rather than setting
580 case GDK_ISO_Left_Tab:
581 end_name_edit (RESPONSE_APPLY);
585 end_name_edit (RESPONSE_ACCEPT);
595 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
597 end_name_edit (RESPONSE_OK);
602 TimeAxisView::begin_name_edit ()
608 if (can_edit_name()) {
610 name_entry = manage (new Gtkmm2ext::FocusEntry);
612 name_entry->set_width_chars(8); // min width, entry expands
614 name_entry->set_name ("EditorTrackNameDisplay");
615 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
616 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
617 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
618 name_entry->set_text (name_label.get_text());
619 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
621 if (name_label.is_ancestor (name_hbox)) {
622 name_hbox.remove (name_label);
625 name_hbox.pack_end (*name_entry, true, true);
628 name_entry->select_region (0, -1);
629 name_entry->set_state (STATE_SELECTED);
630 name_entry->grab_focus ();
631 name_entry->start_editing (0);
636 TimeAxisView::end_name_edit (int response)
642 bool edit_next = false;
643 bool edit_prev = false;
646 case RESPONSE_CANCEL:
649 name_entry_changed ();
651 case RESPONSE_ACCEPT:
652 name_entry_changed ();
655 name_entry_changed ();
659 /* this will delete the name_entry. but it will also drop focus, which
660 * will cause another callback to this function, so set name_entry = 0
661 * first to ensure we don't double-remove etc. etc.
664 Gtk::Entry* tmp = name_entry;
666 name_hbox.remove (*tmp);
668 /* put the name label back */
670 name_hbox.pack_end (name_label);
675 TrackViewList const & allviews = _editor.get_track_views ();
676 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
678 if (i != allviews.end()) {
681 if (++i == allviews.end()) {
685 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
687 if (rtav && rtav->route()->record_enabled()) {
691 if (!(*i)->hidden()) {
698 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
699 _editor.ensure_time_axis_view_is_visible (**i, false);
700 (*i)->begin_name_edit ();
703 } else if (edit_prev) {
705 TrackViewList const & allviews = _editor.get_track_views ();
706 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
708 if (i != allviews.begin()) {
710 if (i == allviews.begin()) {
716 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
718 if (rtav && rtav->route()->record_enabled()) {
722 if (!(*i)->hidden()) {
729 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
730 _editor.ensure_time_axis_view_is_visible (**i, false);
731 (*i)->begin_name_edit ();
737 TimeAxisView::name_entry_changed ()
742 TimeAxisView::can_edit_name () const
748 TimeAxisView::conditionally_add_to_selection ()
750 Selection& s (_editor.get_selection ());
752 if (!s.selected (this)) {
753 _editor.set_selected_track (*this, Selection::Set);
758 TimeAxisView::popup_display_menu (guint32 when)
760 conditionally_add_to_selection ();
762 build_display_menu ();
763 display_menu->popup (1, when);
767 TimeAxisView::set_selected (bool yn)
769 if (yn == _selected) {
773 Selectable::set_selected (yn);
776 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
777 time_axis_frame.set_name ("MixerStripSelectedFrame");
778 controls_ebox.set_name (controls_base_selected_name);
779 controls_vbox.set_name (controls_base_selected_name);
780 time_axis_vbox.set_name (controls_base_selected_name);
782 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
783 time_axis_frame.set_name (controls_base_unselected_name);
784 controls_ebox.set_name (controls_base_unselected_name);
785 controls_vbox.set_name (controls_base_unselected_name);
786 time_axis_vbox.set_name (controls_base_unselected_name);
790 /* children will be set for the yn=true case. but when deselecting
791 the editor only has a list of top-level trackviews, so we
792 have to do this here.
795 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
796 (*i)->set_selected (false);
800 time_axis_frame.show();
805 TimeAxisView::build_display_menu ()
807 using namespace Menu_Helpers;
811 display_menu = new Menu;
812 display_menu->set_name ("ArdourContextMenu");
814 // Just let implementing classes define what goes into the manu
818 TimeAxisView::set_samples_per_pixel (double fpp)
820 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
821 (*i)->set_samples_per_pixel (fpp);
826 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
828 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
829 (*i)->show_timestretch (start, end, layers, layer);
834 TimeAxisView::hide_timestretch ()
836 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
837 (*i)->hide_timestretch ();
842 TimeAxisView::show_selection (TimeSelection& ts)
847 SelectionRect *rect; time_axis_frame.show();
850 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
851 (*i)->show_selection (ts);
854 if (selection_group->visible ()) {
855 while (!used_selection_rects.empty()) {
856 free_selection_rects.push_front (used_selection_rects.front());
857 used_selection_rects.pop_front();
858 free_selection_rects.front()->rect->hide();
859 free_selection_rects.front()->start_trim->hide();
860 free_selection_rects.front()->end_trim->hide();
862 selection_group->hide();
865 selection_group->show();
866 selection_group->raise_to_top();
868 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
869 framepos_t start, end;
874 cnt = end - start + 1;
876 rect = get_selection_rect ((*i).id);
878 x1 = _editor.sample_to_pixel (start);
879 x2 = _editor.sample_to_pixel (start + cnt - 1);
880 y2 = current_height() - 1;
882 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
884 // trim boxes are at the top for selections
887 rect->start_trim->set (ArdourCanvas::Rect (x1, 1, x1 + trim_handle_size, y2));
888 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
890 rect->start_trim->show();
891 rect->end_trim->show();
893 rect->start_trim->hide();
894 rect->end_trim->hide();
898 used_selection_rects.push_back (rect);
903 TimeAxisView::reshow_selection (TimeSelection& ts)
907 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
908 (*i)->show_selection (ts);
913 TimeAxisView::hide_selection ()
915 if (selection_group->visible ()) {
916 while (!used_selection_rects.empty()) {
917 free_selection_rects.push_front (used_selection_rects.front());
918 used_selection_rects.pop_front();
919 free_selection_rects.front()->rect->hide();
920 free_selection_rects.front()->start_trim->hide();
921 free_selection_rects.front()->end_trim->hide();
923 selection_group->hide();
926 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
927 (*i)->hide_selection ();
932 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
934 /* find the selection rect this is for. we have the item corresponding to one
938 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
939 if ((*i)->start_trim == item || (*i)->end_trim == item) {
941 /* make one trim handle be "above" the other so that if they overlap,
942 the top one is the one last used.
945 (*i)->rect->raise_to_top ();
946 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
947 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
955 TimeAxisView::get_selection_rect (uint32_t id)
959 /* check to see if we already have a visible rect for this particular selection ID */
961 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
962 if ((*i)->id == id) {
967 /* ditto for the free rect list */
969 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
970 if ((*i)->id == id) {
971 SelectionRect* ret = (*i);
972 free_selection_rects.erase (i);
977 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
979 if (free_selection_rects.empty()) {
981 rect = new SelectionRect;
983 rect->rect = new ArdourCanvas::Rectangle (selection_group);
984 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
985 rect->rect->set_outline (false);
986 rect->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
988 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
989 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
990 rect->start_trim->set_outline (false);
991 rect->start_trim->set_fill (false);
993 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
994 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
995 rect->end_trim->set_outline (false);
996 rect->end_trim->set_fill (false);
998 free_selection_rects.push_front (rect);
1000 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1001 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1002 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1005 rect = free_selection_rects.front();
1007 free_selection_rects.pop_front();
1011 struct null_deleter { void operator()(void const *) const {} };
1014 TimeAxisView::is_child (TimeAxisView* tav)
1016 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1020 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1022 children.push_back (child);
1026 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1028 Children::iterator i;
1030 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1035 /** Get selectable things within a given range.
1036 * @param start Start time in session frames.
1037 * @param end End time in session frames.
1038 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1039 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1040 * @param result Filled in with selectable things.
1043 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1049 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
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 (_("M"));
1130 one_row_table.set_border_width (border_width);
1131 one_row_table.set_row_spacings (2);
1132 one_row_table.set_col_spacings (2);
1134 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1135 one_row_table.show_all ();
1137 Gtk::Requisition req(one_row_table.size_request ());
1138 button_height = req.height;
1142 TimeAxisView::color_handler ()
1144 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1148 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1150 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1151 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1153 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1154 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1156 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1157 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1160 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1162 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1163 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1165 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1166 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1168 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1169 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1173 /** @return Pair: TimeAxisView, layer index.
1174 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1175 * does. @param y is an offset from the top of the trackview area.
1177 * If the covering object is a child axis, then the child is returned.
1178 * TimeAxisView is 0 otherwise.
1180 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1181 * and is in stacked or expanded * region display mode, otherwise 0.
1183 std::pair<TimeAxisView*, double>
1184 TimeAxisView::covers_y_position (double y) const
1187 return std::make_pair ((TimeAxisView *) 0, 0);
1190 if (_y_position <= y && y < (_y_position + height)) {
1192 /* work out the layer index if appropriate */
1194 switch (layer_display ()) {
1200 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1201 /* clamp to max layers to be on the safe side; sometimes the above calculation
1202 returns a too-high value */
1203 if (l >= view()->layers ()) {
1204 l = view()->layers() - 1;
1210 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1212 if (l >= (view()->layers() - 0.5)) {
1213 l = view()->layers() - 0.5;
1219 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1222 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1224 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1230 return std::make_pair ((TimeAxisView *) 0, 0);
1234 TimeAxisView::covered_by_y_range (double y0, double y1) const
1240 /* if either the top or bottom of the axisview is in the vertical
1241 * range, we cover it.
1244 if ((y0 < _y_position && y1 < _y_position) ||
1245 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1249 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1250 if ((*i)->covered_by_y_range (y0, y1)) {
1259 TimeAxisView::preset_height (Height h)
1263 return (button_height * 2) + extra_height + 260;
1265 return (button_height * 2) + extra_height + 160;
1267 return (button_height * 2) + extra_height + 60;
1269 return (button_height * 2) + extra_height + 10;
1271 return button_height + extra_height;
1278 /** @return Child time axis views that are not hidden */
1279 TimeAxisView::Children
1280 TimeAxisView::get_child_list ()
1284 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1285 if (!(*i)->hidden()) {
1294 TimeAxisView::build_size_menu ()
1296 if (_size_menu && _size_menu->gobj ()) {
1302 using namespace Menu_Helpers;
1304 _size_menu = new Menu;
1305 _size_menu->set_name ("ArdourContextMenu");
1306 MenuList& items = _size_menu->items();
1308 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1309 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1310 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1311 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1312 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1316 TimeAxisView::reset_visual_state ()
1318 /* this method is not required to trigger a global redraw */
1320 string str = gui_property ("height");
1323 set_height (atoi (str));
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 */