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