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