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