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