Remove Start from region list. I don't think it's quite useful enough to be in there...
[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/session_playlists.h"
34 #include "ardour/silentfilesource.h"
35 #include "ardour/profile.h"
36
37 #include "gtkmm2ext/treeutils.h"
38
39 #include "editor.h"
40 #include "editing.h"
41 #include "keyboard.h"
42 #include "ardour_ui.h"
43 #include "gui_thread.h"
44 #include "actions.h"
45 #include "region_view.h"
46 #include "utils.h"
47 #include "editor_regions.h"
48 #include "editor_drag.h"
49
50 #include "i18n.h"
51
52 using namespace std;
53 using namespace ARDOUR;
54 using namespace PBD;
55 using namespace Gtk;
56 using namespace Glib;
57 using namespace Editing;
58 using Gtkmm2ext::Keyboard;
59
60 EditorRegions::EditorRegions (Editor* e)
61         : EditorComponent (e)
62         , old_focus (0)
63         , name_editable (0)
64         , _menu (0)
65         , _show_automatic_regions (true)
66         , _sort_type ((Editing::RegionListSortType) 0)
67         , _no_redisplay (false) 
68         , ignore_region_list_selection_change (false)
69         , ignore_selected_region_change (false)
70         , expanded (false)
71 {
72         _display.set_size_request (100, -1);
73         _display.set_name ("RegionListDisplay");
74         _display.set_rules_hint (true);
75         /* Try to prevent single mouse presses from initiating edits.
76            This relies on a hack in gtktreeview.c:gtk_treeview_button_press()
77         */
78         _display.set_data ("mouse-edits-require-mod1", (gpointer) 0x1);
79
80         _model = TreeStore::create (_columns);
81         _model->set_sort_func (0, sigc::mem_fun (*this, &EditorRegions::sorter));
82         _model->set_sort_column (0, SORT_ASCENDING);
83
84         _display.set_model (_model);
85
86         _display.append_column (_("Regions"), _columns.name);
87         _display.append_column (_("Position"), _columns.position);
88         _display.append_column (_("End"), _columns.end);
89         _display.append_column (_("Length"), _columns.length);
90         _display.append_column (_("Sync"), _columns.sync);
91         _display.append_column (_("Fade In"), _columns.fadein);
92         _display.append_column (_("Fade Out"), _columns.fadeout);
93         _display.append_column (_("L"), _columns.locked);
94         _display.append_column (_("G"), _columns.glued);
95         _display.append_column (_("M"), _columns.muted);
96         _display.append_column (_("O"), _columns.opaque);
97         // _display.append_column (_("Used"), _columns.used);
98         // _display.append_column (_("Path"), _columns.path);
99         _display.set_headers_visible (true);
100         //_display.set_grid_lines (TREE_VIEW_GRID_LINES_BOTH);
101
102         /* show path as the row tooltip */
103         _display.set_tooltip_column (15); /* path */
104
105         CellRendererText* region_name_cell = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (0));
106         region_name_cell->property_editable() = true;
107         region_name_cell->signal_edited().connect (sigc::mem_fun (*this, &EditorRegions::name_edit));
108         region_name_cell->signal_editing_started().connect (sigc::mem_fun (*this, &EditorRegions::name_editing_started));
109
110         _display.get_selection()->set_select_function (sigc::mem_fun (*this, &EditorRegions::selection_filter));
111
112         TreeViewColumn* tv_col = _display.get_column(0);
113         CellRendererText* renderer = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (0));
114         tv_col->add_attribute(renderer->property_text(), _columns.name);
115         tv_col->add_attribute(renderer->property_foreground_gdk(), _columns.color_);
116
117         CellRendererToggle* locked_cell = dynamic_cast<CellRendererToggle*> (_display.get_column_cell_renderer (7));
118         locked_cell->property_activatable() = true;
119         locked_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRegions::locked_changed));
120         TreeViewColumn* locked_col = _display.get_column (7);
121         locked_col->add_attribute (locked_cell->property_visible(), _columns.property_toggles_visible);
122
123         CellRendererToggle* glued_cell = dynamic_cast<CellRendererToggle*> (_display.get_column_cell_renderer (8));
124         glued_cell->property_activatable() = true;
125         glued_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRegions::glued_changed));
126         TreeViewColumn* glued_col = _display.get_column (8);
127         glued_col->add_attribute (glued_cell->property_visible(), _columns.property_toggles_visible);
128
129         CellRendererToggle* muted_cell = dynamic_cast<CellRendererToggle*> (_display.get_column_cell_renderer (9));
130         muted_cell->property_activatable() = true;
131         muted_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRegions::muted_changed));
132         TreeViewColumn* muted_col = _display.get_column (9);
133         muted_col->add_attribute (muted_cell->property_visible(), _columns.property_toggles_visible);
134
135         CellRendererToggle* opaque_cell = dynamic_cast<CellRendererToggle*> (_display.get_column_cell_renderer (10));
136         opaque_cell->property_activatable() = true;
137         opaque_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRegions::opaque_changed));
138         TreeViewColumn* opaque_col = _display.get_column (10);
139         opaque_col->add_attribute (opaque_cell->property_visible(), _columns.property_toggles_visible);
140         
141         _display.get_selection()->set_mode (SELECTION_MULTIPLE);
142         _display.add_object_drag (_columns.region.index(), "regions");
143
144         /* setup DnD handling */
145
146         list<TargetEntry> region_list_target_table;
147
148         region_list_target_table.push_back (TargetEntry ("text/plain"));
149         region_list_target_table.push_back (TargetEntry ("text/uri-list"));
150         region_list_target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
151
152         _display.add_drop_targets (region_list_target_table);
153         _display.signal_drag_data_received().connect (sigc::mem_fun(*this, &EditorRegions::drag_data_received));
154
155         _scroller.add (_display);
156         _scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC);
157
158         _display.signal_button_press_event().connect (sigc::mem_fun(*this, &EditorRegions::button_press), false);
159         _change_connection = _display.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &EditorRegions::selection_changed));
160
161         _scroller.signal_key_press_event().connect (sigc::mem_fun(*this, &EditorRegions::key_press), false);
162         _scroller.signal_focus_in_event().connect (sigc::mem_fun (*this, &EditorRegions::focus_in), false);
163         _scroller.signal_focus_out_event().connect (sigc::mem_fun (*this, &EditorRegions::focus_out));
164
165         _display.signal_enter_notify_event().connect (sigc::mem_fun (*this, &EditorRegions::enter_notify), false);
166         _display.signal_leave_notify_event().connect (sigc::mem_fun (*this, &EditorRegions::leave_notify), false);
167
168         // _display.signal_popup_menu().connect (sigc::bind (sigc::mem_fun (*this, &Editor::show__display_context_menu), 1, 0));
169
170         //ARDOUR_UI::instance()->secondary_clock.mode_changed.connect (sigc::mem_fun(*this, &Editor::redisplay_regions));
171         ARDOUR_UI::instance()->secondary_clock.mode_changed.connect (sigc::mem_fun(*this, &EditorRegions::update_all_rows));
172         ARDOUR::Region::RegionPropertyChanged.connect (region_property_connection, MISSING_INVALIDATOR, ui_bind (&EditorRegions::region_changed, this, _1, _2), gui_context());
173         ARDOUR::RegionFactory::CheckNewRegion.connect (check_new_region_connection, MISSING_INVALIDATOR, ui_bind (&EditorRegions::add_region, this, _1), gui_context());
174 }
175
176 bool
177 EditorRegions::focus_in (GdkEventFocus*)
178 {
179         Window* win = dynamic_cast<Window*> (_scroller.get_toplevel ());
180
181         if (win) {
182                 old_focus = win->get_focus ();
183         } else {
184                 old_focus = 0;
185         }
186
187         name_editable = 0;
188
189         /* try to do nothing on focus in (doesn't work, hence selection_count nonsense) */
190         return true;
191 }
192
193 bool
194 EditorRegions::focus_out (GdkEventFocus*)
195 {
196         if (old_focus) {
197                 old_focus->grab_focus ();
198                 old_focus = 0;
199         }
200
201         name_editable = 0;
202
203         return false;
204 }
205
206 bool
207 EditorRegions::enter_notify (GdkEventCrossing* ev)
208 {
209         /* arm counter so that ::selection_filter() will deny selecting anything for the 
210            next two attempts to change selection status.
211         */
212         _scroller.grab_focus ();
213         Keyboard::magic_widget_grab_focus ();
214         return false;
215 }
216
217 bool
218 EditorRegions::leave_notify (GdkEventCrossing*)
219 {
220         if (old_focus) {
221                 old_focus->grab_focus ();
222                 old_focus = 0;
223         }
224
225         name_editable = 0;
226         Keyboard::magic_widget_drop_focus ();
227         return false;
228 }
229
230 void
231 EditorRegions::set_session (ARDOUR::Session* s)
232 {
233         SessionHandlePtr::set_session (s);
234         redisplay ();
235 }
236
237 void
238 EditorRegions::add_regions (vector<boost::shared_ptr<Region> >& regions)
239 {
240         for (vector<boost::shared_ptr<Region> >::iterator x = regions.begin(); x != regions.end(); ++x) {
241                 add_region (*x);
242         }
243 }
244
245 void
246 EditorRegions::add_region (boost::shared_ptr<Region> region)
247 {
248         if (!region || !_session) {
249                 return;
250         }
251
252         string str;
253         TreeModel::Row row;
254         Gdk::Color c;
255         bool missing_source = boost::dynamic_pointer_cast<SilentFileSource>(region->source());
256
257         if (!_show_automatic_regions && region->automatic()) {
258                 return;
259         }
260
261         if (region->hidden()) {
262                 TreeModel::iterator iter = _model->get_iter ("0");
263                 TreeModel::Row parent;
264                 TreeModel::Row child;
265
266                 if (!iter) {
267                         parent = *(_model->append());
268                         parent[_columns.name] = _("Hidden");
269                         boost::shared_ptr<Region> proxy = parent[_columns.region];
270                         proxy.reset ();
271                 } else {
272                         string s = (*iter)[_columns.name];
273                         if (s != _("Hidden")) {
274                                 parent = *(_model->insert(iter));
275                                 parent[_columns.name] = _("Hidden");
276                                 boost::shared_ptr<Region> proxy = parent[_columns.region];
277                                 proxy.reset ();
278                         } else {
279                                 parent = *iter;
280                         }
281                 }
282                 row = *(_model->append (parent.children()));
283
284         } else if (region->whole_file()) {
285
286                 TreeModel::iterator i;
287                 TreeModel::Children rows = _model->children();
288
289                 for (i = rows.begin(); i != rows.end(); ++i) {
290                         boost::shared_ptr<Region> rr = (*i)[_columns.region];
291
292                         if (rr && region->region_list_equivalent (rr)) {
293                                 return;
294                         }
295                 }
296
297                 row = *(_model->append());
298
299                 if (missing_source) {
300                         c.set_rgb(65535,0,0);     // FIXME: error color from style
301
302                 } else if (region->automatic()){
303                         c.set_rgb(0,65535,0);     // FIXME: error color from style
304
305                 } else {
306                         set_color(c, rgba_from_style ("RegionListWholeFile", 0xff, 0, 0, 0, "fg", Gtk::STATE_NORMAL, false ));
307
308                 }
309
310                 row[_columns.color_] = c;
311
312                 if (region->source()->name()[0] == '/') { // external file
313
314                         if (region->whole_file()) {
315
316                                 boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(region->source());
317                                 str = ".../";
318
319                                 if (afs) {
320                                         str = region_name_from_path (afs->path(), region->n_channels() > 1);
321                                 } else {
322                                         str += region->source()->name();
323                                 }
324
325                         } else {
326                                 str = region->name();
327                         }
328
329                 } else {
330                         str = region->name();
331                 }
332
333                 if (region->n_channels() > 1) {
334                         std::stringstream foo;
335                         foo << region->n_channels ();
336                         str += " [";
337                         str += foo.str();
338                         str += "]";
339                 }
340
341                 row[_columns.name] = str;
342                 row[_columns.region] = region;
343                 row[_columns.property_toggles_visible] = false;
344
345                 if (missing_source) {
346                         row[_columns.path] = _("(MISSING) ") + region->source()->name();
347
348                 } else {
349                         boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource>(region->source());
350                         if (fs) {
351                                 row[_columns.path] = fs->path();
352                         } else {
353                                 row[_columns.path] = region->source()->name();
354                         }
355                 }
356
357                 if (region->automatic()) {
358                         return;
359                 }
360
361         } else {
362
363                 /* find parent node, add as new child */
364
365                 TreeModel::iterator i;
366                 TreeModel::Children rows = _model->children();
367                 bool found_parent = false;
368
369                 for (i = rows.begin(); i != rows.end(); ++i) {
370                         boost::shared_ptr<Region> r = (*i)[_columns.region];
371
372                         if (r && r->whole_file()) {
373
374                                 if (region->source_equivalent (r)) {
375                                         found_parent = true;
376                                 }
377                         }
378                         
379                         TreeModel::iterator ii;
380                         TreeModel::Children subrows = (*i).children();
381
382                         for (ii = subrows.begin(); ii != subrows.end(); ++ii) {
383                                 boost::shared_ptr<Region> rr = (*ii)[_columns.region];
384
385                                 if (region->region_list_equivalent (rr)) {
386                                         return;
387                                 }
388                         }
389
390                         if (found_parent) {
391                                 row = *(_model->append ((*i).children()));
392                                 break;
393                         }
394                 }
395
396                 if (!found_parent) {
397                         row = *(_model->append());
398                 }
399
400                 row[_columns.property_toggles_visible] = true;
401         }
402
403         row[_columns.region] = region;
404
405         populate_row(region, (*row));
406 }
407
408 void
409 EditorRegions::region_changed (boost::shared_ptr<Region> r, const PropertyChange& what_changed)
410 {
411         PropertyChange our_interests;
412
413         our_interests.add (ARDOUR::Properties::name);
414         our_interests.add (ARDOUR::Properties::position);
415         our_interests.add (ARDOUR::Properties::length);
416         our_interests.add (ARDOUR::Properties::start);
417         our_interests.add (ARDOUR::Properties::locked);
418         our_interests.add (ARDOUR::Properties::position_lock_style);
419         our_interests.add (ARDOUR::Properties::muted);
420         our_interests.add (ARDOUR::Properties::opaque);
421         our_interests.add (ARDOUR::Properties::fade_in);
422         our_interests.add (ARDOUR::Properties::fade_out);
423         
424         if (last_row != 0) {
425
426                 TreeModel::iterator j = _model->get_iter (last_row.get_path());
427                 boost::shared_ptr<Region> c = (*j)[_columns.region];
428
429                 if (c == r) {
430                         populate_row (r, (*j));
431                         
432                         if (what_changed.contains (ARDOUR::Properties::hidden)) {
433                                 redisplay ();
434                         }
435                         
436                         return;
437                 }
438         }
439
440
441         if (what_changed.contains (our_interests)) {
442
443                 /* find the region in our model and update its row */
444                 TreeModel::Children rows = _model->children ();
445                 TreeModel::iterator i = rows.begin ();
446                 
447                 while (i != rows.end ()) {
448                         
449                         TreeModel::Children children = (*i)->children ();
450                         TreeModel::iterator j = children.begin ();
451                         
452                         while (j != children.end()) {
453                           
454                                 boost::shared_ptr<Region> c = (*j)[_columns.region];
455                         
456                                 if (c == r) {
457                                         last_row = TreeRowReference(_model, TreePath(j));
458                                         break;
459                                 }
460                                 ++j;
461                         }
462
463                         if (j != children.end()) {
464
465                                 boost::shared_ptr<AudioRegion> audioregion = boost::dynamic_pointer_cast<AudioRegion>(r);
466                                 uint32_t used = _editor->get_regionview_count_from_region_list (r);
467
468                                 if (what_changed.contains (ARDOUR::Properties::name)) {
469                                         populate_row_name (r, *j);
470                                 }
471                                 if (what_changed.contains (ARDOUR::Properties::position)) {
472                                         populate_row_position (r, *j, used);
473                                         populate_row_end (r, *j, used);
474                                 }
475                                 if (what_changed.contains (ARDOUR::Properties::length)) {
476                                         populate_row_end (r, *j, used);
477                                         populate_row_length (r, *j);
478                                 }
479                                 if (what_changed.contains (ARDOUR::Properties::start)) {
480                                         populate_row_length (r, *j);
481                                 }
482                                 if (what_changed.contains (ARDOUR::Properties::locked)) {
483                                         populate_row_locked (r, *j, used);
484                                 }
485                                 if (what_changed.contains (ARDOUR::Properties::position_lock_style)) {
486                                         populate_row_glued (r, *j, used);
487                                 }
488                                 if (what_changed.contains (ARDOUR::Properties::muted)) {
489                                         populate_row_muted (r, *j, used);
490                                 }
491                                 if (what_changed.contains (ARDOUR::Properties::opaque)) {
492                                         populate_row_opaque (r, *j, used);
493                                 }
494                                 if (what_changed.contains (ARDOUR::Properties::fade_in)) {
495                                         populate_row_fade_in (r, *j, used, audioregion);
496                                 }
497                                 if (what_changed.contains (ARDOUR::Properties::fade_out)) {
498                                         populate_row_fade_out (r, *j, used, audioregion);
499                                 }
500
501                                 break;
502                         }
503
504                         ++i;
505                 }
506         }
507
508         if (what_changed.contains (ARDOUR::Properties::hidden)) {
509                 redisplay ();
510         }
511 }
512
513 void
514 EditorRegions::selection_changed ()
515 {
516         if (ignore_region_list_selection_change) {
517                 return;
518         }
519
520         _editor->_region_selection_change_updates_region_list = false;
521
522         if (_display.get_selection()->count_selected_rows() > 0) {
523
524                 TreeIter iter;
525                 TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows ();
526
527                 _editor->get_selection().clear_regions ();
528
529                 for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
530
531                         if (iter = _model->get_iter (*i)) { 
532                                 boost::shared_ptr<Region> region = (*iter)[_columns.region];
533
534                                 // they could have clicked on a row that is just a placeholder, like "Hidden"
535                                 // although that is not allowed by our selection filter. check it anyway
536                                 // since we need a region ptr.
537
538                                 if (region) {
539                                         
540                                         if (region->automatic()) {
541
542                                                 _display.get_selection()->unselect(*i);
543
544                                         } else {
545                                                 _change_connection.block (true);
546                                                 _editor->set_selected_regionview_from_region_list (region, Selection::Add);
547
548                                                 _change_connection.block (false);
549                                         }
550                                 }
551                         }
552                 }
553         } else {
554                 _editor->get_selection().clear_regions ();
555         }
556
557         _editor->_region_selection_change_updates_region_list = true;
558 }
559
560 void
561 EditorRegions::set_selected (RegionSelection& regions)
562 {
563         TreeModel::Children rows = _model->children();
564
565         for (RegionSelection::iterator iter = regions.begin(); iter != regions.end(); ++iter) {
566
567                 TreeModel::iterator i;
568                 
569                 boost::shared_ptr<Region> r ((*iter)->region());
570
571                 for (i = rows.begin(); i != rows.end(); ++i) {
572
573                         boost::shared_ptr<Region> compared_region = (*i)[_columns.region];
574
575                         if (r == compared_region) {
576                                 _display.get_selection()->select(*i);
577                                 break;
578                         }
579
580                         if (!(*i).children().empty()) {
581                                 if (set_selected_in_subrow(r, (*i), 2)) {
582                                         break;
583                                 }
584                         }
585                 }
586         }
587 }
588
589 bool
590 EditorRegions::set_selected_in_subrow (boost::shared_ptr<Region> region, TreeModel::Row const &parent_row, int level)
591 {
592         TreeModel::iterator i;
593         TreeModel::Children subrows = (*parent_row).children();
594
595         for (i = subrows.begin(); i != subrows.end(); ++i) {
596
597                 boost::shared_ptr<Region> compared_region = (*i)[_columns.region];
598
599                 if (region == compared_region) {
600                         _display.get_selection()->select(*i);
601                         return true;
602                 }
603
604                 if (!(*i).children().empty()) {
605                         if (set_selected_in_subrow (region, (*i), level + 1)) {
606                                 return true;
607                         }
608                 }
609         }
610         
611         return false;
612 }
613
614 void
615 EditorRegions::insert_into_tmp_regionlist(boost::shared_ptr<Region> region)
616 {
617         /* keep all whole files at the beginning */
618
619         if (region->whole_file()) {
620                 tmp_region_list.push_front (region);
621         } else {
622                 tmp_region_list.push_back (region);
623         }
624 }
625
626 void
627 EditorRegions::redisplay ()
628 {
629         if (_no_redisplay || !_session) {
630                 return;
631         }
632
633         bool tree_expanded = false;
634
635         /* If the list was expanded prior to rebuilding, expand it again afterwards */
636         if (toggle_full_action()->get_active()) {
637                 tree_expanded = true;
638         }
639
640         _display.set_model (Glib::RefPtr<Gtk::TreeStore>(0));
641         _model->clear ();
642
643         /* now add everything we have, via a temporary list used to help with sorting */
644
645         tmp_region_list.clear();
646
647         const RegionFactory::RegionMap& regions (RegionFactory::regions());
648         for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
649                 insert_into_tmp_regionlist (i->second);
650         }
651
652         for (list<boost::shared_ptr<Region> >::iterator r = tmp_region_list.begin(); r != tmp_region_list.end(); ++r) {
653                 add_region (*r);
654         }
655         tmp_region_list.clear();
656
657         _display.set_model (_model);
658
659         if (tree_expanded) {
660                 _display.expand_all();
661         }
662 }
663
664 void
665 EditorRegions::update_row (boost::shared_ptr<Region> region)
666 {
667         if (!region || !_session) {
668                 return;
669         }
670
671         TreeModel::iterator i;
672         TreeModel::Children rows = _model->children();
673         
674         return;
675
676         for (i = rows.begin(); i != rows.end(); ++i) {
677
678 //              cerr << "Level 1: Compare " << region->name() << " with parent " << (*i)[_columns.name] << "\n";
679
680                 boost::shared_ptr<Region> compared_region = (*i)[_columns.region];
681
682                 if (region == compared_region) {
683 //                      cerr << "Matched\n";
684                         populate_row(region, (*i));
685                         return;
686                 }
687
688                 if (!(*i).children().empty()) {
689                         if (update_subrows(region, (*i), 2)) {
690                                 return;
691                         }
692                 }
693         }
694
695 //      cerr << "Returning - No match\n";
696 }
697
698 bool
699 EditorRegions::update_subrows (boost::shared_ptr<Region> region, TreeModel::Row const &parent_row, int level)
700 {
701         TreeModel::iterator i;
702         TreeModel::Children subrows = (*parent_row).children();
703
704         for (i = subrows.begin(); i != subrows.end(); ++i) {
705
706 //              cerr << "Level " << level << ": Compare " << region->name() << " with child " << (*i)[_columns.name] << "\n";
707
708                 boost::shared_ptr<Region> compared_region = (*i)[_columns.region];
709
710                 if (region == compared_region) {
711                         populate_row(region, (*i));
712 //                      cerr << "Matched\n";
713                         return true;
714                 }
715
716                 if (!(*i).children().empty()) {
717                         if (update_subrows (region, (*i), level + 1)) {
718                                 return true;
719                         }
720                 }
721         }
722
723         return false;
724 }
725
726 void
727 EditorRegions::update_all_rows ()
728 {
729         if (!_session) {
730                 return;
731         }
732         
733         TreeModel::iterator i;
734         TreeModel::Children rows = _model->children();
735
736         for (i = rows.begin(); i != rows.end(); ++i) {
737
738                 boost::shared_ptr<Region> region = (*i)[_columns.region];
739
740                 if (!region->automatic()) {
741                         populate_row(region, (*i));
742                 }
743
744                 if (!(*i).children().empty()) {
745                         update_all_subrows ((*i), 2);
746                 }
747         }
748 }
749
750 void
751 EditorRegions::update_all_subrows (TreeModel::Row const &parent_row, int level)
752 {
753         TreeModel::iterator i;
754         TreeModel::Children subrows = (*parent_row).children();
755
756         for (i = subrows.begin(); i != subrows.end(); ++i) {
757
758                 boost::shared_ptr<Region> region = (*i)[_columns.region];
759
760                 if (!region->automatic()) {
761                         populate_row(region, (*i));
762                 }
763
764                 if (!(*i).children().empty()) {
765                         update_all_subrows ((*i), level + 1);
766                 }
767         }
768 }
769
770 void
771 EditorRegions::format_position (framepos_t pos, char* buf, size_t bufsize)
772 {
773         BBT_Time bbt;
774         Timecode::Time timecode;
775
776         switch (ARDOUR_UI::instance()->secondary_clock.mode ()) {
777         case AudioClock::BBT:
778                 _session->tempo_map().bbt_time (pos, bbt);
779                 snprintf (buf, bufsize, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks);
780                 break;
781
782         case AudioClock::MinSec:
783                 framepos_t left;
784                 int hrs;
785                 int mins;
786                 float secs;
787
788                 left = pos;
789                 hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
790                 left -= (nframes_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
791                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
792                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
793                 secs = left / (float) _session->frame_rate();
794                 snprintf (buf, bufsize, "%02d:%02d:%06.3f", hrs, mins, secs);
795                 break;
796
797         case AudioClock::Frames:
798                 snprintf (buf, bufsize, "%" PRId64, pos);
799                 break;
800
801         case AudioClock::Timecode:
802         case AudioClock::Off: /* If the secondary clock is off, default to Timecode */
803         default:
804                 _session->timecode_time (pos, timecode);
805                 snprintf (buf, bufsize, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
806                 break;
807         }
808 }
809
810 void
811 EditorRegions::populate_row (boost::shared_ptr<Region> region, TreeModel::Row const &row)
812 {
813         boost::shared_ptr<AudioRegion> audioregion = boost::dynamic_pointer_cast<AudioRegion>(region);
814         uint32_t used = _session->playlists->region_use_count (region);
815
816         populate_row_position (region, row, used);
817         populate_row_end (region, row, used);
818         populate_row_sync (region, row, used);
819         populate_row_fade_in (region, row, used, audioregion);
820         populate_row_fade_out (region, row, used, audioregion);
821         populate_row_locked (region, row, used);
822         populate_row_glued (region, row, used);
823         populate_row_muted (region, row, used);
824         populate_row_opaque (region, row, used);
825         populate_row_length (region, row);
826         populate_row_source (region, row);
827         populate_row_name (region, row);
828         populate_row_used (region, row, used);
829 }
830
831 #if 0
832         if (audioRegion && fades_in_seconds) {
833
834                 nframes_t left;
835                 int mins;
836                 int millisecs;
837
838                 left = audioRegion->fade_in()->back()->when;
839                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
840                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
841                 millisecs = (int) floor ((left * 1000.0f) / _session->frame_rate());
842
843                 if (audioRegion->fade_in()->back()->when >= _session->frame_rate()) {
844                         sprintf (fadein_str, "%01dM %01dmS", mins, millisecs);
845                 } else {
846                         sprintf (fadein_str, "%01dmS", millisecs);
847                 }
848
849                 left = audioRegion->fade_out()->back()->when;
850                 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
851                 left -= (nframes_t) floor (mins * _session->frame_rate() * 60.0f);
852                 millisecs = (int) floor ((left * 1000.0f) / _session->frame_rate());
853
854                 if (audioRegion->fade_out()->back()->when >= _session->frame_rate()) {
855                         sprintf (fadeout_str, "%01dM %01dmS", mins, millisecs);
856                 } else {
857                         sprintf (fadeout_str, "%01dmS", millisecs);
858                 }
859         }
860 #endif
861
862 void
863 EditorRegions::populate_row_used (boost::shared_ptr<Region> region, TreeModel::Row const& row, uint32_t used)
864 {
865         char buf[8];
866         snprintf (buf, sizeof (buf), "%4d" , used);
867         row[_columns.used] = buf;
868 }
869
870 void
871 EditorRegions::populate_row_length (boost::shared_ptr<Region> region, TreeModel::Row const &row)
872 {
873         char buf[16];
874         format_position (region->length(), buf, sizeof (buf));
875         row[_columns.length] = buf;
876 }
877
878 void
879 EditorRegions::populate_row_end (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used)
880 {
881         if (region->whole_file()) {
882                 row[_columns.end] = "";
883         } else if (used > 1) {
884                 row[_columns.end] = _("Mult.");
885         } else {
886                 char buf[16];
887                 format_position (region->last_frame(), buf, sizeof (buf));
888                 row[_columns.end] = buf;
889         }
890 }
891
892 void
893 EditorRegions::populate_row_position (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used)
894 {
895         if (region->whole_file()) {
896                 row[_columns.position] = "";
897         } else if (used > 1) {
898                 row[_columns.position] = _("Mult.");
899         } else {
900                 char buf[16];
901                 format_position (region->position(), buf, sizeof (buf));
902                 row[_columns.position] = buf;
903         }
904 }
905
906 void
907 EditorRegions::populate_row_sync (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used)
908 {
909         if (region->whole_file()) {
910                 row[_columns.sync] = "";
911         } else if (used > 1) {
912                 row[_columns.sync] = _("Mult."); /* translators: a short phrase for "multiple" as in "many" */
913         } else {
914                 if (region->sync_position() == region->position()) {
915                         row[_columns.sync] = _("Start");
916                 } else if (region->sync_position() == (region->last_frame())) {
917                         row[_columns.sync] = _("End");
918                 } else {
919                         char buf[16];
920                         format_position (region->sync_position(), buf, sizeof (buf));
921                         row[_columns.sync] = buf;
922                 }
923         }
924 }
925
926 void
927 EditorRegions::populate_row_fade_in (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used, boost::shared_ptr<AudioRegion> audioregion)
928 {
929         if (!audioregion || region->whole_file()) {
930                         row[_columns.fadein] = "";
931         } else {
932                 if (used > 1) {
933                         row[_columns.fadein] = _("Multiple");
934                 } else {
935
936                         char buf[16];
937                         format_position (audioregion->fade_in()->back()->when, buf, sizeof (buf));
938                         row[_columns.fadein] = buf;
939                         
940                         if (audioregion->fade_in_active()) {
941                                 row[_columns.fadein] = string_compose("%1%2%3", " ", buf, " ");
942                         } else {
943                                 row[_columns.fadein] = string_compose("%1%2%3", "(", buf, ")");
944                         }
945                 }
946         }
947 }
948
949 void
950 EditorRegions::populate_row_fade_out (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used, boost::shared_ptr<AudioRegion> audioregion)
951 {
952         if (!audioregion || region->whole_file()) {
953                 row[_columns.fadeout] = "";
954         } else {
955                 if (used > 1) {
956                         row[_columns.fadeout] = _("Multiple");
957                 } else {
958                         char buf[16];
959                         format_position (audioregion->fade_out()->back()->when, buf, sizeof (buf));
960                         
961                         if (audioregion->fade_out_active()) {
962                                 row[_columns.fadeout] = string_compose("%1%2%3", " ", buf, " ");
963                         } else {
964                                 row[_columns.fadeout] = string_compose("%1%2%3", "(", buf, ")");
965                         }
966                 } 
967         }
968 }
969         
970 void
971 EditorRegions::populate_row_locked (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used)
972 {
973         if (region->whole_file()) {
974                 row[_columns.locked] = false;
975         } else if (used > 1) {
976                 row[_columns.locked] = false;
977         } else {
978                 row[_columns.locked] = region->locked();
979         }
980 }
981
982 void
983 EditorRegions::populate_row_glued (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used)
984 {
985         if (region->whole_file() || used > 1) {
986                 row[_columns.glued] = false;
987         } else {
988                 if (region->position_lock_style() == MusicTime) {
989                         row[_columns.glued] = true;
990                 } else {
991                         row[_columns.glued] = false;
992                 }
993         }
994 }
995
996 void
997 EditorRegions::populate_row_muted (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used)
998 {
999         if (region->whole_file() || used > 1) {
1000                 row[_columns.muted] = false;
1001         } else {
1002                 row[_columns.muted] = region->muted();
1003         }
1004 }
1005
1006 void
1007 EditorRegions::populate_row_opaque (boost::shared_ptr<Region> region, TreeModel::Row const &row, uint32_t used)
1008 {
1009         if (region->whole_file() || used > 1) {
1010                 row[_columns.opaque] = false;
1011         } else {
1012                 row[_columns.opaque] = region->opaque();
1013         }
1014 }
1015
1016 void
1017 EditorRegions::populate_row_name (boost::shared_ptr<Region> region, TreeModel::Row const &row)
1018 {
1019         if (region->n_channels() > 1) {
1020                 row[_columns.name] = string_compose("%1  [%2]", region->name(), region->n_channels());
1021         } else {
1022                 row[_columns.name] = region->name();
1023         }
1024 }        
1025
1026 void
1027 EditorRegions::populate_row_source (boost::shared_ptr<Region> region, TreeModel::Row const &row)
1028 {
1029         if (boost::dynamic_pointer_cast<SilentFileSource>(region->source())) {
1030                 row[_columns.path] = _("MISSING ") + region->source()->name();
1031         } else {
1032                 row[_columns.path] = region->source()->name();
1033         }
1034 }
1035
1036 void
1037 EditorRegions::toggle_show_auto_regions ()
1038 {
1039         _show_automatic_regions = toggle_show_auto_regions_action()->get_active();
1040         redisplay ();
1041 }
1042
1043 void
1044 EditorRegions::toggle_full ()
1045 {
1046         set_full (toggle_full_action()->get_active ());
1047 }
1048
1049 void
1050 EditorRegions::set_full (bool f)
1051 {
1052         if (f) {
1053                 _display.expand_all ();
1054                 expanded = true;
1055         } else {
1056                 _display.collapse_all ();
1057                 expanded = false;
1058         }
1059 }
1060
1061 void
1062 EditorRegions::show_context_menu (int button, int time)
1063 {
1064         if (_menu == 0) {
1065                 _menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/RegionListMenu"));
1066         }
1067
1068         if (_display.get_selection()->count_selected_rows() > 0) {
1069                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, true);
1070         } else {
1071                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, false);
1072         }
1073
1074         /* Enable the "Show" option if any selected regions are hidden, and vice versa for "Hide" */
1075
1076         bool have_shown = false;
1077         bool have_hidden = false;
1078         
1079         TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows ();
1080         for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
1081                 TreeIter t = _model->get_iter (*i);
1082                 boost::shared_ptr<Region> r = (*t)[_columns.region];
1083                 if (r) {
1084                         if (r->hidden ()) {
1085                                 have_hidden = true;
1086                         } else {
1087                                 have_shown = true;
1088                         }
1089                 }
1090         }
1091
1092         hide_action()->set_sensitive (have_shown);
1093         show_action()->set_sensitive (have_hidden);
1094
1095         _menu->popup (button, time);
1096 }
1097
1098 bool
1099 EditorRegions::key_press (GdkEventKey* ev)
1100 {
1101         TreeViewColumn *col;
1102
1103         switch (ev->keyval) {
1104         case GDK_Tab:
1105         case GDK_ISO_Left_Tab:
1106                 
1107                 if (name_editable) {
1108                         name_editable->editing_done ();
1109                         name_editable = 0;
1110                 }
1111
1112                 col = _display.get_column (0); // select&focus on name column
1113
1114                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
1115                         treeview_select_previous (_display, _model, col);
1116                 } else {
1117                         treeview_select_next (_display, _model, col);
1118                 }
1119
1120                 return true;
1121                 break;
1122
1123         default:
1124                 break;
1125         }
1126
1127         return false;
1128 }
1129
1130 bool
1131 EditorRegions::button_press (GdkEventButton *ev)
1132 {
1133         boost::shared_ptr<Region> region;
1134         TreeIter iter;
1135         TreeModel::Path path;
1136         TreeViewColumn* column;
1137         int cellx;
1138         int celly;
1139
1140         if (_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
1141                 if ((iter = _model->get_iter (path))) {
1142                         region = (*iter)[_columns.region];
1143                 }
1144         }
1145
1146         if (Keyboard::is_context_menu_event (ev)) {
1147                 show_context_menu (ev->button, ev->time);
1148                 return false;
1149         }
1150
1151         if (region != 0 && Keyboard::is_button2_event (ev)) {
1152                 // start/stop audition
1153                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1154                         _editor->consider_auditioning (region);
1155                 }
1156                 return true;
1157         }
1158
1159         return false;
1160 }
1161
1162 int
1163 EditorRegions::sorter (TreeModel::iterator a, TreeModel::iterator b)
1164 {
1165         int cmp = 0;
1166
1167         boost::shared_ptr<Region> r1 = (*a)[_columns.region];
1168         boost::shared_ptr<Region> r2 = (*b)[_columns.region];
1169
1170         /* handle rows without regions, like "Hidden" */
1171
1172         if (r1 == 0) {
1173                 return -1;
1174         }
1175
1176         if (r2 == 0) {
1177                 return 1;
1178         }
1179
1180         boost::shared_ptr<AudioRegion> region1 = boost::dynamic_pointer_cast<AudioRegion> (r1);
1181         boost::shared_ptr<AudioRegion> region2 = boost::dynamic_pointer_cast<AudioRegion> (r2);
1182
1183         if (region1 == 0 || region2 == 0) {
1184                 std::string s1;
1185                 std::string s2;
1186                 switch (_sort_type) {
1187                 case ByName:
1188                         s1 = (*a)[_columns.name];
1189                         s2 = (*b)[_columns.name];
1190                         return (s1.compare (s2));
1191                 default:
1192                         return 0;
1193                 }
1194         }
1195
1196         switch (_sort_type) {
1197         case ByName:
1198                 cmp = strcasecmp (region1->name().c_str(), region2->name().c_str());
1199                 break;
1200
1201         case ByLength:
1202                 cmp = region1->length() - region2->length();
1203                 break;
1204
1205         case ByPosition:
1206                 cmp = region1->position() - region2->position();
1207                 break;
1208
1209         case ByTimestamp:
1210                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
1211                 break;
1212
1213         case ByStartInFile:
1214                 cmp = region1->start() - region2->start();
1215                 break;
1216
1217         case ByEndInFile:
1218                 // cerr << "Compare " << (region1->start() + region1->length()) << " and " << (region2->start() + region2->length()) << endl;
1219                 cmp = (region1->start() + region1->length()) - (region2->start() + region2->length());
1220                 break;
1221
1222         case BySourceFileName:
1223                 cmp = strcasecmp (region1->source()->name().c_str(), region2->source()->name().c_str());
1224                 break;
1225
1226         case BySourceFileLength:
1227                 cmp = region1->source_length(0) - region2->source_length(0);
1228                 break;
1229
1230         case BySourceFileCreationDate:
1231                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
1232                 break;
1233
1234         case BySourceFileFS:
1235                 if (region1->source()->name() == region2->source()->name()) {
1236                         cmp = strcasecmp (region1->name().c_str(),  region2->name().c_str());
1237                 } else {
1238                         cmp = strcasecmp (region1->source()->name().c_str(),  region2->source()->name().c_str());
1239                 }
1240                 break;
1241         }
1242
1243         // cerr << "Comparison on " << enum_2_string (_sort_type) << " gives " << cmp << endl;
1244
1245         if (cmp < 0) {
1246                 return -1;
1247         } else if (cmp > 0) {
1248                 return 1;
1249         } else {
1250                 return 0;
1251         }
1252 }
1253
1254 void
1255 EditorRegions::reset_sort_type (RegionListSortType type, bool force)
1256 {
1257         if (type != _sort_type || force) {
1258                 _sort_type = type;
1259                 _model->set_sort_func (0, (sigc::mem_fun (*this, &EditorRegions::sorter)));
1260         }
1261 }
1262
1263 void
1264 EditorRegions::reset_sort_direction (bool up)
1265 {
1266         _model->set_sort_column (0, up ? SORT_ASCENDING : SORT_DESCENDING);
1267 }
1268
1269 void
1270 EditorRegions::selection_mapover (sigc::slot<void,boost::shared_ptr<Region> > sl)
1271 {
1272         Glib::RefPtr<TreeSelection> selection = _display.get_selection();
1273         TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
1274         TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
1275
1276         if (selection->count_selected_rows() == 0 || _session == 0) {
1277                 return;
1278         }
1279
1280         for (; i != rows.end(); ++i) {
1281                 TreeIter iter;
1282
1283                 if ((iter = _model->get_iter (*i))) {
1284
1285                         /* some rows don't have a region associated with them, but can still be
1286                            selected (XXX maybe prevent them from being selected)
1287                         */
1288
1289                         boost::shared_ptr<Region> r = (*iter)[_columns.region];
1290
1291                         if (r) {
1292                                 sl (r);
1293                         }
1294                 }
1295         }
1296 }
1297
1298
1299 void
1300 EditorRegions::drag_data_received (const RefPtr<Gdk::DragContext>& context,
1301                                    int x, int y,
1302                                    const SelectionData& data,
1303                                    guint info, guint time)
1304 {
1305         vector<string> paths;
1306
1307         if (data.get_target() == "GTK_TREE_MODEL_ROW") {
1308                 /* something is being dragged over the region list */
1309                 _editor->_drags->abort ();
1310                 _display.on_drag_data_received (context, x, y, data, info, time);
1311                 return;
1312         }
1313
1314         if (_editor->convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
1315                 framepos_t pos = 0;
1316                 if (Profile->get_sae() || Config->get_only_copy_imported_files()) {
1317                         _editor->do_import (paths, Editing::ImportDistinctFiles, Editing::ImportAsRegion, SrcBest, pos);
1318                 } else {
1319                         _editor->do_embed (paths, Editing::ImportDistinctFiles, ImportAsRegion, pos);
1320                 }
1321                 context->drag_finish (true, false, time);
1322         }
1323 }
1324
1325 bool
1326 EditorRegions::selection_filter (const RefPtr<TreeModel>& model, const TreeModel::Path& path, bool already_selected)
1327 {
1328         /* not possible to select rows that do not represent regions, like "Hidden" */
1329
1330         if (already_selected) {
1331                 /* deselecting anything is OK with us */
1332                 return true;
1333         }
1334
1335         TreeModel::iterator iter = model->get_iter (path);
1336
1337         if (iter) {
1338                 boost::shared_ptr<Region> r =(*iter)[_columns.region];
1339                 if (!r) {
1340                         return false;
1341                 }
1342         }
1343
1344         return true;
1345 }
1346
1347 void
1348 EditorRegions::name_editing_started (CellEditable* ce, const Glib::ustring&)
1349 {
1350         name_editable = ce;
1351 }
1352                           
1353 void
1354 EditorRegions::name_edit (const std::string& path, const std::string& new_text)
1355 {
1356         name_editable = 0;
1357
1358         boost::shared_ptr<Region> region;
1359         TreeIter iter;
1360
1361         if ((iter = _model->get_iter (path))) {
1362                 region = (*iter)[_columns.region];
1363                 (*iter)[_columns.name] = new_text;
1364         }
1365
1366         /* now mapover everything */
1367
1368         if (region) {
1369                 vector<RegionView*> equivalents;
1370                 _editor->get_regions_corresponding_to (region, equivalents);
1371
1372                 for (vector<RegionView*>::iterator i = equivalents.begin(); i != equivalents.end(); ++i) {
1373                         if (new_text != (*i)->region()->name()) {
1374                                 (*i)->region()->set_name (new_text);
1375                         }
1376                 }
1377         }
1378
1379 }
1380
1381 /** @return Region that has been dragged out of the list, or 0 */
1382 boost::shared_ptr<Region>
1383 EditorRegions::get_dragged_region ()
1384 {
1385         list<boost::shared_ptr<Region> > regions;
1386         TreeView* source;
1387         _display.get_object_drag_data (regions, &source);
1388
1389         if (regions.empty()) {
1390                 return boost::shared_ptr<Region> ();
1391         }
1392         
1393         assert (regions.size() == 1);
1394         return regions.front ();
1395 }
1396
1397 void
1398 EditorRegions::clear ()
1399 {
1400         _display.set_model (Glib::RefPtr<Gtk::TreeStore> (0));
1401         _model->clear ();
1402         _display.set_model (_model);
1403 }
1404
1405 boost::shared_ptr<Region>
1406 EditorRegions::get_single_selection ()
1407 {
1408         Glib::RefPtr<TreeSelection> selected = _display.get_selection();
1409
1410         if (selected->count_selected_rows() != 1) {
1411                 return boost::shared_ptr<Region> ();
1412         }
1413
1414         TreeView::Selection::ListHandle_Path rows = selected->get_selected_rows ();
1415
1416         /* only one row selected, so rows.begin() is it */
1417
1418         TreeIter iter = _model->get_iter (*rows.begin());
1419
1420         if (!iter) {
1421                 return boost::shared_ptr<Region> ();
1422         }
1423
1424         return (*iter)[_columns.region];
1425 }
1426
1427 void
1428 EditorRegions::locked_changed (std::string const & path)
1429 {
1430         TreeIter i = _model->get_iter (path);
1431         if (i) {
1432                 boost::shared_ptr<ARDOUR::Region> region = (*i)[_columns.region];
1433                 if (region) {
1434                         region->set_locked (!(*i)[_columns.locked]);
1435                 }
1436         }
1437 }
1438
1439 void
1440 EditorRegions::glued_changed (std::string const & path)
1441 {
1442         TreeIter i = _model->get_iter (path);
1443         if (i) {
1444                 boost::shared_ptr<ARDOUR::Region> region = (*i)[_columns.region];
1445                 if (region) {
1446                         /* `glued' means MusicTime, and we're toggling here */
1447                         region->set_position_lock_style ((*i)[_columns.glued] ? AudioTime : MusicTime);
1448                 }
1449         }
1450
1451 }
1452
1453 void
1454 EditorRegions::muted_changed (std::string const & path)
1455 {
1456         TreeIter i = _model->get_iter (path);
1457         if (i) {
1458                 boost::shared_ptr<ARDOUR::Region> region = (*i)[_columns.region];
1459                 if (region) {
1460                         region->set_muted (!(*i)[_columns.muted]);
1461                 }
1462         }
1463
1464 }
1465
1466 void
1467 EditorRegions::opaque_changed (std::string const & path)
1468 {
1469         TreeIter i = _model->get_iter (path);
1470         if (i) {
1471                 boost::shared_ptr<ARDOUR::Region> region = (*i)[_columns.region];
1472                 if (region) {
1473                         region->set_opaque (!(*i)[_columns.opaque]);
1474                 }
1475         }
1476
1477 }
1478
1479 XMLNode &
1480 EditorRegions::get_state () const
1481 {
1482         XMLNode* node = new XMLNode (X_("RegionList"));
1483
1484         node->add_property (X_("sort-type"), enum_2_string (_sort_type));
1485
1486         RefPtr<Action> act = ActionManager::get_action (X_("RegionList"), X_("SortAscending"));
1487         bool const ascending = RefPtr<RadioAction>::cast_dynamic(act)->get_active ();
1488         node->add_property (X_("sort-ascending"), ascending ? "yes" : "no");
1489         node->add_property (X_("show-all"), toggle_full_action()->get_active() ? "yes" : "no");
1490         node->add_property (X_("show-automatic-regions"), _show_automatic_regions ? "yes" : "no");
1491
1492         return *node;
1493 }
1494                 
1495 void
1496 EditorRegions::set_state (const XMLNode & node)
1497 {
1498         bool changed = false;
1499
1500         if (node.name() != X_("RegionList")) {
1501                 return;
1502         }
1503
1504         XMLProperty const * p = node.property (X_("sort-type"));
1505         if (p) {
1506                 Editing::RegionListSortType const t = static_cast<Editing::RegionListSortType> (string_2_enum (p->value(), _sort_type));
1507                 if (_sort_type != t) {
1508                         changed = true;
1509                 }
1510                 reset_sort_type (t, true);
1511                 RefPtr<RadioAction> ract = sort_type_action (t);
1512                 ract->set_active ();
1513         }
1514
1515         p = node.property (X_("sort-ascending"));
1516         if (p) {
1517                 bool const yn = string_is_affirmative (p->value ());
1518                 SortType old_sort_type;
1519                 int old_sort_column;
1520
1521                 _model->get_sort_column_id (old_sort_column, old_sort_type);
1522                 if (old_sort_type != (yn ? SORT_ASCENDING : SORT_DESCENDING)) {
1523                         changed = true;
1524                 }
1525                 reset_sort_direction (yn);
1526                 RefPtr<Action> act;
1527                 if (yn) {
1528                         act = ActionManager::get_action (X_("RegionList"), X_("SortAscending"));
1529                 } else {
1530                         act = ActionManager::get_action (X_("RegionList"), X_("SortDescending"));
1531                 }
1532
1533                 RefPtr<RadioAction>::cast_dynamic(act)->set_active ();
1534         }
1535
1536         p = node.property (X_("show-all"));
1537         if (p) {
1538                 bool const yn = string_is_affirmative (p->value ());
1539                 if (expanded != yn) {
1540                         changed = true;
1541                 }
1542                 set_full (yn);
1543                 toggle_full_action()->set_active (yn);
1544         }
1545
1546         p = node.property (X_("show-automatic-regions"));
1547         if (p) {
1548                 bool const yn = string_is_affirmative (p->value ());
1549                 if (yn != _show_automatic_regions) {
1550                         _show_automatic_regions = yn;
1551                         toggle_show_auto_regions_action()->set_active (yn);
1552                         /* no need to set changed because the above toggle 
1553                            will have triggered a redisplay 
1554                         */
1555                 }
1556         }
1557         
1558         if (changed) {
1559                 redisplay ();
1560         }
1561 }
1562
1563 RefPtr<RadioAction>
1564 EditorRegions::sort_type_action (Editing::RegionListSortType t) const
1565 {
1566         const char* action = 0;
1567
1568         switch (t) {
1569         case Editing::ByName:
1570                 action = X_("SortByRegionName");
1571                 break;
1572         case Editing::ByLength:
1573                 action = X_("SortByRegionLength");
1574                 break;
1575         case Editing::ByPosition:
1576                 action = X_("SortByRegionPosition");
1577                 break;
1578         case Editing::ByTimestamp:
1579                 action = X_("SortByRegionTimestamp");
1580                 break;
1581         case Editing::ByStartInFile:
1582                 action = X_("SortByRegionStartinFile");
1583                 break;
1584         case Editing::ByEndInFile:
1585                 action = X_("SortByRegionEndinFile");
1586                 break;
1587         case Editing::BySourceFileName:
1588                 action = X_("SortBySourceFileName");
1589                 break;
1590         case Editing::BySourceFileLength:
1591                 action = X_("SortBySourceFileLength");
1592                 break;
1593         case Editing::BySourceFileCreationDate:
1594                 action = X_("SortBySourceFileCreationDate");
1595                 break;
1596         case Editing::BySourceFileFS:
1597                 action = X_("SortBySourceFilesystem");
1598                 break;
1599         default:
1600                 fatal << string_compose (_("programming error: %1: %2"), "EditorRegions: impossible sort type", (int) t) << endmsg;
1601                 /*NOTREACHED*/
1602         }
1603
1604         RefPtr<Action> act = ActionManager::get_action (X_("RegionList"), action);
1605         assert (act);
1606
1607         return RefPtr<RadioAction>::cast_dynamic (act);
1608 }
1609
1610 RefPtr<Action>
1611 EditorRegions::hide_action () const
1612 {
1613         return ActionManager::get_action (X_("RegionList"), X_("rlHide"));
1614         
1615 }
1616
1617 RefPtr<Action>
1618 EditorRegions::show_action () const
1619 {
1620         return ActionManager::get_action (X_("RegionList"), X_("rlShow"));
1621 }
1622
1623 RefPtr<ToggleAction>
1624 EditorRegions::toggle_full_action () const
1625 {
1626         Glib::RefPtr<Action> act = ActionManager::get_action (X_("RegionList"), X_("rlShowAll"));
1627         assert (act);
1628         return Glib::RefPtr<ToggleAction>::cast_dynamic (act);
1629 }
1630
1631 RefPtr<ToggleAction>
1632 EditorRegions::toggle_show_auto_regions_action () const
1633 {
1634         Glib::RefPtr<Action> act = ActionManager::get_action (X_("RegionList"), X_("rlShowAuto"));
1635         assert (act);
1636         return Glib::RefPtr<ToggleAction>::cast_dynamic (act);
1637 }