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