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