use libsndfile for all audio file i/o, and rename DiskStream AudioDiskStream
[ardour.git] / gtk2_ardour / editor_region_list.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     $Id$
19 */
20
21 #include <cstdlib>
22 #include <cmath>
23 #include <algorithm>
24 #include <string>
25
26 #include <pbd/basename.h>
27
28 #include <ardour/audioregion.h>
29 #include <ardour/audiosource.h>
30 #include <ardour/session_region.h>
31
32 #include <gtkmm2ext/stop_signal.h>
33
34 #include "editor.h"
35 #include "editing.h"
36 #include "ardour_ui.h"
37 #include "gui_thread.h"
38 #include "actions.h"
39 #include "utils.h"
40
41 #include "i18n.h"
42
43 using namespace sigc;
44 using namespace ARDOUR;
45 using namespace Gtk;
46 using namespace Glib;
47 using namespace Editing;
48
49 void
50 Editor::handle_audio_region_removed (AudioRegion* region)
51 {
52         ENSURE_GUI_THREAD (bind (mem_fun (*this, &Editor::handle_audio_region_removed), region));
53         redisplay_regions ();
54 }
55
56 void
57 Editor::handle_new_audio_region (AudioRegion *region)
58 {
59         ENSURE_GUI_THREAD (bind (mem_fun (*this, &Editor::handle_new_audio_region), region));
60
61         /* don't copy region - the one we are being notified
62            about belongs to the session, and so it will
63            never be edited.
64         */
65         add_audio_region_to_region_display (region);
66 }
67
68 void
69 Editor::region_hidden (Region* r)
70 {
71         ENSURE_GUI_THREAD(bind (mem_fun(*this, &Editor::region_hidden), r));    
72
73         redisplay_regions ();
74 }
75
76 void
77 Editor::add_audio_region_to_region_display (AudioRegion *region)
78 {
79         string str;
80         TreeModel::Row row;
81         Gdk::Color c;
82
83         if (!show_automatic_regions_in_region_list && region->automatic()) {
84                 return;
85         }
86
87         if (region->hidden()) {
88
89                 TreeModel::iterator iter = region_list_model->get_iter ("0");
90                 TreeModel::Row parent;
91                 TreeModel::Row child;
92
93                 if (iter == region_list_model->children().end()) {
94                         
95                         parent = *(region_list_model->append());
96                         
97                         parent[region_list_columns.name] = _("Hidden");
98                         parent[region_list_columns.region] = 0;
99
100                 } else {
101
102                         if ((*iter)[region_list_columns.name] != _("Hidden")) {
103
104                                 parent = *(region_list_model->insert(iter));
105                                 parent[region_list_columns.name] = _("Hidden");
106                                 parent[region_list_columns.region] = 0;
107
108                         } else {
109                                 parent = *iter;
110                         }
111
112                 }
113
114                 row = *(region_list_model->append (parent.children()));
115
116         } else if (region->whole_file()) {
117
118                 row = *(region_list_model->append());
119                 set_color(c, rgba_from_style ("RegionListWholeFile", 0xff, 0, 0, 0, "fg", Gtk::STATE_NORMAL, false ));
120                 row[region_list_columns.color_] = c;
121
122                 if (region->source().name()[0] == '/') { // external file
123
124                         if (region->whole_file()) {
125                                 str = ".../";
126                                 str += PBD::basename_nosuffix (region->source().name());
127                                 
128                         } else {
129                                 str = region->name();
130                         }
131
132                 } else {
133
134                         str = region->name();
135
136                 }
137
138                 row[region_list_columns.name] = str;
139                 row[region_list_columns.region] = region;
140
141                 return;
142                 
143         } else {
144
145                 /* find parent node, add as new child */
146                 
147                 TreeModel::iterator i;
148                 TreeModel::Children rows = region_list_model->children();
149                 bool found_parent = false;
150
151                 for (i = rows.begin(); i != rows.end(); ++i) {
152
153                         Region* rr = (*i)[region_list_columns.region];
154                         AudioRegion* r = dynamic_cast<AudioRegion*>(rr);
155
156                         if (r && r->whole_file()) {
157                                 if (region->source_equivalent (*r)) {
158                                         row = *(region_list_model->append ((*i).children()));
159                                         found_parent = true;
160                                         break;
161                                 }
162                         }
163                 }
164
165                 if (!found_parent) {
166                         row = *(region_list_model->append());
167                 }
168
169                 
170         }
171         
172         row[region_list_columns.region] = region;
173         
174         if (region->n_channels() > 1) {
175                 row[region_list_columns.name] = string_compose("%1  [%2]", region->name(), region->n_channels());
176         } else {
177                 row[region_list_columns.name] = region->name();
178         }
179 }
180
181 void
182 Editor::region_list_selection_changed() 
183 {
184         bool selected;
185
186         if (region_list_display.get_selection()->count_selected_rows() > 0) {
187                 selected = true;
188         } else {
189                 selected = false;
190         }
191         
192         if (selected) {
193                 TreeView::Selection::ListHandle_Path rows = region_list_display.get_selection()->get_selected_rows ();
194                 TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
195                 TreeIter iter;
196
197                 /* just set the first selected region (in fact, the selection model might be SINGLE, which
198                    means there can only be one.
199                 */
200                 
201                 if ((iter = region_list_model->get_iter (*i))) {
202                         set_selected_regionview_from_region_list (*((*iter)[region_list_columns.region]), Selection::Set);
203                 }
204         }
205 }
206
207 void
208 Editor::insert_into_tmp_audio_regionlist(AudioRegion* region)
209 {
210         /* keep all whole files at the beginning */
211         
212         if (region->whole_file()) {
213                 tmp_audio_region_list.push_front (region);
214         } else {
215                 tmp_audio_region_list.push_back (region);
216         }
217 }
218
219 void
220 Editor::redisplay_regions ()
221 {
222         if (session) {
223
224                 region_list_display.set_model (Glib::RefPtr<Gtk::TreeStore>(0));
225                 region_list_model->clear ();
226
227                 /* now add everything we have, via a temporary list used to help with
228                    sorting.
229                 */
230                 
231                 tmp_audio_region_list.clear();
232                 session->foreach_audio_region (this, &Editor::insert_into_tmp_audio_regionlist);
233
234                 for (list<AudioRegion*>::iterator r = tmp_audio_region_list.begin(); r != tmp_audio_region_list.end(); ++r) {
235                         add_audio_region_to_region_display (*r);
236                 }
237                 
238                 region_list_display.set_model (region_list_model);
239         }
240 }
241
242 void
243 Editor::region_list_clear ()
244 {
245         region_list_model->clear();
246 }
247
248 void
249 Editor::build_region_list_menu ()
250 {
251         region_list_menu = dynamic_cast<Menu*>(ActionManager::get_widget ("/RegionListMenu"));
252                                                
253         /* now grab specific menu items that we need */
254
255         Glib::RefPtr<Action> act;
256
257         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAll"));
258         if (act) {
259                 toggle_full_region_list_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
260         }
261
262         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAuto"));
263         if (act) {
264                 toggle_show_auto_regions_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
265         }
266 }
267
268 void
269 Editor::toggle_show_auto_regions ()
270 {
271         show_automatic_regions_in_region_list = toggle_show_auto_regions_action->get_active();
272         redisplay_regions ();
273 }
274
275 void
276 Editor::toggle_full_region_list ()
277 {
278         if (toggle_full_region_list_action->get_active()) {
279                 region_list_display.expand_all ();
280         } else {
281                 region_list_display.collapse_all ();
282         }
283 }
284
285 void
286 Editor::show_region_list_display_context_menu (int button, int time)
287 {
288         if (region_list_menu == 0) {
289                 build_region_list_menu ();
290         }
291
292         if (region_list_display.get_selection()->count_selected_rows() > 0) {
293                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, true);
294         } else {
295                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, false);
296         }
297
298         region_list_menu->popup (button, time);
299 }
300
301 bool
302 Editor::region_list_display_key_press (GdkEventKey* ev)
303 {
304         return false;
305 }
306
307 bool
308 Editor::region_list_display_key_release (GdkEventKey* ev)
309 {
310         switch (ev->keyval) {
311         case GDK_Delete:
312                 remove_region_from_region_list ();
313                 return true;
314                 break;
315         default:
316                 break;
317         }
318
319         return false;
320 }
321
322 bool
323 Editor::region_list_display_button_press (GdkEventButton *ev)
324 {
325         Region* region;
326         TreeIter iter;
327         TreeModel::Path path;
328         TreeViewColumn* column;
329         int cellx;
330         int celly;
331
332         if (region_list_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
333                 if ((iter = region_list_model->get_iter (path))) {
334                         region = (*iter)[region_list_columns.region];
335                 }
336         }
337
338         if (region == 0) {
339                 return false;
340         }
341
342         if (Keyboard::is_delete_event (ev)) {
343                 session->remove_region_from_region_list (*region);
344                 return true;
345         }
346
347         if (Keyboard::is_context_menu_event (ev)) {
348                 show_region_list_display_context_menu (ev->button, ev->time);
349                 return true;
350         }
351
352         switch (ev->button) {
353         case 1:
354                 /* audition on double click */
355                 if (ev->type == GDK_2BUTTON_PRESS) {
356                         consider_auditioning (*region);
357                         return true;
358                 }
359                 return false;
360                 break;
361
362         case 2:
363                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
364                         consider_auditioning (*region);
365                 }
366                 return true;
367                 break;
368
369         default:
370                 break; 
371         }
372
373         return false;
374 }       
375
376 bool
377 Editor::region_list_display_button_release (GdkEventButton *ev)
378 {
379         TreeIter iter;
380         TreeModel::Path path;
381         TreeViewColumn* column;
382         int cellx;
383         int celly;
384         Region* region = 0;
385
386         if (region_list_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
387                 if ((iter = region_list_model->get_iter (path))) {
388                         region = (*iter)[region_list_columns.region];
389                 }
390         }
391
392         if (region && Keyboard::is_delete_event (ev)) {
393                 session->remove_region_from_region_list (*region);
394                 return true;
395         }
396
397         return false;
398 }
399
400 void
401 Editor::consider_auditioning (Region& region)
402 {
403         AudioRegion* r = dynamic_cast<AudioRegion*> (&region);
404
405         if (r == 0) {
406                 session->cancel_audition ();
407                 return;
408         }
409
410         if (session->is_auditioning()) {
411                 session->cancel_audition ();
412                 if (r == last_audition_region) {
413                         return;
414                 }
415         }
416
417         session->audition_region (*r);
418         last_audition_region = r;
419 }
420
421 int
422 Editor::region_list_sorter (TreeModel::iterator a, TreeModel::iterator b)
423 {
424         int cmp = 0;
425
426         Region* r1 = (*a)[region_list_columns.region];
427         Region* r2 = (*b)[region_list_columns.region];
428
429         /* handle rows without regions, like "Hidden" */
430
431         if (r1 == 0) {
432                 return -1;
433         }
434
435         if (r2 == 0) {
436                 return 1;
437         }
438
439         AudioRegion* region1 = dynamic_cast<AudioRegion*> (r1);
440         AudioRegion* region2 = dynamic_cast<AudioRegion*> (r2);
441
442         if (region1 == 0 || region2 == 0) {
443                 Glib::ustring s1;
444                 Glib::ustring s2;
445                 switch (region_list_sort_type) {
446                 case ByName:
447                         s1 = (*a)[region_list_columns.name];
448                         s2 = (*b)[region_list_columns.name];
449                         return (s1.compare (s2));
450                 default:
451                         return 0;
452                 }
453         }
454
455         switch (region_list_sort_type) {
456         case ByName:
457                 cmp = strcasecmp (region1->name().c_str(), region2->name().c_str());
458                 break;
459
460         case ByLength:
461                 cmp = region1->length() - region2->length();
462                 break;
463                 
464         case ByPosition:
465                 cmp = region1->position() - region2->position();
466                 break;
467                 
468         case ByTimestamp:
469                 cmp = region1->source().timestamp() - region2->source().timestamp();
470                 break;
471         
472         case ByStartInFile:
473                 cmp = region1->start() - region2->start();
474                 break;
475                 
476         case ByEndInFile:
477                 cmp = (region1->start() + region1->length()) - (region2->start() + region2->length());
478                 break;
479                 
480         case BySourceFileName:
481                 cmp = strcasecmp (region1->source().name().c_str(), region2->source().name().c_str());
482                 break;
483
484         case BySourceFileLength:
485                 cmp = region1->source().length() - region2->source().length();
486                 break;
487                 
488         case BySourceFileCreationDate:
489                 cmp = region1->source().timestamp() - region2->source().timestamp();
490                 break;
491
492         case BySourceFileFS:
493                 if (region1->source().name() == region2->source().name()) {
494                         cmp = strcasecmp (region1->name().c_str(),  region2->name().c_str());
495                 } else {
496                         cmp = strcasecmp (region1->source().name().c_str(),  region2->source().name().c_str());
497                 }
498                 break;
499         }
500
501         if (cmp < 0) {
502                 return -1;
503         } else if (cmp > 0) {
504                 return 1;
505         } else {
506                 return 0;
507         }
508 }
509
510 void
511 Editor::reset_region_list_sort_type (RegionListSortType type)
512 {
513         if (type != region_list_sort_type) {
514                 region_list_sort_type = type;
515                 region_list_model->set_sort_func (0, (mem_fun (*this, &Editor::region_list_sorter)));
516         }
517 }
518
519 void
520 Editor::reset_region_list_sort_direction (bool up)
521 {
522         region_list_model->set_sort_column (0, up ? SORT_ASCENDING : SORT_DESCENDING);
523 }
524
525 void
526 Editor::region_list_selection_mapover (slot<void,Region&> sl)
527 {
528         Glib::RefPtr<TreeSelection> selection = region_list_display.get_selection();
529         TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
530         TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
531
532         if (selection->count_selected_rows() == 0 || session == 0) {
533                 return;
534         }
535
536         for (; i != rows.end(); ++i) {
537                 TreeIter iter;
538
539                 if ((iter = region_list_model->get_iter (*i))) {
540                         sl (*((*iter)[region_list_columns.region]));
541                 }
542         }
543 }
544
545 void
546 Editor::hide_a_region (Region& r)
547 {
548         r.set_hidden (true);
549 }
550
551 void
552 Editor::remove_a_region (Region& r)
553 {
554         session->remove_region_from_region_list (r);
555 }
556
557 void
558 Editor::audition_region_from_region_list ()
559 {
560         region_list_selection_mapover (mem_fun (*this, &Editor::consider_auditioning));
561 }
562
563 void
564 Editor::hide_region_from_region_list ()
565 {
566         region_list_selection_mapover (mem_fun (*this, &Editor::hide_a_region));
567 }
568
569 void
570 Editor::remove_region_from_region_list ()
571 {
572         region_list_selection_mapover (mem_fun (*this, &Editor::remove_a_region));
573 }
574
575 void  
576 Editor::region_list_display_drag_data_received (const RefPtr<Gdk::DragContext>& context,
577                                                 int x, int y, 
578                                                 const SelectionData& data,
579                                                 guint info, guint time)
580 {
581         vector<ustring> paths;
582
583         if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
584                 jack_nframes_t pos = 0;
585                 do_embed (paths, false, ImportAsRegion, 0, pos, true);
586                 context->drag_finish (true, false, time);
587         }
588 }
589
590 bool
591 Editor::region_list_selection_filter (const RefPtr<TreeModel>& model, const TreeModel::Path& path, bool yn)
592 {
593         /* not possible to select rows that do not represent regions, like "Hidden" */
594
595         return (*(model->get_iter (path)))[region_list_columns.region] != 0;
596 }