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