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