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.add (top_hbox);
161 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
162 Gdk::BUTTON_RELEASE_MASK|
163 Gdk::POINTER_MOTION_MASK|
164 Gdk::ENTER_NOTIFY_MASK|
165 Gdk::LEAVE_NOTIFY_MASK|
167 controls_ebox.set_flags (CAN_FOCUS);
169 /* note that this handler connects *before* the default handler */
170 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
171 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
172 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
173 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
174 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
175 controls_ebox.show ();
177 time_axis_frame.set_shadow_type (Gtk::SHADOW_OUT);
178 time_axis_frame.add(controls_ebox);
179 time_axis_frame.show();
181 HSeparator* separator = manage (new HSeparator());
182 separator->set_name("TrackSeparator");
183 separator->set_size_request(-1, 1);
186 time_axis_vbox.pack_start (time_axis_frame, true, true);
187 time_axis_vbox.pack_end (*separator, false, false);
188 time_axis_vbox.show();
190 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
192 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
195 TimeAxisView::~TimeAxisView()
197 in_destructor = true;
199 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
203 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
205 delete (*i)->start_trim;
206 delete (*i)->end_trim;
210 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
212 delete (*i)->start_trim;
213 delete (*i)->end_trim;
216 delete selection_group;
219 delete _canvas_display;
229 TimeAxisView::hide ()
235 _canvas_display->hide ();
237 if (control_parent) {
238 control_parent->remove (time_axis_frame);
245 /* now hide children */
247 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
251 /* if its hidden, it cannot be selected */
252 _editor.get_selection().remove (this);
253 /* and neither can its regions */
254 _editor.get_selection().remove_regions (this);
259 /** Display this TimeAxisView as the nth component of the parent box, at y.
261 * @param y y position.
262 * @param nth index for this TimeAxisView, increased if this view has children.
263 * @param parent parent component.
264 * @return height of this TimeAxisView.
267 TimeAxisView::show_at (double y, int& nth, VBox *parent)
269 if (control_parent) {
270 control_parent->reorder_child (time_axis_vbox, nth);
272 control_parent = parent;
273 parent->pack_start (time_axis_vbox, false, false);
274 parent->reorder_child (time_axis_vbox, nth);
279 if (_y_position != y) {
280 _canvas_display->set_y_position (y);
285 _canvas_display->raise_to_top ();
286 _canvas_display->show ();
290 _effective_height = current_height ();
292 /* now show relevant children */
294 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
295 if ((*i)->marked_for_display()) {
297 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
303 return _effective_height;
307 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
309 switch (ev->direction) {
311 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
312 /* See Editor::_stepping_axis_view for notes on this hack */
313 Editor& e = dynamic_cast<Editor&> (_editor);
314 if (!e.stepping_axis_view ()) {
315 e.set_stepping_axis_view (this);
317 e.stepping_axis_view()->step_height (false);
322 case GDK_SCROLL_DOWN:
323 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
324 /* See Editor::_stepping_axis_view for notes on this hack */
325 Editor& e = dynamic_cast<Editor&> (_editor);
326 if (!e.stepping_axis_view ()) {
327 e.set_stepping_axis_view (this);
329 e.stepping_axis_view()->step_height (true);
335 /* no handling for left/right, yet */
339 /* Just forward to the normal canvas scroll method. The coordinate
340 systems are different but since the canvas is always larger than the
341 track headers, and aligned with the trackview area, this will work.
343 In the not too distant future this layout is going away anyway and
344 headers will be on the canvas.
346 return _editor.canvas_scroll_event (ev, false);
350 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
352 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
353 /* see if it is inside the name label */
354 if (name_label.is_ancestor (controls_ebox)) {
357 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
358 Gtk::Allocation a = name_label.get_allocation ();
359 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
361 _ebox_release_can_act = false;
368 _ebox_release_can_act = true;
370 if (maybe_set_cursor (event->y) > 0) {
371 _resize_drag_start = event->y_root;
378 TimeAxisView::idle_resize (uint32_t h)
385 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
387 if (_resize_drag_start >= 0) {
389 /* (ab)use the DragManager to do autoscrolling - basically we
390 * are pretending that the drag is taking place over the canvas
391 * (which perhaps in the glorious future, when track headers
392 * and the canvas are unified, will actually be true.)
395 _editor.maybe_autoscroll (false, true, true);
397 /* now schedule the actual TAV resize */
398 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
399 _editor.add_to_idle_resize (this, delta);
400 _resize_drag_start = ev->y_root;
402 /* not dragging but ... */
403 maybe_set_cursor (ev->y);
406 gdk_event_request_motions(ev);
411 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
413 if (_have_preresize_cursor) {
414 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
415 _have_preresize_cursor = false;
421 TimeAxisView::maybe_set_cursor (int y)
423 /* XXX no Gtkmm Gdk::Window::get_cursor() */
424 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
426 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
428 /* y-coordinate in lower 25% */
430 if (!_have_preresize_cursor) {
431 _preresize_cursor = gdk_window_get_cursor (win->gobj());
432 _have_preresize_cursor = true;
433 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
438 } else if (_have_preresize_cursor) {
439 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
440 _have_preresize_cursor = false;
449 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
451 if (_resize_drag_start >= 0) {
452 if (_have_preresize_cursor) {
453 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
454 _preresize_cursor = 0;
455 _have_preresize_cursor = false;
457 _editor.stop_canvas_autoscroll ();
458 _resize_drag_start = -1;
461 if (!_ebox_release_can_act) {
465 switch (ev->button) {
467 selection_click (ev);
471 popup_display_menu (ev->time);
479 TimeAxisView::selection_click (GdkEventButton* ev)
481 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
482 _editor.set_selected_track (*this, op, false);
486 /** Steps through the defined heights for this TrackView.
487 * @param coarser true if stepping should decrease in size, otherwise false.
490 TimeAxisView::step_height (bool coarser)
492 static const uint32_t step = 25;
496 if (height <= preset_height (HeightSmall)) {
498 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
499 set_height_enum (HeightSmall);
501 set_height (height - step);
506 if (height <= preset_height(HeightSmall)) {
507 set_height_enum (HeightNormal);
509 set_height (height + step);
516 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
518 if (apply_to_selection) {
519 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
521 set_height (preset_height (h));
526 TimeAxisView::set_height (uint32_t h)
528 if (h < preset_height (HeightSmall)) {
529 h = preset_height (HeightSmall);
532 time_axis_vbox.property_height_request () = h;
536 snprintf (buf, sizeof (buf), "%u", height);
537 set_gui_property ("height", buf);
539 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
543 if (selection_group->visible ()) {
544 /* resize the selection rect */
545 show_selection (_editor.get_selection().time);
548 _editor.override_visible_track_count ();
552 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
554 /* steal escape, tabs from GTK */
556 switch (ev->keyval) {
558 case GDK_ISO_Left_Tab:
566 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
568 TrackViewList::iterator i;
570 switch (ev->keyval) {
572 end_name_edit (RESPONSE_CANCEL);
575 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
576 * generates a different ev->keyval, rather than setting
579 case GDK_ISO_Left_Tab:
580 end_name_edit (RESPONSE_APPLY);
584 end_name_edit (RESPONSE_ACCEPT);
594 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
596 end_name_edit (RESPONSE_OK);
601 TimeAxisView::begin_name_edit ()
607 if (can_edit_name()) {
609 name_entry = manage (new Gtkmm2ext::FocusEntry);
611 name_entry->set_width_chars(8); // min width, entry expands
613 name_entry->set_name ("EditorTrackNameDisplay");
614 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
615 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
616 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
617 name_entry->set_text (name_label.get_text());
618 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
620 if (name_label.is_ancestor (name_hbox)) {
621 name_hbox.remove (name_label);
624 name_hbox.pack_end (*name_entry, true, true);
627 name_entry->select_region (0, -1);
628 name_entry->set_state (STATE_SELECTED);
629 name_entry->grab_focus ();
630 name_entry->start_editing (0);
635 TimeAxisView::end_name_edit (int response)
641 bool edit_next = false;
642 bool edit_prev = false;
645 case RESPONSE_CANCEL:
648 name_entry_changed ();
650 case RESPONSE_ACCEPT:
651 name_entry_changed ();
654 name_entry_changed ();
658 /* this will delete the name_entry. but it will also drop focus, which
659 * will cause another callback to this function, so set name_entry = 0
660 * first to ensure we don't double-remove etc. etc.
663 Gtk::Entry* tmp = name_entry;
665 name_hbox.remove (*tmp);
667 /* put the name label back */
669 name_hbox.pack_end (name_label);
674 TrackViewList const & allviews = _editor.get_track_views ();
675 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
677 if (i != allviews.end()) {
680 if (++i == allviews.end()) {
684 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
686 if (rtav && rtav->route()->record_enabled()) {
690 if (!(*i)->hidden()) {
697 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
698 _editor.ensure_time_axis_view_is_visible (**i, false);
699 (*i)->begin_name_edit ();
702 } else if (edit_prev) {
704 TrackViewList const & allviews = _editor.get_track_views ();
705 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
707 if (i != allviews.begin()) {
709 if (i == allviews.begin()) {
715 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
717 if (rtav && rtav->route()->record_enabled()) {
721 if (!(*i)->hidden()) {
728 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
729 _editor.ensure_time_axis_view_is_visible (**i, false);
730 (*i)->begin_name_edit ();
736 TimeAxisView::name_entry_changed ()
741 TimeAxisView::can_edit_name () const
747 TimeAxisView::conditionally_add_to_selection ()
749 Selection& s (_editor.get_selection ());
751 if (!s.selected (this)) {
752 _editor.set_selected_track (*this, Selection::Set);
757 TimeAxisView::popup_display_menu (guint32 when)
759 conditionally_add_to_selection ();
761 build_display_menu ();
762 display_menu->popup (1, when);
766 TimeAxisView::set_selected (bool yn)
768 if (yn == _selected) {
772 Selectable::set_selected (yn);
775 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
776 time_axis_frame.set_name ("MixerStripSelectedFrame");
777 controls_ebox.set_name (controls_base_selected_name);
778 controls_vbox.set_name (controls_base_selected_name);
779 time_axis_vbox.set_name (controls_base_selected_name);
781 time_axis_frame.set_shadow_type (Gtk::SHADOW_OUT);
782 time_axis_frame.set_name (controls_base_unselected_name);
783 controls_ebox.set_name (controls_base_unselected_name);
784 controls_vbox.set_name (controls_base_unselected_name);
785 time_axis_vbox.set_name (controls_base_unselected_name);
789 /* children will be set for the yn=true case. but when deselecting
790 the editor only has a list of top-level trackviews, so we
791 have to do this here.
794 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
795 (*i)->set_selected (false);
799 time_axis_frame.show();
804 TimeAxisView::build_display_menu ()
806 using namespace Menu_Helpers;
810 display_menu = new Menu;
811 display_menu->set_name ("ArdourContextMenu");
813 // Just let implementing classes define what goes into the manu
817 TimeAxisView::set_samples_per_pixel (double fpp)
819 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
820 (*i)->set_samples_per_pixel (fpp);
825 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
827 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
828 (*i)->show_timestretch (start, end, layers, layer);
833 TimeAxisView::hide_timestretch ()
835 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
836 (*i)->hide_timestretch ();
841 TimeAxisView::show_selection (TimeSelection& ts)
846 SelectionRect *rect; time_axis_frame.show();
849 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
850 (*i)->show_selection (ts);
853 if (selection_group->visible ()) {
854 while (!used_selection_rects.empty()) {
855 free_selection_rects.push_front (used_selection_rects.front());
856 used_selection_rects.pop_front();
857 free_selection_rects.front()->rect->hide();
858 free_selection_rects.front()->start_trim->hide();
859 free_selection_rects.front()->end_trim->hide();
861 selection_group->hide();
864 selection_group->show();
865 selection_group->raise_to_top();
867 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
868 framepos_t start, end;
873 cnt = end - start + 1;
875 rect = get_selection_rect ((*i).id);
877 x1 = _editor.sample_to_pixel (start);
878 x2 = _editor.sample_to_pixel (start + cnt - 1);
879 y2 = current_height() - 1;
881 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
883 // trim boxes are at the top for selections
886 rect->start_trim->set (ArdourCanvas::Rect (x1, 1, x1 + trim_handle_size, y2));
887 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
889 rect->start_trim->show();
890 rect->end_trim->show();
892 rect->start_trim->hide();
893 rect->end_trim->hide();
897 used_selection_rects.push_back (rect);
902 TimeAxisView::reshow_selection (TimeSelection& ts)
906 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
907 (*i)->show_selection (ts);
912 TimeAxisView::hide_selection ()
914 if (selection_group->visible ()) {
915 while (!used_selection_rects.empty()) {
916 free_selection_rects.push_front (used_selection_rects.front());
917 used_selection_rects.pop_front();
918 free_selection_rects.front()->rect->hide();
919 free_selection_rects.front()->start_trim->hide();
920 free_selection_rects.front()->end_trim->hide();
922 selection_group->hide();
925 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
926 (*i)->hide_selection ();
931 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
933 /* find the selection rect this is for. we have the item corresponding to one
937 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
938 if ((*i)->start_trim == item || (*i)->end_trim == item) {
940 /* make one trim handle be "above" the other so that if they overlap,
941 the top one is the one last used.
944 (*i)->rect->raise_to_top ();
945 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
946 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
954 TimeAxisView::get_selection_rect (uint32_t id)
958 /* check to see if we already have a visible rect for this particular selection ID */
960 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
961 if ((*i)->id == id) {
966 /* ditto for the free rect list */
968 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
969 if ((*i)->id == id) {
970 SelectionRect* ret = (*i);
971 free_selection_rects.erase (i);
976 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
978 if (free_selection_rects.empty()) {
980 rect = new SelectionRect;
982 rect->rect = new ArdourCanvas::Rectangle (selection_group);
983 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
984 rect->rect->set_outline (false);
985 rect->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
987 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
988 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
989 rect->start_trim->set_outline (false);
990 rect->start_trim->set_fill (false);
992 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
993 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
994 rect->end_trim->set_outline (false);
995 rect->end_trim->set_fill (false);
997 free_selection_rects.push_front (rect);
999 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1000 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1001 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1004 rect = free_selection_rects.front();
1006 free_selection_rects.pop_front();
1010 struct null_deleter { void operator()(void const *) const {} };
1013 TimeAxisView::is_child (TimeAxisView* tav)
1015 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1019 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1021 children.push_back (child);
1025 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1027 Children::iterator i;
1029 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1034 /** Get selectable things within a given range.
1035 * @param start Start time in session frames.
1036 * @param end End time in session frames.
1037 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1038 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1039 * @param result Filled in with selectable things.
1042 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1048 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1054 TimeAxisView::add_ghost (RegionView* rv)
1056 GhostRegion* gr = rv->add_ghost (*this);
1059 ghosts.push_back(gr);
1064 TimeAxisView::remove_ghost (RegionView* rv)
1066 rv->remove_ghost_in (*this);
1070 TimeAxisView::erase_ghost (GhostRegion* gr)
1072 if (in_destructor) {
1076 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1085 TimeAxisView::touched (double top, double bot)
1087 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1088 y_position is the "origin" or "top" of the track.
1091 double mybot = _y_position + current_height();
1093 return ((_y_position <= bot && _y_position >= top) ||
1094 ((mybot <= bot) && (top < mybot)) ||
1095 (mybot >= bot && _y_position < top));
1099 TimeAxisView::set_parent (TimeAxisView& p)
1105 TimeAxisView::reset_height ()
1107 set_height (height);
1109 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1110 (*i)->set_height ((*i)->height);
1115 TimeAxisView::compute_heights ()
1117 // TODO this function should be re-evaluated when font-scaling changes (!)
1118 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1119 Gtk::Table one_row_table (1, 8);
1120 ArdourButton* test_button = manage (new ArdourButton);
1121 const int border_width = 2;
1122 const int frame_height = 2;
1123 extra_height = (2 * border_width) + frame_height;
1125 window.add (one_row_table);
1126 test_button->set_name ("mute button");
1127 test_button->set_text (_("M"));
1129 one_row_table.set_border_width (border_width);
1130 one_row_table.set_row_spacings (2);
1131 one_row_table.set_col_spacings (2);
1133 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1134 one_row_table.show_all ();
1136 Gtk::Requisition req(one_row_table.size_request ());
1137 button_height = req.height;
1141 TimeAxisView::color_handler ()
1143 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1147 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1149 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1150 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1152 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1153 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1155 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1156 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1159 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1161 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1162 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1164 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1165 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1167 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1168 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1172 /** @return Pair: TimeAxisView, layer index.
1173 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1174 * does. @param y is an offset from the top of the trackview area.
1176 * If the covering object is a child axis, then the child is returned.
1177 * TimeAxisView is 0 otherwise.
1179 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1180 * and is in stacked or expanded * region display mode, otherwise 0.
1182 std::pair<TimeAxisView*, double>
1183 TimeAxisView::covers_y_position (double y) const
1186 return std::make_pair ((TimeAxisView *) 0, 0);
1189 if (_y_position <= y && y < (_y_position + height)) {
1191 /* work out the layer index if appropriate */
1193 switch (layer_display ()) {
1199 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1200 /* clamp to max layers to be on the safe side; sometimes the above calculation
1201 returns a too-high value */
1202 if (l >= view()->layers ()) {
1203 l = view()->layers() - 1;
1209 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1211 if (l >= (view()->layers() - 0.5)) {
1212 l = view()->layers() - 0.5;
1218 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1221 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1223 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1229 return std::make_pair ((TimeAxisView *) 0, 0);
1233 TimeAxisView::covered_by_y_range (double y0, double y1) const
1239 /* if either the top or bottom of the axisview is in the vertical
1240 * range, we cover it.
1243 if ((y0 < _y_position && y1 < _y_position) ||
1244 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1248 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1249 if ((*i)->covered_by_y_range (y0, y1)) {
1258 TimeAxisView::preset_height (Height h)
1262 return (button_height * 2) + extra_height + 260;
1264 return (button_height * 2) + extra_height + 160;
1266 return (button_height * 2) + extra_height + 60;
1268 return (button_height * 2) + extra_height + 10;
1270 return button_height + extra_height;
1277 /** @return Child time axis views that are not hidden */
1278 TimeAxisView::Children
1279 TimeAxisView::get_child_list ()
1283 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1284 if (!(*i)->hidden()) {
1293 TimeAxisView::build_size_menu ()
1295 if (_size_menu && _size_menu->gobj ()) {
1301 using namespace Menu_Helpers;
1303 _size_menu = new Menu;
1304 _size_menu->set_name ("ArdourContextMenu");
1305 MenuList& items = _size_menu->items();
1307 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1308 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1309 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1310 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1311 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1315 TimeAxisView::reset_visual_state ()
1317 /* this method is not required to trigger a global redraw */
1319 string str = gui_property ("height");
1322 set_height (atoi (str));
1324 set_height (preset_height (HeightNormal));
1329 TrackViewList::filter_to_unique_playlists ()
1331 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1334 for (iterator i = begin(); i != end(); ++i) {
1335 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1337 /* not a route: include it anyway */
1340 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1342 if (playlists.insert (t->playlist()).second) {
1343 /* playlist not seen yet */
1347 /* not a track: include it anyway */