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"
38 #include "canvas/utils.h"
40 #include "ardour/profile.h"
42 #include "ardour_ui.h"
43 #include "ardour_dialog.h"
44 #include "global_signals.h"
45 #include "gui_thread.h"
46 #include "public_editor.h"
47 #include "time_axis_view.h"
48 #include "region_view.h"
49 #include "ghostregion.h"
50 #include "selection.h"
52 #include "rgb_macros.h"
54 #include "streamview.h"
55 #include "editor_drag.h"
63 using namespace ARDOUR;
64 using namespace ARDOUR_UI_UTILS;
66 using namespace Editing;
67 using namespace ArdourCanvas;
68 using Gtkmm2ext::Keyboard;
70 #define TOP_LEVEL_WIDGET controls_ebox
72 const double trim_handle_size = 6.0; /* pixels */
73 uint32_t TimeAxisView::button_height = 0;
74 uint32_t TimeAxisView::extra_height = 0;
75 int const TimeAxisView::_max_order = 512;
76 unsigned int TimeAxisView::name_width_px = 100; // TODO adjust with font-scaling on style-change
77 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
78 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
79 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
81 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
83 , controls_table (3, 3)
84 , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
85 , _name_editing (false)
92 , in_destructor (false)
100 , _effective_height (0)
101 , _resize_drag_start (-1)
102 , _did_resize (false)
103 , _preresize_cursor (0)
104 , _have_preresize_cursor (false)
105 , _ebox_release_can_act (true)
107 if (!controls_meters_size_group) {
108 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
110 if (!midi_scroomer_size_group) {
111 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
113 if (extra_height == 0) {
117 _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
118 CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
119 _canvas_display->hide(); // reveal as needed
121 _canvas_separator = new ArdourCanvas::Line(_canvas_display);
122 CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
123 _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
124 _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
125 _canvas_separator->set_outline_width(1.0);
126 _canvas_separator->hide();
128 selection_group = new ArdourCanvas::Container (_canvas_display);
129 CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
130 selection_group->set_data (X_("timeselection"), (void *) 1);
131 selection_group->hide();
133 _ghost_group = new ArdourCanvas::Container (_canvas_display);
134 CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
135 _ghost_group->lower_to_bottom();
136 _ghost_group->show();
138 name_label.set_name ("TrackLabel");
139 name_label.set_alignment (0.0, 0.5);
140 name_label.set_width_chars (12);
141 ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
143 Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
144 an_entry->set_name ("EditorTrackNameDisplay");
145 Gtk::Requisition req;
146 an_entry->size_request (req);
147 name_label.set_size_request (-1, req.height);
148 name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
151 name_hbox.pack_end (name_label, true, true);
153 // set min. track-header width if fader is not visible
154 name_hbox.set_size_request(name_width_px, -1);
159 controls_table.set_row_spacings (2);
160 controls_table.set_col_spacings (2);
161 controls_table.set_border_width (2);
163 if (ARDOUR::Profile->get_mixbus() ) {
164 controls_table.attach (name_hbox, 4, 5, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
166 controls_table.attach (name_hbox, 1, 2, 0, 2, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
168 controls_table.show_all ();
169 controls_table.set_no_show_all ();
171 controls_vbox.pack_start (controls_table, false, false);
172 controls_vbox.show ();
174 top_hbox.pack_start (controls_vbox, true, true);
177 controls_ebox.add (time_axis_hbox);
178 controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
179 Gdk::BUTTON_RELEASE_MASK|
180 Gdk::POINTER_MOTION_MASK|
181 Gdk::ENTER_NOTIFY_MASK|
182 Gdk::LEAVE_NOTIFY_MASK|
184 controls_ebox.set_flags (CAN_FOCUS);
186 /* note that this handler connects *before* the default handler */
187 controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
188 controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
189 controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
190 controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
191 controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
192 controls_ebox.show ();
194 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
195 time_axis_frame.add(top_hbox);
196 time_axis_frame.show();
198 HSeparator* separator = manage (new HSeparator());
199 separator->set_name("TrackSeparator");
200 separator->set_size_request(-1, 1);
203 scroomer_placeholder.set_size_request (-1, -1);
204 scroomer_placeholder.show();
205 midi_scroomer_size_group->add_widget (scroomer_placeholder);
207 time_axis_vbox.pack_start (*separator, false, false);
208 time_axis_vbox.pack_start (time_axis_frame, true, true);
209 time_axis_vbox.show();
210 time_axis_hbox.pack_start (time_axis_vbox, true, true);
211 time_axis_hbox.show();
212 top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
214 ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
216 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
219 TimeAxisView::~TimeAxisView()
221 in_destructor = true;
223 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
227 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
229 delete (*i)->start_trim;
230 delete (*i)->end_trim;
234 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
236 delete (*i)->start_trim;
237 delete (*i)->end_trim;
240 delete selection_group;
243 delete _canvas_display;
253 TimeAxisView::hide ()
259 _canvas_display->hide ();
260 _canvas_separator->hide ();
262 if (control_parent) {
263 control_parent->remove (TOP_LEVEL_WIDGET);
270 /* now hide children */
272 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
276 /* if its hidden, it cannot be selected */
277 _editor.get_selection().remove (this);
278 /* and neither can its regions */
279 _editor.get_selection().remove_regions (this);
284 /** Display this TimeAxisView as the nth component of the parent box, at y.
286 * @param y y position.
287 * @param nth index for this TimeAxisView, increased if this view has children.
288 * @param parent parent component.
289 * @return height of this TimeAxisView.
292 TimeAxisView::show_at (double y, int& nth, VBox *parent)
294 if (control_parent) {
295 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
297 control_parent = parent;
298 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
299 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
304 if (_y_position != y) {
305 _canvas_display->set_y_position (y);
309 _canvas_display->raise_to_top ();
310 _canvas_display->show ();
314 _effective_height = current_height ();
316 /* now show relevant children */
318 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
319 if ((*i)->marked_for_display()) {
321 _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
327 /* put separator at the bottom of this time axis view */
329 _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
330 _canvas_separator->lower_to_bottom ();
331 _canvas_separator->show ();
333 return _effective_height;
337 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
339 switch (ev->direction) {
341 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
342 /* See Editor::_stepping_axis_view for notes on this hack */
343 Editor& e = dynamic_cast<Editor&> (_editor);
344 if (!e.stepping_axis_view ()) {
345 e.set_stepping_axis_view (this);
347 e.stepping_axis_view()->step_height (false);
352 case GDK_SCROLL_DOWN:
353 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
354 /* See Editor::_stepping_axis_view for notes on this hack */
355 Editor& e = dynamic_cast<Editor&> (_editor);
356 if (!e.stepping_axis_view ()) {
357 e.set_stepping_axis_view (this);
359 e.stepping_axis_view()->step_height (true);
365 /* no handling for left/right, yet */
369 /* Just forward to the normal canvas scroll method. The coordinate
370 systems are different but since the canvas is always larger than the
371 track headers, and aligned with the trackview area, this will work.
373 In the not too distant future this layout is going away anyway and
374 headers will be on the canvas.
376 return _editor.canvas_scroll_event (ev, false);
380 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
382 if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
383 /* see if it is inside the name label */
384 if (name_label.is_ancestor (controls_ebox)) {
387 controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
388 Gtk::Allocation a = name_label.get_allocation ();
389 if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
391 _ebox_release_can_act = false;
398 _ebox_release_can_act = true;
400 if (maybe_set_cursor (event->y) > 0) {
401 _resize_drag_start = event->y_root;
408 TimeAxisView::idle_resize (uint32_t h)
415 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
417 if (_resize_drag_start >= 0) {
419 /* (ab)use the DragManager to do autoscrolling - basically we
420 * are pretending that the drag is taking place over the canvas
421 * (which perhaps in the glorious future, when track headers
422 * and the canvas are unified, will actually be true.)
425 _editor.maybe_autoscroll (false, true, true);
427 /* now schedule the actual TAV resize */
428 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
429 _editor.add_to_idle_resize (this, delta);
430 _resize_drag_start = ev->y_root;
433 /* not dragging but ... */
434 maybe_set_cursor (ev->y);
437 gdk_event_request_motions(ev);
442 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
444 if (_have_preresize_cursor) {
445 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
446 _have_preresize_cursor = false;
452 TimeAxisView::maybe_set_cursor (int y)
454 /* XXX no Gtkmm Gdk::Window::get_cursor() */
455 Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
457 if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
459 /* y-coordinate in lower 25% */
461 if (!_have_preresize_cursor) {
462 _preresize_cursor = gdk_window_get_cursor (win->gobj());
463 _have_preresize_cursor = true;
464 win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
469 } else if (_have_preresize_cursor) {
470 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
471 _have_preresize_cursor = false;
480 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
482 if (_resize_drag_start >= 0) {
483 if (_have_preresize_cursor) {
484 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
485 _preresize_cursor = 0;
486 _have_preresize_cursor = false;
488 _editor.stop_canvas_autoscroll ();
489 _resize_drag_start = -1;
492 // don't change selection
497 if (!_ebox_release_can_act) {
501 switch (ev->button) {
503 selection_click (ev);
507 popup_display_menu (ev->time);
515 TimeAxisView::selection_click (GdkEventButton* ev)
517 Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
518 _editor.set_selected_track (*this, op, false);
522 /** Steps through the defined heights for this TrackView.
523 * @param coarser true if stepping should decrease in size, otherwise false.
526 TimeAxisView::step_height (bool coarser)
528 static const uint32_t step = 25;
532 if (height <= preset_height (HeightSmall)) {
534 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
535 set_height_enum (HeightSmall);
537 set_height (height - step);
542 if (height <= preset_height(HeightSmall)) {
543 set_height_enum (HeightNormal);
545 set_height (height + step);
552 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
554 if (apply_to_selection) {
555 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
557 set_height (preset_height (h));
562 TimeAxisView::set_height (uint32_t h)
564 if (h < preset_height (HeightSmall)) {
565 h = preset_height (HeightSmall);
568 TOP_LEVEL_WIDGET.property_height_request () = h;
572 snprintf (buf, sizeof (buf), "%u", height);
573 set_gui_property ("height", buf);
575 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
579 if (selection_group->visible ()) {
580 /* resize the selection rect */
581 show_selection (_editor.get_selection().time);
584 _editor.override_visible_track_count ();
588 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
590 /* steal escape, tabs from GTK */
592 switch (ev->keyval) {
594 case GDK_ISO_Left_Tab:
602 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
604 TrackViewList::iterator i;
606 switch (ev->keyval) {
608 end_name_edit (RESPONSE_CANCEL);
611 /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
612 * generates a different ev->keyval, rather than setting
615 case GDK_ISO_Left_Tab:
616 end_name_edit (RESPONSE_APPLY);
620 end_name_edit (RESPONSE_ACCEPT);
630 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
632 end_name_edit (RESPONSE_OK);
637 TimeAxisView::begin_name_edit ()
643 if (can_edit_name()) {
645 name_entry = manage (new Gtkmm2ext::FocusEntry);
647 name_entry->set_width_chars(8); // min width, entry expands
649 name_entry->set_name ("EditorTrackNameDisplay");
650 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
651 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
652 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
653 name_entry->set_text (name_label.get_text());
654 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
656 if (name_label.is_ancestor (name_hbox)) {
657 name_hbox.remove (name_label);
660 name_hbox.pack_end (*name_entry, true, true);
663 name_entry->select_region (0, -1);
664 name_entry->set_state (STATE_SELECTED);
665 name_entry->grab_focus ();
666 name_entry->start_editing (0);
671 TimeAxisView::end_name_edit (int response)
677 bool edit_next = false;
678 bool edit_prev = false;
681 case RESPONSE_CANCEL:
684 name_entry_changed ();
686 case RESPONSE_ACCEPT:
687 name_entry_changed ();
690 name_entry_changed ();
694 /* this will delete the name_entry. but it will also drop focus, which
695 * will cause another callback to this function, so set name_entry = 0
696 * first to ensure we don't double-remove etc. etc.
699 Gtk::Entry* tmp = name_entry;
701 name_hbox.remove (*tmp);
703 /* put the name label back */
705 name_hbox.pack_end (name_label);
710 TrackViewList const & allviews = _editor.get_track_views ();
711 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
713 if (i != allviews.end()) {
716 if (++i == allviews.end()) {
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 ();
738 } else if (edit_prev) {
740 TrackViewList const & allviews = _editor.get_track_views ();
741 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
743 if (i != allviews.begin()) {
745 if (i == allviews.begin()) {
751 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
753 if (rtav && rtav->route()->record_enabled()) {
757 if (!(*i)->hidden()) {
764 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
765 _editor.ensure_time_axis_view_is_visible (**i, false);
766 (*i)->begin_name_edit ();
772 TimeAxisView::name_entry_changed ()
777 TimeAxisView::can_edit_name () const
783 TimeAxisView::conditionally_add_to_selection ()
785 Selection& s (_editor.get_selection ());
787 if (!s.selected (this)) {
788 _editor.set_selected_track (*this, Selection::Set);
793 TimeAxisView::popup_display_menu (guint32 when)
795 conditionally_add_to_selection ();
797 build_display_menu ();
798 display_menu->popup (1, when);
802 TimeAxisView::set_selected (bool yn)
804 if (can_edit_name() && name_entry && name_entry->get_visible()) {
805 end_name_edit (RESPONSE_CANCEL);
808 if (yn == _selected) {
812 Selectable::set_selected (yn);
815 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
816 time_axis_frame.set_name ("MixerStripSelectedFrame");
817 controls_ebox.set_name (controls_base_selected_name);
818 controls_vbox.set_name (controls_base_selected_name);
819 time_axis_vbox.set_name (controls_base_selected_name);
821 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
822 time_axis_frame.set_name (controls_base_unselected_name);
823 controls_ebox.set_name (controls_base_unselected_name);
824 controls_vbox.set_name (controls_base_unselected_name);
825 time_axis_vbox.set_name (controls_base_unselected_name);
829 /* children will be set for the yn=true case. but when deselecting
830 the editor only has a list of top-level trackviews, so we
831 have to do this here.
834 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
835 (*i)->set_selected (false);
839 time_axis_frame.show();
844 TimeAxisView::build_display_menu ()
846 using namespace Menu_Helpers;
850 display_menu = new Menu;
851 display_menu->set_name ("ArdourContextMenu");
853 // Just let implementing classes define what goes into the manu
857 TimeAxisView::set_samples_per_pixel (double fpp)
859 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
860 (*i)->set_samples_per_pixel (fpp);
865 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
867 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
868 (*i)->show_timestretch (start, end, layers, layer);
873 TimeAxisView::hide_timestretch ()
875 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
876 (*i)->hide_timestretch ();
881 TimeAxisView::show_selection (TimeSelection& ts)
886 SelectionRect *rect; time_axis_frame.show();
889 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
890 (*i)->show_selection (ts);
893 if (selection_group->visible ()) {
894 while (!used_selection_rects.empty()) {
895 free_selection_rects.push_front (used_selection_rects.front());
896 used_selection_rects.pop_front();
897 free_selection_rects.front()->rect->hide();
898 free_selection_rects.front()->start_trim->hide();
899 free_selection_rects.front()->end_trim->hide();
901 selection_group->hide();
904 selection_group->show();
905 selection_group->raise_to_top();
907 for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
908 framepos_t start, end;
913 cnt = end - start + 1;
915 rect = get_selection_rect ((*i).id);
917 x1 = _editor.sample_to_pixel (start);
918 x2 = _editor.sample_to_pixel (start + cnt - 1);
919 y2 = current_height() - 1;
921 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
923 // trim boxes are at the top for selections
926 rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
927 rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
929 rect->start_trim->show();
930 rect->end_trim->show();
932 rect->start_trim->hide();
933 rect->end_trim->hide();
937 used_selection_rects.push_back (rect);
942 TimeAxisView::reshow_selection (TimeSelection& ts)
946 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
947 (*i)->show_selection (ts);
952 TimeAxisView::hide_selection ()
954 if (selection_group->visible ()) {
955 while (!used_selection_rects.empty()) {
956 free_selection_rects.push_front (used_selection_rects.front());
957 used_selection_rects.pop_front();
958 free_selection_rects.front()->rect->hide();
959 free_selection_rects.front()->start_trim->hide();
960 free_selection_rects.front()->end_trim->hide();
962 selection_group->hide();
965 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
966 (*i)->hide_selection ();
971 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
973 /* find the selection rect this is for. we have the item corresponding to one
977 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
978 if ((*i)->start_trim == item || (*i)->end_trim == item) {
980 /* make one trim handle be "above" the other so that if they overlap,
981 the top one is the one last used.
984 (*i)->rect->raise_to_top ();
985 (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
986 (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
994 TimeAxisView::get_selection_rect (uint32_t id)
998 /* check to see if we already have a visible rect for this particular selection ID */
1000 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1001 if ((*i)->id == id) {
1006 /* ditto for the free rect list */
1008 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1009 if ((*i)->id == id) {
1010 SelectionRect* ret = (*i);
1011 free_selection_rects.erase (i);
1016 /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1018 if (free_selection_rects.empty()) {
1020 rect = new SelectionRect;
1022 rect->rect = new ArdourCanvas::TimeRectangle (selection_group);
1023 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1024 rect->rect->set_outline (false);
1025 rect->rect->set_fill_color (ARDOUR_UI::config()->get_SelectionRect());
1027 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1028 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1029 rect->start_trim->set_outline (false);
1030 rect->start_trim->set_fill (false);
1032 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1033 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1034 rect->end_trim->set_outline (false);
1035 rect->end_trim->set_fill (false);
1037 free_selection_rects.push_front (rect);
1039 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1040 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1041 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1044 rect = free_selection_rects.front();
1046 free_selection_rects.pop_front();
1050 struct null_deleter { void operator()(void const *) const {} };
1053 TimeAxisView::is_child (TimeAxisView* tav)
1055 return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1059 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1061 children.push_back (child);
1065 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1067 Children::iterator i;
1069 if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1074 /** Get selectable things within a given range.
1075 * @param start Start time in session frames.
1076 * @param end End time in session frames.
1077 * @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1078 * @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1079 * @param result Filled in with selectable things.
1082 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1088 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1094 TimeAxisView::add_ghost (RegionView* rv)
1096 GhostRegion* gr = rv->add_ghost (*this);
1099 ghosts.push_back(gr);
1104 TimeAxisView::remove_ghost (RegionView* rv)
1106 rv->remove_ghost_in (*this);
1110 TimeAxisView::erase_ghost (GhostRegion* gr)
1112 if (in_destructor) {
1116 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1125 TimeAxisView::touched (double top, double bot)
1127 /* remember: this is X Window - coordinate space starts in upper left and moves down.
1128 y_position is the "origin" or "top" of the track.
1131 double mybot = _y_position + current_height();
1133 return ((_y_position <= bot && _y_position >= top) ||
1134 ((mybot <= bot) && (top < mybot)) ||
1135 (mybot >= bot && _y_position < top));
1139 TimeAxisView::set_parent (TimeAxisView& p)
1145 TimeAxisView::reset_height ()
1147 set_height (height);
1149 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1150 (*i)->set_height ((*i)->height);
1155 TimeAxisView::compute_heights ()
1157 // TODO this function should be re-evaluated when font-scaling changes (!)
1158 Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1159 Gtk::Table one_row_table (1, 1);
1160 ArdourButton* test_button = manage (new ArdourButton);
1161 const int border_width = 2;
1162 const int frame_height = 2;
1163 extra_height = (2 * border_width) + frame_height;
1165 window.add (one_row_table);
1166 test_button->set_name ("mute button");
1167 test_button->set_text (_("M"));
1168 test_button->set_tweaks (ArdourButton::TrackHeader);
1170 one_row_table.set_border_width (border_width);
1171 one_row_table.set_row_spacings (2);
1172 one_row_table.set_col_spacings (2);
1174 one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1175 one_row_table.show_all ();
1177 Gtk::Requisition req(one_row_table.size_request ());
1178 button_height = req.height;
1182 TimeAxisView::color_handler ()
1184 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1188 for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1190 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_SelectionRect());
1191 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_Selection());
1193 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_Selection());
1194 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_Selection());
1196 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_Selection());
1197 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_Selection());
1200 for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1202 (*i)->rect->set_fill_color (ARDOUR_UI::config()->get_SelectionRect());
1203 (*i)->rect->set_outline_color (ARDOUR_UI::config()->get_Selection());
1205 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->get_Selection());
1206 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->get_Selection());
1208 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->get_Selection());
1209 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->get_Selection());
1213 /** @return Pair: TimeAxisView, layer index.
1214 * TimeAxisView is non-0 if this object covers @param y, or one of its children
1215 * does. @param y is an offset from the top of the trackview area.
1217 * If the covering object is a child axis, then the child is returned.
1218 * TimeAxisView is 0 otherwise.
1220 * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1221 * and is in stacked or expanded * region display mode, otherwise 0.
1223 std::pair<TimeAxisView*, double>
1224 TimeAxisView::covers_y_position (double y) const
1227 return std::make_pair ((TimeAxisView *) 0, 0);
1230 if (_y_position <= y && y < (_y_position + height)) {
1232 /* work out the layer index if appropriate */
1234 switch (layer_display ()) {
1240 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1241 /* clamp to max layers to be on the safe side; sometimes the above calculation
1242 returns a too-high value */
1243 if (l >= view()->layers ()) {
1244 l = view()->layers() - 1;
1250 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1252 if (l >= (view()->layers() - 0.5)) {
1253 l = view()->layers() - 0.5;
1259 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1262 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1264 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1270 return std::make_pair ((TimeAxisView *) 0, 0);
1274 TimeAxisView::covered_by_y_range (double y0, double y1) const
1280 /* if either the top or bottom of the axisview is in the vertical
1281 * range, we cover it.
1284 if ((y0 < _y_position && y1 < _y_position) ||
1285 (y0 >= _y_position + height && y1 >= _y_position + height)) {
1289 for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1290 if ((*i)->covered_by_y_range (y0, y1)) {
1299 TimeAxisView::preset_height (Height h)
1303 return (button_height * 2) + extra_height + 260;
1305 return (button_height * 2) + extra_height + 160;
1307 return (button_height * 2) + extra_height + 60;
1309 return (button_height * 2) + extra_height + 10;
1311 return button_height + extra_height;
1318 /** @return Child time axis views that are not hidden */
1319 TimeAxisView::Children
1320 TimeAxisView::get_child_list ()
1324 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1325 if (!(*i)->hidden()) {
1334 TimeAxisView::build_size_menu ()
1336 if (_size_menu && _size_menu->gobj ()) {
1342 using namespace Menu_Helpers;
1344 _size_menu = new Menu;
1345 _size_menu->set_name ("ArdourContextMenu");
1346 MenuList& items = _size_menu->items();
1348 items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1349 items.push_back (MenuElem (_("Larger"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1350 items.push_back (MenuElem (_("Large"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1351 items.push_back (MenuElem (_("Normal"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1352 items.push_back (MenuElem (_("Small"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1356 TimeAxisView::reset_visual_state ()
1358 /* this method is not required to trigger a global redraw */
1360 string str = gui_property ("height");
1363 set_height (atoi (str));
1365 set_height (preset_height (HeightNormal));
1370 TrackViewList::filter_to_unique_playlists ()
1372 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1375 for (iterator i = begin(); i != end(); ++i) {
1376 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1378 /* not a route: include it anyway */
1381 boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1383 if (playlists.insert (t->playlist()).second) {
1384 /* playlist not seen yet */
1388 /* not a track: include it anyway */