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