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