apply font-scale to overall layout (rulers, track-header) - fixes #6088
[ardour.git] / gtk2_ardour / time_axis_view.cc
1 /*
2     Copyright (C) 2000 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <cstdlib>
21 #include <cmath>
22 #include <algorithm>
23 #include <string>
24 #include <list>
25
26
27 #include "pbd/error.h"
28 #include "pbd/convert.h"
29 #include "pbd/stacktrace.h"
30
31 #include <gtkmm2ext/doi.h>
32 #include <gtkmm2ext/utils.h>
33 #include <gtkmm2ext/selector.h>
34
35 #include "canvas/canvas.h"
36 #include "canvas/rectangle.h"
37 #include "canvas/debug.h"
38 #include "canvas/utils.h"
39 #include "canvas/colors.h"
40
41 #include "ardour/profile.h"
42
43 #include "ardour_ui.h"
44 #include "ardour_dialog.h"
45 #include "global_signals.h"
46 #include "gui_thread.h"
47 #include "public_editor.h"
48 #include "time_axis_view.h"
49 #include "region_view.h"
50 #include "ghostregion.h"
51 #include "selection.h"
52 #include "keyboard.h"
53 #include "rgb_macros.h"
54 #include "utils.h"
55 #include "streamview.h"
56 #include "editor_drag.h"
57 #include "editor.h"
58
59 #include "i18n.h"
60
61 using namespace std;
62 using namespace Gtk;
63 using namespace Gdk;
64 using namespace ARDOUR;
65 using namespace ARDOUR_UI_UTILS;
66 using namespace PBD;
67 using namespace Editing;
68 using namespace ArdourCanvas;
69 using Gtkmm2ext::Keyboard;
70
71 #define TOP_LEVEL_WIDGET controls_ebox
72
73 const double trim_handle_size = 6.0; /* pixels */
74 uint32_t TimeAxisView::button_height = 0;
75 uint32_t TimeAxisView::extra_height = 0;
76 int const TimeAxisView::_max_order = 512;
77 unsigned int TimeAxisView::name_width_px = 100;
78 PBD::Signal1<void,TimeAxisView*> TimeAxisView::CatchDeletion;
79 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::controls_meters_size_group = Glib::RefPtr<Gtk::SizeGroup>();
80 Glib::RefPtr<Gtk::SizeGroup> TimeAxisView::midi_scroomer_size_group = Glib::RefPtr<Gtk::SizeGroup>();
81
82 void
83 TimeAxisView::setup_sizes()
84 {
85         name_width_px = ceil (100. * ARDOUR_UI::config()->get_font_scale() / 102400.);
86 }
87
88 TimeAxisView::TimeAxisView (ARDOUR::Session* sess, PublicEditor& ed, TimeAxisView* rent, Canvas& /*canvas*/)
89         : AxisView (sess)
90         , controls_table (3, 3)
91         , controls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
92         , _name_editing (false)
93         , height (0)
94         , display_menu (0)
95         , parent (rent)
96         , selection_group (0)
97         , _ghost_group (0)
98         , _hidden (false)
99         , in_destructor (false)
100         , _size_menu (0)
101         , _canvas_display (0)
102         , _y_position (0)
103         , _editor (ed)
104         , name_entry (0)
105         , control_parent (0)
106         , _order (0)
107         , _effective_height (0)
108         , _resize_drag_start (-1)
109         , _did_resize (false)
110         , _preresize_cursor (0)
111         , _have_preresize_cursor (false)
112         , _ebox_release_can_act (true)
113 {
114         if (!controls_meters_size_group) {
115                 controls_meters_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
116         }
117         if (!midi_scroomer_size_group) {
118                 midi_scroomer_size_group = SizeGroup::create (SIZE_GROUP_HORIZONTAL);
119         }
120         if (extra_height == 0) {
121                 compute_heights ();
122         }
123
124         _canvas_display = new ArdourCanvas::Container (ed.get_trackview_group ());
125         CANVAS_DEBUG_NAME (_canvas_display, "main for TAV");
126         _canvas_display->hide(); // reveal as needed
127
128         _canvas_separator = new ArdourCanvas::Line(_canvas_display);
129         CANVAS_DEBUG_NAME (_canvas_separator, "separator for TAV");
130         _canvas_separator->set (ArdourCanvas::Duple(0.0, 0.0), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, 0.0));
131         _canvas_separator->set_outline_color(ArdourCanvas::rgba_to_color (0, 0, 0, 1.0));
132         _canvas_separator->set_outline_width(1.0);
133         _canvas_separator->hide();
134
135         selection_group = new ArdourCanvas::Container (_canvas_display);
136         CANVAS_DEBUG_NAME (selection_group, "selection for TAV");
137         selection_group->set_data (X_("timeselection"), (void *) 1);
138         selection_group->hide();
139         
140         _ghost_group = new ArdourCanvas::Container (_canvas_display);
141         CANVAS_DEBUG_NAME (_ghost_group, "ghost for TAV");
142         _ghost_group->lower_to_bottom();
143         _ghost_group->show();
144
145         name_label.set_name ("TrackLabel");
146         name_label.set_alignment (0.0, 0.5);
147         name_label.set_width_chars (12);
148         ARDOUR_UI::instance()->set_tip (name_label, _("Track/Bus name (double click to edit)"));
149
150         Gtk::Entry* an_entry = new Gtkmm2ext::FocusEntry;
151         an_entry->set_name ("EditorTrackNameDisplay");
152         Gtk::Requisition req;
153         an_entry->size_request (req);
154         name_label.set_size_request (-1, req.height);
155         name_label.set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
156         delete an_entry;
157
158         name_hbox.pack_end (name_label, true, true);
159
160         // set min. track-header width if fader is not visible
161         name_hbox.set_size_request(name_width_px, -1);
162
163         name_hbox.show ();
164         name_label.show ();
165
166         controls_table.set_row_spacings (2);
167         controls_table.set_col_spacings (2);
168         controls_table.set_border_width (2);
169
170         if (ARDOUR::Profile->get_mixbus() ) {
171                 controls_table.attach (name_hbox, 4, 5, 0, 2,  Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
172         } else {
173                 controls_table.attach (name_hbox, 1, 2, 0, 2,  Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK, 0, 0);
174         }
175         controls_table.show_all ();
176         controls_table.set_no_show_all ();
177
178         controls_vbox.pack_start (controls_table, false, false);
179         controls_vbox.show ();
180
181         top_hbox.pack_start (controls_vbox, true, true);
182         top_hbox.show ();
183
184         controls_ebox.add (time_axis_hbox);
185         controls_ebox.add_events (Gdk::BUTTON_PRESS_MASK|
186                                   Gdk::BUTTON_RELEASE_MASK|
187                                   Gdk::POINTER_MOTION_MASK|
188                                   Gdk::ENTER_NOTIFY_MASK|
189                                   Gdk::LEAVE_NOTIFY_MASK|
190                                   Gdk::SCROLL_MASK);
191         controls_ebox.set_flags (CAN_FOCUS);
192
193         /* note that this handler connects *before* the default handler */
194         controls_ebox.signal_scroll_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_scroll), true);
195         controls_ebox.signal_button_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_press));
196         controls_ebox.signal_button_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_button_release));
197         controls_ebox.signal_motion_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_motion));
198         controls_ebox.signal_leave_notify_event().connect (sigc::mem_fun (*this, &TimeAxisView::controls_ebox_leave));
199         controls_ebox.show ();
200
201         time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
202         time_axis_frame.add(top_hbox);
203         time_axis_frame.show();
204
205         HSeparator* separator = manage (new HSeparator());
206         separator->set_name("TrackSeparator");
207         separator->set_size_request(-1, 1);
208         separator->show();
209
210         scroomer_placeholder.set_size_request (-1, -1);
211         scroomer_placeholder.show();
212         midi_scroomer_size_group->add_widget (scroomer_placeholder);
213
214         time_axis_vbox.pack_start (*separator, false, false);
215         time_axis_vbox.pack_start (time_axis_frame, true, true);
216         time_axis_vbox.show();
217         time_axis_hbox.pack_start (time_axis_vbox, true, true);
218         time_axis_hbox.show();
219         top_hbox.pack_start (scroomer_placeholder, false, false); // OR pack_end to move after meters ?
220
221         ColorsChanged.connect (sigc::mem_fun (*this, &TimeAxisView::color_handler));
222
223         GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&TimeAxisView::erase_ghost, this, _1), gui_context());
224 }
225
226 TimeAxisView::~TimeAxisView()
227 {
228         CatchDeletion (this);
229
230         in_destructor = true;
231
232         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
233                 delete *i;
234         }
235
236         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
237                 delete (*i)->rect; (*i)->rect=0;
238                 delete (*i)->start_trim; (*i)->start_trim = 0;
239                 delete (*i)->end_trim; (*i)->end_trim = 0;
240
241         }
242
243         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
244                 delete (*i)->rect; (*i)->rect = 0;
245                 delete (*i)->start_trim; (*i)->start_trim = 0;
246                 delete (*i)->end_trim; (*i)->end_trim = 0;
247         }
248
249         delete selection_group;
250         selection_group = 0;
251
252         delete _canvas_display;
253         _canvas_display = 0;
254
255         delete display_menu;
256         display_menu = 0;
257
258         delete _size_menu;
259 }
260
261 void
262 TimeAxisView::hide ()
263 {
264         if (_hidden) {
265                 return;
266         }
267
268         _canvas_display->hide ();
269         _canvas_separator->hide ();
270
271         if (control_parent) {
272                 control_parent->remove (TOP_LEVEL_WIDGET);
273                 control_parent = 0;
274         }
275
276         _y_position = -1;
277         _hidden = true;
278
279         /* now hide children */
280
281         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
282                 (*i)->hide ();
283         }
284
285         /* if its hidden, it cannot be selected */
286         _editor.get_selection().remove (this);
287         /* and neither can its regions */
288         _editor.get_selection().remove_regions (this);
289
290         Hiding ();
291 }
292
293 /** Display this TimeAxisView as the nth component of the parent box, at y.
294 *
295 * @param y y position.
296 * @param nth index for this TimeAxisView, increased if this view has children.
297 * @param parent parent component.
298 * @return height of this TimeAxisView.
299 */
300 guint32
301 TimeAxisView::show_at (double y, int& nth, VBox *parent)
302 {
303         if (control_parent) {
304                 control_parent->reorder_child (TOP_LEVEL_WIDGET, nth);
305         } else {
306                 control_parent = parent;
307                 parent->pack_start (TOP_LEVEL_WIDGET, false, false);
308                 parent->reorder_child (TOP_LEVEL_WIDGET, nth);
309         }
310
311         _order = nth;
312
313         if (_y_position != y) {
314                 _canvas_display->set_y_position (y);
315                 _y_position = y;
316         }
317
318         _canvas_display->raise_to_top ();
319         _canvas_display->show ();
320         
321         _hidden = false;
322
323         _effective_height = current_height ();
324
325         /* now show relevant children */
326
327         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
328                 if ((*i)->marked_for_display()) {
329                         ++nth;
330                         _effective_height += (*i)->show_at (y + _effective_height, nth, parent);
331                 } else {
332                         (*i)->hide ();
333                 }
334         }
335
336         /* put separator at the bottom of this time axis view */
337
338         _canvas_separator->set (ArdourCanvas::Duple(0, height), ArdourCanvas::Duple(ArdourCanvas::COORD_MAX, height));
339         _canvas_separator->lower_to_bottom ();
340         _canvas_separator->show ();
341
342         return _effective_height;
343 }
344
345 bool
346 TimeAxisView::controls_ebox_scroll (GdkEventScroll* ev)
347 {
348         switch (ev->direction) {
349         case GDK_SCROLL_UP:
350                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
351                         /* See Editor::_stepping_axis_view for notes on this hack */
352                         Editor& e = dynamic_cast<Editor&> (_editor);
353                         if (!e.stepping_axis_view ()) {
354                                 e.set_stepping_axis_view (this);
355                         }
356                         e.stepping_axis_view()->step_height (false);
357                         return true;
358                 } 
359                 break;
360
361         case GDK_SCROLL_DOWN:
362                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
363                         /* See Editor::_stepping_axis_view for notes on this hack */
364                         Editor& e = dynamic_cast<Editor&> (_editor);
365                         if (!e.stepping_axis_view ()) {
366                                 e.set_stepping_axis_view (this);
367                         }
368                         e.stepping_axis_view()->step_height (true);
369                         return true;
370                 } 
371                 break;
372
373         default:
374                 /* no handling for left/right, yet */
375                 break;
376         }
377
378         /* Just forward to the normal canvas scroll method. The coordinate
379            systems are different but since the canvas is always larger than the
380            track headers, and aligned with the trackview area, this will work.
381
382            In the not too distant future this layout is going away anyway and
383            headers will be on the canvas.
384         */
385         return _editor.canvas_scroll_event (ev, false);
386 }
387
388 bool
389 TimeAxisView::controls_ebox_button_press (GdkEventButton* event)
390 {
391         if ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (event)) {
392                 /* see if it is inside the name label */
393                 if (name_label.is_ancestor (controls_ebox)) {
394                         int nlx;
395                         int nly;
396                         controls_ebox.translate_coordinates (name_label, event->x, event->y, nlx, nly);
397                         Gtk::Allocation a = name_label.get_allocation ();
398                         if (nlx > 0 && nlx < a.get_width() && nly > 0 && nly < a.get_height()) {
399                                 begin_name_edit ();
400                                 _ebox_release_can_act = false;
401                                 return true;
402                         }
403                 }
404
405         }
406
407         _ebox_release_can_act = true;
408                         
409         if (maybe_set_cursor (event->y) > 0) {
410                 _resize_drag_start = event->y_root;
411         }
412
413         return true;
414 }
415
416 void
417 TimeAxisView::idle_resize (int32_t h)
418 {
419         set_height (std::max(0, h));
420 }
421
422
423 bool
424 TimeAxisView::controls_ebox_motion (GdkEventMotion* ev)
425 {
426         if (_resize_drag_start >= 0) {
427
428                 /* (ab)use the DragManager to do autoscrolling - basically we
429                  * are pretending that the drag is taking place over the canvas
430                  * (which perhaps in the glorious future, when track headers
431                  * and the canvas are unified, will actually be true.)
432                  */
433
434                 _editor.maybe_autoscroll (false, true, true);
435
436                 /* now schedule the actual TAV resize */
437                 int32_t const delta = (int32_t) floor (ev->y_root - _resize_drag_start);
438                 _editor.add_to_idle_resize (this, delta);
439                 _resize_drag_start = ev->y_root;
440                 _did_resize = true;
441         } else {
442                 /* not dragging but ... */
443                 maybe_set_cursor (ev->y);
444         }
445
446         gdk_event_request_motions(ev);
447         return true;
448 }
449
450 bool
451 TimeAxisView::controls_ebox_leave (GdkEventCrossing*)
452 {
453         if (_have_preresize_cursor) {
454                 gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
455                 _have_preresize_cursor = false;
456         }
457         return true;
458 }
459
460 bool
461 TimeAxisView::maybe_set_cursor (int y)
462 {
463         /* XXX no Gtkmm Gdk::Window::get_cursor() */
464         Glib::RefPtr<Gdk::Window> win = controls_ebox.get_window();
465
466         if (y > (gint) floor (controls_ebox.get_height() * 0.75)) {
467
468                 /* y-coordinate in lower 25% */
469
470                 if (!_have_preresize_cursor) {
471                         _preresize_cursor = gdk_window_get_cursor (win->gobj());
472                         _have_preresize_cursor = true;
473                         win->set_cursor (Gdk::Cursor(Gdk::SB_V_DOUBLE_ARROW));
474                 }
475
476                 return 1;
477
478         } else if (_have_preresize_cursor) {
479                 gdk_window_set_cursor (win->gobj(), _preresize_cursor);
480                 _have_preresize_cursor = false;
481
482                 return -1;
483         }
484
485         return 0;
486 }
487
488 bool
489 TimeAxisView::controls_ebox_button_release (GdkEventButton* ev)
490 {
491         if (_resize_drag_start >= 0) {
492                 if (_have_preresize_cursor) {
493                         gdk_window_set_cursor (controls_ebox.get_window()->gobj(), _preresize_cursor);
494                         _preresize_cursor = 0;
495                         _have_preresize_cursor = false;
496                 }
497                 _editor.stop_canvas_autoscroll ();
498                 _resize_drag_start = -1;
499                 if (_did_resize) {
500                         _did_resize = false;
501                         // don't change selection
502                         return true;
503                 }
504         }
505
506         if (!_ebox_release_can_act) {
507                 return true;
508         }
509
510         switch (ev->button) {
511         case 1:
512                 selection_click (ev);
513                 break;
514
515         case 3:
516                 popup_display_menu (ev->time);
517                 break;
518         }
519
520         return true;
521 }
522
523 void
524 TimeAxisView::selection_click (GdkEventButton* ev)
525 {
526         Selection::Operation op = ArdourKeyboard::selection_type (ev->state);
527         _editor.set_selected_track (*this, op, false);
528 }
529
530
531 /** Steps through the defined heights for this TrackView.
532  *  @param coarser true if stepping should decrease in size, otherwise false.
533  */
534 void
535 TimeAxisView::step_height (bool coarser)
536 {
537         static const uint32_t step = 25;
538
539         if (coarser) {
540
541                 if (height <= preset_height (HeightSmall)) {
542                         return;
543                 } else if (height <= preset_height (HeightNormal) && height > preset_height (HeightSmall)) {
544                         set_height_enum (HeightSmall);
545                 } else {
546                         set_height (height - step);
547                 }
548
549         } else {
550
551                 if (height <= preset_height(HeightSmall)) {
552                         set_height_enum (HeightNormal);
553                 } else {
554                         set_height (height + step);
555                 }
556
557         }
558 }
559
560 void
561 TimeAxisView::set_height_enum (Height h, bool apply_to_selection)
562 {
563         if (apply_to_selection) {
564                 _editor.get_selection().tracks.foreach_time_axis (boost::bind (&TimeAxisView::set_height_enum, _1, h, false));
565         } else {
566                 set_height (preset_height (h));
567         }
568 }
569
570 void
571 TimeAxisView::set_height (uint32_t h)
572 {
573         if (h < preset_height (HeightSmall)) {
574                 h = preset_height (HeightSmall);
575         }
576
577         TOP_LEVEL_WIDGET.property_height_request () = h;
578         height = h;
579
580         char buf[32];
581         snprintf (buf, sizeof (buf), "%u", height);
582         set_gui_property ("height", buf);
583
584         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
585                 (*i)->set_height ();
586         }
587
588         if (selection_group->visible ()) {
589                 /* resize the selection rect */
590                 show_selection (_editor.get_selection().time);
591         }
592
593         _editor.override_visible_track_count ();
594 }
595
596 bool
597 TimeAxisView::name_entry_key_press (GdkEventKey* ev)
598 {
599         /* steal escape, tabs from GTK */
600
601         switch (ev->keyval) {
602         case GDK_Escape:
603         case GDK_ISO_Left_Tab:
604         case GDK_Tab:
605                 return true;
606         }
607         return false;
608 }
609
610 bool
611 TimeAxisView::name_entry_key_release (GdkEventKey* ev)
612 {
613         TrackViewList::iterator i;
614
615         switch (ev->keyval) {
616         case GDK_Escape:
617                 end_name_edit (RESPONSE_CANCEL);
618                 return true;
619
620         /* Shift+Tab Keys Pressed. Note that for Shift+Tab, GDK actually
621          * generates a different ev->keyval, rather than setting
622          * ev->state.
623          */
624         case GDK_ISO_Left_Tab:
625                 end_name_edit (RESPONSE_APPLY);
626                 return true;
627
628         case GDK_Tab:
629                 end_name_edit (RESPONSE_ACCEPT);
630                 return true;
631         default:
632                 break;
633         }
634
635         return false;
636 }
637
638 bool
639 TimeAxisView::name_entry_focus_out (GdkEventFocus*)
640 {
641         end_name_edit (RESPONSE_OK);
642         return false;
643 }
644
645 void
646 TimeAxisView::begin_name_edit ()
647 {
648         if (name_entry) {
649                 return;
650         }
651
652         if (can_edit_name()) {
653
654                 name_entry = manage (new Gtkmm2ext::FocusEntry);
655                 
656                 name_entry->set_width_chars(8); // min width, entry expands
657
658                 name_entry->set_name ("EditorTrackNameDisplay");
659                 name_entry->signal_key_press_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_press), false);
660                 name_entry->signal_key_release_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_key_release), false);
661                 name_entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &TimeAxisView::name_entry_focus_out));
662                 name_entry->set_text (name_label.get_text());
663                 name_entry->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &TimeAxisView::end_name_edit), RESPONSE_OK));
664
665                 if (name_label.is_ancestor (name_hbox)) {
666                         name_hbox.remove (name_label);
667                 }
668                 
669                 name_hbox.pack_end (*name_entry, true, true);
670                 name_entry->show ();
671
672                 name_entry->select_region (0, -1);
673                 name_entry->set_state (STATE_SELECTED);
674                 name_entry->grab_focus ();
675                 name_entry->start_editing (0);
676         }
677 }
678
679 void
680 TimeAxisView::end_name_edit (int response)
681 {
682         if (!name_entry) {
683                 return;
684         }
685         
686         bool edit_next = false;
687         bool edit_prev = false;
688
689         switch (response) {
690         case RESPONSE_CANCEL:
691                 break;
692         case RESPONSE_OK:
693                 name_entry_changed ();
694                 break;
695         case RESPONSE_ACCEPT:
696                 name_entry_changed ();
697                 edit_next = true;
698         case RESPONSE_APPLY:
699                 name_entry_changed ();
700                 edit_prev = true;
701         }
702
703         /* this will delete the name_entry. but it will also drop focus, which
704          * will cause another callback to this function, so set name_entry = 0
705          * first to ensure we don't double-remove etc. etc.
706          */
707
708         Gtk::Entry* tmp = name_entry;
709         name_entry = 0;
710         name_hbox.remove (*tmp);
711
712         /* put the name label back */
713
714         name_hbox.pack_end (name_label);
715         name_label.show ();
716
717         if (edit_next) {
718
719                 TrackViewList const & allviews = _editor.get_track_views ();
720                 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
721                 
722                 if (i != allviews.end()) {
723                         
724                         do {
725                                 if (++i == allviews.end()) {
726                                         return;
727                                 }
728                                 
729                                 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
730                         
731                                 if (rtav && rtav->route()->record_enabled()) {
732                                         continue;
733                                 }
734                                 
735                                 if (!(*i)->hidden()) {
736                                         break;
737                                 }
738                                 
739                         } while (true);
740                 }
741
742                 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
743                         _editor.ensure_time_axis_view_is_visible (**i, false);
744                         (*i)->begin_name_edit ();
745                 } 
746
747         } else if (edit_prev) {
748
749                 TrackViewList const & allviews = _editor.get_track_views ();
750                 TrackViewList::const_iterator i = find (allviews.begin(), allviews.end(), this);
751                 
752                 if (i != allviews.begin()) {
753                         do {
754                                 if (i == allviews.begin()) {
755                                         return;
756                                 }
757                                 
758                                 --i;
759                                 
760                                 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*>(*i);
761                                 
762                                 if (rtav && rtav->route()->record_enabled()) {
763                                         continue;
764                                 }
765                                 
766                                 if (!(*i)->hidden()) {
767                                         break;
768                                 }
769                                 
770                         } while (true);
771                 }
772                 
773                 if ((i != allviews.end()) && (*i != this) && !(*i)->hidden()) {
774                         _editor.ensure_time_axis_view_is_visible (**i, false);
775                         (*i)->begin_name_edit ();
776                 } 
777         }
778 }
779
780 void
781 TimeAxisView::name_entry_changed ()
782 {
783 }
784
785 bool
786 TimeAxisView::can_edit_name () const
787 {
788         return true;
789 }
790
791 void
792 TimeAxisView::conditionally_add_to_selection ()
793 {
794         Selection& s (_editor.get_selection ());
795
796         if (!s.selected (this)) {
797                 _editor.set_selected_track (*this, Selection::Set);
798         }
799 }
800
801 void
802 TimeAxisView::popup_display_menu (guint32 when)
803 {
804         conditionally_add_to_selection ();
805
806         build_display_menu ();
807         display_menu->popup (1, when);
808 }
809
810 void
811 TimeAxisView::set_selected (bool yn)
812 {
813         if (can_edit_name() && name_entry && name_entry->get_visible()) {
814                 end_name_edit (RESPONSE_CANCEL);
815         }
816
817         if (yn == _selected) {
818                 return;
819         }
820
821         Selectable::set_selected (yn);
822
823         if (_selected) {
824                 time_axis_frame.set_shadow_type (Gtk::SHADOW_IN);
825                 time_axis_frame.set_name ("MixerStripSelectedFrame");
826                 controls_ebox.set_name (controls_base_selected_name);
827                 controls_vbox.set_name (controls_base_selected_name);
828                 time_axis_vbox.set_name (controls_base_selected_name);
829         } else {
830                 time_axis_frame.set_shadow_type (Gtk::SHADOW_NONE);
831                 time_axis_frame.set_name (controls_base_unselected_name);
832                 controls_ebox.set_name (controls_base_unselected_name);
833                 controls_vbox.set_name (controls_base_unselected_name);
834                 time_axis_vbox.set_name (controls_base_unselected_name);
835
836                 hide_selection ();
837
838                 /* children will be set for the yn=true case. but when deselecting
839                    the editor only has a list of top-level trackviews, so we
840                    have to do this here.
841                 */
842
843                 for (Children::iterator i = children.begin(); i != children.end(); ++i) {
844                         (*i)->set_selected (false);
845                 }
846         }
847
848         time_axis_frame.show();
849
850 }
851
852 void
853 TimeAxisView::build_display_menu ()
854 {
855         using namespace Menu_Helpers;
856
857         delete display_menu;
858
859         display_menu = new Menu;
860         display_menu->set_name ("ArdourContextMenu");
861
862         // Just let implementing classes define what goes into the manu
863 }
864
865 void
866 TimeAxisView::set_samples_per_pixel (double fpp)
867 {
868         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
869                 (*i)->set_samples_per_pixel (fpp);
870         }
871 }
872
873 void
874 TimeAxisView::show_timestretch (framepos_t start, framepos_t end, int layers, int layer)
875 {
876         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
877                 (*i)->show_timestretch (start, end, layers, layer);
878         }
879 }
880
881 void
882 TimeAxisView::hide_timestretch ()
883 {
884         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
885                 (*i)->hide_timestretch ();
886         }
887 }
888
889 void
890 TimeAxisView::show_selection (TimeSelection& ts)
891 {
892         double x1;
893         double x2;
894         double y2;
895         SelectionRect *rect;    time_axis_frame.show();
896
897
898         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
899                 (*i)->show_selection (ts);
900         }
901
902         if (selection_group->visible ()) {
903                 while (!used_selection_rects.empty()) {
904                         free_selection_rects.push_front (used_selection_rects.front());
905                         used_selection_rects.pop_front();
906                         free_selection_rects.front()->rect->hide();
907                         free_selection_rects.front()->start_trim->hide();
908                         free_selection_rects.front()->end_trim->hide();
909                 }
910                 selection_group->hide();
911         }
912
913         selection_group->show();
914         selection_group->raise_to_top();
915
916         for (list<AudioRange>::iterator i = ts.begin(); i != ts.end(); ++i) {
917                 framepos_t start, end;
918                 framecnt_t cnt;
919
920                 start = (*i).start;
921                 end = (*i).end;
922                 cnt = end - start + 1;
923
924                 rect = get_selection_rect ((*i).id);
925
926                 x1 = _editor.sample_to_pixel (start);
927                 x2 = _editor.sample_to_pixel (start + cnt - 1);
928                 y2 = current_height() - 1;
929
930                 rect->rect->set (ArdourCanvas::Rect (x1, 0, x2, y2));
931
932                 // trim boxes are at the top for selections
933
934                 if (x2 > x1) {
935                         rect->start_trim->set (ArdourCanvas::Rect (x1, 0, x1 + trim_handle_size, y2));
936                         rect->end_trim->set (ArdourCanvas::Rect (x2 - trim_handle_size, 1, x2, y2));
937
938                         rect->start_trim->show();
939                         rect->end_trim->show();
940                 } else {
941                         rect->start_trim->hide();
942                         rect->end_trim->hide();
943                 }
944
945                 rect->rect->show ();
946                 used_selection_rects.push_back (rect);
947         }
948 }
949
950 void
951 TimeAxisView::reshow_selection (TimeSelection& ts)
952 {
953         show_selection (ts);
954
955         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
956                 (*i)->show_selection (ts);
957         }
958 }
959
960 void
961 TimeAxisView::hide_selection ()
962 {
963         if (selection_group->visible ()) {
964                 while (!used_selection_rects.empty()) {
965                         free_selection_rects.push_front (used_selection_rects.front());
966                         used_selection_rects.pop_front();
967                         free_selection_rects.front()->rect->hide();
968                         free_selection_rects.front()->start_trim->hide();
969                         free_selection_rects.front()->end_trim->hide();
970                 }
971                 selection_group->hide();
972         }
973
974         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
975                 (*i)->hide_selection ();
976         }
977 }
978
979 void
980 TimeAxisView::order_selection_trims (ArdourCanvas::Item *item, bool put_start_on_top)
981 {
982         /* find the selection rect this is for. we have the item corresponding to one
983            of the trim handles.
984          */
985
986         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
987                 if ((*i)->start_trim == item || (*i)->end_trim == item) {
988
989                         /* make one trim handle be "above" the other so that if they overlap,
990                            the top one is the one last used.
991                         */
992
993                         (*i)->rect->raise_to_top ();
994                         (put_start_on_top ? (*i)->start_trim : (*i)->end_trim)->raise_to_top ();
995                         (put_start_on_top ? (*i)->end_trim : (*i)->start_trim)->raise_to_top ();
996
997                         break;
998                 }
999         }
1000 }
1001
1002 SelectionRect *
1003 TimeAxisView::get_selection_rect (uint32_t id)
1004 {
1005         SelectionRect *rect;
1006
1007         /* check to see if we already have a visible rect for this particular selection ID */
1008
1009         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1010                 if ((*i)->id == id) {
1011                         return (*i);
1012                 }
1013         }
1014
1015         /* ditto for the free rect list */
1016
1017         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1018                 if ((*i)->id == id) {
1019                         SelectionRect* ret = (*i);
1020                         free_selection_rects.erase (i);
1021                         return ret;
1022                 }
1023         }
1024
1025         /* no existing matching rect, so go get a new one from the free list, or create one if there are none */
1026
1027         if (free_selection_rects.empty()) {
1028
1029                 rect = new SelectionRect;
1030
1031                 rect->rect = new ArdourCanvas::TimeRectangle (selection_group);
1032                 CANVAS_DEBUG_NAME (rect->rect, "selection rect");
1033                 rect->rect->set_outline (false);
1034                 rect->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1035
1036                 rect->start_trim = new ArdourCanvas::Rectangle (selection_group);
1037                 CANVAS_DEBUG_NAME (rect->start_trim, "selection rect start trim");
1038                 rect->start_trim->set_outline (false);
1039                 rect->start_trim->set_fill (false);
1040
1041                 rect->end_trim = new ArdourCanvas::Rectangle (selection_group);
1042                 CANVAS_DEBUG_NAME (rect->end_trim, "selection rect end trim");
1043                 rect->end_trim->set_outline (false);
1044                 rect->end_trim->set_fill (false);
1045
1046                 free_selection_rects.push_front (rect);
1047
1048                 rect->rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_rect_event), rect->rect, rect));
1049                 rect->start_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_start_trim_event), rect->rect, rect));
1050                 rect->end_trim->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_selection_end_trim_event), rect->rect, rect));
1051         }
1052
1053         rect = free_selection_rects.front();
1054         rect->id = id;
1055         free_selection_rects.pop_front();
1056         return rect;
1057 }
1058
1059 struct null_deleter { void operator()(void const *) const {} };
1060
1061 bool
1062 TimeAxisView::is_child (TimeAxisView* tav)
1063 {
1064         return find (children.begin(), children.end(), boost::shared_ptr<TimeAxisView>(tav, null_deleter())) != children.end();
1065 }
1066
1067 void
1068 TimeAxisView::add_child (boost::shared_ptr<TimeAxisView> child)
1069 {
1070         children.push_back (child);
1071 }
1072
1073 void
1074 TimeAxisView::remove_child (boost::shared_ptr<TimeAxisView> child)
1075 {
1076         Children::iterator i;
1077
1078         if ((i = find (children.begin(), children.end(), child)) != children.end()) {
1079                 children.erase (i);
1080         }
1081 }
1082
1083 /** Get selectable things within a given range.
1084  *  @param start Start time in session frames.
1085  *  @param end End time in session frames.
1086  *  @param top Top y range, in trackview coordinates (ie 0 is the top of the track view)
1087  *  @param bot Bottom y range, in trackview coordinates (ie 0 is the top of the track view)
1088  *  @param result Filled in with selectable things.
1089  */
1090 void
1091 TimeAxisView::get_selectables (framepos_t /*start*/, framepos_t /*end*/, double /*top*/, double /*bot*/, list<Selectable*>& /*result*/)
1092 {
1093         return;
1094 }
1095
1096 void
1097 TimeAxisView::get_inverted_selectables (Selection& /*sel*/, list<Selectable*>& /*result*/)
1098 {
1099         return;
1100 }
1101
1102 void
1103 TimeAxisView::add_ghost (RegionView* rv)
1104 {
1105         GhostRegion* gr = rv->add_ghost (*this);
1106
1107         if (gr) {
1108                 ghosts.push_back(gr);
1109         }
1110 }
1111
1112 void
1113 TimeAxisView::remove_ghost (RegionView* rv)
1114 {
1115         rv->remove_ghost_in (*this);
1116 }
1117
1118 void
1119 TimeAxisView::erase_ghost (GhostRegion* gr)
1120 {
1121         if (in_destructor) {
1122                 return;
1123         }
1124
1125         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1126                 if ((*i) == gr) {
1127                         ghosts.erase (i);
1128                         break;
1129                 }
1130         }
1131 }
1132
1133 bool
1134 TimeAxisView::touched (double top, double bot)
1135 {
1136         /* remember: this is X Window - coordinate space starts in upper left and moves down.
1137           y_position is the "origin" or "top" of the track.
1138         */
1139
1140         double mybot = _y_position + current_height();
1141
1142         return ((_y_position <= bot && _y_position >= top) ||
1143                 ((mybot <= bot) && (top < mybot)) ||
1144                 (mybot >= bot && _y_position < top));
1145 }
1146
1147 void
1148 TimeAxisView::set_parent (TimeAxisView& p)
1149 {
1150         parent = &p;
1151 }
1152
1153 void
1154 TimeAxisView::reset_height ()
1155 {
1156         set_height (height);
1157
1158         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1159                 (*i)->set_height ((*i)->height);
1160         }
1161 }
1162
1163 void
1164 TimeAxisView::compute_heights ()
1165 {
1166         // TODO this function should be re-evaluated when font-scaling changes (!)
1167         Gtk::Window window (Gtk::WINDOW_TOPLEVEL);
1168         Gtk::Table one_row_table (1, 1);
1169         ArdourButton* test_button = manage (new ArdourButton);
1170         const int border_width = 2;
1171         const int frame_height = 2;
1172         extra_height = (2 * border_width) + frame_height;
1173
1174         window.add (one_row_table);
1175         test_button->set_name ("mute button");
1176         test_button->set_text (_("M"));
1177         test_button->set_tweaks (ArdourButton::TrackHeader);
1178
1179         one_row_table.set_border_width (border_width);
1180         one_row_table.set_row_spacings (2);
1181         one_row_table.set_col_spacings (2);
1182
1183         one_row_table.attach (*test_button, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
1184         one_row_table.show_all ();
1185
1186         Gtk::Requisition req(one_row_table.size_request ());
1187         button_height = req.height;
1188 }
1189
1190 void
1191 TimeAxisView::color_handler ()
1192 {
1193         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); i++) {
1194                 (*i)->set_colors();
1195         }
1196
1197         for (list<SelectionRect*>::iterator i = used_selection_rects.begin(); i != used_selection_rects.end(); ++i) {
1198
1199                 (*i)->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1200                 (*i)->rect->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1201
1202                 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1203                 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1204                 
1205                 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1206                 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1207         }
1208         
1209         for (list<SelectionRect*>::iterator i = free_selection_rects.begin(); i != free_selection_rects.end(); ++i) {
1210                 
1211                 (*i)->rect->set_fill_color (ARDOUR_UI::config()->color_mod ("selection rect", "selection rect"));
1212                 (*i)->rect->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1213                 
1214                 (*i)->start_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1215                 (*i)->start_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1216                 
1217                 (*i)->end_trim->set_fill_color (ARDOUR_UI::config()->color ("selection"));
1218                 (*i)->end_trim->set_outline_color (ARDOUR_UI::config()->color ("selection"));
1219         }
1220 }
1221
1222 /** @return Pair: TimeAxisView, layer index.
1223  * TimeAxisView is non-0 if this object covers @param y, or one of its children
1224  * does. @param y is an offset from the top of the trackview area.
1225  *
1226  * If the covering object is a child axis, then the child is returned.
1227  * TimeAxisView is 0 otherwise.
1228  *
1229  * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid
1230  * and is in stacked or expanded * region display mode, otherwise 0.
1231  */
1232 std::pair<TimeAxisView*, double>
1233 TimeAxisView::covers_y_position (double y) const
1234 {
1235         if (hidden()) {
1236                 return std::make_pair ((TimeAxisView *) 0, 0);
1237         }
1238
1239         if (_y_position <= y && y < (_y_position + height)) {
1240
1241                 /* work out the layer index if appropriate */
1242                 double l = 0;
1243                 switch (layer_display ()) {
1244                 case Overlaid:
1245                         break;
1246                 case Stacked:
1247                         if (view ()) {
1248                                 /* compute layer */
1249                                 l = layer_t ((_y_position + height - y) / (view()->child_height ()));
1250                                 /* clamp to max layers to be on the safe side; sometimes the above calculation
1251                                    returns a too-high value */
1252                                 if (l >= view()->layers ()) {
1253                                         l = view()->layers() - 1;
1254                                 }
1255                         }
1256                         break;
1257                 case Expanded:
1258                         if (view ()) {
1259                                 int const n = floor ((_y_position + height - y) / (view()->child_height ()));
1260                                 l = n * 0.5 - 0.5;
1261                                 if (l >= (view()->layers() - 0.5)) {
1262                                         l = view()->layers() - 0.5;
1263                                 }
1264                         }
1265                         break;
1266                 }
1267
1268                 return std::make_pair (const_cast<TimeAxisView*>(this), l);
1269         }
1270
1271         for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1272
1273                 std::pair<TimeAxisView*, int> const r = (*i)->covers_y_position (y);
1274                 if (r.first) {
1275                         return r;
1276                 }
1277         }
1278
1279         return std::make_pair ((TimeAxisView *) 0, 0);
1280 }
1281
1282 bool
1283 TimeAxisView::covered_by_y_range (double y0, double y1) const
1284 {
1285         if (hidden()) {
1286                 return false;
1287         }
1288
1289         /* if either the top or bottom of the axisview is in the vertical
1290          * range, we cover it.
1291          */
1292         
1293         if ((y0 < _y_position && y1 < _y_position) ||
1294             (y0 >= _y_position + height && y1 >= _y_position + height)) {
1295                 return false;
1296         }
1297
1298         for (Children::const_iterator i = children.begin(); i != children.end(); ++i) {
1299                 if ((*i)->covered_by_y_range (y0, y1)) {
1300                         return true;
1301                 }
1302         }
1303
1304         return true;
1305 }
1306
1307 uint32_t
1308 TimeAxisView::preset_height (Height h)
1309 {
1310         switch (h) {
1311         case HeightLargest:
1312                 return (button_height * 2) + extra_height + 260;
1313         case HeightLarger:
1314                 return (button_height * 2) + extra_height + 160;
1315         case HeightLarge:
1316                 return (button_height * 2) + extra_height + 60;
1317         case HeightNormal:
1318                 return (button_height * 2) + extra_height + 10;
1319         case HeightSmall:
1320                 return button_height + extra_height;
1321         }
1322
1323         abort(); /* NOTREACHED */
1324         return 0;
1325 }
1326
1327 /** @return Child time axis views that are not hidden */
1328 TimeAxisView::Children
1329 TimeAxisView::get_child_list ()
1330 {
1331         Children c;
1332
1333         for (Children::iterator i = children.begin(); i != children.end(); ++i) {
1334                 if (!(*i)->hidden()) {
1335                         c.push_back(*i);
1336                 }
1337         }
1338
1339         return c;
1340 }
1341
1342 void
1343 TimeAxisView::build_size_menu ()
1344 {
1345         if (_size_menu && _size_menu->gobj ()) {
1346                 return;
1347         }
1348
1349         delete _size_menu;
1350
1351         using namespace Menu_Helpers;
1352
1353         _size_menu = new Menu;
1354         _size_menu->set_name ("ArdourContextMenu");
1355         MenuList& items = _size_menu->items();
1356
1357         items.push_back (MenuElem (_("Largest"), sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLargest, true)));
1358         items.push_back (MenuElem (_("Larger"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarger, true)));
1359         items.push_back (MenuElem (_("Large"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightLarge, true)));
1360         items.push_back (MenuElem (_("Normal"),  sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightNormal, true)));
1361         items.push_back (MenuElem (_("Small"),   sigc::bind (sigc::mem_fun (*this, &TimeAxisView::set_height_enum), HeightSmall, true)));
1362 }
1363
1364 void
1365 TimeAxisView::reset_visual_state ()
1366 {
1367         /* this method is not required to trigger a global redraw */
1368
1369         string str = gui_property ("height");
1370         
1371         if (!str.empty()) {
1372                 set_height (atoi (str));
1373         } else {
1374                 set_height (preset_height (HeightNormal));
1375         }
1376 }
1377
1378 TrackViewList
1379 TrackViewList::filter_to_unique_playlists ()
1380 {
1381         std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists;
1382         TrackViewList ts;
1383
1384         for (iterator i = begin(); i != end(); ++i) {
1385                 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
1386                 if (!rtav) {
1387                         /* not a route: include it anyway */
1388                         ts.push_back (*i);
1389                 } else {
1390                         boost::shared_ptr<ARDOUR::Track> t = rtav->track();
1391                         if (t) {
1392                                 if (playlists.insert (t->playlist()).second) {
1393                                         /* playlist not seen yet */
1394                                         ts.push_back (*i);
1395                                 }
1396                         } else {
1397                                 /* not a track: include it anyway */
1398                                 ts.push_back (*i);
1399                         }
1400                 }
1401         }
1402         return ts;
1403 }