some fixes/improvements for track selection; add upload target to manual makefile...
[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_audio_region_removed (boost::weak_ptr<AudioRegion> wregion)
52 {
53         ENSURE_GUI_THREAD (mem_fun (*this, &Editor::redisplay_regions));
54         redisplay_regions ();
55 }
56
57 void
58 Editor::handle_new_audio_region (boost::weak_ptr<AudioRegion> wregion)
59 {
60         ENSURE_GUI_THREAD (bind (mem_fun (*this, &Editor::handle_new_audio_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<AudioRegion> region (wregion.lock());
68         
69         if (region) {
70                 add_audio_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_audio_region_to_region_display (boost::shared_ptr<AudioRegion> 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_audio_regionlist(boost::shared_ptr<AudioRegion> region)
224 {
225         /* keep all whole files at the beginning */
226         
227         if (region->whole_file()) {
228                 tmp_audio_region_list.push_front (region);
229         } else {
230                 tmp_audio_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_audio_region_list.clear();
247                 session->foreach_audio_region (this, &Editor::insert_into_tmp_audio_regionlist);
248
249                 for (list<boost::shared_ptr<AudioRegion> >::iterator r = tmp_audio_region_list.begin(); r != tmp_audio_region_list.end(); ++r) {
250                         add_audio_region_to_region_display (*r);
251                 }
252                 tmp_audio_region_list.clear();
253                 
254                 region_list_display.set_model (region_list_model);
255         }
256 }
257
258 void
259 Editor::build_region_list_menu ()
260 {
261         region_list_menu = dynamic_cast<Menu*>(ActionManager::get_widget ("/RegionListMenu"));
262                                                
263         /* now grab specific menu items that we need */
264
265         Glib::RefPtr<Action> act;
266
267         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAll"));
268         if (act) {
269                 toggle_full_region_list_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
270         }
271
272         act = ActionManager::get_action (X_("RegionList"), X_("rlShowAuto"));
273         if (act) {
274                 toggle_show_auto_regions_action = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
275         }
276 }
277
278 void
279 Editor::toggle_show_auto_regions ()
280 {
281         show_automatic_regions_in_region_list = toggle_show_auto_regions_action->get_active();
282         redisplay_regions ();
283 }
284
285 void
286 Editor::toggle_full_region_list ()
287 {
288         if (toggle_full_region_list_action->get_active()) {
289                 region_list_display.expand_all ();
290         } else {
291                 region_list_display.collapse_all ();
292         }
293 }
294
295 void
296 Editor::show_region_list_display_context_menu (int button, int time)
297 {
298         if (region_list_menu == 0) {
299                 build_region_list_menu ();
300         }
301
302         if (region_list_display.get_selection()->count_selected_rows() > 0) {
303                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, true);
304         } else {
305                 ActionManager::set_sensitive (ActionManager::region_list_selection_sensitive_actions, false);
306         }
307
308         region_list_menu->popup (button, time);
309 }
310
311 bool
312 Editor::region_list_display_key_press (GdkEventKey* ev)
313 {
314         return false;
315 }
316
317 bool
318 Editor::region_list_display_key_release (GdkEventKey* ev)
319 {
320         switch (ev->keyval) {
321         case GDK_Delete:
322                 remove_region_from_region_list ();
323                 return true;
324                 break;
325         default:
326                 break;
327         }
328
329         return false;
330 }
331
332 bool
333 Editor::region_list_display_button_press (GdkEventButton *ev)
334 {
335         boost::shared_ptr<Region> region;
336         TreeIter iter;
337         TreeModel::Path path;
338         TreeViewColumn* column;
339         int cellx;
340         int celly;
341
342         if (region_list_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
343                 if ((iter = region_list_model->get_iter (path))) {
344                         region = (*iter)[region_list_columns.region];
345                 }
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         if (region == 0) {
354                 return false;
355         }
356
357         switch (ev->button) {
358         case 1:
359                 /* audition on double click */
360                 if (ev->type == GDK_2BUTTON_PRESS) {
361                         consider_auditioning (region);
362                         return true;
363                 }
364                 return false;
365                 break;
366
367         case 2:
368                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
369                         consider_auditioning (region);
370                 }
371                 return true;
372                 break;
373
374         default:
375                 break; 
376         }
377
378         return false;
379 }       
380
381 bool
382 Editor::region_list_display_button_release (GdkEventButton *ev)
383 {
384         TreeIter iter;
385         TreeModel::Path path;
386         TreeViewColumn* column;
387         int cellx;
388         int celly;
389         boost::shared_ptr<Region> region;
390
391         if (region_list_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
392                 if ((iter = region_list_model->get_iter (path))) {
393                         region = (*iter)[region_list_columns.region];
394                 }
395         }
396
397         if (region && Keyboard::is_delete_event (ev)) {
398                 session->remove_region_from_region_list (region);
399                 return true;
400         }
401
402         return false;
403 }
404
405 void
406 Editor::consider_auditioning (boost::shared_ptr<Region> region)
407 {
408         boost::shared_ptr<AudioRegion> r = boost::dynamic_pointer_cast<AudioRegion> (region);
409
410         if (r == 0) {
411                 session->cancel_audition ();
412                 return;
413         }
414
415         if (session->is_auditioning()) {
416                 session->cancel_audition ();
417                 if (r == last_audition_region) {
418                         return;
419                 }
420         }
421
422         session->audition_region (r);
423         last_audition_region = r;
424 }
425
426 int
427 Editor::region_list_sorter (TreeModel::iterator a, TreeModel::iterator b)
428 {
429         int cmp = 0;
430
431         boost::shared_ptr<Region> r1 = (*a)[region_list_columns.region];
432         boost::shared_ptr<Region> r2 = (*b)[region_list_columns.region];
433
434         /* handle rows without regions, like "Hidden" */
435
436         if (r1 == 0) {
437                 return -1;
438         }
439
440         if (r2 == 0) {
441                 return 1;
442         }
443
444         boost::shared_ptr<AudioRegion> region1 = boost::dynamic_pointer_cast<AudioRegion> (r1);
445         boost::shared_ptr<AudioRegion> region2 = boost::dynamic_pointer_cast<AudioRegion> (r2);
446
447         if (region1 == 0 || region2 == 0) {
448                 Glib::ustring s1;
449                 Glib::ustring s2;
450                 switch (region_list_sort_type) {
451                 case ByName:
452                         s1 = (*a)[region_list_columns.name];
453                         s2 = (*b)[region_list_columns.name];
454                         return (s1.compare (s2));
455                 default:
456                         return 0;
457                 }
458         }
459
460         switch (region_list_sort_type) {
461         case ByName:
462                 cmp = strcasecmp (region1->name().c_str(), region2->name().c_str());
463                 break;
464
465         case ByLength:
466                 cmp = region1->length() - region2->length();
467                 break;
468                 
469         case ByPosition:
470                 cmp = region1->position() - region2->position();
471                 break;
472                 
473         case ByTimestamp:
474                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
475                 break;
476         
477         case ByStartInFile:
478                 cmp = region1->start() - region2->start();
479                 break;
480                 
481         case ByEndInFile:
482                 cmp = (region1->start() + region1->length()) - (region2->start() + region2->length());
483                 break;
484                 
485         case BySourceFileName:
486                 cmp = strcasecmp (region1->source()->name().c_str(), region2->source()->name().c_str());
487                 break;
488
489         case BySourceFileLength:
490                 cmp = region1->source()->length() - region2->source()->length();
491                 break;
492                 
493         case BySourceFileCreationDate:
494                 cmp = region1->source()->timestamp() - region2->source()->timestamp();
495                 break;
496
497         case BySourceFileFS:
498                 if (region1->source()->name() == region2->source()->name()) {
499                         cmp = strcasecmp (region1->name().c_str(),  region2->name().c_str());
500                 } else {
501                         cmp = strcasecmp (region1->source()->name().c_str(),  region2->source()->name().c_str());
502                 }
503                 break;
504         }
505
506         if (cmp < 0) {
507                 return -1;
508         } else if (cmp > 0) {
509                 return 1;
510         } else {
511                 return 0;
512         }
513 }
514
515 void
516 Editor::reset_region_list_sort_type (RegionListSortType type)
517 {
518         if (type != region_list_sort_type) {
519                 region_list_sort_type = type;
520                 region_list_model->set_sort_func (0, (mem_fun (*this, &Editor::region_list_sorter)));
521         }
522 }
523
524 void
525 Editor::reset_region_list_sort_direction (bool up)
526 {
527         region_list_model->set_sort_column (0, up ? SORT_ASCENDING : SORT_DESCENDING);
528 }
529
530 void
531 Editor::region_list_selection_mapover (slot<void,boost::shared_ptr<Region> > sl)
532 {
533         Glib::RefPtr<TreeSelection> selection = region_list_display.get_selection();
534         TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
535         TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
536
537         if (selection->count_selected_rows() == 0 || session == 0) {
538                 return;
539         }
540
541         for (; i != rows.end(); ++i) {
542                 TreeIter iter;
543
544                 if ((iter = region_list_model->get_iter (*i))) {
545                         sl (((*iter)[region_list_columns.region]));
546                 }
547         }
548 }
549
550 void
551 Editor::hide_a_region (boost::shared_ptr<Region> r)
552 {
553         r->set_hidden (true);
554 }
555
556 void
557 Editor::remove_a_region (boost::shared_ptr<Region> r)
558 {
559         cerr << "remove " << r->name();
560         session->remove_region_from_region_list (r);
561 }
562
563 void
564 Editor::audition_region_from_region_list ()
565 {
566         region_list_selection_mapover (mem_fun (*this, &Editor::consider_auditioning));
567 }
568
569 void
570 Editor::hide_region_from_region_list ()
571 {
572         region_list_selection_mapover (mem_fun (*this, &Editor::hide_a_region));
573 }
574
575 void
576 Editor::remove_region_from_region_list ()
577 {
578         cerr << "Mapping remove over region selection\n";
579         region_list_selection_mapover (mem_fun (*this, &Editor::remove_a_region));
580 }
581
582 void  
583 Editor::region_list_display_drag_data_received (const RefPtr<Gdk::DragContext>& context,
584                                                 int x, int y, 
585                                                 const SelectionData& data,
586                                                 guint info, guint time)
587 {
588         vector<ustring> paths;
589
590         if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) {
591                 nframes_t pos = 0;
592                 do_embed (paths, false, ImportAsRegion, 0, pos, true);
593                 context->drag_finish (true, false, time);
594         }
595 }
596
597 bool
598 Editor::region_list_selection_filter (const RefPtr<TreeModel>& model, const TreeModel::Path& path, bool yn)
599 {
600         /* not possible to select rows that do not represent regions, like "Hidden" */
601
602         /// XXXX FIXME boost::shared_ptr<Region> r = ((model->get_iter (path)))[region_list_columns.region];
603         /// return r != 0;
604         return true;
605 }