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