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 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
74 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
76 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
78 , controls_table (4, 4)
79 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
80 , _name_editing (false)
87 , in_destructor (false)
95 , _effective_height (0)
96 , _resize_drag_start (-1)
97 , _preresize_cursor (0)
98 , _have_preresize_cursor (false)
99 , _ebox_release_can_act (true)
101 if (extra_height == 0) {
105 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group (), ArdourCanvas::Duple (0.0, 0.0));
106 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
107 _canvas_display->hide(); // reveal as needed
109 selection_group = new ArdourCanvas::Container (_canvas_display);
110 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
111 selection_group->set_data (X_("timeselection"), (void *) 1);
112 selection_group->hide();
114 _ghost_group = new ArdourCanvas::Container (_canvas_display);
115 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
116 _ghost_group->lower_to_bottom();
117 _ghost_group->show();
119 name_label.set_name ("TrackLabel");
120 name_label.set_alignment (0.0, 0.5);
121 name_label.set_width_chars (12);
122 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
124 Gtk::Entry* an_entry = new Gtk::Entry;
125 Gtk::Requisition req;
126 an_entry->size_request (req);
127 name_label.set_size_request (-1, req.height);
128 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
131 name_hbox.pack_end (name_label, true, true);
133 // set min. track-header width if fader is not visible
134 if (ARDOUR::Profile->get_mixbus() ) {
135 name_hbox.set_size_request(100, 0);
137 name_hbox.set_size_request(90, 0);
142 controls_table.set_row_spacings (2);
143 controls_table.set_col_spacings (2);
144 controls_table.set_border_width (2);
146 if (ARDOUR::Profile->get_mixbus() ) {
147 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
149 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
151 controls_table.show_all ();
152 controls_table.set_no_show_all ();
154 controls_vbox.pack_start (controls_table, false, false);
155 controls_vbox.show ();
157 top_hbox.pack_start (controls_vbox, true, true);
160 //controls_ebox.set_name ("TimeAxisViewControlsBaseUnselected");
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_OUT);
179 time_axis_frame.add(controls_ebox);
180 time_axis_frame.show();
182 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
184 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
187 TimeAxisView::~TimeAxisView()
189 in_destructor = true;
191 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
195 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
197 delete (*i)->start_trim;
198 delete (*i)->end_trim;
202 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
204 delete (*i)->start_trim;
205 delete (*i)->end_trim;
208 delete selection_group;
211 delete _canvas_display;
221 TimeAxisView::hide ()
227 _canvas_display->hide ();
229 if (control_parent) {
230 control_parent->remove (time_axis_frame);
237 /* now hide children */
239 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
243 /* if its hidden, it cannot be selected */
244 _editor.get_selection().remove (this);
245 /* and neither can its regions */
246 _editor.get_selection().remove_regions (this);
251 /** Display this TimeAxisView as the nth component of the parent box, at y.
253 * @param y y position.
254 * @param nth index for this TimeAxisView, increased if this view has children.
255 * @param parent parent component.
256 * @return height of this TimeAxisView.
259 TimeAxisView::show_at (double y, int& nth, VBox *parent)
261 if (control_parent) {
262 control_parent->reorder_child (time_axis_frame, nth);
264 control_parent = parent;
265 parent->pack_start (time_axis_frame, false, false);
266 parent->reorder_child (time_axis_frame, nth);
271 if (_y_position != y) {
272 _canvas_display->set_y_position (y);
277 _canvas_display->raise_to_top ();
278 _canvas_display->show ();
282 _effective_height = current_height ();
284 /* now show relevant children */
286 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
287 if ((*i)->marked_for_display()) {
289 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
295 return _effective_height;
299 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
301 switch (ev->direction) {
303 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
304 /* See Editor::_stepping_axis_view for notes on this hack */
305 Editor& e = dynamic_cast<Editor&> (_editor);
306 if (!e.stepping_axis_view ()) {
307 e.set_stepping_axis_view (this);
309 e.stepping_axis_view()->step_height (false);
314 case GDK_SCROLL_DOWN:
315 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
316 /* See Editor::_stepping_axis_view for notes on this hack */
317 Editor& e = dynamic_cast<Editor&> (_editor);
318 if (!e.stepping_axis_view ()) {
319 e.set_stepping_axis_view (this);
321 e.stepping_axis_view()->step_height (true);
327 /* no handling for left/right, yet */
331 /* Just forward to the normal canvas scroll method. The coordinate
332 systems are different but since the canvas is always larger than the
333 track headers, and aligned with the trackview area, this will work.
335 In the not too distant future this layout is going away anyway and
336 headers will be on the canvas.
338 return _editor.canvas_scroll_event (ev, false);
342 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
344 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
345 /* see if it is inside the name label */
346 if (name_label.is_ancestor (controls_ebox)) {
349 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
350 Gtk::Allocation a = name_label.get_allocation ();
351 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
353 _ebox_release_can_act = false;
360 _ebox_release_can_act = true;
362 if (maybe_set_cursor (event->y) > 0) {
363 _resize_drag_start = event->y_root;
370 TimeAxisView::idle_resize (uint32_t h)
377 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
379 if (_resize_drag_start >= 0) {
381 /* (ab)use the DragManager to do autoscrolling - basically we
382 * are pretending that the drag is taking place over the canvas
383 * (which perhaps in the glorious future, when track headers
384 * and the canvas are unified, will actually be true.)
387 _editor.maybe_autoscroll (false, true, true);
389 /* now schedule the actual TAV resize */
390 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
391 _editor.add_to_idle_resize (this, delta);
392 _resize_drag_start = ev->y_root;
394 /* not dragging but ... */
395 maybe_set_cursor (ev->y);
398 gdk_event_request_motions(ev);
403 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
405 if (_have_preresize_cursor) {
406 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
407 _have_preresize_cursor = false;
413 TimeAxisView::maybe_set_cursor (int y)
415 /* XXX no Gtkmm Gdk::Window::get_cursor() */
416 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
418 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
420 /* y-coordinate in lower 25% */
422 if (!_have_preresize_cursor) {
423 _preresize_cursor = gdk_window_get_cursor (win->gobj());
424 _have_preresize_cursor = true;
425 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
430 } else if (_have_preresize_cursor) {
431 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
432 _have_preresize_cursor = false;
441 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
443 if (_resize_drag_start >= 0) {
444 if (_have_preresize_cursor) {
445 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
446 _preresize_cursor = 0;
447 _have_preresize_cursor = false;
449 _editor.stop_canvas_autoscroll ();
450 _resize_drag_start = -1;
453 if (!_ebox_release_can_act) {
457 switch (ev->button) {
459 selection_click (ev);
463 popup_display_menu (ev->time);
471 TimeAxisView::selection_click (GdkEventButton* ev)
473 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
474 _editor.set_selected_track (*this, op, false);
478 /** Steps through the defined heights for this TrackView.
479 * @param coarser true if stepping should decrease in size, otherwise false.
482 TimeAxisView::step_height (bool coarser)
484 static const uint32_t step = 25;
488 if (height <= preset_height (HeightSmall)) {
490 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
491 set_height_enum (HeightSmall);
493 set_height (height - step);
498 if (height <= preset_height(HeightSmall)) {
499 set_height_enum (HeightNormal);
501 set_height (height + step);
508 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
510 if (apply_to_selection) {
511 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
513 set_height (preset_height (h));
518 TimeAxisView::set_height (uint32_t h)
520 if (h < preset_height (HeightSmall)) {
521 h = preset_height (HeightSmall);
524 time_axis_frame.property_height_request () = h;
528 snprintf (buf, sizeof (buf), "%u", height);
529 set_gui_property ("height", buf);
531 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
535 if (selection_group->visible ()) {
536 /* resize the selection rect */
537 show_selection (_editor.get_selection().time);
540 _editor.override_visible_track_count ();
544 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
546 /* steal escape, tabs from GTK */
548 switch (ev->keyval) {
550 case GDK_ISO_Left_Tab:
558 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
560 TrackViewList::iterator i;
562 switch (ev->keyval) {
564 end_name_edit (RESPONSE_CANCEL);
567 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
568 * generates a different ev->keyval, rather than setting
571 case GDK_ISO_Left_Tab:
572 end_name_edit (RESPONSE_APPLY);
576 end_name_edit (RESPONSE_ACCEPT);
586 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
588 end_name_edit (RESPONSE_OK);
593 TimeAxisView::begin_name_edit ()
599 if (can_edit_name()) {
601 name_entry = manage (new Gtkmm2ext::FocusEntry);
603 name_entry->set_width_chars(8); // min width, entry expands
605 name_entry->set_name ("EditorTrackNameDisplay");
606 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
607 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
608 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
609 name_entry->set_text (name_label.get_text());
610 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
612 if (name_label.is_ancestor (name_hbox)) {
613 name_hbox.remove (name_label);
616 name_hbox.pack_end (*name_entry, true, true);
619 name_entry->select_region (0, -1);
620 name_entry->set_state (STATE_SELECTED);
621 name_entry->grab_focus ();
622 name_entry->start_editing (0);
627 TimeAxisView::end_name_edit (int response)
633 bool edit_next = false;
634 bool edit_prev = false;
637 case RESPONSE_CANCEL:
640 name_entry_changed ();
642 case RESPONSE_ACCEPT:
643 name_entry_changed ();
646 name_entry_changed ();
650 /* this will delete the name_entry. but it will also drop focus, which
651 * will cause another callback to this function, so set name_entry = 0
652 * first to ensure we don't double-remove etc. etc.
655 Gtk::Entry* tmp = name_entry;
657 name_hbox.remove (*tmp);
659 /* put the name label back */
661 name_hbox.pack_end (name_label);
666 TrackViewList const & allviews = _editor.get_track_views ();
667 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
669 if (i != allviews.end()) {
672 if (++i == allviews.end()) {
676 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
678 if (rtav && rtav->route()->record_enabled()) {
682 if (!(*i)->hidden()) {
689 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
690 _editor.ensure_time_axis_view_is_visible (**i, false);
691 (*i)->begin_name_edit ();
694 } else if (edit_prev) {
696 TrackViewList const & allviews = _editor.get_track_views ();
697 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
699 if (i != allviews.begin()) {
701 if (i == allviews.begin()) {
707 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
709 if (rtav && rtav->route()->record_enabled()) {
713 if (!(*i)->hidden()) {
720 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
721 _editor.ensure_time_axis_view_is_visible (**i, false);
722 (*i)->begin_name_edit ();
728 TimeAxisView::name_entry_changed ()
733 TimeAxisView::can_edit_name () const
739 TimeAxisView::conditionally_add_to_selection ()
741 Selection& s (_editor.get_selection ());
743 if (!s.selected (this)) {
744 _editor.set_selected_track (*this, Selection::Set);
749 TimeAxisView::popup_display_menu (guint32 when)
751 conditionally_add_to_selection ();
753 build_display_menu ();
754 display_menu->popup (1, when);
758 TimeAxisView::set_selected (bool yn)
760 if (yn == _selected) {
764 Selectable::set_selected (yn);
767 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
768 time_axis_frame.set_name ("MixerStripSelectedFrame");
769 controls_ebox.set_name (controls_base_selected_name);
770 controls_vbox.set_name (controls_base_selected_name);
772 time_axis_frame.set_shadow_type (Gtk::SHADOW_OUT);
773 time_axis_frame.set_name (controls_base_unselected_name);
774 controls_ebox.set_name (controls_base_unselected_name);
775 controls_vbox.set_name (controls_base_unselected_name);
779 /* children will be set for the yn=true case. but when deselecting
780 the editor only has a list of top-level trackviews, so we
781 have to do this here.
784 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
785 (*i)->set_selected (false);
789 time_axis_frame.show();
794 TimeAxisView::build_display_menu ()
796 using namespace Menu_Helpers;
800 display_menu = new Menu;
801 display_menu->set_name ("ArdourContextMenu");
803 // Just let implementing classes define what goes into the manu
807 TimeAxisView::set_samples_per_pixel (double fpp)
809 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
810 (*i)->set_samples_per_pixel (fpp);
815 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
817 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
818 (*i)->show_timestretch (start, end, layers, layer);
823 TimeAxisView::hide_timestretch ()
825 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
826 (*i)->hide_timestretch ();
831 TimeAxisView::show_selection (TimeSelection& ts)
836 SelectionRect *rect; time_axis_frame.show();
839 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
840 (*i)->show_selection (ts);
843 if (selection_group->visible ()) {
844 while (!used_selection_rects.empty()) {
845 free_selection_rects.push_front (used_selection_rects.front());
846 used_selection_rects.pop_front();
847 free_selection_rects.front()->rect->hide();
848 free_selection_rects.front()->start_trim->hide();
849 free_selection_rects.front()->end_trim->hide();
851 selection_group->hide();
854 selection_group->show();
855 selection_group->raise_to_top();
857 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
858 framepos_t start, end;
863 cnt = end - start + 1;
865 rect = get_selection_rect ((*i).id);
867 x1 = _editor.sample_to_pixel (start);
868 x2 = _editor.sample_to_pixel (start + cnt - 1);
869 y2 = current_height() - 1;
871 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
873 // trim boxes are at the top for selections
876 rect->start_trim->set (ArdourCanvas::Rect (x1, 1, x1 + trim_handle_size, y2));
877 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
879 rect->start_trim->show();
880 rect->end_trim->show();
882 rect->start_trim->hide();
883 rect->end_trim->hide();
887 used_selection_rects.push_back (rect);
892 TimeAxisView::reshow_selection (TimeSelection& ts)
896 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
897 (*i)->show_selection (ts);
902 TimeAxisView::hide_selection ()
904 if (selection_group->visible ()) {
905 while (!used_selection_rects.empty()) {
906 free_selection_rects.push_front (used_selection_rects.front());
907 used_selection_rects.pop_front();
908 free_selection_rects.front()->rect->hide();
909 free_selection_rects.front()->start_trim->hide();
910 free_selection_rects.front()->end_trim->hide();
912 selection_group->hide();
915 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
916 (*i)->hide_selection ();
921 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
923 /* find the selection rect this is for. we have the item corresponding to one
927 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
928 if ((*i)->start_trim == item || (*i)->end_trim == item) {
930 /* make one trim handle be "above" the other so that if they overlap,
931 the top one is the one last used.
934 (*i)->rect->raise_to_top ();
935 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
936 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
944 TimeAxisView::get_selection_rect (uint32_t id)
948 /* check to see if we already have a visible rect for this particular selection ID */
950 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
951 if ((*i)->id == id) {
956 /* ditto for the free rect list */
958 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
959 if ((*i)->id == id) {
960 SelectionRect* ret = (*i);
961 free_selection_rects.erase (i);
966 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
968 if (free_selection_rects.empty()) {
970 rect = new SelectionRect;
972 rect->rect = new ArdourCanvas::Rectangle (selection_group);
973 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
974 rect->rect->set_outline (false);
975 rect->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
977 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
978 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
979 rect->start_trim->set_outline (false);
980 rect->start_trim->set_fill (false);
982 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
983 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
984 rect->end_trim->set_outline (false);
985 rect->end_trim->set_fill (false);
987 free_selection_rects.push_front (rect);
989 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
990 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
991 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
994 rect = free_selection_rects.front();
996 free_selection_rects.pop_front();
1000 struct null_deleter { void operator()(void const *) const {} };
1003 TimeAxisView::is_child (TimeAxisView* tav)
1005 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1009 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1011 children.push_back (child);
1015 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1017 Children::iterator i;
1019 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1024 /** Get selectable things within a given range.
1025 * @param start Start time in session frames.
1026 * @param end End time in session frames.
1027 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1028 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1029 * @param result Filled in with selectable things.
1032 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1038 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1044 TimeAxisView::add_ghost (RegionView* rv)
1046 GhostRegion* gr = rv->add_ghost (*this);
1049 ghosts.push_back(gr);
1054 TimeAxisView::remove_ghost (RegionView* rv)
1056 rv->remove_ghost_in (*this);
1060 TimeAxisView::erase_ghost (GhostRegion* gr)
1062 if (in_destructor) {
1066 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1075 TimeAxisView::touched (double top, double bot)
1077 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1078 y_position is the "origin" or "top" of the track.
1081 double mybot = _y_position + current_height();
1083 return ((_y_position <= bot && _y_position >= top) ||
1084 ((mybot <= bot) && (top < mybot)) ||
1085 (mybot >= bot && _y_position < top));
1089 TimeAxisView::set_parent (TimeAxisView& p)
1095 TimeAxisView::reset_height ()
1097 set_height (height);
1099 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1100 (*i)->set_height ((*i)->height);
1105 TimeAxisView::compute_heights ()
1107 // TODO this function should be re-evaluated when font-scaling changes (!)
1108 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1109 Gtk::Table one_row_table (1, 8);
1110 ArdourButton* test_button = manage (new ArdourButton);
1111 const int border_width = 2;
1112 const int frame_height = 2;
1113 extra_height = (2 * border_width) + frame_height;
1115 window.add (one_row_table);
1116 test_button->set_name ("mute button");
1117 test_button->set_text (_("M"));
1119 one_row_table.set_border_width (border_width);
1120 one_row_table.set_row_spacings (2);
1121 one_row_table.set_col_spacings (2);
1123 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1124 one_row_table.show_all ();
1126 Gtk::Requisition req(one_row_table.size_request ());
1127 button_height = req.height;
1131 TimeAxisView::color_handler ()
1133 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1137 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1139 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1140 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1142 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1143 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1145 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1146 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1149 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1151 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1152 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1154 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1155 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1157 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1158 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1162 /** @return Pair: TimeAxisView, layer index.
1163 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1164 * does. @param y is an offset from the top of the trackview area.
1166 * If the covering object is a child axis, then the child is returned.
1167 * TimeAxisView is 0 otherwise.
1169 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1170 * and is in stacked or expanded * region display mode, otherwise 0.
1172 std::pair<TimeAxisView*, double>
1173 TimeAxisView::covers_y_position (double y) const
1176 return std::make_pair ((TimeAxisView *) 0, 0);
1179 if (_y_position <= y && y < (_y_position + height)) {
1181 /* work out the layer index if appropriate */
1183 switch (layer_display ()) {
1189 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1190 /* clamp to max layers to be on the safe side; sometimes the above calculation
1191 returns a too-high value */
1192 if (l >= view()->layers ()) {
1193 l = view()->layers() - 1;
1199 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1201 if (l >= (view()->layers() - 0.5)) {
1202 l = view()->layers() - 0.5;
1208 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1211 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1213 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1219 return std::make_pair ((TimeAxisView *) 0, 0);
1223 TimeAxisView::covered_by_y_range (double y0, double y1) const
1229 /* if either the top or bottom of the axisview is in the vertical
1230 * range, we cover it.
1233 if ((y0 < _y_position && y1 < _y_position) ||
1234 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1238 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1239 if ((*i)->covered_by_y_range (y0, y1)) {
1248 TimeAxisView::preset_height (Height h)
1252 return (button_height * 2) + extra_height + 260;
1254 return (button_height * 2) + extra_height + 160;
1256 return (button_height * 2) + extra_height + 60;
1258 return (button_height * 2) + extra_height + 10;
1260 return button_height + extra_height;
1267 /** @return Child time axis views that are not hidden */
1268 TimeAxisView::Children
1269 TimeAxisView::get_child_list ()
1273 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1274 if (!(*i)->hidden()) {
1283 TimeAxisView::build_size_menu ()
1285 if (_size_menu && _size_menu->gobj ()) {
1291 using namespace Menu_Helpers;
1293 _size_menu = new Menu;
1294 _size_menu->set_name ("ArdourContextMenu");
1295 MenuList& items = _size_menu->items();
1297 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1298 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1299 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1300 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1301 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1305 TimeAxisView::reset_visual_state ()
1307 /* this method is not required to trigger a global redraw */
1309 string str = gui_property ("height");
1312 set_height (atoi (str));
1314 set_height (preset_height (HeightNormal));
1319 TrackViewList::filter_to_unique_playlists ()
1321 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1324 for (iterator i = begin(); i != end(); ++i) {
1325 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1327 /* not a route: include it anyway */
1330 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1332 if (playlists.insert (t->playlist()).second) {
1333 /* playlist not seen yet */
1337 /* not a track: include it anyway */