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