modified fix from carl for region copy-moves-original-to-start bug; change verbose...
[ardour.git] / gtk2_ardour / editor_region_list.cc
1 /*
2     Copyright (C) 2000-2005 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cstdlib>
21 #include <cmath>
22 #include <algorithm>
23 #include <string>
24 #include <sstream>
25
26 #include <pbd/basename.h>
27
28 #include <ardour/audioregion.h>
29 #include <ardour/audiofilesource.h>
30 #include <ardour/silentfilesource.h>
31 #include <ardour/session_region.h>
32
33 #include <gtkmm2ext/stop_signal.h>
34
35 #include "editor.h"
36 #include "editing.h"
37 #include "keyboard.h"
38 #include "ardour_ui.h"
39 #include "gui_thread.h"
40 #include "actions.h"
41 #include "utils.h"
42
43 #include "i18n.h"
44
45 using namespace sigc;
46 using namespace ARDOUR;
47 using namespace PBD;
48 using namespace Gtk;
49 using namespace Glib;
50 using namespace Editing;
51
52 void
53 Editor::handle_audio_region_removed (boost::weak_ptr<AudioRegion> wregion)
54 {
55         ENSURE_GUI_THREAD (mem_fun (*this, &Editor::redisplay_regions));
56         redisplay_regions ();
57 }
58
59 void
60 Editor::handle_new_audio_region (boost::weak_ptr<AudioRegion> wregion)
61 {
62         ENSURE_GUI_THREAD (bind (mem_fun (*this, &Editor::handle_new_audio_region), wregion));
63
64         /* don't copy region - the one we are being notified
65            about belongs to the session, and so it will
66            never be edited.
67         */
68
69         boost::shared_ptr<AudioRegion> region (wregion.lock());
70         
71         if (region) {
72                 add_audio_region_to_region_display (region);
73         }
74 }
75
76 void
77 Editor::region_hidden (boost::shared_ptr<Region> r)
78 {
79         ENSURE_GUI_THREAD(bind (mem_fun(*this, &Editor::region_hidden), r));    
80
81         redisplay_regions ();
82 }
83
84 void
85 Editor::add_audio_region_to_region_display (boost::shared_ptr<AudioRegion> region)
86 {
87         string str;
88         TreeModel::Row row;
89         Gdk::Color c;
90         bool missing_source;
91
92         missing_source = boost::dynamic_pointer_cast<SilentFileSource>(region->source());
93
94         if (!show_automatic_regions_in_region_list && region->automatic()) {
95                 return;
96         }
97
98         if (region->hidden()) {
99
100                 TreeModel::iterator iter = region_list_model->get_iter ("0");
101                 TreeModel::Row parent;
102                 TreeModel::Row child;
103
104                 if (iter == region_list_model->children().end()) {
105                         
106                         parent = *(region_list_model->append());
107                         
108                         parent[region_list_columns.name] = _("Hidden");
109                         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                 /* just set the first selected region (in fact, the selection model might be SINGLE, which
235                    means there can only be one.
236                 */
237                 
238                 if ((iter = region_list_model->get_iter (*i))) {
239                         set_selected_regionview_from_region_list (((*iter)[region_list_columns.region]), Selection::Set);
240                 }
241         }
242 }
243
244 void
245 Editor::insert_into_tmp_audio_regionlist(boost::shared_ptr<AudioRegion> region)
246 {
247         /* keep all whole files at the beginning */
248         
249         if (region->whole_file()) {
250                 tmp_audio_region_list.push_front (region);
251         } else {
252                 tmp_audio_region_list.push_back (region);
253         }
254 }
255
256 void
257 Editor::redisplay_regions ()
258 {
259         if (session) {
260
261                 region_list_display.set_model (Glib::RefPtr<Gtk::TreeStore>(0));
262                 region_list_model->clear ();
263
264                 /* now add everything we have, via a temporary list used to help with
265                    sorting.
266                 */
267                 
268                 tmp_audio_region_list.clear();
269                 session->foreach_audio_region (this, &Editor::insert_into_tmp_audio_regionlist);
270
271                 for (list<boost::shared_ptr<AudioRegion> >::iterator r = tmp_audio_region_list.begin(); r != tmp_audio_region_list.end(); ++r) {
272                         add_audio_region_to_region_display (*r);
273                 }
274                 tmp_audio_region_list.clear();
275                 
276                 region_list_display.set_model (region_list_model);
277         }
278 }
279
280 void
281 Editor::build_region_list_menu ()
282 {
283         region_list_menu = dynamic_cast<Menu*>(ActionManager::get_widget ("/RegionListMenu"));
284                                                
285         /* now grab specific menu items that we need */
286
287         Glib::RefPtr<Action> act;
288
289         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAll"));
290         if (act) {
291                 toggle_full_region_list_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
292         }
293
294         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAuto"));
295         if (act) {
296                 toggle_show_auto_regions_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
297         }
298 }
299
300 void
301 Editor::toggle_show_auto_regions ()
302 {
303         show_automatic_regions_in_region_list = toggle_show_auto_regions_action->get_active();
304         redisplay_regions ();
305 }
306
307 void
308 Editor::toggle_full_region_list ()
309 {
310         if (toggle_full_region_list_action->get_active()) {
311                 region_list_display.expand_all ();
312         } else {
313                 region_list_display.collapse_all ();
314         }
315 }
316
317 void
318 Editor::show_region_list_display_context_menu (int button, int time)
319 {
320         if (region_list_menu == 0) {
321                 build_region_list_menu ();
322         }
323
324         if (region_list_display.get_selection()->count_selected_rows() > 0) {
325                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, true);
326         } else {
327                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, false);
328         }
329
330         region_list_menu->popup (button, time);
331 }
332
333 bool
334 Editor::region_list_display_key_press (GdkEventKey* ev)
335 {
336         return false;
337 }
338
339 bool
340 Editor::region_list_display_key_release (GdkEventKey* ev)
341 {
342         switch (ev->keyval) {
343         case GDK_Delete:
344                 remove_region_from_region_list ();
345                 return true;
346                 break;
347         default:
348                 break;
349         }
350
351         return false;
352 }
353
354 bool
355 Editor::region_list_display_button_press (GdkEventButton *ev)
356 {
357         boost::shared_ptr<Region> region;
358         TreeIter iter;
359         TreeModel::Path path;
360         TreeViewColumn* column;
361         int cellx;
362         int celly;
363
364         if (region_list_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
365                 if ((iter = region_list_model->get_iter (path))) {
366                         region = (*iter)[region_list_columns.region];
367                 }
368         }
369
370         if (Keyboard::is_context_menu_event (ev)) {
371                 show_region_list_display_context_menu (ev->button, ev->time);
372                 return true;
373         }
374
375         if (region == 0) {
376                 return false;
377         }
378
379         switch (ev->button) {
380         case 1:
381                 /* audition on double click */
382                 if (ev->type == GDK_2BUTTON_PRESS) {
383                         consider_auditioning (region);
384                         return true;
385                 }
386                 return false;
387                 break;
388
389         case 2:
390                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
391                         consider_auditioning (region);
392                 }
393                 return true;
394                 break;
395
396         default:
397                 break; 
398         }
399
400         return false;
401 }       
402
403 bool
404 Editor::region_list_display_button_release (GdkEventButton *ev)
405 {
406         TreeIter iter;
407         TreeModel::Path path;
408         TreeViewColumn* column;
409         int cellx;
410         int celly;
411         boost::shared_ptr<Region> region;
412
413         if (region_list_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
414                 if ((iter = region_list_model->get_iter (path))) {
415                         region = (*iter)[region_list_columns.region];
416                 }
417         }
418
419         if (region && Keyboard::is_delete_event (ev)) {
420                 session->remove_region_from_region_list (region);
421                 return true;
422         }
423
424         return false;
425 }
426
427 void
428 Editor::consider_auditioning (boost::shared_ptr<Region> region)
429 {
430         boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
431
432         if (r == 0) {
433                 session->cancel_audition ();
434                 return;
435         }
436
437         if (session->is_auditioning()) {
438                 session->cancel_audition ();
439                 if (r == last_audition_region) {
440                         return;
441                 }
442         }
443
444         session->audition_region (r);
445         last_audition_region = r;
446 }
447
448 int
449 Editor::region_list_sorter (TreeModel::iterator a, TreeModel::iterator b)
450 {
451         int cmp = 0;
452
453         boost::shared_ptr<Region> r1 = (*a)[region_list_columns.region];
454         boost::shared_ptr<Region> r2 = (*b)[region_list_columns.region];
455
456         /* handle rows without regions, like "Hidden" */
457
458         if (r1 == 0) {
459                 return -1;
460         }
461
462         if (r2 == 0) {
463                 return 1;
464         }
465
466         boost::shared_ptr<AudioRegion> region1 = boost::dynamic_pointer_cast<AudioRegion> (r1);
467         boost::shared_ptr<AudioRegion> region2 = boost::dynamic_pointer_cast<AudioRegion> (r2);
468
469         if (region1 == 0 || region2 == 0) {
470                 Glib::ustring s1;
471                 Glib::ustring s2;
472                 switch (region_list_sort_type) {
473                 case ByName:
474                         s1 = (*a)[region_list_columns.name];
475                         s2 = (*b)[region_list_columns.name];
476                         return (s1.compare (s2));
477                 default:
478                         return 0;
479                 }
480         }
481
482         switch (region_list_sort_type) {
483         case ByName:
484                 cmp = strcasecmp (region1->name().c_str(), region2->name().c_str());
485                 break;
486
487         case ByLength:
488                 cmp = region1->length() - region2->length();
489                 break;
490                 
491         case ByPosition:
492                 cmp = region1->position() - region2->position();
493                 break;
494                 
495         case ByTimestamp:
496                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
497                 break;
498         
499         case ByStartInFile:
500                 cmp = region1->start() - region2->start();
501                 break;
502                 
503         case ByEndInFile:
504                 cmp = (region1->start() + region1->length()) - (region2->start() + region2->length());
505                 break;
506                 
507         case BySourceFileName:
508                 cmp = strcasecmp (region1->source()->name().c_str(), region2->source()->name().c_str());
509                 break;
510
511         case BySourceFileLength:
512                 cmp = region1->source()->length() - region2->source()->length();
513                 break;
514                 
515         case BySourceFileCreationDate:
516                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
517                 break;
518
519         case BySourceFileFS:
520                 if (region1->source()->name() == region2->source()->name()) {
521                         cmp = strcasecmp (region1->name().c_str(),  region2->name().c_str());
522                 } else {
523                         cmp = strcasecmp (region1->source()->name().c_str(),  region2->source()->name().c_str());
524                 }
525                 break;
526         }
527
528         if (cmp < 0) {
529                 return -1;
530         } else if (cmp > 0) {
531                 return 1;
532         } else {
533                 return 0;
534         }
535 }
536
537 void
538 Editor::reset_region_list_sort_type (RegionListSortType type)
539 {
540         if (type != region_list_sort_type) {
541                 region_list_sort_type = type;
542                 region_list_model->set_sort_func (0, (mem_fun (*this, &Editor::region_list_sorter)));
543         }
544 }
545
546 void
547 Editor::reset_region_list_sort_direction (bool up)
548 {
549         region_list_model->set_sort_column (0, up ? SORT_ASCENDING : SORT_DESCENDING);
550 }
551
552 void
553 Editor::region_list_selection_mapover (slot<void,boost::shared_ptr<Region> > sl)
554 {
555         Glib::RefPtr<TreeSelection> selection = region_list_display.get_selection();
556         TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
557         TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
558
559         if (selection->count_selected_rows() == 0 || session == 0) {
560                 return;
561         }
562
563         for (; i != rows.end(); ++i) {
564                 TreeIter iter;
565
566                 if ((iter = region_list_model->get_iter (*i))) {
567                         sl (((*iter)[region_list_columns.region]));
568                 }
569         }
570 }
571
572 void
573 Editor::hide_a_region (boost::shared_ptr<Region> r)
574 {
575         r->set_hidden (true);
576 }
577
578 void
579 Editor::remove_a_region (boost::shared_ptr<Region> r)
580 {
581         cerr << "remove " << r->name();
582         session->remove_region_from_region_list (r);
583 }
584
585 void
586 Editor::audition_region_from_region_list ()
587 {
588         region_list_selection_mapover (mem_fun (*this, &Editor::consider_auditioning));
589 }
590
591 void
592 Editor::hide_region_from_region_list ()
593 {
594         region_list_selection_mapover (mem_fun (*this, &Editor::hide_a_region));
595 }
596
597 void
598 Editor::remove_region_from_region_list ()
599 {
600         cerr << "Mapping remove over region selection\n";
601         region_list_selection_mapover (mem_fun (*this, &Editor::remove_a_region));
602 }
603
604 void  
605 Editor::region_list_display_drag_data_received (const RefPtr<Gdk::DragContext>& context,
606                                                 int x, int y, 
607                                                 const SelectionData& data,
608                                                 guint info, guint time)
609 {
610         vector<ustring> paths;
611
612         if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
613                 nframes_t pos = 0;
614                 do_embed (paths, false, ImportAsRegion, 0, pos, true);
615                 context->drag_finish (true, false, time);
616         }
617 }
618
619 bool
620 Editor::region_list_selection_filter (const RefPtr<TreeModel>& model, const TreeModel::Path& path, bool yn)
621 {
622         /* not possible to select rows that do not represent regions, like "Hidden" */
623
624         /// XXXX FIXME boost::shared_ptr<Region> r = ((model->get_iter (path)))[region_list_columns.region];
625         /// return r != 0;
626         return true;
627 }