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 (1.0, 0.0));
110 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
111 _canvas_display->hide(); // reveal as needed
113 _canvas_separator = new ArdourCanvas::Line(ed.get_trackview_group ());
114 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
115 _canvas_separator->set_outline_color(RGBA_TO_UINT (0, 0, 0, 255));
116 _canvas_separator->set_outline_width(1.0);
117 _canvas_separator->hide();
119 selection_group = new ArdourCanvas::Container (_canvas_display);
120 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
121 selection_group->set_data (X_("timeselection"), (void *) 1);
122 selection_group->hide();
124 _ghost_group = new ArdourCanvas::Container (_canvas_display);
125 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
126 _ghost_group->lower_to_bottom();
127 _ghost_group->show();
129 name_label.set_name ("TrackLabel");
130 name_label.set_alignment (0.0, 0.5);
131 name_label.set_width_chars (12);
132 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
134 Gtk::Entry* an_entry = new Gtk::Entry;
135 Gtk::Requisition req;
136 an_entry->size_request (req);
137 name_label.set_size_request (-1, req.height);
138 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
141 name_hbox.pack_end (name_label, true, true);
143 // set min. track-header width if fader is not visible
144 name_hbox.set_size_request(name_width_px, -1);
149 controls_table.set_row_spacings (2);
150 controls_table.set_col_spacings (2);
151 controls_table.set_border_width (2);
153 if (ARDOUR::Profile->get_mixbus() ) {
154 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
156 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
158 controls_table.show_all ();
159 controls_table.set_no_show_all ();
161 controls_vbox.pack_start (controls_table, false, false);
162 controls_vbox.show ();
164 top_hbox.pack_start (controls_vbox, true, true);
167 controls_ebox.add (top_hbox);
168 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
169 Gdk::BUTTON_RELEASE_MASK|
170 Gdk::POINTER_MOTION_MASK|
171 Gdk::ENTER_NOTIFY_MASK|
172 Gdk::LEAVE_NOTIFY_MASK|
174 controls_ebox.set_flags (CAN_FOCUS);
176 /* note that this handler connects *before* the default handler */
177 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
178 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
179 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
180 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
181 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
182 controls_ebox.show ();
184 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
185 time_axis_frame.add(controls_ebox);
186 time_axis_frame.show();
188 HSeparator* separator = manage (new HSeparator());
189 separator->set_name("TrackSeparator");
190 separator->set_size_request(-1, 1);
193 time_axis_vbox.pack_start (*separator, false, false);
194 time_axis_vbox.pack_start (time_axis_frame, true, true);
195 time_axis_vbox.show();
196 time_axis_hbox.pack_start (time_axis_vbox, true, true);
197 time_axis_hbox.show();
199 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
201 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
204 TimeAxisView::~TimeAxisView()
206 in_destructor = true;
208 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
212 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
214 delete (*i)->start_trim;
215 delete (*i)->end_trim;
219 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
221 delete (*i)->start_trim;
222 delete (*i)->end_trim;
225 delete selection_group;
228 delete _canvas_display;
231 delete _canvas_separator;
232 _canvas_separator = 0;
241 TimeAxisView::hide ()
247 _canvas_display->hide ();
248 _canvas_separator->hide ();
250 if (control_parent) {
251 control_parent->remove (time_axis_hbox);
258 /* now hide children */
260 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
264 /* if its hidden, it cannot be selected */
265 _editor.get_selection().remove (this);
266 /* and neither can its regions */
267 _editor.get_selection().remove_regions (this);
272 /** Display this TimeAxisView as the nth component of the parent box, at y.
274 * @param y y position.
275 * @param nth index for this TimeAxisView, increased if this view has children.
276 * @param parent parent component.
277 * @return height of this TimeAxisView.
280 TimeAxisView::show_at (double y, int& nth, VBox *parent)
282 if (control_parent) {
283 control_parent->reorder_child (time_axis_hbox, nth);
285 control_parent = parent;
286 parent->pack_start (time_axis_hbox, false, false);
287 parent->reorder_child (time_axis_hbox, nth);
292 if (_y_position != y) {
293 _canvas_separator->set (ArdourCanvas::Duple(0, y), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, y));
294 _canvas_display->set_y_position (y + 1);
298 _canvas_display->raise_to_top ();
299 _canvas_display->show ();
301 _canvas_separator->raise_to_top ();
302 _canvas_separator->show ();
306 _effective_height = current_height ();
308 /* now show relevant children */
310 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
311 if ((*i)->marked_for_display()) {
313 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
319 return _effective_height;
323 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
325 switch (ev->direction) {
327 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
328 /* See Editor::_stepping_axis_view for notes on this hack */
329 Editor& e = dynamic_cast<Editor&> (_editor);
330 if (!e.stepping_axis_view ()) {
331 e.set_stepping_axis_view (this);
333 e.stepping_axis_view()->step_height (false);
338 case GDK_SCROLL_DOWN:
339 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
340 /* See Editor::_stepping_axis_view for notes on this hack */
341 Editor& e = dynamic_cast<Editor&> (_editor);
342 if (!e.stepping_axis_view ()) {
343 e.set_stepping_axis_view (this);
345 e.stepping_axis_view()->step_height (true);
351 /* no handling for left/right, yet */
355 /* Just forward to the normal canvas scroll method. The coordinate
356 systems are different but since the canvas is always larger than the
357 track headers, and aligned with the trackview area, this will work.
359 In the not too distant future this layout is going away anyway and
360 headers will be on the canvas.
362 return _editor.canvas_scroll_event (ev, false);
366 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
368 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
369 /* see if it is inside the name label */
370 if (name_label.is_ancestor (controls_ebox)) {
373 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
374 Gtk::Allocation a = name_label.get_allocation ();
375 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
377 _ebox_release_can_act = false;
384 _ebox_release_can_act = true;
386 if (maybe_set_cursor (event->y) > 0) {
387 _resize_drag_start = event->y_root;
394 TimeAxisView::idle_resize (uint32_t h)
401 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
403 if (_resize_drag_start >= 0) {
405 /* (ab)use the DragManager to do autoscrolling - basically we
406 * are pretending that the drag is taking place over the canvas
407 * (which perhaps in the glorious future, when track headers
408 * and the canvas are unified, will actually be true.)
411 _editor.maybe_autoscroll (false, true, true);
413 /* now schedule the actual TAV resize */
414 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
415 _editor.add_to_idle_resize (this, delta);
416 _resize_drag_start = ev->y_root;
418 /* not dragging but ... */
419 maybe_set_cursor (ev->y);
422 gdk_event_request_motions(ev);
427 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
429 if (_have_preresize_cursor) {
430 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
431 _have_preresize_cursor = false;
437 TimeAxisView::maybe_set_cursor (int y)
439 /* XXX no Gtkmm Gdk::Window::get_cursor() */
440 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
442 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
444 /* y-coordinate in lower 25% */
446 if (!_have_preresize_cursor) {
447 _preresize_cursor = gdk_window_get_cursor (win->gobj());
448 _have_preresize_cursor = true;
449 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
454 } else if (_have_preresize_cursor) {
455 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
456 _have_preresize_cursor = false;
465 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
467 if (_resize_drag_start >= 0) {
468 if (_have_preresize_cursor) {
469 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
470 _preresize_cursor = 0;
471 _have_preresize_cursor = false;
473 _editor.stop_canvas_autoscroll ();
474 _resize_drag_start = -1;
477 if (!_ebox_release_can_act) {
481 switch (ev->button) {
483 selection_click (ev);
487 popup_display_menu (ev->time);
495 TimeAxisView::selection_click (GdkEventButton* ev)
497 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
498 _editor.set_selected_track (*this, op, false);
502 /** Steps through the defined heights for this TrackView.
503 * @param coarser true if stepping should decrease in size, otherwise false.
506 TimeAxisView::step_height (bool coarser)
508 static const uint32_t step = 25;
512 if (height <= preset_height (HeightSmall)) {
514 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
515 set_height_enum (HeightSmall);
517 set_height (height - step);
522 if (height <= preset_height(HeightSmall)) {
523 set_height_enum (HeightNormal);
525 set_height (height + step);
532 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
534 if (apply_to_selection) {
535 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
537 set_height (preset_height (h));
542 TimeAxisView::set_height (uint32_t h)
544 if (h < preset_height (HeightSmall)) {
545 h = preset_height (HeightSmall);
548 time_axis_hbox.property_height_request () = h;
552 snprintf (buf, sizeof (buf), "%u", height);
553 set_gui_property ("height", buf);
555 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
559 if (selection_group->visible ()) {
560 /* resize the selection rect */
561 show_selection (_editor.get_selection().time);
564 _editor.override_visible_track_count ();
568 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
570 /* steal escape, tabs from GTK */
572 switch (ev->keyval) {
574 case GDK_ISO_Left_Tab:
582 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
584 TrackViewList::iterator i;
586 switch (ev->keyval) {
588 end_name_edit (RESPONSE_CANCEL);
591 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
592 * generates a different ev->keyval, rather than setting
595 case GDK_ISO_Left_Tab:
596 end_name_edit (RESPONSE_APPLY);
600 end_name_edit (RESPONSE_ACCEPT);
610 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
612 end_name_edit (RESPONSE_OK);
617 TimeAxisView::begin_name_edit ()
623 if (can_edit_name()) {
625 name_entry = manage (new Gtkmm2ext::FocusEntry);
627 name_entry->set_width_chars(8); // min width, entry expands
629 name_entry->set_name ("EditorTrackNameDisplay");
630 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
631 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
632 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
633 name_entry->set_text (name_label.get_text());
634 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
636 if (name_label.is_ancestor (name_hbox)) {
637 name_hbox.remove (name_label);
640 name_hbox.pack_end (*name_entry, true, true);
643 name_entry->select_region (0, -1);
644 name_entry->set_state (STATE_SELECTED);
645 name_entry->grab_focus ();
646 name_entry->start_editing (0);
651 TimeAxisView::end_name_edit (int response)
657 bool edit_next = false;
658 bool edit_prev = false;
661 case RESPONSE_CANCEL:
664 name_entry_changed ();
666 case RESPONSE_ACCEPT:
667 name_entry_changed ();
670 name_entry_changed ();
674 /* this will delete the name_entry. but it will also drop focus, which
675 * will cause another callback to this function, so set name_entry = 0
676 * first to ensure we don't double-remove etc. etc.
679 Gtk::Entry* tmp = name_entry;
681 name_hbox.remove (*tmp);
683 /* put the name label back */
685 name_hbox.pack_end (name_label);
690 TrackViewList const & allviews = _editor.get_track_views ();
691 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
693 if (i != allviews.end()) {
696 if (++i == allviews.end()) {
700 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
702 if (rtav && rtav->route()->record_enabled()) {
706 if (!(*i)->hidden()) {
713 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
714 _editor.ensure_time_axis_view_is_visible (**i, false);
715 (*i)->begin_name_edit ();
718 } else if (edit_prev) {
720 TrackViewList const & allviews = _editor.get_track_views ();
721 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
723 if (i != allviews.begin()) {
725 if (i == allviews.begin()) {
731 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
733 if (rtav && rtav->route()->record_enabled()) {
737 if (!(*i)->hidden()) {
744 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
745 _editor.ensure_time_axis_view_is_visible (**i, false);
746 (*i)->begin_name_edit ();
752 TimeAxisView::name_entry_changed ()
757 TimeAxisView::can_edit_name () const
763 TimeAxisView::conditionally_add_to_selection ()
765 Selection& s (_editor.get_selection ());
767 if (!s.selected (this)) {
768 _editor.set_selected_track (*this, Selection::Set);
773 TimeAxisView::popup_display_menu (guint32 when)
775 conditionally_add_to_selection ();
777 build_display_menu ();
778 display_menu->popup (1, when);
782 TimeAxisView::set_selected (bool yn)
784 if (can_edit_name() && name_entry.get_visible()) {
785 end_name_edit (RESPONSE_CANCEL);
788 if (yn == _selected) {
792 Selectable::set_selected (yn);
795 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
796 time_axis_frame.set_name ("MixerStripSelectedFrame");
797 controls_ebox.set_name (controls_base_selected_name);
798 controls_vbox.set_name (controls_base_selected_name);
799 time_axis_vbox.set_name (controls_base_selected_name);
801 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
802 time_axis_frame.set_name (controls_base_unselected_name);
803 controls_ebox.set_name (controls_base_unselected_name);
804 controls_vbox.set_name (controls_base_unselected_name);
805 time_axis_vbox.set_name (controls_base_unselected_name);
809 /* children will be set for the yn=true case. but when deselecting
810 the editor only has a list of top-level trackviews, so we
811 have to do this here.
814 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
815 (*i)->set_selected (false);
819 time_axis_frame.show();
824 TimeAxisView::build_display_menu ()
826 using namespace Menu_Helpers;
830 display_menu = new Menu;
831 display_menu->set_name ("ArdourContextMenu");
833 // Just let implementing classes define what goes into the manu
837 TimeAxisView::set_samples_per_pixel (double fpp)
839 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
840 (*i)->set_samples_per_pixel (fpp);
845 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
847 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
848 (*i)->show_timestretch (start, end, layers, layer);
853 TimeAxisView::hide_timestretch ()
855 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
856 (*i)->hide_timestretch ();
861 TimeAxisView::show_selection (TimeSelection& ts)
866 SelectionRect *rect; time_axis_frame.show();
869 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
870 (*i)->show_selection (ts);
873 if (selection_group->visible ()) {
874 while (!used_selection_rects.empty()) {
875 free_selection_rects.push_front (used_selection_rects.front());
876 used_selection_rects.pop_front();
877 free_selection_rects.front()->rect->hide();
878 free_selection_rects.front()->start_trim->hide();
879 free_selection_rects.front()->end_trim->hide();
881 selection_group->hide();
884 selection_group->show();
885 selection_group->raise_to_top();
887 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
888 framepos_t start, end;
893 cnt = end - start + 1;
895 rect = get_selection_rect ((*i).id);
897 x1 = _editor.sample_to_pixel (start);
898 x2 = _editor.sample_to_pixel (start + cnt - 1);
899 y2 = current_height() - 1;
901 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
903 // trim boxes are at the top for selections
906 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
907 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
909 rect->start_trim->show();
910 rect->end_trim->show();
912 rect->start_trim->hide();
913 rect->end_trim->hide();
917 used_selection_rects.push_back (rect);
922 TimeAxisView::reshow_selection (TimeSelection& ts)
926 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
927 (*i)->show_selection (ts);
932 TimeAxisView::hide_selection ()
934 if (selection_group->visible ()) {
935 while (!used_selection_rects.empty()) {
936 free_selection_rects.push_front (used_selection_rects.front());
937 used_selection_rects.pop_front();
938 free_selection_rects.front()->rect->hide();
939 free_selection_rects.front()->start_trim->hide();
940 free_selection_rects.front()->end_trim->hide();
942 selection_group->hide();
945 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
946 (*i)->hide_selection ();
951 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
953 /* find the selection rect this is for. we have the item corresponding to one
957 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
958 if ((*i)->start_trim == item || (*i)->end_trim == item) {
960 /* make one trim handle be "above" the other so that if they overlap,
961 the top one is the one last used.
964 (*i)->rect->raise_to_top ();
965 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
966 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
974 TimeAxisView::get_selection_rect (uint32_t id)
978 /* check to see if we already have a visible rect for this particular selection ID */
980 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
981 if ((*i)->id == id) {
986 /* ditto for the free rect list */
988 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
989 if ((*i)->id == id) {
990 SelectionRect* ret = (*i);
991 free_selection_rects.erase (i);
996 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
998 if (free_selection_rects.empty()) {
1000 rect = new SelectionRect;
1002 rect->rect = new ArdourCanvas::Rectangle (selection_group);
1003 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1004 rect->rect->set_outline (false);
1005 rect->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1007 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1008 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1009 rect->start_trim->set_outline (false);
1010 rect->start_trim->set_fill (false);
1012 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1013 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1014 rect->end_trim->set_outline (false);
1015 rect->end_trim->set_fill (false);
1017 free_selection_rects.push_front (rect);
1019 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1020 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1021 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1024 rect = free_selection_rects.front();
1026 free_selection_rects.pop_front();
1030 struct null_deleter { void operator()(void const *) const {} };
1033 TimeAxisView::is_child (TimeAxisView* tav)
1035 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1039 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1041 children.push_back (child);
1045 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1047 Children::iterator i;
1049 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1054 /** Get selectable things within a given range.
1055 * @param start Start time in session frames.
1056 * @param end End time in session frames.
1057 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1058 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1059 * @param result Filled in with selectable things.
1062 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1068 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1074 TimeAxisView::add_ghost (RegionView* rv)
1076 GhostRegion* gr = rv->add_ghost (*this);
1079 ghosts.push_back(gr);
1084 TimeAxisView::remove_ghost (RegionView* rv)
1086 rv->remove_ghost_in (*this);
1090 TimeAxisView::erase_ghost (GhostRegion* gr)
1092 if (in_destructor) {
1096 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1105 TimeAxisView::touched (double top, double bot)
1107 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1108 y_position is the "origin" or "top" of the track.
1111 double mybot = _y_position + current_height();
1113 return ((_y_position <= bot && _y_position >= top) ||
1114 ((mybot <= bot) && (top < mybot)) ||
1115 (mybot >= bot && _y_position < top));
1119 TimeAxisView::set_parent (TimeAxisView& p)
1125 TimeAxisView::reset_height ()
1127 set_height (height);
1129 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1130 (*i)->set_height ((*i)->height);
1135 TimeAxisView::compute_heights ()
1137 // TODO this function should be re-evaluated when font-scaling changes (!)
1138 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1139 Gtk::Table one_row_table (1, 1);
1140 ArdourButton* test_button = manage (new ArdourButton);
1141 const int border_width = 2;
1142 const int frame_height = 2;
1143 extra_height = (2 * border_width) + frame_height;
1145 window.add (one_row_table);
1146 test_button->set_name ("mute button");
1147 test_button->set_text (_("M"));
1149 one_row_table.set_border_width (border_width);
1150 one_row_table.set_row_spacings (2);
1151 one_row_table.set_col_spacings (2);
1153 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1154 one_row_table.show_all ();
1156 Gtk::Requisition req(one_row_table.size_request ());
1157 button_height = req.height;
1161 TimeAxisView::color_handler ()
1163 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1167 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1169 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1170 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1172 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1173 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1175 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1176 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1179 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1181 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1182 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1184 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1185 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1187 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1188 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1192 /** @return Pair: TimeAxisView, layer index.
1193 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1194 * does. @param y is an offset from the top of the trackview area.
1196 * If the covering object is a child axis, then the child is returned.
1197 * TimeAxisView is 0 otherwise.
1199 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1200 * and is in stacked or expanded * region display mode, otherwise 0.
1202 std::pair<TimeAxisView*, double>
1203 TimeAxisView::covers_y_position (double y) const
1206 return std::make_pair ((TimeAxisView *) 0, 0);
1209 if (_y_position <= y && y < (_y_position + height)) {
1211 /* work out the layer index if appropriate */
1213 switch (layer_display ()) {
1219 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1220 /* clamp to max layers to be on the safe side; sometimes the above calculation
1221 returns a too-high value */
1222 if (l >= view()->layers ()) {
1223 l = view()->layers() - 1;
1229 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1231 if (l >= (view()->layers() - 0.5)) {
1232 l = view()->layers() - 0.5;
1238 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1241 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1243 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1249 return std::make_pair ((TimeAxisView *) 0, 0);
1253 TimeAxisView::covered_by_y_range (double y0, double y1) const
1259 /* if either the top or bottom of the axisview is in the vertical
1260 * range, we cover it.
1263 if ((y0 < _y_position && y1 < _y_position) ||
1264 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1268 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1269 if ((*i)->covered_by_y_range (y0, y1)) {
1278 TimeAxisView::preset_height (Height h)
1282 return (button_height * 2) + extra_height + 260;
1284 return (button_height * 2) + extra_height + 160;
1286 return (button_height * 2) + extra_height + 60;
1288 return (button_height * 2) + extra_height + 10;
1290 return button_height + extra_height;
1297 /** @return Child time axis views that are not hidden */
1298 TimeAxisView::Children
1299 TimeAxisView::get_child_list ()
1303 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1304 if (!(*i)->hidden()) {
1313 TimeAxisView::build_size_menu ()
1315 if (_size_menu && _size_menu->gobj ()) {
1321 using namespace Menu_Helpers;
1323 _size_menu = new Menu;
1324 _size_menu->set_name ("ArdourContextMenu");
1325 MenuList& items = _size_menu->items();
1327 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1328 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1329 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1330 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1331 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1335 TimeAxisView::reset_visual_state ()
1337 /* this method is not required to trigger a global redraw */
1339 string str = gui_property ("height");
1342 set_height (atoi (str));
1344 set_height (preset_height (HeightNormal));
1349 TrackViewList::filter_to_unique_playlists ()
1351 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1354 for (iterator i = begin(); i != end(); ++i) {
1355 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1357 /* not a route: include it anyway */
1360 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1362 if (playlists.insert (t->playlist()).second) {
1363 /* playlist not seen yet */
1367 /* not a track: include it anyway */