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