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