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