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