fixes for destructive track offsets of various kinds; move from jack_nframes_t -...
[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     $Id$
19 */
20
21 #include <cstdlib>
22 #include <cmath>
23 #include <algorithm>
24 #include <string>
25
26 #include <pbd/basename.h>
27
28 #include <ardour/audioregion.h>
29 #include <ardour/audiosource.h>
30 #include <ardour/session_region.h>
31
32 #include <gtkmm2ext/stop_signal.h>
33
34 #include "editor.h"
35 #include "editing.h"
36 #include "ardour_ui.h"
37 #include "gui_thread.h"
38 #include "actions.h"
39 #include "utils.h"
40
41 #include "i18n.h"
42
43 using namespace sigc;
44 using namespace ARDOUR;
45 using namespace PBD;
46 using namespace Gtk;
47 using namespace Glib;
48 using namespace Editing;
49
50 void
51 Editor::handle_audio_region_removed (boost::shared_ptr<AudioRegion> region)
52 {
53         ENSURE_GUI_THREAD (bind (mem_fun (*this, &Editor::handle_audio_region_removed), region));
54         redisplay_regions ();
55 }
56
57 void
58 Editor::handle_new_audio_region (boost::shared_ptr<AudioRegion> region)
59 {
60         ENSURE_GUI_THREAD (bind (mem_fun (*this, &Editor::handle_new_audio_region), region));
61
62         /* don't copy region - the one we are being notified
63            about belongs to the session, and so it will
64            never be edited.
65         */
66         add_audio_region_to_region_display (region);
67 }
68
69 void
70 Editor::region_hidden (boost::shared_ptr<Region> r)
71 {
72         ENSURE_GUI_THREAD(bind (mem_fun(*this, &Editor::region_hidden), r));    
73
74         redisplay_regions ();
75 }
76
77 void
78 Editor::add_audio_region_to_region_display (boost::shared_ptr<AudioRegion> region)
79 {
80         string str;
81         TreeModel::Row row;
82         Gdk::Color c;
83
84         if (!show_automatic_regions_in_region_list && region->automatic()) {
85                 return;
86         }
87
88         if (region->hidden()) {
89
90                 TreeModel::iterator iter = region_list_model->get_iter ("0");
91                 TreeModel::Row parent;
92                 TreeModel::Row child;
93
94                 if (iter == region_list_model->children().end()) {
95                         
96                         parent = *(region_list_model->append());
97                         
98                         parent[region_list_columns.name] = _("Hidden");
99                         /// XXX FIX ME parent[region_list_columns.region]->reset ();
100
101                 } else {
102
103                         if ((*iter)[region_list_columns.name] != _("Hidden")) {
104
105                                 parent = *(region_list_model->insert(iter));
106                                 parent[region_list_columns.name] = _("Hidden");
107                                 /// XXX FIX ME parent[region_list_columns.region]->reset ();
108
109                         } else {
110                                 parent = *iter;
111                         }
112
113                 }
114
115                 row = *(region_list_model->append (parent.children()));
116
117         } else if (region->whole_file()) {
118
119                 row = *(region_list_model->append());
120                 set_color(c, rgba_from_style ("RegionListWholeFile", 0xff, 0, 0, 0, "fg", Gtk::STATE_NORMAL, false ));
121                 row[region_list_columns.color_] = c;
122
123                 if (region->source()->name()[0] == '/') { // external file
124
125                         if (region->whole_file()) {
126                                 str = ".../";
127                                 str += PBD::basename_nosuffix (region->source()->name());
128                                 
129                         } else {
130                                 str = region->name();
131                         }
132
133                 } else {
134
135                         str = region->name();
136
137                 }
138
139                 row[region_list_columns.name] = str;
140                 row[region_list_columns.region] = region;
141
142                 return;
143                 
144         } else {
145
146                 /* find parent node, add as new child */
147                 
148                 TreeModel::iterator i;
149                 TreeModel::Children rows = region_list_model->children();
150                 bool found_parent = false;
151
152                 for (i = rows.begin(); i != rows.end(); ++i) {
153
154                         boost::shared_ptr<Region> rr = (*i)[region_list_columns.region];
155                         boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion>(rr);
156
157                         if (r && r->whole_file()) {
158                                 if (region->source_equivalent (r)) {
159                                         row = *(region_list_model->append ((*i).children()));
160                                         found_parent = true;
161                                         break;
162                                 }
163                         }
164                 }
165
166                 if (!found_parent) {
167                         row = *(region_list_model->append());
168                 }
169
170                 
171         }
172         
173         row[region_list_columns.region] = region;
174         
175         if (region->n_channels() > 1) {
176                 row[region_list_columns.name] = string_compose("%1  [%2]", region->name(), region->n_channels());
177         } else {
178                 row[region_list_columns.name] = region->name();
179         }
180 }
181
182 void
183 Editor::region_list_selection_changed() 
184 {
185         bool selected;
186
187         if (region_list_display.get_selection()->count_selected_rows() > 0) {
188                 selected = true;
189         } else {
190                 selected = false;
191         }
192         
193         if (selected) {
194                 TreeView::Selection::ListHandle_Path rows = region_list_display.get_selection()->get_selected_rows ();
195                 TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
196                 TreeIter iter;
197
198                 /* just set the first selected region (in fact, the selection model might be SINGLE, which
199                    means there can only be one.
200                 */
201                 
202                 if ((iter = region_list_model->get_iter (*i))) {
203                         set_selected_regionview_from_region_list (((*iter)[region_list_columns.region]), Selection::Set);
204                 }
205         }
206 }
207
208 void
209 Editor::insert_into_tmp_audio_regionlist(boost::shared_ptr<AudioRegion> region)
210 {
211         /* keep all whole files at the beginning */
212         
213         if (region->whole_file()) {
214                 tmp_audio_region_list.push_front (region);
215         } else {
216                 tmp_audio_region_list.push_back (region);
217         }
218 }
219
220 void
221 Editor::redisplay_regions ()
222 {
223         if (session) {
224
225                 region_list_display.set_model (Glib::RefPtr<Gtk::TreeStore>(0));
226                 region_list_model->clear ();
227
228                 /* now add everything we have, via a temporary list used to help with
229                    sorting.
230                 */
231                 
232                 tmp_audio_region_list.clear();
233                 session->foreach_audio_region (this, &Editor::insert_into_tmp_audio_regionlist);
234
235                 for (list<boost::shared_ptr<AudioRegion> >::iterator r = tmp_audio_region_list.begin(); r != tmp_audio_region_list.end(); ++r) {
236                         add_audio_region_to_region_display (*r);
237                 }
238                 
239                 region_list_display.set_model (region_list_model);
240         }
241 }
242
243 void
244 Editor::region_list_clear ()
245 {
246         region_list_model->clear();
247 }
248
249 void
250 Editor::build_region_list_menu ()
251 {
252         region_list_menu = dynamic_cast<Menu*>(ActionManager::get_widget ("/RegionListMenu"));
253                                                
254         /* now grab specific menu items that we need */
255
256         Glib::RefPtr<Action> act;
257
258         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAll"));
259         if (act) {
260                 toggle_full_region_list_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
261         }
262
263         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAuto"));
264         if (act) {
265                 toggle_show_auto_regions_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
266         }
267 }
268
269 void
270 Editor::toggle_show_auto_regions ()
271 {
272         show_automatic_regions_in_region_list = toggle_show_auto_regions_action->get_active();
273         redisplay_regions ();
274 }
275
276 void
277 Editor::toggle_full_region_list ()
278 {
279         if (toggle_full_region_list_action->get_active()) {
280                 region_list_display.expand_all ();
281         } else {
282                 region_list_display.collapse_all ();
283         }
284 }
285
286 void
287 Editor::show_region_list_display_context_menu (int button, int time)
288 {
289         if (region_list_menu == 0) {
290                 build_region_list_menu ();
291         }
292
293         if (region_list_display.get_selection()->count_selected_rows() > 0) {
294                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, true);
295         } else {
296                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, false);
297         }
298
299         region_list_menu->popup (button, time);
300 }
301
302 bool
303 Editor::region_list_display_key_press (GdkEventKey* ev)
304 {
305         return false;
306 }
307
308 bool
309 Editor::region_list_display_key_release (GdkEventKey* ev)
310 {
311         switch (ev->keyval) {
312         case GDK_Delete:
313                 remove_region_from_region_list ();
314                 return true;
315                 break;
316         default:
317                 break;
318         }
319
320         return false;
321 }
322
323 bool
324 Editor::region_list_display_button_press (GdkEventButton *ev)
325 {
326         boost::shared_ptr<Region> region;
327         TreeIter iter;
328         TreeModel::Path path;
329         TreeViewColumn* column;
330         int cellx;
331         int celly;
332
333         if (region_list_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
334                 if ((iter = region_list_model->get_iter (path))) {
335                         region = (*iter)[region_list_columns.region];
336                 }
337         }
338
339         if (region == 0) {
340                 return false;
341         }
342
343         if (Keyboard::is_delete_event (ev)) {
344                 session->remove_region_from_region_list (region);
345                 return true;
346         }
347
348         if (Keyboard::is_context_menu_event (ev)) {
349                 show_region_list_display_context_menu (ev->button, ev->time);
350                 return true;
351         }
352
353         switch (ev->button) {
354         case 1:
355                 /* audition on double click */
356                 if (ev->type == GDK_2BUTTON_PRESS) {
357                         consider_auditioning (region);
358                         return true;
359                 }
360                 return false;
361                 break;
362
363         case 2:
364                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
365                         consider_auditioning (region);
366                 }
367                 return true;
368                 break;
369
370         default:
371                 break; 
372         }
373
374         return false;
375 }       
376
377 bool
378 Editor::region_list_display_button_release (GdkEventButton *ev)
379 {
380         TreeIter iter;
381         TreeModel::Path path;
382         TreeViewColumn* column;
383         int cellx;
384         int celly;
385         boost::shared_ptr<Region> region;
386
387         if (region_list_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
388                 if ((iter = region_list_model->get_iter (path))) {
389                         region = (*iter)[region_list_columns.region];
390                 }
391         }
392
393         if (region && Keyboard::is_delete_event (ev)) {
394                 session->remove_region_from_region_list (region);
395                 return true;
396         }
397
398         return false;
399 }
400
401 void
402 Editor::consider_auditioning (boost::shared_ptr<Region> region)
403 {
404         boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
405
406         if (r == 0) {
407                 session->cancel_audition ();
408                 return;
409         }
410
411         if (session->is_auditioning()) {
412                 session->cancel_audition ();
413                 if (r == last_audition_region) {
414                         return;
415                 }
416         }
417
418         session->audition_region (r);
419         last_audition_region = r;
420 }
421
422 int
423 Editor::region_list_sorter (TreeModel::iterator a, TreeModel::iterator b)
424 {
425         int cmp = 0;
426
427         boost::shared_ptr<Region> r1 = (*a)[region_list_columns.region];
428         boost::shared_ptr<Region> r2 = (*b)[region_list_columns.region];
429
430         /* handle rows without regions, like "Hidden" */
431
432         if (r1 == 0) {
433                 return -1;
434         }
435
436         if (r2 == 0) {
437                 return 1;
438         }
439
440         boost::shared_ptr<AudioRegion> region1 = boost::dynamic_pointer_cast<AudioRegion> (r1);
441         boost::shared_ptr<AudioRegion> region2 = boost::dynamic_pointer_cast<AudioRegion> (r2);
442
443         if (region1 == 0 || region2 == 0) {
444                 Glib::ustring s1;
445                 Glib::ustring s2;
446                 switch (region_list_sort_type) {
447                 case ByName:
448                         s1 = (*a)[region_list_columns.name];
449                         s2 = (*b)[region_list_columns.name];
450                         return (s1.compare (s2));
451                 default:
452                         return 0;
453                 }
454         }
455
456         switch (region_list_sort_type) {
457         case ByName:
458                 cmp = strcasecmp (region1->name().c_str(), region2->name().c_str());
459                 break;
460
461         case ByLength:
462                 cmp = region1->length() - region2->length();
463                 break;
464                 
465         case ByPosition:
466                 cmp = region1->position() - region2->position();
467                 break;
468                 
469         case ByTimestamp:
470                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
471                 break;
472         
473         case ByStartInFile:
474                 cmp = region1->start() - region2->start();
475                 break;
476                 
477         case ByEndInFile:
478                 cmp = (region1->start() + region1->length()) - (region2->start() + region2->length());
479                 break;
480                 
481         case BySourceFileName:
482                 cmp = strcasecmp (region1->source()->name().c_str(), region2->source()->name().c_str());
483                 break;
484
485         case BySourceFileLength:
486                 cmp = region1->source()->length() - region2->source()->length();
487                 break;
488                 
489         case BySourceFileCreationDate:
490                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
491                 break;
492
493         case BySourceFileFS:
494                 if (region1->source()->name() == region2->source()->name()) {
495                         cmp = strcasecmp (region1->name().c_str(),  region2->name().c_str());
496                 } else {
497                         cmp = strcasecmp (region1->source()->name().c_str(),  region2->source()->name().c_str());
498                 }
499                 break;
500         }
501
502         if (cmp < 0) {
503                 return -1;
504         } else if (cmp > 0) {
505                 return 1;
506         } else {
507                 return 0;
508         }
509 }
510
511 void
512 Editor::reset_region_list_sort_type (RegionListSortType type)
513 {
514         if (type != region_list_sort_type) {
515                 region_list_sort_type = type;
516                 region_list_model->set_sort_func (0, (mem_fun (*this, &Editor::region_list_sorter)));
517         }
518 }
519
520 void
521 Editor::reset_region_list_sort_direction (bool up)
522 {
523         region_list_model->set_sort_column (0, up ? SORT_ASCENDING : SORT_DESCENDING);
524 }
525
526 void
527 Editor::region_list_selection_mapover (slot<void,boost::shared_ptr<Region> > sl)
528 {
529         Glib::RefPtr<TreeSelection> selection = region_list_display.get_selection();
530         TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
531         TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
532
533         if (selection->count_selected_rows() == 0 || session == 0) {
534                 return;
535         }
536
537         for (; i != rows.end(); ++i) {
538                 TreeIter iter;
539
540                 if ((iter = region_list_model->get_iter (*i))) {
541                         sl (((*iter)[region_list_columns.region]));
542                 }
543         }
544 }
545
546 void
547 Editor::hide_a_region (boost::shared_ptr<Region> r)
548 {
549         r->set_hidden (true);
550 }
551
552 void
553 Editor::remove_a_region (boost::shared_ptr<Region> r)
554 {
555         session->remove_region_from_region_list (r);
556 }
557
558 void
559 Editor::audition_region_from_region_list ()
560 {
561         region_list_selection_mapover (mem_fun (*this, &Editor::consider_auditioning));
562 }
563
564 void
565 Editor::hide_region_from_region_list ()
566 {
567         region_list_selection_mapover (mem_fun (*this, &Editor::hide_a_region));
568 }
569
570 void
571 Editor::remove_region_from_region_list ()
572 {
573         region_list_selection_mapover (mem_fun (*this, &Editor::remove_a_region));
574 }
575
576 void  
577 Editor::region_list_display_drag_data_received (const RefPtr<Gdk::DragContext>& context,
578                                                 int x, int y, 
579                                                 const SelectionData& data,
580                                                 guint info, guint time)
581 {
582         vector<ustring> paths;
583
584         if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
585                 nframes_t pos = 0;
586                 do_embed (paths, false, ImportAsRegion, 0, pos, true);
587                 context->drag_finish (true, false, time);
588         }
589 }
590
591 bool
592 Editor::region_list_selection_filter (const RefPtr<TreeModel>& model, const TreeModel::Path& path, bool yn)
593 {
594         /* not possible to select rows that do not represent regions, like "Hidden" */
595
596         /// XXXX FIXME boost::shared_ptr<Region> r = ((model->get_iter (path)))[region_list_columns.region];
597         /// return r != 0;
598         return true;
599 }