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