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