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