(Source List) Replace missing initializer; fixes a bug where Sources did not appear...
[ardour.git] / gtk2_ardour / editor_sources.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/source.h"
31 #include "ardour/audiofilesource.h"
32 #include "ardour/silentfilesource.h"
33 #include "ardour/region_factory.h"
34 #include "ardour/session.h"
35 #include "ardour/session_directory.h"
36 #include "ardour/profile.h"
37
38 #include "gtkmm2ext/treeutils.h"
39 #include "gtkmm2ext/utils.h"
40
41 #include "widgets/choice.h"
42 #include "widgets/tooltips.h"
43
44 #include "audio_clock.h"
45 #include "context_menu_helper.h"
46 #include "editor.h"
47 #include "editing.h"
48 #include "editing_convert.h"
49 #include "keyboard.h"
50 #include "ardour_ui.h"
51 #include "gui_thread.h"
52 #include "actions.h"
53 #include "region_view.h"
54 #include "utils.h"
55 #include "editor_drag.h"
56 #include "main_clock.h"
57 #include "ui_config.h"
58
59 #include "pbd/i18n.h"
60
61 #include "editor_sources.h"
62
63 using namespace std;
64 using namespace ARDOUR;
65 using namespace ArdourWidgets;
66 using namespace ARDOUR_UI_UTILS;
67 using namespace PBD;
68 using namespace Gtk;
69 using namespace Glib;
70 using namespace Editing;
71 using Gtkmm2ext::Keyboard;
72
73 struct ColumnInfo {
74         int         index;
75         const char* label;
76         const char* tooltip;
77 };
78
79 EditorSources::EditorSources (Editor* e)
80         : EditorComponent (e)
81         , old_focus (0)
82         , _menu (0)
83         , _selection (0)
84         , _no_redisplay (false)
85 {
86         _display.set_size_request (100, -1);
87         _display.set_rules_hint (true);
88         _display.set_name ("SourcesList");
89         _display.set_fixed_height_mode (true);
90
91         /* Try to prevent single mouse presses from initiating edits.
92            This relies on a hack in gtktreeview.c:gtk_treeview_button_press()
93         */
94         _display.set_data ("mouse-edits-require-mod1", (gpointer) 0x1);
95
96         _model = TreeStore::create (_columns);
97         _model->set_sort_column (0, SORT_ASCENDING);
98
99         /* column widths */
100         int bbt_width, date_width, height;
101
102         Glib::RefPtr<Pango::Layout> layout = _display.create_pango_layout (X_("000|000|000"));
103         Gtkmm2ext::get_pixel_size (layout, bbt_width, height);
104
105         Glib::RefPtr<Pango::Layout> layout2 = _display.create_pango_layout (X_("2018-10-14 12:12:30"));
106         Gtkmm2ext::get_pixel_size (layout2, date_width, height);
107
108         TreeViewColumn* col_name = manage (new TreeViewColumn ("", _columns.name));
109         col_name->set_fixed_width (bbt_width*2);
110         col_name->set_sizing (TREE_VIEW_COLUMN_FIXED);
111         col_name->set_sort_column(0);
112
113         TreeViewColumn* col_take_id = manage (new TreeViewColumn ("", _columns.take_id));
114         col_take_id->set_fixed_width (date_width);
115         col_take_id->set_sizing (TREE_VIEW_COLUMN_FIXED);
116         col_take_id->set_sort_column(1);
117
118         TreeViewColumn* col_nat_pos = manage (new TreeViewColumn ("", _columns.natural_pos));
119         col_nat_pos->set_fixed_width (bbt_width);
120         col_nat_pos->set_sizing (TREE_VIEW_COLUMN_FIXED);
121         col_nat_pos->set_sort_column(6);
122
123         TreeViewColumn* col_path = manage (new TreeViewColumn ("", _columns.path));
124         col_path->set_fixed_width (bbt_width);
125         col_path->set_sizing (TREE_VIEW_COLUMN_FIXED);
126         col_path->set_sort_column(3);
127
128         _display.append_column (*col_name);
129         _display.append_column (*col_take_id);
130         _display.append_column (*col_nat_pos);
131         _display.append_column (*col_path);
132
133         TreeViewColumn* col;
134         Gtk::Label* l;
135
136         ColumnInfo ci[] = {
137                 { 0,   _("Source"),    _("Source name, with number of channels in []'s") },
138                 { 1,   _("Take ID"),   _("Take ID") },
139                 { 2,   _("Orig Pos"),  _("Original Position of the file on timeline, when it was recorded") },
140                 { 3,   _("Path"),      _("Path (folder) of the file locationlosition of end of region") },
141                 { -1, 0, 0 }
142         };
143
144         for (int i = 0; ci[i].index >= 0; ++i) {
145                 col = _display.get_column (ci[i].index);
146                 l = manage (new Label (ci[i].label));
147                 set_tooltip (*l, ci[i].tooltip);
148                 col->set_widget (*l);
149                 l->show ();
150         }
151         _display.set_model (_model);
152
153         _display.set_headers_visible (true);
154         _display.set_rules_hint ();
155
156         _display.get_selection()->set_select_function (sigc::mem_fun (*this, &EditorSources::selection_filter));
157
158         //set the color of the name field
159         TreeViewColumn* tv_col = _display.get_column(0);
160         CellRendererText* renderer = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (0));
161         tv_col->add_attribute(renderer->property_text(), _columns.name);
162         tv_col->add_attribute(renderer->property_foreground_gdk(), _columns.color_);
163
164         //right-align the Natural Pos column
165         TreeViewColumn* nat_col = _display.get_column(2);
166         nat_col->set_alignment (ALIGN_RIGHT);
167         renderer = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (2));
168         if (renderer) {
169                 renderer->property_xalign() = ( 1.0 );
170         }
171
172         //the PATH field should expand when the pane is opened wider
173         tv_col = _display.get_column(3);
174         renderer = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (3));
175         tv_col->add_attribute(renderer->property_text(), _columns.path);
176         tv_col->set_expand (true);
177
178         _display.get_selection()->set_mode (SELECTION_MULTIPLE);
179         _display.add_object_drag (_columns.region.index(), "regions");
180         _display.set_drag_column (_columns.name.index());
181
182         /* setup DnD handling */
183
184         list<TargetEntry> source_list_target_table;
185
186         source_list_target_table.push_back (TargetEntry ("text/plain"));
187         source_list_target_table.push_back (TargetEntry ("text/uri-list"));
188         source_list_target_table.push_back (TargetEntry ("application/x-rootwin-drop"));
189
190         _display.add_drop_targets (source_list_target_table);
191         _display.signal_drag_data_received().connect (sigc::mem_fun(*this, &EditorSources::drag_data_received));
192
193         _scroller.add (_display);
194         _scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC);
195
196         _display.signal_button_press_event().connect (sigc::mem_fun(*this, &EditorSources::button_press), false);
197         _change_connection = _display.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &EditorSources::selection_changed));
198
199         _scroller.signal_key_press_event().connect (sigc::mem_fun(*this, &EditorSources::key_press), false);
200         _scroller.signal_focus_in_event().connect (sigc::mem_fun (*this, &EditorSources::focus_in), false);
201         _scroller.signal_focus_out_event().connect (sigc::mem_fun (*this, &EditorSources::focus_out));
202
203         _display.signal_enter_notify_event().connect (sigc::mem_fun (*this, &EditorSources::enter_notify), false);
204         _display.signal_leave_notify_event().connect (sigc::mem_fun (*this, &EditorSources::leave_notify), false);
205
206         ARDOUR_UI::instance()->primary_clock->mode_changed.connect (sigc::mem_fun(*this, &EditorSources::clock_format_changed));
207
208         e->EditorFreeze.connect (editor_freeze_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::freeze_tree_model, this), gui_context());
209         e->EditorThaw.connect (editor_thaw_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::thaw_tree_model, this), gui_context());
210 }
211
212 bool
213 EditorSources::focus_in (GdkEventFocus*)
214 {
215         Window* win = dynamic_cast<Window*> (_scroller.get_toplevel ());
216
217         if (win) {
218                 old_focus = win->get_focus ();
219         } else {
220                 old_focus = 0;
221         }
222
223         /* try to do nothing on focus in (doesn't work, hence selection_count nonsense) */
224         return true;
225 }
226
227 bool
228 EditorSources::focus_out (GdkEventFocus*)
229 {
230         if (old_focus) {
231                 old_focus->grab_focus ();
232                 old_focus = 0;
233         }
234
235         return false;
236 }
237
238 bool
239 EditorSources::enter_notify (GdkEventCrossing*)
240 {
241         /* arm counter so that ::selection_filter() will deny selecting anything for the
242            next two attempts to change selection status.
243         */
244         _scroller.grab_focus ();
245         Keyboard::magic_widget_grab_focus ();
246         return false;
247 }
248
249 bool
250 EditorSources::leave_notify (GdkEventCrossing*)
251 {
252         if (old_focus) {
253                 old_focus->grab_focus ();
254                 old_focus = 0;
255         }
256
257         Keyboard::magic_widget_drop_focus ();
258         return false;
259 }
260
261 void
262 EditorSources::set_session (ARDOUR::Session* s)
263 {
264         SessionHandlePtr::set_session (s);
265
266         if (s) {
267
268                 /*  Currently, none of the displayed properties are mutable, so there is no reason to register for changes
269                  * ARDOUR::Region::RegionPropertyChanged.connect (region_property_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::source_changed, this, _1, _2), gui_context());
270                 */
271                 
272                 ARDOUR::RegionFactory::CheckNewRegion.connect (check_new_region_connection, MISSING_INVALIDATOR, boost::bind (&EditorSources::add_source, this, _1), gui_context());
273
274                 redisplay();
275
276         } else {
277                 clear();
278         }
279 }
280
281 void
282 EditorSources::remove_source (boost::shared_ptr<ARDOUR::Region> region)
283 {
284         TreeModel::iterator i;
285         TreeModel::Children rows = _model->children();
286         for (i = rows.begin(); i != rows.end(); ++i) {
287                 boost::shared_ptr<ARDOUR::Region> rr = (*i)[_columns.region];
288                 if (region == rr) {
289                         _model->erase(i);
290                         break;
291                 }
292         }
293 }
294
295 void
296 EditorSources::populate_row (TreeModel::Row row, boost::shared_ptr<ARDOUR::Region> region)
297 {
298         ENSURE_GUI_THREAD (*this, &ARDOUR_UI::record_state_changed, row, region);
299
300         if (!region) {
301                 return;
302         }
303
304         boost::shared_ptr<ARDOUR::Source> source = region->source();  //ToDo:  is it OK to use only the first source?
305
306         //COLOR  (for missing files)
307         Gdk::Color c;
308         bool missing_source = boost::dynamic_pointer_cast<SilentFileSource>(source) != NULL;
309         if (missing_source) {
310                 set_color_from_rgba (c, UIConfiguration::instance().color ("region list missing source"));
311         } else {
312                 set_color_from_rgba (c, UIConfiguration::instance().color ("region list whole file"));
313         }
314         row[_columns.color_] = c;
315
316         //NAME
317         std::string str = region->name();
318         //if a multichannel region, show the number of channels  ToDo:  make a sortable column for this?
319         if ( region->n_channels() > 1 ) {
320                 str += string_compose("[%1]", region->n_channels());
321         }
322         row[_columns.name] = str;
323
324         row[_columns.region] = region;
325         row[_columns.take_id] = source->take_id();
326
327         //PATH
328         if (missing_source) {
329                 row[_columns.path] = _("(MISSING) ") + Gtkmm2ext::markup_escape_text (source->name());
330         } else {
331                 boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource>(source);
332                 if (fs) {
333                         const string sound_directory = _session->session_directory().sound_path();
334                         if ( fs->path().find(sound_directory) == std::string::npos ) { // external file
335                                 row[_columns.path] = Gtkmm2ext::markup_escape_text (fs->path());
336                         } else {
337                                 row[_columns.path] = source->name();
338                         }
339                 } else {
340                         row[_columns.path] = Gtkmm2ext::markup_escape_text (source->name());
341                 }
342         }
343
344         //Natural Position (samples, an invisible column for sorting)
345         row[_columns.natural_s] = source->natural_position();
346
347         //Natural Position (text representation)
348         if (source->have_natural_position()) {
349                 char buf[64];
350                 format_position (source->natural_position(), buf, sizeof (buf));
351                 row[_columns.natural_pos] = buf;
352         } else {
353                 row[_columns.natural_pos] = X_("--");
354         }
355 }
356
357 void
358 EditorSources::redisplay ()
359 {
360         if (_no_redisplay || !_session) {
361                 return;
362         }
363
364         _display.set_model (Glib::RefPtr<Gtk::TreeStore>(0));
365         _model->clear ();
366         _model->set_sort_column (-2, SORT_ASCENDING); //Disable sorting to gain performance
367
368         //Ask the region factory to fill our list of whole-file regions
369         RegionFactory::foreach_region (sigc::mem_fun (*this, &EditorSources::add_source));
370
371         _model->set_sort_column (0, SORT_ASCENDING); // re-enable sorting
372         _display.set_model (_model);
373 }
374
375 void
376 EditorSources::add_source (boost::shared_ptr<ARDOUR::Region> region)
377 {
378         if (!region || !_session ) {
379                 return;
380         }
381
382         //by definition, the Source List only shows whole-file regions
383         //this roughly equates to Source objects, but preserves the stereo-ness (or multichannel-ness) of a stereo source file.
384         if ( !region->whole_file() ) {
385                 return;
386         }
387         
388         //we only show files-on-disk.  if there's some other kind of source, we ignore it (for now)
389         boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (region->source());
390         if (!fs || fs->empty()) {
391                 return;
392         }
393
394         TreeModel::Row row = *(_model->append());
395         populate_row (row, region);
396 }
397
398 void
399 EditorSources::source_changed (boost::shared_ptr<ARDOUR::Region> region)
400 {
401         /* Currently never reached .. we have no mutable properties shown in the list*/
402         
403         TreeModel::iterator i;
404         TreeModel::Children rows = _model->children();
405
406         for (i = rows.begin(); i != rows.end(); ++i) {
407                 boost::shared_ptr<ARDOUR::Region> rr = (*i)[_columns.region];
408                 if (region == rr) {
409                         populate_row(*i, region);
410                         break;
411                 }
412         }
413 }
414
415 void
416 EditorSources::selection_changed ()
417 {
418
419         if (_display.get_selection()->count_selected_rows() > 0) {
420
421                 TreeIter iter;
422                 TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows ();
423
424                 _editor->get_selection().clear_regions ();
425
426                 for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
427
428                         if ((iter = _model->get_iter (*i))) {
429
430                                 //highlight any regions in the editor that use this region's source
431                                 boost::shared_ptr<ARDOUR::Region> region = (*iter)[_columns.region];
432                                 if (!region) continue;
433
434                                 boost::shared_ptr<ARDOUR::Source> source = region->source();
435                                 if (source) {
436
437                                         set<boost::shared_ptr<Region> > regions;
438                                         RegionFactory::get_regions_using_source ( source, regions );
439
440                                         for (set<boost::shared_ptr<Region> >::iterator region = regions.begin(); region != regions.end(); region++ ) {
441                                                 _change_connection.block (true);
442                                                 _editor->set_selected_regionview_from_region_list (*region, Selection::Add);
443                                                 _change_connection.block (false);
444
445                                         }
446                                 }
447                         }
448                 }
449         } else {
450                 _editor->get_selection().clear_regions ();
451         }
452
453 }
454
455 void
456 EditorSources::clock_format_changed ()
457 {
458         TreeModel::iterator i;
459         TreeModel::Children rows = _model->children();
460         for (i = rows.begin(); i != rows.end(); ++i) {
461                 boost::shared_ptr<ARDOUR::Region> rr = (*i)[_columns.region];
462                 populate_row(*i, rr);
463         }
464 }
465
466 void
467 EditorSources::format_position (samplepos_t pos, char* buf, size_t bufsize, bool onoff)
468 {
469         Timecode::BBT_Time bbt;
470         Timecode::Time timecode;
471
472         if (pos < 0) {
473                 error << string_compose (_("EditorSources::format_position: negative timecode position: %1"), pos) << endmsg;
474                 snprintf (buf, bufsize, "invalid");
475                 return;
476         }
477
478         switch (ARDOUR_UI::instance()->primary_clock->mode ()) {
479         case AudioClock::BBT:
480                 bbt = _session->tempo_map().bbt_at_sample (pos);
481                 if (onoff) {
482                         snprintf (buf, bufsize, "%03d|%02d|%04d" , bbt.bars, bbt.beats, bbt.ticks);
483                 } else {
484                         snprintf (buf, bufsize, "(%03d|%02d|%04d)" , bbt.bars, bbt.beats, bbt.ticks);
485                 }
486                 break;
487
488         case AudioClock::MinSec:
489                 samplepos_t left;
490                 int hrs;
491                 int mins;
492                 float secs;
493
494                 left = pos;
495                 hrs = (int) floor (left / (_session->sample_rate() * 60.0f * 60.0f));
496                 left -= (samplecnt_t) floor (hrs * _session->sample_rate() * 60.0f * 60.0f);
497                 mins = (int) floor (left / (_session->sample_rate() * 60.0f));
498                 left -= (samplecnt_t) floor (mins * _session->sample_rate() * 60.0f);
499                 secs = left / (float) _session->sample_rate();
500                 if (onoff) {
501                         snprintf (buf, bufsize, "%02d:%02d:%06.3f", hrs, mins, secs);
502                 } else {
503                         snprintf (buf, bufsize, "(%02d:%02d:%06.3f)", hrs, mins, secs);
504                 }
505                 break;
506
507         case AudioClock::Seconds:
508                 if (onoff) {
509                         snprintf (buf, bufsize, "%.1f", pos / (float)_session->sample_rate());
510                 } else {
511                         snprintf (buf, bufsize, "(%.1f)", pos / (float)_session->sample_rate());
512                 }
513                 break;
514
515         case AudioClock::Samples:
516                 if (onoff) {
517                         snprintf (buf, bufsize, "%" PRId64, pos);
518                 } else {
519                         snprintf (buf, bufsize, "(%" PRId64 ")", pos);
520                 }
521                 break;
522
523         case AudioClock::Timecode:
524         default:
525                 _session->timecode_time (pos, timecode);
526                 if (onoff) {
527                         snprintf (buf, bufsize, "%02d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
528                 } else {
529                         snprintf (buf, bufsize, "(%02d:%02d:%02d:%02d)", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
530                 }
531                 break;
532         }
533 }
534
535 void
536 EditorSources::show_context_menu (int button, int time)
537 {
538         using namespace Gtk::Menu_Helpers;
539         Gtk::Menu* menu = ARDOUR_UI_UTILS::shared_popup_menu ();
540         MenuList&  items = menu->items();
541 #ifdef RECOVER_REGIONS_IS_WORKING
542         items.push_back(MenuElem(_("Recover the selected Sources to their original Track & Position"),
543                                                          sigc::mem_fun(*this, &EditorSources::recover_selected_sources)));
544 #endif
545         items.push_back(MenuElem(_("Remove the selected Sources"),
546                                                          sigc::mem_fun(*this, &EditorSources::remove_selected_sources)));
547         menu->popup(1, time);
548 }
549
550 void
551 EditorSources::recover_selected_sources ()
552 {
553         ARDOUR::RegionList to_be_recovered;
554         
555         if (_display.get_selection()->count_selected_rows() > 0) {
556
557                 TreeIter iter;
558                 TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows ();
559                 for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
560                         if ((iter = _model->get_iter (*i))) {
561                                 boost::shared_ptr<ARDOUR::Region> region = (*iter)[_columns.region];
562                                 if (region) {
563                                         to_be_recovered.push_back(region);
564                                 }
565                         }
566                 }
567         }
568
569
570         /* ToDo */
571         _editor->recover_regions(to_be_recovered);  //this operation should be undo-able
572 }
573
574
575 void
576 EditorSources::remove_selected_sources ()
577 {
578         vector<string> choices;
579         string prompt;
580
581         prompt  = _("Do you want to remove the selected Sources?"
582                                 "\nThis operation cannot be undone."
583                                 "\nThe source files will not actually be deleted until you execute Session->Cleanup.");
584
585         choices.push_back (_("No, do nothing."));
586         choices.push_back (_("Only remove the Regions that use these Sources."));
587         choices.push_back (_("Yes, remove the Regions and Sources (cannot be undone!"));
588
589         Choice prompter (_("Remove selected Sources"), prompt, choices);
590
591         int opt = prompter.run ();
592
593         if ( opt >= 1) {
594                 
595                 std::list<boost::weak_ptr<ARDOUR::Source> > to_be_removed;
596                 
597                 if (_display.get_selection()->count_selected_rows() > 0) {
598
599                         TreeIter iter;
600                         TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows ();
601
602                         _editor->get_selection().clear_regions ();
603
604                         for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
605
606                                 if ((iter = _model->get_iter (*i))) {
607
608                                         boost::shared_ptr<ARDOUR::Region> region = (*iter)[_columns.region];
609         
610                                         if (!region) continue;
611
612                                         boost::shared_ptr<ARDOUR::Source> source = region->source();
613                                         if (source) {
614                                                 set<boost::shared_ptr<Region> > regions;
615                                                 RegionFactory::get_regions_using_source ( source, regions );
616
617                                                 for (set<boost::shared_ptr<Region> >::iterator region = regions.begin(); region != regions.end(); region++ ) {
618                                                         _change_connection.block (true);
619                                                         _editor->set_selected_regionview_from_region_list (*region, Selection::Add);
620                                                         _change_connection.block (false);
621                                                 }
622                                                 
623                                                 to_be_removed.push_back(source);
624                                         }
625                                 }
626
627                         }
628
629                         _editor->remove_selected_regions();  //this operation is undo-able
630
631                         if (opt==2) {   
632                                 for (std::list<boost::weak_ptr<ARDOUR::Source> >::iterator i = to_be_removed.begin(); i != to_be_removed.end(); ++i) {
633                                                 _session->remove_source(*i);  //this operation is (currently) not undo-able
634                                 }
635                         }
636                 }
637         }
638
639 }
640
641
642 bool
643 EditorSources::key_press (GdkEventKey* ev)
644 {
645         switch (ev->keyval) {
646         case GDK_Delete:
647         case GDK_BackSpace:
648                 remove_selected_sources();
649                 return true; 
650         }
651
652         return false;
653 }
654
655 bool
656 EditorSources::button_press (GdkEventButton *ev)
657 {
658         boost::shared_ptr<ARDOUR::Region> region;
659         TreeIter iter;
660         TreeModel::Path path;
661         TreeViewColumn* column;
662         int cellx;
663         int celly;
664
665         if (_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
666                 if ((iter = _model->get_iter (path))) {
667                         region = (*iter)[_columns.region];
668                 }
669         }
670
671         if (Keyboard::is_context_menu_event (ev)) {
672                 show_context_menu (ev->button, ev->time);
673                 return false;
674         }
675
676         return false;
677 }
678
679 void
680 EditorSources::selection_mapover (sigc::slot<void,boost::shared_ptr<Region> > sl)
681 {
682
683 }
684
685
686 void
687 EditorSources::drag_data_received (const RefPtr<Gdk::DragContext>& context,
688                                    int x, int y,
689                                    const SelectionData& data,
690                                    guint info, guint time)
691 {
692         /* ToDo:  allow dropping files/loops into the source list?  */
693 }
694
695 bool
696 EditorSources::selection_filter (const RefPtr<TreeModel>& model, const TreeModel::Path& path, bool already_selected)
697 {
698         return true;
699 }
700
701 /** @return Region that has been dragged out of the list, or 0 */
702 boost::shared_ptr<ARDOUR::Region>
703 EditorSources::get_dragged_region ()
704 {
705         list<boost::shared_ptr<ARDOUR::Region> > regions;
706         TreeView* region;
707         _display.get_object_drag_data (regions, &region);
708
709         if (regions.empty()) {
710                 return boost::shared_ptr<ARDOUR::Region> ();
711         }
712
713         assert (regions.size() == 1);
714         return regions.front ();
715 }
716
717 void
718 EditorSources::clear ()
719 {
720         _display.set_model (Glib::RefPtr<Gtk::TreeStore> (0));
721         _model->clear ();
722         _display.set_model (_model);
723 }
724
725 boost::shared_ptr<ARDOUR::Region>
726 EditorSources::get_single_selection ()
727 {
728         Glib::RefPtr<TreeSelection> selected = _display.get_selection();
729
730         if (selected->count_selected_rows() != 1) {
731                 return boost::shared_ptr<ARDOUR::Region> ();
732         }
733
734         TreeView::Selection::ListHandle_Path rows = selected->get_selected_rows ();
735
736         /* only one row selected, so rows.begin() is it */
737
738         TreeIter iter = _model->get_iter (*rows.begin());
739
740         if (!iter) {
741                 return boost::shared_ptr<ARDOUR::Region> ();
742         }
743
744         return (*iter)[_columns.region];
745 }
746
747 void
748 EditorSources::freeze_tree_model ()
749 {
750         _display.set_model (Glib::RefPtr<Gtk::TreeStore>(0));
751         _model->set_sort_column (-2, SORT_ASCENDING); //Disable sorting to gain performance
752 }
753
754 void
755 EditorSources::thaw_tree_model (){
756
757         _model->set_sort_column (0, SORT_ASCENDING); // renabale sorting
758         _display.set_model (_model);
759 }
760
761 XMLNode &
762 EditorSources::get_state () const
763 {
764         XMLNode* node = new XMLNode (X_("SourcesList"));
765
766         //TODO:  save sort state?
767
768         return *node;
769 }
770
771 void
772 EditorSources::set_state (const XMLNode & node)
773 {
774         bool changed = false;
775
776 }