2 Copyright (C) 2000 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include "pbd/error.h"
28 #include "pbd/convert.h"
29 #include "pbd/stacktrace.h"
31 #include <gtkmm2ext/doi.h>
32 #include <gtkmm2ext/utils.h>
33 #include <gtkmm2ext/selector.h>
35 #include "canvas/canvas.h"
36 #include "canvas/rectangle.h"
37 #include "canvas/debug.h"
39 #include "ardour/profile.h"
41 #include "ardour_ui.h"
42 #include "ardour_dialog.h"
43 #include "global_signals.h"
44 #include "gui_thread.h"
45 #include "public_editor.h"
46 #include "time_axis_view.h"
47 #include "region_view.h"
48 #include "ghostregion.h"
49 #include "selection.h"
51 #include "rgb_macros.h"
53 #include "streamview.h"
54 #include "editor_drag.h"
62 using namespace ARDOUR;
63 using namespace ARDOUR_UI_UTILS;
65 using namespace Editing;
66 using namespace ArdourCanvas;
67 using Gtkmm2ext::Keyboard;
69 const double trim_handle_size = 6.0; /* pixels */
70 uint32_t TimeAxisView::button_height = 0;
71 uint32_t TimeAxisView::extra_height = 0;
72 int const TimeAxisView::_max_order = 512;
73 unsigned int TimeAxisView::name_width_px = 100; // TODO adjust with font-scaling on style-change
74 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
75 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
77 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
79 , controls_table (3, 3)
80 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
81 , _name_editing (false)
88 , in_destructor (false)
96 , _effective_height (0)
97 , _resize_drag_start (-1)
98 , _preresize_cursor (0)
99 , _have_preresize_cursor (false)
100 , _ebox_release_can_act (true)
102 if (!controls_meters_size_group) {
103 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
105 if (extra_height == 0) {
109 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group (), ArdourCanvas::Duple (0.0, 0.0));
110 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
111 _canvas_display->hide(); // reveal as needed
113 selection_group = new ArdourCanvas::Container (_canvas_display);
114 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
115 selection_group->set_data (X_("timeselection"), (void *) 1);
116 selection_group->hide();
118 _ghost_group = new ArdourCanvas::Container (_canvas_display);
119 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
120 _ghost_group->lower_to_bottom();
121 _ghost_group->show();
123 name_label.set_name ("TrackLabel");
124 name_label.set_alignment (0.0, 0.5);
125 name_label.set_width_chars (12);
126 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
128 Gtk::Entry* an_entry = new Gtk::Entry;
129 Gtk::Requisition req;
130 an_entry->size_request (req);
131 name_label.set_size_request (-1, req.height);
132 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
135 name_hbox.pack_end (name_label, true, true);
137 // set min. track-header width if fader is not visible
138 name_hbox.set_size_request(name_width_px, -1);
143 controls_table.set_row_spacings (2);
144 controls_table.set_col_spacings (2);
145 controls_table.set_border_width (2);
147 if (ARDOUR::Profile->get_mixbus() ) {
148 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
150 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
152 controls_table.show_all ();
153 controls_table.set_no_show_all ();
155 controls_vbox.pack_start (controls_table, false, false);
156 controls_vbox.show ();
158 top_hbox.pack_start (controls_vbox, true, true);
161 controls_ebox.add (top_hbox);
162 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
163 Gdk::BUTTON_RELEASE_MASK|
164 Gdk::POINTER_MOTION_MASK|
165 Gdk::ENTER_NOTIFY_MASK|
166 Gdk::LEAVE_NOTIFY_MASK|
168 controls_ebox.set_flags (CAN_FOCUS);
170 /* note that this handler connects *before* the default handler */
171 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
172 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
173 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
174 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
175 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
176 controls_ebox.show ();
178 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
179 time_axis_frame.add(controls_ebox);
180 time_axis_frame.show();
182 HSeparator* separator = manage (new HSeparator());
183 separator->set_name("TrackSeparator");
184 separator->set_size_request(-1, 1);
187 time_axis_vbox.pack_start (*separator, false, false);
188 time_axis_vbox.pack_start (time_axis_frame, true, true);
189 time_axis_vbox.show();
190 time_axis_hbox.pack_start (time_axis_vbox, true, true);
191 time_axis_hbox.show();
193 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
195 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
198 TimeAxisView::~TimeAxisView()
200 in_destructor = true;
202 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
206 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
208 delete (*i)->start_trim;
209 delete (*i)->end_trim;
213 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
215 delete (*i)->start_trim;
216 delete (*i)->end_trim;
219 delete selection_group;
222 delete _canvas_display;
232 TimeAxisView::hide ()
238 _canvas_display->hide ();
240 if (control_parent) {
241 control_parent->remove (time_axis_hbox);
248 /* now hide children */
250 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
254 /* if its hidden, it cannot be selected */
255 _editor.get_selection().remove (this);
256 /* and neither can its regions */
257 _editor.get_selection().remove_regions (this);
262 /** Display this TimeAxisView as the nth component of the parent box, at y.
264 * @param y y position.
265 * @param nth index for this TimeAxisView, increased if this view has children.
266 * @param parent parent component.
267 * @return height of this TimeAxisView.
270 TimeAxisView::show_at (double y, int& nth, VBox *parent)
272 if (control_parent) {
273 control_parent->reorder_child (time_axis_hbox, nth);
275 control_parent = parent;
276 parent->pack_start (time_axis_hbox, false, false);
277 parent->reorder_child (time_axis_hbox, nth);
282 if (_y_position != y) {
283 // XXX +1 is a quick hack to align the track-header with the canvas
284 // with the separator line at the top.
285 _canvas_display->set_y_position (y + 1);
290 _canvas_display->raise_to_top ();
291 _canvas_display->show ();
295 _effective_height = current_height ();
297 /* now show relevant children */
299 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
300 if ((*i)->marked_for_display()) {
302 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
308 return _effective_height;
312 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
314 switch (ev->direction) {
316 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
317 /* See Editor::_stepping_axis_view for notes on this hack */
318 Editor& e = dynamic_cast<Editor&> (_editor);
319 if (!e.stepping_axis_view ()) {
320 e.set_stepping_axis_view (this);
322 e.stepping_axis_view()->step_height (false);
327 case GDK_SCROLL_DOWN:
328 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
329 /* See Editor::_stepping_axis_view for notes on this hack */
330 Editor& e = dynamic_cast<Editor&> (_editor);
331 if (!e.stepping_axis_view ()) {
332 e.set_stepping_axis_view (this);
334 e.stepping_axis_view()->step_height (true);
340 /* no handling for left/right, yet */
344 /* Just forward to the normal canvas scroll method. The coordinate
345 systems are different but since the canvas is always larger than the
346 track headers, and aligned with the trackview area, this will work.
348 In the not too distant future this layout is going away anyway and
349 headers will be on the canvas.
351 return _editor.canvas_scroll_event (ev, false);
355 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
357 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
358 /* see if it is inside the name label */
359 if (name_label.is_ancestor (controls_ebox)) {
362 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
363 Gtk::Allocation a = name_label.get_allocation ();
364 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
366 _ebox_release_can_act = false;
373 _ebox_release_can_act = true;
375 if (maybe_set_cursor (event->y) > 0) {
376 _resize_drag_start = event->y_root;
383 TimeAxisView::idle_resize (uint32_t h)
390 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
392 if (_resize_drag_start >= 0) {
394 /* (ab)use the DragManager to do autoscrolling - basically we
395 * are pretending that the drag is taking place over the canvas
396 * (which perhaps in the glorious future, when track headers
397 * and the canvas are unified, will actually be true.)
400 _editor.maybe_autoscroll (false, true, true);
402 /* now schedule the actual TAV resize */
403 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
404 _editor.add_to_idle_resize (this, delta);
405 _resize_drag_start = ev->y_root;
407 /* not dragging but ... */
408 maybe_set_cursor (ev->y);
411 gdk_event_request_motions(ev);
416 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
418 if (_have_preresize_cursor) {
419 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
420 _have_preresize_cursor = false;
426 TimeAxisView::maybe_set_cursor (int y)
428 /* XXX no Gtkmm Gdk::Window::get_cursor() */
429 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
431 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
433 /* y-coordinate in lower 25% */
435 if (!_have_preresize_cursor) {
436 _preresize_cursor = gdk_window_get_cursor (win->gobj());
437 _have_preresize_cursor = true;
438 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
443 } else if (_have_preresize_cursor) {
444 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
445 _have_preresize_cursor = false;
454 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
456 if (_resize_drag_start >= 0) {
457 if (_have_preresize_cursor) {
458 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
459 _preresize_cursor = 0;
460 _have_preresize_cursor = false;
462 _editor.stop_canvas_autoscroll ();
463 _resize_drag_start = -1;
466 if (!_ebox_release_can_act) {
470 switch (ev->button) {
472 selection_click (ev);
476 popup_display_menu (ev->time);
484 TimeAxisView::selection_click (GdkEventButton* ev)
486 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
487 _editor.set_selected_track (*this, op, false);
491 /** Steps through the defined heights for this TrackView.
492 * @param coarser true if stepping should decrease in size, otherwise false.
495 TimeAxisView::step_height (bool coarser)
497 static const uint32_t step = 25;
501 if (height <= preset_height (HeightSmall)) {
503 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
504 set_height_enum (HeightSmall);
506 set_height (height - step);
511 if (height <= preset_height(HeightSmall)) {
512 set_height_enum (HeightNormal);
514 set_height (height + step);
521 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
523 if (apply_to_selection) {
524 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
526 set_height (preset_height (h));
531 TimeAxisView::set_height (uint32_t h)
533 if (h < preset_height (HeightSmall)) {
534 h = preset_height (HeightSmall);
537 time_axis_hbox.property_height_request () = h;
541 snprintf (buf, sizeof (buf), "%u", height);
542 set_gui_property ("height", buf);
544 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
548 if (selection_group->visible ()) {
549 /* resize the selection rect */
550 show_selection (_editor.get_selection().time);
553 _editor.override_visible_track_count ();
557 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
559 /* steal escape, tabs from GTK */
561 switch (ev->keyval) {
563 case GDK_ISO_Left_Tab:
571 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
573 TrackViewList::iterator i;
575 switch (ev->keyval) {
577 end_name_edit (RESPONSE_CANCEL);
580 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
581 * generates a different ev->keyval, rather than setting
584 case GDK_ISO_Left_Tab:
585 end_name_edit (RESPONSE_APPLY);
589 end_name_edit (RESPONSE_ACCEPT);
599 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
601 end_name_edit (RESPONSE_OK);
606 TimeAxisView::begin_name_edit ()
612 if (can_edit_name()) {
614 name_entry = manage (new Gtkmm2ext::FocusEntry);
616 name_entry->set_width_chars(8); // min width, entry expands
618 name_entry->set_name ("EditorTrackNameDisplay");
619 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
620 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
621 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
622 name_entry->set_text (name_label.get_text());
623 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
625 if (name_label.is_ancestor (name_hbox)) {
626 name_hbox.remove (name_label);
629 name_hbox.pack_end (*name_entry, true, true);
632 name_entry->select_region (0, -1);
633 name_entry->set_state (STATE_SELECTED);
634 name_entry->grab_focus ();
635 name_entry->start_editing (0);
640 TimeAxisView::end_name_edit (int response)
646 bool edit_next = false;
647 bool edit_prev = false;
650 case RESPONSE_CANCEL:
653 name_entry_changed ();
655 case RESPONSE_ACCEPT:
656 name_entry_changed ();
659 name_entry_changed ();
663 /* this will delete the name_entry. but it will also drop focus, which
664 * will cause another callback to this function, so set name_entry = 0
665 * first to ensure we don't double-remove etc. etc.
668 Gtk::Entry* tmp = name_entry;
670 name_hbox.remove (*tmp);
672 /* put the name label back */
674 name_hbox.pack_end (name_label);
679 TrackViewList const & allviews = _editor.get_track_views ();
680 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
682 if (i != allviews.end()) {
685 if (++i == allviews.end()) {
689 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
691 if (rtav && rtav->route()->record_enabled()) {
695 if (!(*i)->hidden()) {
702 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
703 _editor.ensure_time_axis_view_is_visible (**i, false);
704 (*i)->begin_name_edit ();
707 } else if (edit_prev) {
709 TrackViewList const & allviews = _editor.get_track_views ();
710 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
712 if (i != allviews.begin()) {
714 if (i == allviews.begin()) {
720 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
722 if (rtav && rtav->route()->record_enabled()) {
726 if (!(*i)->hidden()) {
733 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
734 _editor.ensure_time_axis_view_is_visible (**i, false);
735 (*i)->begin_name_edit ();
741 TimeAxisView::name_entry_changed ()
746 TimeAxisView::can_edit_name () const
752 TimeAxisView::conditionally_add_to_selection ()
754 Selection& s (_editor.get_selection ());
756 if (!s.selected (this)) {
757 _editor.set_selected_track (*this, Selection::Set);
762 TimeAxisView::popup_display_menu (guint32 when)
764 conditionally_add_to_selection ();
766 build_display_menu ();
767 display_menu->popup (1, when);
771 TimeAxisView::set_selected (bool yn)
773 if (yn == _selected) {
777 Selectable::set_selected (yn);
780 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
781 time_axis_frame.set_name ("MixerStripSelectedFrame");
782 controls_ebox.set_name (controls_base_selected_name);
783 controls_vbox.set_name (controls_base_selected_name);
784 time_axis_vbox.set_name (controls_base_selected_name);
786 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
787 time_axis_frame.set_name (controls_base_unselected_name);
788 controls_ebox.set_name (controls_base_unselected_name);
789 controls_vbox.set_name (controls_base_unselected_name);
790 time_axis_vbox.set_name (controls_base_unselected_name);
794 /* children will be set for the yn=true case. but when deselecting
795 the editor only has a list of top-level trackviews, so we
796 have to do this here.
799 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
800 (*i)->set_selected (false);
804 time_axis_frame.show();
809 TimeAxisView::build_display_menu ()
811 using namespace Menu_Helpers;
815 display_menu = new Menu;
816 display_menu->set_name ("ArdourContextMenu");
818 // Just let implementing classes define what goes into the manu
822 TimeAxisView::set_samples_per_pixel (double fpp)
824 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
825 (*i)->set_samples_per_pixel (fpp);
830 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
832 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
833 (*i)->show_timestretch (start, end, layers, layer);
838 TimeAxisView::hide_timestretch ()
840 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
841 (*i)->hide_timestretch ();
846 TimeAxisView::show_selection (TimeSelection& ts)
851 SelectionRect *rect; time_axis_frame.show();
854 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
855 (*i)->show_selection (ts);
858 if (selection_group->visible ()) {
859 while (!used_selection_rects.empty()) {
860 free_selection_rects.push_front (used_selection_rects.front());
861 used_selection_rects.pop_front();
862 free_selection_rects.front()->rect->hide();
863 free_selection_rects.front()->start_trim->hide();
864 free_selection_rects.front()->end_trim->hide();
866 selection_group->hide();
869 selection_group->show();
870 selection_group->raise_to_top();
872 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
873 framepos_t start, end;
878 cnt = end - start + 1;
880 rect = get_selection_rect ((*i).id);
882 x1 = _editor.sample_to_pixel (start);
883 x2 = _editor.sample_to_pixel (start + cnt - 1);
884 y2 = current_height() - 1;
886 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
888 // trim boxes are at the top for selections
891 rect->start_trim->set (ArdourCanvas::Rect (x1, 1, x1 + trim_handle_size, y2));
892 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
894 rect->start_trim->show();
895 rect->end_trim->show();
897 rect->start_trim->hide();
898 rect->end_trim->hide();
902 used_selection_rects.push_back (rect);
907 TimeAxisView::reshow_selection (TimeSelection& ts)
911 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
912 (*i)->show_selection (ts);
917 TimeAxisView::hide_selection ()
919 if (selection_group->visible ()) {
920 while (!used_selection_rects.empty()) {
921 free_selection_rects.push_front (used_selection_rects.front());
922 used_selection_rects.pop_front();
923 free_selection_rects.front()->rect->hide();
924 free_selection_rects.front()->start_trim->hide();
925 free_selection_rects.front()->end_trim->hide();
927 selection_group->hide();
930 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
931 (*i)->hide_selection ();
936 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
938 /* find the selection rect this is for. we have the item corresponding to one
942 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
943 if ((*i)->start_trim == item || (*i)->end_trim == item) {
945 /* make one trim handle be "above" the other so that if they overlap,
946 the top one is the one last used.
949 (*i)->rect->raise_to_top ();
950 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
951 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
959 TimeAxisView::get_selection_rect (uint32_t id)
963 /* check to see if we already have a visible rect for this particular selection ID */
965 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
966 if ((*i)->id == id) {
971 /* ditto for the free rect list */
973 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
974 if ((*i)->id == id) {
975 SelectionRect* ret = (*i);
976 free_selection_rects.erase (i);
981 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
983 if (free_selection_rects.empty()) {
985 rect = new SelectionRect;
987 rect->rect = new ArdourCanvas::Rectangle (selection_group);
988 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
989 rect->rect->set_outline (false);
990 rect->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
992 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
993 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
994 rect->start_trim->set_outline (false);
995 rect->start_trim->set_fill (false);
997 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
998 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
999 rect->end_trim->set_outline (false);
1000 rect->end_trim->set_fill (false);
1002 free_selection_rects.push_front (rect);
1004 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1005 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1006 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1009 rect = free_selection_rects.front();
1011 free_selection_rects.pop_front();
1015 struct null_deleter { void operator()(void const *) const {} };
1018 TimeAxisView::is_child (TimeAxisView* tav)
1020 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1024 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1026 children.push_back (child);
1030 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1032 Children::iterator i;
1034 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1039 /** Get selectable things within a given range.
1040 * @param start Start time in session frames.
1041 * @param end End time in session frames.
1042 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1043 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1044 * @param result Filled in with selectable things.
1047 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1053 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1059 TimeAxisView::add_ghost (RegionView* rv)
1061 GhostRegion* gr = rv->add_ghost (*this);
1064 ghosts.push_back(gr);
1069 TimeAxisView::remove_ghost (RegionView* rv)
1071 rv->remove_ghost_in (*this);
1075 TimeAxisView::erase_ghost (GhostRegion* gr)
1077 if (in_destructor) {
1081 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1090 TimeAxisView::touched (double top, double bot)
1092 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1093 y_position is the "origin" or "top" of the track.
1096 double mybot = _y_position + current_height();
1098 return ((_y_position <= bot && _y_position >= top) ||
1099 ((mybot <= bot) && (top < mybot)) ||
1100 (mybot >= bot && _y_position < top));
1104 TimeAxisView::set_parent (TimeAxisView& p)
1110 TimeAxisView::reset_height ()
1112 set_height (height);
1114 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1115 (*i)->set_height ((*i)->height);
1120 TimeAxisView::compute_heights ()
1122 // TODO this function should be re-evaluated when font-scaling changes (!)
1123 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1124 Gtk::Table one_row_table (1, 1);
1125 ArdourButton* test_button = manage (new ArdourButton);
1126 const int border_width = 2;
1127 const int frame_height = 2;
1128 extra_height = (2 * border_width) + frame_height;
1130 window.add (one_row_table);
1131 test_button->set_name ("mute button");
1132 test_button->set_text (_("M"));
1134 one_row_table.set_border_width (border_width);
1135 one_row_table.set_row_spacings (2);
1136 one_row_table.set_col_spacings (2);
1138 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1139 one_row_table.show_all ();
1141 Gtk::Requisition req(one_row_table.size_request ());
1142 button_height = req.height;
1146 TimeAxisView::color_handler ()
1148 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1152 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1154 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1155 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1157 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1158 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1160 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1161 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1164 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1166 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_SelectionRect());
1167 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1169 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1170 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1172 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1173 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_canvasvar_Selection());
1177 /** @return Pair: TimeAxisView, layer index.
1178 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1179 * does. @param y is an offset from the top of the trackview area.
1181 * If the covering object is a child axis, then the child is returned.
1182 * TimeAxisView is 0 otherwise.
1184 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1185 * and is in stacked or expanded * region display mode, otherwise 0.
1187 std::pair<TimeAxisView*, double>
1188 TimeAxisView::covers_y_position (double y) const
1191 return std::make_pair ((TimeAxisView *) 0, 0);
1194 if (_y_position <= y && y < (_y_position + height)) {
1196 /* work out the layer index if appropriate */
1198 switch (layer_display ()) {
1204 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1205 /* clamp to max layers to be on the safe side; sometimes the above calculation
1206 returns a too-high value */
1207 if (l >= view()->layers ()) {
1208 l = view()->layers() - 1;
1214 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1216 if (l >= (view()->layers() - 0.5)) {
1217 l = view()->layers() - 0.5;
1223 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1226 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1228 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1234 return std::make_pair ((TimeAxisView *) 0, 0);
1238 TimeAxisView::covered_by_y_range (double y0, double y1) const
1244 /* if either the top or bottom of the axisview is in the vertical
1245 * range, we cover it.
1248 if ((y0 < _y_position && y1 < _y_position) ||
1249 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1253 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1254 if ((*i)->covered_by_y_range (y0, y1)) {
1263 TimeAxisView::preset_height (Height h)
1267 return (button_height * 2) + extra_height + 260;
1269 return (button_height * 2) + extra_height + 160;
1271 return (button_height * 2) + extra_height + 60;
1273 return (button_height * 2) + extra_height + 10;
1275 return button_height + extra_height;
1282 /** @return Child time axis views that are not hidden */
1283 TimeAxisView::Children
1284 TimeAxisView::get_child_list ()
1288 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1289 if (!(*i)->hidden()) {
1298 TimeAxisView::build_size_menu ()
1300 if (_size_menu && _size_menu->gobj ()) {
1306 using namespace Menu_Helpers;
1308 _size_menu = new Menu;
1309 _size_menu->set_name ("ArdourContextMenu");
1310 MenuList& items = _size_menu->items();
1312 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1313 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1314 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1315 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1316 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1320 TimeAxisView::reset_visual_state ()
1322 /* this method is not required to trigger a global redraw */
1324 string str = gui_property ("height");
1327 set_height (atoi (str));
1329 set_height (preset_height (HeightNormal));
1334 TrackViewList::filter_to_unique_playlists ()
1336 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1339 for (iterator i = begin(); i != end(); ++i) {
1340 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1342 /* not a route: include it anyway */
1345 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1347 if (playlists.insert (t->playlist()).second) {
1348 /* playlist not seen yet */
1352 /* not a track: include it anyway */