2501f93726dcedef1f134cc59a586d9bafc230ec
[ardour.git] / gtk2_ardour / editor_regions.cc
1 /*
2     Copyright (C) 2000-2005 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 <sstream>
25
26 #include "pbd/basename.h"
27
28 #include "ardour/audioregion.h"
29 #include "ardour/audiofilesource.h"
30 #include "ardour/silentfilesource.h"
31 #include "ardour/session_region.h"
32 #include "ardour/profile.h"
33
34 #include <gtkmm2ext/stop_signal.h>
35
36 #include "editor.h"
37 #include "editing.h"
38 #include "keyboard.h"
39 #include "ardour_ui.h"
40 #include "gui_thread.h"
41 #include "actions.h"
42 #include "region_view.h"
43 #include "utils.h"
44 #include "editor_regions.h"
45
46 #include "i18n.h"
47
48 using namespace std;
49 using namespace ARDOUR;
50 using namespace PBD;
51 using namespace Gtk;
52 using namespace Glib;
53 using namespace Editing;
54 using Gtkmm2ext::Keyboard;
55
56 EditorRegions::EditorRegions (Editor* e)
57         : EditorComponent (e),
58           _menu (0),
59           _show_automatic_regions (true),
60           _sort_type ((Editing::RegionListSortType) 0),
61           _no_redisplay (false)
62 {
63         _display.set_size_request (100, -1);
64         _display.set_name ("RegionListDisplay");
65         /* Try to prevent single mouse presses from initiating edits.
66            This relies on a hack in gtktreeview.c:gtk_treeview_button_press()
67         */
68         _display.set_data ("mouse-edits-require-mod1", (gpointer) 0x1);
69
70         _model = TreeStore::create (_columns);
71         _model->set_sort_func (0, sigc::mem_fun (*this, &EditorRegions::sorter));
72         _model->set_sort_column (0, SORT_ASCENDING);
73
74         _display.set_model (_model);
75         _display.append_column (_("Regions"), _columns.name);
76         _display.append_column (_("Start"), _columns.start);
77         _display.append_column (_("End"), _columns.end);
78         _display.append_column (_("Length"), _columns.length);
79         _display.append_column (_("Sync"), _columns.sync);
80         _display.append_column (_("Fade In"), _columns.fadein);
81         _display.append_column (_("Fade Out"), _columns.fadeout);
82         _display.append_column (_("L"), _columns.locked);
83         _display.append_column (_("G"), _columns.glued);
84         _display.append_column (_("M"), _columns.muted);
85         _display.append_column (_("O"), _columns.opaque);
86         _display.append_column (_("Used"), _columns.used);
87         _display.append_column (_("Path"), _columns.path);
88         _display.set_headers_visible (true);
89         //_display.set_grid_lines (TREE_VIEW_GRID_LINES_BOTH);
90
91         CellRendererText* region_name_cell = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (0));
92         region_name_cell->property_editable() = true;
93         region_name_cell->signal_edited().connect (sigc::mem_fun (*this, &EditorRegions::name_edit));
94
95         _display.get_selection()->set_select_function (sigc::mem_fun (*this, &EditorRegions::selection_filter));
96
97         TreeViewColumn* tv_col = _display.get_column(0);
98         CellRendererText* renderer = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (0));
99         tv_col->add_attribute(renderer->property_text(), _columns.name);
100         tv_col->add_attribute(renderer->property_foreground_gdk(), _columns.color_);
101
102         _display.get_selection()->set_mode (SELECTION_MULTIPLE);
103         _display.add_object_drag (_columns.region.index(), "regions");
104
105         /* setup DnD handling */
106
107         list<TargetEntry> region_list_target_table;
108
109         region_list_target_table.push_back (TargetEntry ("text/plain"));
110         region_list_target_table.push_back (TargetEntry ("text/uri-list"));
111         region_list_target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
112
113         _display.add_drop_targets (region_list_target_table);
114         _display.signal_drag_data_received().connect (sigc::mem_fun(*this, &EditorRegions::drag_data_received));
115
116         _scroller.add (_display);
117         _scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC);
118
119         _display.signal_key_press_event().connect (sigc::mem_fun(*this, &EditorRegions::key_press));
120         _display.signal_key_release_event().connect (sigc::mem_fun(*this, &EditorRegions::key_release));
121         _display.signal_button_press_event().connect (sigc::mem_fun(*this, &EditorRegions::button_press), false);
122         _display.signal_button_release_event().connect (sigc::mem_fun(*this, &EditorRegions::button_release));
123         _change_connection = _display.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &EditorRegions::selection_changed));
124         // _display.signal_popup_menu().connect (sigc::bind (sigc::mem_fun (*this, &Editor::show__display_context_menu), 1, 0));
125
126         //ARDOUR_UI::instance()->secondary_clock.mode_changed.connect (sigc::mem_fun(*this, &Editor::redisplay_regions));
127         ARDOUR_UI::instance()->secondary_clock.mode_changed.connect (sigc::mem_fun(*this, &EditorRegions::update_all_rows));
128         ARDOUR::Region::RegionPropertyChanged.connect (sigc::mem_fun(*this, &EditorRegions::update_row));
129
130 }
131
132 void
133 EditorRegions::set_session (ARDOUR::Session* s)
134 {
135         EditorComponent::set_session (s);
136
137         if (_session) {
138                 _session_connections.add_connection (_session->RegionsAdded.connect (boost::bind (&EditorRegions::handle_new_regions, this, _1)));
139                 _session_connections.add_connection (_session->RegionRemoved.connect (boost::bind (&EditorRegions::handle_region_removed, this, _1)));
140                 _session_connections.add_connection (_session->RegionHiddenChange.connect (boost::bind (&EditorRegions::region_hidden, this, _1)));
141         }
142
143         redisplay ();
144 }
145
146 void
147 EditorRegions::handle_region_removed (boost::weak_ptr<Region> wregion)
148 {
149         ENSURE_GUI_THREAD (*this, &EditorRegions::handle_region_removed, wregion)
150
151         redisplay ();
152 }
153
154 void
155 EditorRegions::handle_new_regions (vector<boost::weak_ptr<Region> >& v)
156 {
157         ENSURE_GUI_THREAD (*this, &EditorRegions::handle_new_regions, v)
158         add_regions (v);
159 }
160
161 void
162 EditorRegions::region_hidden_weak (boost::weak_ptr<Region> wr)
163 {
164         boost::shared_ptr<Region> r (wr.lock());
165
166         if (r) {
167                 region_hidden (r);
168         }
169 }
170
171 void
172 EditorRegions::region_hidden (boost::shared_ptr<Region> r)
173 {
174         ENSURE_GUI_THREAD (*this, &EditorRegions::region_hidden, r)
175         redisplay ();
176 }
177
178
179 void
180 EditorRegions::add_regions (vector<boost::weak_ptr<Region> >& regions)
181 {
182         for (vector<boost::weak_ptr<Region> >::iterator x = regions.begin(); x != regions.end(); ++x) {
183                 boost::shared_ptr<Region> region ((*x).lock());
184                 if (region) {
185                         add_region (region);
186                 }
187         }
188 }
189
190 void
191 EditorRegions::add_region (boost::shared_ptr<Region> region)
192 {
193         if (!region || !_session) {
194                 return;
195         }
196
197         string str;
198         TreeModel::Row row;
199         Gdk::Color c;
200         bool missing_source = boost::dynamic_pointer_cast<SilentFileSource>(region->source());
201
202         if (!_show_automatic_regions && region->automatic()) {
203                 return;
204         }
205
206         if (region->hidden()) {
207                 TreeModel::iterator iter = _model->get_iter ("0");
208                 TreeModel::Row parent;
209                 TreeModel::Row child;
210
211                 if (!iter) {
212                         parent = *(_model->append());
213                         parent[_columns.name] = _("Hidden");
214                         boost::shared_ptr<Region> proxy = parent[_columns.region];
215                         proxy.reset ();
216                 } else {
217                         if ((*iter)[_columns.name] != _("Hidden")) {
218                                 parent = *(_model->insert(iter));
219                                 parent[_columns.name] = _("Hidden");
220                                 boost::shared_ptr<Region> proxy = parent[_columns.region];
221                                 proxy.reset ();
222                         } else {
223                                 parent = *iter;
224                         }
225                 }
226
227                 row = *(_model->append (parent.children()));
228
229         } else if (region->whole_file()) {
230
231                 TreeModel::iterator i;
232                 TreeModel::Children rows = _model->children();
233
234                 for (i = rows.begin(); i != rows.end(); ++i) {
235                         boost::shared_ptr<Region> rr = (*i)[_columns.region];
236
237                         if (rr && region->region_list_equivalent (rr)) {
238                                 return;
239                         }
240                 }
241
242                 row = *(_model->append());
243
244                 if (missing_source) {
245                         c.set_rgb(65535,0,0);     // FIXME: error color from style
246
247                 } else if (region->automatic()){
248                         c.set_rgb(0,65535,0);     // FIXME: error color from style
249
250                 } else {
251                         set_color(c, rgba_from_style ("RegionListWholeFile", 0xff, 0, 0, 0, "fg", Gtk::STATE_NORMAL, false ));
252
253                 }
254
255                 row[_columns.color_] = c;
256
257                 if (region->source()->name()[0] == '/') { // external file
258
259                         if (region->whole_file()) {
260
261                                 boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(region->source());
262                                 str = ".../";
263
264                                 if (afs) {
265                                         str = region_name_from_path (afs->path(), region->n_channels() > 1);
266                                 } else {
267                                         str += region->source()->name();
268                                 }
269
270                         } else {
271                                 str = region->name();
272                         }
273
274                 } else {
275                         str = region->name();
276                 }
277
278                 if (region->n_channels() > 1) {
279                         std::stringstream foo;
280                         foo << region->n_channels ();
281                         str += " [";
282                         str += foo.str();
283                         str += "]";
284                 }
285
286                 row[_columns.name] = str;
287                 row[_columns.region] = region;
288
289                 if (missing_source) {
290                         row[_columns.path] = _("(MISSING) ") + region->source()->name();
291
292                 } else {
293                         row[_columns.path] = region->source()->name();
294
295                 }
296
297                 if (region->automatic()) {
298                         return;
299                 }
300
301         } else {
302
303                 /* find parent node, add as new child */
304
305                 TreeModel::iterator i;
306                 TreeModel::Children rows = _model->children();
307                 bool found_parent = false;
308
309                 for (i = rows.begin(); i != rows.end(); ++i) {
310                         boost::shared_ptr<Region> rr = (*i)[_columns.region];
311                         boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion>(rr);
312
313                         if (r && r->whole_file()) {
314
315                                 if (region->source_equivalent (r)) {
316                                         row = *(_model->append ((*i).children()));
317                                         found_parent = true;
318                                         break;
319                                 }
320                         }
321
322                         TreeModel::iterator ii;
323                         TreeModel::Children subrows = (*i).children();
324
325                         for (ii = subrows.begin(); ii != subrows.end(); ++ii) {
326                                 boost::shared_ptr<Region> rrr = (*ii)[_columns.region];
327
328                                 if (region->region_list_equivalent (rrr)) {
329                                         return;
330
331                                 }
332                         }
333                 }
334
335                 if (!found_parent) {
336                         row = *(_model->append());
337                 }
338         }
339
340         row[_columns.region] = region;
341
342         populate_row(region, (*row));
343 }
344
345
346 void
347 EditorRegions::region_changed (Change what_changed, boost::weak_ptr<Region> region)
348 {
349         ENSURE_GUI_THREAD (*this, &EditorRegions::region_changed, what_changed, region)
350
351         boost::shared_ptr<Region> r = region.lock ();
352
353         if (!r) {
354                 return;
355         }
356
357         if (what_changed & ARDOUR::NameChanged) {
358                 /* find the region in our model and change its name */
359                 TreeModel::Children rows = _model->children ();
360                 TreeModel::iterator i = rows.begin ();
361                 while (i != rows.end ()) {
362                         TreeModel::Children children = (*i)->children ();
363                         TreeModel::iterator j = children.begin ();
364                         while (j != children.end()) {
365                                 boost::shared_ptr<Region> c = (*j)[_columns.region];
366                                 if (c == r) {
367                                         break;
368                                 }
369                                 ++j;
370                         }
371
372                         if (j != children.end()) {
373                                 (*j)[_columns.name] = r->name ();
374                                 break;
375                         }
376
377                         ++i;
378                 }
379
380         }
381 }
382
383 void
384 EditorRegions::selection_changed ()
385 {
386         if (_display.get_selection()->count_selected_rows() > 0) {
387
388                 TreeIter iter;
389                 TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows ();
390
391                 _editor->deselect_all ();
392
393                 for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
394
395                         if (iter = _model->get_iter (*i)) {                                                                     // they could have clicked on a row that is just a placeholder, like "Hidden"
396                                 boost::shared_ptr<Region> region = (*iter)[_columns.region];
397
398                                 if (region) {
399
400                                         if (region->automatic()) {
401                                                 _display.get_selection()->unselect(*i);
402
403                                         } else {
404                                                 _change_connection.block (true);
405                                                 //editor_regions_selection_changed_connection.block(true);
406
407                                                 _editor->set_selected_regionview_from_region_list (region, Selection::Add);
408
409                                                 _change_connection.block (false);
410                                                 //editor_regions_selection_changed_connection.block(false);
411                                         }
412                                 }
413                         }
414                 }
415         } else {
416                 _editor->deselect_all ();
417         }
418 }
419
420 void
421 EditorRegions::set_selected (RegionSelection& regions)
422 {
423         for (RegionSelection::iterator iter = regions.begin(); iter != regions.end(); ++iter) {
424
425                 TreeModel::iterator i;
426                 TreeModel::Children rows = _model->children();
427                 boost::shared_ptr<Region> r ((*iter)->region());
428
429                 for (i = rows.begin(); i != rows.end(); ++i) {
430
431                         boost::shared_ptr<Region> compared_region = (*i)[_columns.region];
432
433                         if (r == compared_region) {
434                                 _display.get_selection()->select(*i);
435                                 break;
436                         }
437
438                         if (!(*i).children().empty()) {
439                                 if (set_selected_in_subrow(r, (*i), 2)) {
440                                         break;
441                                 }
442                         }
443                 }
444         }
445 }
446
447 bool
448 EditorRegions::set_selected_in_subrow (boost::shared_ptr<Region> region, TreeModel::Row const &parent_row, int level)
449 {
450         TreeModel::iterator i;
451         TreeModel::Children subrows = (*parent_row).children();
452
453         for (i = subrows.begin(); i != subrows.end(); ++i) {
454
455                 boost::shared_ptr<Region> compared_region = (*i)[_columns.region];
456
457                 if (region == compared_region) {
458                         _display.get_selection()->select(*i);
459                         return true;
460                 }
461
462                 if (!(*i).children().empty()) {
463                         if (set_selected_in_subrow (region, (*i), level + 1)) {
464                                 return true;
465                         }
466                 }
467         }
468         return false;
469 }
470
471 void
472 EditorRegions::insert_into_tmp_regionlist(boost::shared_ptr<Region> region)
473 {
474         /* keep all whole files at the beginning */
475
476         if (region->whole_file()) {
477                 tmp_region_list.push_front (region);
478         } else {
479                 tmp_region_list.push_back (region);
480         }
481 }
482
483 void
484 EditorRegions::redisplay ()
485 {
486         if (_no_redisplay || !_session) {
487                 return;
488         }
489
490         bool tree_expanded = false;
491
492         if (_toggle_full_action && _toggle_full_action->get_active()) {   //If the list was expanded prior to rebuilding,
493                 tree_expanded = true;                                                                                                                           //expand it again afterwards
494         }
495
496         _display.set_model (Glib::RefPtr<Gtk::TreeStore>(0));
497         _model->clear ();
498
499         /* now add everything we have, via a temporary list used to help with
500                 sorting.
501         */
502
503         tmp_region_list.clear();
504         _session->foreach_region (this, &EditorRegions::insert_into_tmp_regionlist);
505
506         for (list<boost::shared_ptr<Region> >::iterator r = tmp_region_list.begin(); r != tmp_region_list.end(); ++r) {
507                 add_region (*r);
508         }
509         tmp_region_list.clear();
510
511         _display.set_model (_model);
512
513         if (tree_expanded) {
514                 _display.expand_all();
515         }
516 }
517
518 void
519 EditorRegions::update_row (boost::shared_ptr<Region> region)
520 {
521         if (!region || !_session) {
522                 return;
523         }
524
525         TreeModel::iterator i;
526         TreeModel::Children rows = _model->children();
527
528         for (i = rows.begin(); i != rows.end(); ++i) {
529
530 //              cerr << "Level 1: Compare " << region->name() << " with parent " << (*i)[_columns.name] << "\n";
531
532                 boost::shared_ptr<Region> compared_region = (*i)[_columns.region];
533
534                 if (region == compared_region) {
535 //                      cerr << "Matched\n";
536                         populate_row(region, (*i));
537                         return;
538                 }
539
540                 if (!(*i).children().empty()) {
541                         if (update_subrows(region, (*i), 2)) {
542                                 return;
543                         }
544                 }
545         }
546 //      cerr << "Returning - No match\n";
547 }
548
549 bool
550 EditorRegions::update_subrows (boost::shared_ptr<Region> region, TreeModel::Row const &parent_row, int level)
551 {
552         TreeModel::iterator i;
553         TreeModel::Children subrows = (*parent_row).children();
554
555         for (i = subrows.begin(); i != subrows.end(); ++i) {
556
557 //              cerr << "Level " << level << ": Compare " << region->name() << " with child " << (*i)[_columns.name] << "\n";
558
559                 boost::shared_ptr<Region> compared_region = (*i)[_columns.region];
560
561                 if (region == compared_region) {
562                         populate_row(region, (*i));
563 //                      cerr << "Matched\n";
564                         return true;
565                 }
566
567                 if (!(*i).children().empty()) {
568                         if (update_subrows (region, (*i), level + 1)) {
569                                 return true;
570                         }
571                 }
572         }
573         return false;
574 }
575
576 void
577 EditorRegions::update_all_rows ()
578 {
579         if (!_session) {
580                 return;
581         }
582
583         TreeModel::iterator i;
584         TreeModel::Children rows = _model->children();
585
586         for (i = rows.begin(); i != rows.end(); ++i) {
587
588                 boost::shared_ptr<Region> region = (*i)[_columns.region];
589
590                 if (!region->automatic()) {
591                         cerr << "level 1 : Updating " << region->name() << "\n";
592                         populate_row(region, (*i));
593                 }
594
595                 if (!(*i).children().empty()) {
596                         update_all_subrows ((*i), 2);
597                 }
598         }
599 }
600
601 void
602 EditorRegions::update_all_subrows (TreeModel::Row const &parent_row, int level)
603 {
604         TreeModel::iterator i;
605         TreeModel::Children subrows = (*parent_row).children();
606
607         for (i = subrows.begin(); i != subrows.end(); ++i) {
608
609                 boost::shared_ptr<Region> region = (*i)[_columns.region];
610
611                 if (!region->automatic()) {
612                         cerr << "level " << level << " : Updating " << region->name() << "\n";
613                         populate_row(region, (*i));
614                 }
615
616                 if (!(*i).children().empty()) {
617                         update_all_subrows ((*i), level + 1);
618                 }
619         }
620 }
621
622 void
623 EditorRegions::populate_row (boost::shared_ptr<Region> region, TreeModel::Row const &row)
624 {
625         char start_str[16];
626         char end_str[16];
627         char length_str[16];
628         char sync_str[16];
629         char fadein_str[16];
630         char fadeout_str[16];
631         char used_str[8];
632         int used;
633         BBT_Time bbt;
634         Timecode::Time timecode;
635
636         bool missing_source = boost::dynamic_pointer_cast<SilentFileSource>(region->source());
637
638         boost::shared_ptr<AudioRegion> audioRegion = boost::dynamic_pointer_cast<AudioRegion>(region);
639
640         bool fades_in_seconds = false;
641
642         start_str[0] = '\0';
643         end_str[0] = '\0';
644         length_str[0] = '\0';
645         sync_str[0] = '\0';
646         fadein_str[0] = '\0';
647         fadeout_str[0] = '\0';
648         used_str[0] = '\0';
649
650         used = _editor->get_regionview_count_from_region_list (region);
651         sprintf (used_str, "%4d" , used);
652
653         switch (ARDOUR_UI::instance()->secondary_clock.mode ()) {
654         case AudioClock::Timecode:
655         case AudioClock::Off:                                                                                           /* If the secondary clock is off, default to Timecode */
656                 _session->timecode_time (region->position(), timecode);
657                 sprintf (start_str, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
658                 _session->timecode_time (region->position() + region->length() - 1, timecode);
659                 sprintf (end_str, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
660                 _session->timecode_time (region->length(), timecode);
661                 sprintf (length_str, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
662                 _session->timecode_time (region->sync_position() + region->position(), timecode);
663                 sprintf (sync_str, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
664
665                 if (audioRegion && !fades_in_seconds) {
666                         _session->timecode_time (audioRegion->fade_in()->back()->when, timecode);
667                         sprintf (fadein_str, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
668                         _session->timecode_time (audioRegion->fade_out()->back()->when, timecode);
669                         sprintf (fadeout_str, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
670                 }
671
672                 break;
673
674         case AudioClock::BBT:
675                 _session->tempo_map().bbt_time (region->position(), bbt);
676                 sprintf (start_str, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks);
677                 _session->tempo_map().bbt_time (region->position() + region->length() - 1, bbt);
678                 sprintf (end_str, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks);
679                 _session->tempo_map().bbt_time (region->length(), bbt);
680                 sprintf (length_str, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks);
681                 _session->tempo_map().bbt_time (region->sync_position() + region->position(), bbt);
682                 sprintf (sync_str, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks);
683
684                 if (audioRegion && !fades_in_seconds) {
685                         _session->tempo_map().bbt_time (audioRegion->fade_in()->back()->when, bbt);
686                         sprintf (fadein_str, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks);
687                         _session->tempo_map().bbt_time (audioRegion->fade_out()->back()->when, bbt);
688                         sprintf (fadeout_str, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks);
689                 }
690                 break;
691
692         case AudioClock::MinSec:
693                 nframes_t left;
694                 int hrs;
695                 int mins;
696                 float secs;
697
698                 left = region->position();
699                 hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
700                 left -= (nframes_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
701                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
702                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
703                 secs = left / (float) _session->frame_rate();
704                 sprintf (start_str, "%02d:%02d:%06.3f", hrs, mins, secs);
705
706                 left = region->position() + region->length() - 1;
707                 hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
708                 left -= (nframes_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
709                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
710                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
711                 secs = left / (float) _session->frame_rate();
712                 sprintf (end_str, "%02d:%02d:%06.3f", hrs, mins, secs);
713
714                 left = region->length();
715                 hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
716                 left -= (nframes_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
717                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
718                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
719                 secs = left / (float) _session->frame_rate();
720                 sprintf (length_str, "%02d:%02d:%06.3f", hrs, mins, secs);
721
722                 left = region->sync_position() + region->position();
723                 hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
724                 left -= (nframes_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
725                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
726                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
727                 secs = left / (float) _session->frame_rate();
728                 sprintf (sync_str, "%02d:%02d:%06.3f", hrs, mins, secs);
729
730                 if (audioRegion && !fades_in_seconds) {
731                         left = audioRegion->fade_in()->back()->when;
732                         hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
733                         left -= (nframes_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
734                         mins = (int) floor (left / (_session->frame_rate() * 60.0f));
735                         left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
736                         secs = left / (float) _session->frame_rate();
737                         sprintf (fadein_str, "%02d:%02d:%06.3f", hrs, mins, secs);
738
739                         left = audioRegion->fade_out()->back()->when;
740                         hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
741                         left -= (nframes_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
742                         mins = (int) floor (left / (_session->frame_rate() * 60.0f));
743                         left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
744                         secs = left / (float) _session->frame_rate();
745                         sprintf (fadeout_str, "%02d:%02d:%06.3f", hrs, mins, secs);
746                 }
747
748                 break;
749
750         case AudioClock::Frames:
751                 snprintf (start_str, sizeof (start_str), "%u", region->position());
752                 snprintf (end_str, sizeof (end_str), "%u", (region->position() + region->length() - 1));
753                 snprintf (length_str, sizeof (length_str), "%u", region->length());
754                 snprintf (sync_str, sizeof (sync_str), "%u", region->sync_position() + region->position());
755
756                 if (audioRegion && !fades_in_seconds) {
757                         snprintf (fadein_str, sizeof (fadein_str), "%u", uint (audioRegion->fade_in()->back()->when));
758                         snprintf (fadeout_str, sizeof (fadeout_str), "%u", uint (audioRegion->fade_out()->back()->when));
759                 }
760
761                 break;
762
763         default:
764                 break;
765         }
766
767         if (audioRegion && fades_in_seconds) {
768
769                 nframes_t left;
770                 int mins;
771                 int millisecs;
772
773                 left = audioRegion->fade_in()->back()->when;
774                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
775                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
776                 millisecs = (int) floor ((left * 1000.0f) / _session->frame_rate());
777
778                 if (audioRegion->fade_in()->back()->when >= _session->frame_rate()) {
779                         sprintf (fadein_str, "%01dM %01dmS", mins, millisecs);
780                 } else {
781                         sprintf (fadein_str, "%01dmS", millisecs);
782                 }
783
784                 left = audioRegion->fade_out()->back()->when;
785                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
786                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
787                 millisecs = (int) floor ((left * 1000.0f) / _session->frame_rate());
788
789                 if (audioRegion->fade_out()->back()->when >= _session->frame_rate()) {
790                         sprintf (fadeout_str, "%01dM %01dmS", mins, millisecs);
791                 } else {
792                         sprintf (fadeout_str, "%01dmS", millisecs);
793                 }
794         }
795
796         if (used > 1) {
797                 row[_columns.start] = _("Multiple");
798                 row[_columns.end] = _("Multiple");
799                 row[_columns.sync] = _("Multiple");
800                 row[_columns.fadein] = _("Multiple");
801                 row[_columns.fadeout] = _("Multiple");
802                 row[_columns.locked] = false;
803                 row[_columns.glued] = false;
804                 row[_columns.muted] = false;
805                 row[_columns.opaque] = false;
806         } else {
807                 row[_columns.start] = start_str;
808                 row[_columns.end] = end_str;
809
810                 if (region->sync_position() == 0) {
811                         row[_columns.sync] = _("Start");
812                 } else if (region->sync_position() == region->length() - 1) {
813                         row[_columns.sync] = _("End");
814                 } else {
815                         row[_columns.sync] = sync_str;
816                 }
817
818                 if (audioRegion) {
819                         if (audioRegion->fade_in_active()) {
820                                 row[_columns.fadein] = string_compose("%1%2%3", " ", fadein_str, " ");
821                         } else {
822                                 row[_columns.fadein] = string_compose("%1%2%3", "(", fadein_str, ")");
823                         }
824                 } else {
825                         row[_columns.fadein] = "";
826                 }
827
828                 if (audioRegion) {
829                         if (audioRegion->fade_out_active()) {
830                                 row[_columns.fadeout] = string_compose("%1%2%3", " ", fadeout_str, " ");
831                         } else {
832                                 row[_columns.fadeout] = string_compose("%1%2%3", "(", fadeout_str, ")");
833                         }
834                 } else {
835                         row[_columns.fadeout] = "";
836                 }
837
838                 row[_columns.locked] = region->locked();
839
840                 if (region->positional_lock_style() == Region::MusicTime) {
841                         row[_columns.glued] = true;
842                 } else {
843                         row[_columns.glued] = false;
844                 }
845
846                 row[_columns.muted] = region->muted();
847                 row[_columns.opaque] = region->opaque();
848         }
849
850         row[_columns.length] = length_str;
851         row[_columns.used] = used_str;
852
853         if (missing_source) {
854                 row[_columns.path] = _("MISSING ") + region->source()->name();
855         } else {
856                 row[_columns.path] = region->source()->name();
857         }
858
859         if (region->n_channels() > 1) {
860                 row[_columns.name] = string_compose("%1  [%2]", region->name(), region->n_channels());
861         } else {
862                 row[_columns.name] = region->name();
863         }
864 }
865
866 void
867 EditorRegions::build_menu ()
868 {
869         _menu = dynamic_cast<Menu*>(ActionManager::get_widget ("/RegionListMenu"));
870
871         /* now grab specific menu items that we need */
872
873         Glib::RefPtr<Action> act;
874
875         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAll"));
876         if (act) {
877                 _toggle_full_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
878         }
879
880         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAuto"));
881         if (act) {
882                 _toggle_show_auto_regions_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
883         }
884 }
885
886 void
887 EditorRegions::toggle_show_auto_regions ()
888 {
889         _show_automatic_regions = _toggle_show_auto_regions_action->get_active();
890         redisplay ();
891 }
892
893 void
894 EditorRegions::toggle_full ()
895 {
896         if (_toggle_full_action->get_active()) {
897                 _display.expand_all ();
898         } else {
899                 _display.collapse_all ();
900         }
901 }
902
903 void
904 EditorRegions::show_context_menu (int button, int time)
905 {
906         if (_menu == 0) {
907                 build_menu ();
908         }
909
910         if (_display.get_selection()->count_selected_rows() > 0) {
911                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, true);
912         } else {
913                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, false);
914         }
915
916         _menu->popup (button, time);
917 }
918
919 bool
920 EditorRegions::key_press (GdkEventKey* /*ev*/)
921 {
922         return false;
923 }
924
925 bool
926 EditorRegions::key_release (GdkEventKey* ev)
927 {
928         switch (ev->keyval) {
929         case GDK_Delete:
930                 remove_region ();
931                 return true;
932                 break;
933         default:
934                 break;
935         }
936
937         return false;
938 }
939
940 bool
941 EditorRegions::button_press (GdkEventButton *ev)
942 {
943         boost::shared_ptr<Region> region;
944         TreeIter iter;
945         TreeModel::Path path;
946         TreeViewColumn* column;
947         int cellx;
948         int celly;
949
950         if (_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
951                 if ((iter = _model->get_iter (path))) {
952                         region = (*iter)[_columns.region];
953                 }
954         }
955
956         if (Keyboard::is_context_menu_event (ev)) {
957                 show_context_menu (ev->button, ev->time);
958                 return true;
959         }
960
961         if (region != 0 && Keyboard::is_button2_event (ev)) {
962                 // start/stop audition
963                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
964                         _editor->consider_auditioning (region);
965                 }
966                 return true;
967         }
968
969         return false;
970 }
971
972 bool
973 EditorRegions::button_release (GdkEventButton *ev)
974 {
975         TreeIter iter;
976         TreeModel::Path path;
977         TreeViewColumn* column;
978         int cellx;
979         int celly;
980         boost::shared_ptr<Region> region;
981
982         if (_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
983                 if ((iter = _model->get_iter (path))) {
984                         region = (*iter)[_columns.region];
985                 }
986         }
987
988         if (region && Keyboard::is_delete_event (ev)) {
989                 _session->remove_region_from_region_list (region);
990                 return true;
991         }
992
993         return false;
994 }
995
996 int
997 EditorRegions::sorter (TreeModel::iterator a, TreeModel::iterator b)
998 {
999         int cmp = 0;
1000
1001         boost::shared_ptr<Region> r1 = (*a)[_columns.region];
1002         boost::shared_ptr<Region> r2 = (*b)[_columns.region];
1003
1004         /* handle rows without regions, like "Hidden" */
1005
1006         if (r1 == 0) {
1007                 return -1;
1008         }
1009
1010         if (r2 == 0) {
1011                 return 1;
1012         }
1013
1014         boost::shared_ptr<AudioRegion> region1 = boost::dynamic_pointer_cast<AudioRegion> (r1);
1015         boost::shared_ptr<AudioRegion> region2 = boost::dynamic_pointer_cast<AudioRegion> (r2);
1016
1017         if (region1 == 0 || region2 == 0) {
1018                 Glib::ustring s1;
1019                 Glib::ustring s2;
1020                 switch (_sort_type) {
1021                 case ByName:
1022                         s1 = (*a)[_columns.name];
1023                         s2 = (*b)[_columns.name];
1024                         return (s1.compare (s2));
1025                 default:
1026                         return 0;
1027                 }
1028         }
1029
1030         switch (_sort_type) {
1031         case ByName:
1032                 cmp = strcasecmp (region1->name().c_str(), region2->name().c_str());
1033                 break;
1034
1035         case ByLength:
1036                 cmp = region1->length() - region2->length();
1037                 break;
1038
1039         case ByPosition:
1040                 cmp = region1->position() - region2->position();
1041                 break;
1042
1043         case ByTimestamp:
1044                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
1045                 break;
1046
1047         case ByStartInFile:
1048                 cmp = region1->start() - region2->start();
1049                 break;
1050
1051         case ByEndInFile:
1052                 cmp = (region1->start() + region1->length()) - (region2->start() + region2->length());
1053                 break;
1054
1055         case BySourceFileName:
1056                 cmp = strcasecmp (region1->source()->name().c_str(), region2->source()->name().c_str());
1057                 break;
1058
1059         case BySourceFileLength:
1060                 cmp = region1->source_length(0) - region2->source_length(0);
1061                 break;
1062
1063         case BySourceFileCreationDate:
1064                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
1065                 break;
1066
1067         case BySourceFileFS:
1068                 if (region1->source()->name() == region2->source()->name()) {
1069                         cmp = strcasecmp (region1->name().c_str(),  region2->name().c_str());
1070                 } else {
1071                         cmp = strcasecmp (region1->source()->name().c_str(),  region2->source()->name().c_str());
1072                 }
1073                 break;
1074         }
1075
1076         if (cmp < 0) {
1077                 return -1;
1078         } else if (cmp > 0) {
1079                 return 1;
1080         } else {
1081                 return 0;
1082         }
1083 }
1084
1085 void
1086 EditorRegions::reset_sort_type (RegionListSortType type, bool force)
1087 {
1088         if (type != _sort_type || force) {
1089                 _sort_type = type;
1090                 _model->set_sort_func (0, (sigc::mem_fun (*this, &EditorRegions::sorter)));
1091         }
1092 }
1093
1094 void
1095 EditorRegions::reset_sort_direction (bool up)
1096 {
1097         _model->set_sort_column (0, up ? SORT_ASCENDING : SORT_DESCENDING);
1098 }
1099
1100 void
1101 EditorRegions::selection_mapover (sigc::slot<void,boost::shared_ptr<Region> > sl)
1102 {
1103         Glib::RefPtr<TreeSelection> selection = _display.get_selection();
1104         TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
1105         TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
1106
1107         if (selection->count_selected_rows() == 0 || _session == 0) {
1108                 return;
1109         }
1110
1111         for (; i != rows.end(); ++i) {
1112                 TreeIter iter;
1113
1114                 if ((iter = _model->get_iter (*i))) {
1115
1116                         /* some rows don't have a region associated with them, but can still be
1117                            selected (XXX maybe prevent them from being selected)
1118                         */
1119
1120                         boost::shared_ptr<Region> r = (*iter)[_columns.region];
1121
1122                         if (r) {
1123                                 sl (r);
1124                         }
1125                 }
1126         }
1127 }
1128
1129
1130 void
1131 EditorRegions::remove_region ()
1132 {
1133         selection_mapover (sigc::mem_fun (*_editor, &Editor::remove_a_region));
1134 }
1135
1136 void
1137 EditorRegions::drag_data_received (const RefPtr<Gdk::DragContext>& context,
1138                                    int x, int y,
1139                                    const SelectionData& data,
1140                                    guint info, guint time)
1141 {
1142         vector<ustring> paths;
1143
1144         if (data.get_target() == "GTK_TREE_MODEL_ROW") {
1145                 _display.on_drag_data_received (context, x, y, data, info, time);
1146                 return;
1147         }
1148
1149         if (_editor->convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
1150                 nframes64_t pos = 0;
1151                 if (Profile->get_sae() || Config->get_only_copy_imported_files()) {
1152                         _editor->do_import (paths, Editing::ImportDistinctFiles, Editing::ImportAsRegion, SrcBest, pos);
1153                 } else {
1154                         _editor->do_embed (paths, Editing::ImportDistinctFiles, ImportAsRegion, pos);
1155                 }
1156                 context->drag_finish (true, false, time);
1157         }
1158 }
1159
1160 bool
1161 EditorRegions::selection_filter (const RefPtr<TreeModel>& model, const TreeModel::Path& path, bool /*yn*/)
1162 {
1163         /* not possible to select rows that do not represent regions, like "Hidden" */
1164
1165         TreeModel::iterator iter = model->get_iter (path);
1166
1167         if (iter) {
1168                 boost::shared_ptr<Region> r =(*iter)[_columns.region];
1169                 if (!r) {
1170                         return false;
1171                 }
1172         }
1173
1174         return true;
1175 }
1176
1177 void
1178 EditorRegions::name_edit (const Glib::ustring& path, const Glib::ustring& new_text)
1179 {
1180         boost::shared_ptr<Region> region;
1181         TreeIter iter;
1182
1183         if ((iter = _model->get_iter (path))) {
1184                 region = (*iter)[_columns.region];
1185                 (*iter)[_columns.name] = new_text;
1186         }
1187
1188         /* now mapover everything */
1189
1190         if (region) {
1191                 vector<RegionView*> equivalents;
1192                 _editor->get_regions_corresponding_to (region, equivalents);
1193
1194                 for (vector<RegionView*>::iterator i = equivalents.begin(); i != equivalents.end(); ++i) {
1195                         if (new_text != (*i)->region()->name()) {
1196                                 (*i)->region()->set_name (new_text);
1197                         }
1198                 }
1199         }
1200
1201 }
1202
1203 boost::shared_ptr<Region>
1204 EditorRegions::get_dragged_region ()
1205 {
1206         list<boost::shared_ptr<Region> > regions;
1207         TreeView* source;
1208         _display.get_object_drag_data (regions, &source);
1209         assert (regions.size() == 1);
1210         return regions.front ();
1211 }
1212
1213 void
1214 EditorRegions::clear ()
1215 {
1216         _display.set_model (Glib::RefPtr<Gtk::TreeStore> (0));
1217         _model->clear ();
1218         _display.set_model (_model);
1219 }
1220
1221 boost::shared_ptr<Region>
1222 EditorRegions::get_single_selection ()
1223 {
1224         Glib::RefPtr<TreeSelection> selected = _display.get_selection();
1225
1226         if (selected->count_selected_rows() != 1) {
1227                 return boost::shared_ptr<Region> ();
1228         }
1229
1230         TreeView::Selection::ListHandle_Path rows = selected->get_selected_rows ();
1231
1232         /* only one row selected, so rows.begin() is it */
1233
1234         TreeIter iter = _model->get_iter (*rows.begin());
1235
1236         if (!iter) {
1237                 return boost::shared_ptr<Region> ();
1238         }
1239
1240         return (*iter)[_columns.region];
1241 }