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