various fixes to MidiRegionView selection handling, key handling, drawing of ghost...
[ardour2.git] / gtk2_ardour / editor_selection.cc
blobc2338347d2d4b85235bca3392c1bc7f5cc3d4c5d
1 /*
2 Copyright (C) 2000-2006 Paul Davis
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.
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.
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.
20 #include <algorithm>
21 #include <cstdlib>
23 #include "pbd/stacktrace.h"
25 #include "ardour/session.h"
26 #include "ardour/playlist.h"
27 #include "ardour/route_group.h"
28 #include "ardour/profile.h"
29 #include "ardour/midi_region.h"
31 #include "editor.h"
32 #include "actions.h"
33 #include "audio_time_axis.h"
34 #include "audio_region_view.h"
35 #include "audio_streamview.h"
36 #include "automation_line.h"
37 #include "control_point.h"
38 #include "editor_regions.h"
39 #include "editor_cursors.h"
40 #include "midi_region_view.h"
42 #include "i18n.h"
44 using namespace std;
45 using namespace ARDOUR;
46 using namespace PBD;
47 using namespace Gtk;
48 using namespace Glib;
49 using namespace Gtkmm2ext;
50 using namespace Editing;
52 struct TrackViewByPositionSorter
54 bool operator() (const TimeAxisView* a, const TimeAxisView *b) {
55 return a->y_position() < b->y_position();
59 bool
60 Editor::extend_selection_to_track (TimeAxisView& view)
62 if (selection->selected (&view)) {
63 /* already selected, do nothing */
64 return false;
67 if (selection->tracks.empty()) {
69 if (!selection->selected (&view)) {
70 selection->set (&view);
71 return true;
72 } else {
73 return false;
77 /* something is already selected, so figure out which range of things to add */
79 TrackViewList to_be_added;
80 TrackViewList sorted = track_views;
81 TrackViewByPositionSorter cmp;
82 bool passed_clicked = false;
83 bool forwards = true;
85 sorted.sort (cmp);
87 if (!selection->selected (&view)) {
88 to_be_added.push_back (&view);
91 /* figure out if we should go forward or backwards */
93 for (TrackViewList::iterator i = sorted.begin(); i != sorted.end(); ++i) {
95 if ((*i) == &view) {
96 passed_clicked = true;
99 if (selection->selected (*i)) {
100 if (passed_clicked) {
101 forwards = true;
102 } else {
103 forwards = false;
105 break;
109 passed_clicked = false;
111 if (forwards) {
113 for (TrackViewList::iterator i = sorted.begin(); i != sorted.end(); ++i) {
115 if ((*i) == &view) {
116 passed_clicked = true;
117 continue;
120 if (passed_clicked) {
121 if ((*i)->hidden()) {
122 continue;
124 if (selection->selected (*i)) {
125 break;
126 } else if (!(*i)->hidden()) {
127 to_be_added.push_back (*i);
132 } else {
134 for (TrackViewList::reverse_iterator r = sorted.rbegin(); r != sorted.rend(); ++r) {
136 if ((*r) == &view) {
137 passed_clicked = true;
138 continue;
141 if (passed_clicked) {
143 if ((*r)->hidden()) {
144 continue;
147 if (selection->selected (*r)) {
148 break;
149 } else if (!(*r)->hidden()) {
150 to_be_added.push_back (*r);
156 if (!to_be_added.empty()) {
157 selection->add (to_be_added);
158 return true;
161 return false;
164 void
165 Editor::select_all_tracks ()
167 TrackViewList visible_views;
168 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
169 if ((*i)->marked_for_display()) {
170 visible_views.push_back (*i);
173 selection->set (visible_views);
176 /** Select clicked_axisview, unless there are no currently selected
177 * tracks, in which case nothing will happen unless `force' is true.
179 void
180 Editor::set_selected_track_as_side_effect (Selection::Operation op, bool /*force*/)
182 if (!clicked_axisview) {
183 return;
186 #if 1
187 if (!clicked_routeview) {
188 return;
191 bool had_tracks = !selection->tracks.empty();
192 RouteGroup* group = clicked_routeview->route()->route_group();
193 RouteGroup& arg (_session->all_route_group());
195 switch (op) {
196 case Selection::Toggle:
197 if (selection->selected (clicked_axisview)) {
198 if (arg.is_select() && arg.is_active()) {
199 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) {
200 selection->remove(*i);
202 } else if (group && group->is_active()) {
203 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) {
204 if ((*i)->route_group() == group)
205 selection->remove(*i);
207 } else {
208 selection->remove (clicked_axisview);
210 } else {
211 if (arg.is_select() && arg.is_active()) {
212 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) {
213 selection->add(*i);
215 } else if (group && group->is_active()) {
216 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) {
217 if ( (*i)->route_group() == group)
218 selection->add(*i);
220 } else {
221 selection->add (clicked_axisview);
224 break;
226 case Selection::Add:
227 if (!had_tracks && arg.is_select() && arg.is_active()) {
228 /* nothing was selected already, and all group is active etc. so use
229 all tracks.
231 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) {
232 selection->add(*i);
234 } else if (group && group->is_active()) {
235 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) {
236 if ((*i)->route_group() == group)
237 selection->add(*i);
239 } else {
240 selection->add (clicked_axisview);
242 break;
244 case Selection::Set:
245 selection->clear();
246 if (!had_tracks && arg.is_select() && arg.is_active()) {
247 /* nothing was selected already, and all group is active etc. so use
248 all tracks.
250 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) {
251 selection->add(*i);
253 } else if (group && group->is_active()) {
254 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) {
255 if ((*i)->route_group() == group)
256 selection->add(*i);
258 } else {
259 selection->set (clicked_axisview);
261 break;
263 case Selection::Extend:
264 selection->clear();
265 cerr << ("Editor::set_selected_track_as_side_effect case Selection::Add not yet implemented\n");
266 break;
269 #else // the older version
271 if (!selection->tracks.empty()) {
272 if (!selection->selected (clicked_axisview)) {
273 selection->add (clicked_axisview);
276 } else if (force) {
277 selection->set (clicked_axisview);
279 #endif
282 void
283 Editor::set_selected_track (TimeAxisView& view, Selection::Operation op, bool no_remove)
285 switch (op) {
286 case Selection::Toggle:
287 if (selection->selected (&view)) {
288 if (!no_remove) {
289 selection->remove (&view);
291 } else {
292 selection->add (&view);
294 break;
296 case Selection::Add:
297 if (!selection->selected (&view)) {
298 selection->add (&view);
300 break;
302 case Selection::Set:
303 selection->set (&view);
304 break;
306 case Selection::Extend:
307 extend_selection_to_track (view);
308 break;
312 void
313 Editor::set_selected_track_from_click (bool press, Selection::Operation op, bool no_remove)
315 if (!clicked_routeview) {
316 return;
319 if (!press) {
320 return;
323 set_selected_track (*clicked_routeview, op, no_remove);
326 bool
327 Editor::set_selected_control_point_from_click (Selection::Operation op, bool /*no_remove*/)
329 if (!clicked_control_point) {
330 return false;
333 switch (op) {
334 case Selection::Set:
335 selection->set (clicked_control_point);
336 break;
337 case Selection::Add:
338 selection->add (clicked_control_point);
339 break;
340 case Selection::Toggle:
341 selection->toggle (clicked_control_point);
342 break;
343 case Selection::Extend:
344 /* XXX */
345 break;
348 return true;
351 void
352 Editor::get_onscreen_tracks (TrackViewList& tvl)
354 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
355 if ((*i)->y_position() < _canvas_height) {
356 tvl.push_back (*i);
361 /** Call a slot for a given `basis' track and also for any track that is in the same
362 * active route group with a particular set of properties.
364 * @param sl Slot to call.
365 * @param basis Basis track.
366 * @param prop Properties that active edit groups must share to be included in the map.
369 void
370 Editor::mapover_tracks (sigc::slot<void, RouteTimeAxisView&, uint32_t> sl, TimeAxisView* basis, PBD::PropertyID prop) const
372 RouteTimeAxisView* route_basis = dynamic_cast<RouteTimeAxisView*> (basis);
374 if (route_basis == 0) {
375 return;
378 set<RouteTimeAxisView*> tracks;
379 tracks.insert (route_basis);
381 RouteGroup* group = route_basis->route()->route_group();
383 if (group && group->enabled_property(prop) && group->enabled_property (Properties::active.property_id) ) {
385 /* the basis is a member of an active route group, with the appropriate
386 properties; find other members */
388 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
389 RouteTimeAxisView* v = dynamic_cast<RouteTimeAxisView*> (*i);
390 if (v && v->route()->route_group() == group) {
391 tracks.insert (v);
396 /* call the slots */
397 uint32_t const sz = tracks.size ();
399 for (set<RouteTimeAxisView*>::iterator i = tracks.begin(); i != tracks.end(); ++i) {
400 sl (**i, sz);
404 void
405 Editor::mapped_get_equivalent_regions (RouteTimeAxisView& tv, uint32_t, RegionView * basis, vector<RegionView*>* all_equivs) const
407 boost::shared_ptr<Playlist> pl;
408 vector<boost::shared_ptr<Region> > results;
409 RegionView* marv;
410 boost::shared_ptr<Track> tr;
412 if ((tr = tv.track()) == 0) {
413 /* bus */
414 return;
417 if (&tv == &basis->get_time_axis_view()) {
418 /* looking in same track as the original */
419 return;
422 if ((pl = tr->playlist()) != 0) {
423 pl->get_equivalent_regions (basis->region(), results);
426 for (vector<boost::shared_ptr<Region> >::iterator ir = results.begin(); ir != results.end(); ++ir) {
427 if ((marv = tv.view()->find_view (*ir)) != 0) {
428 all_equivs->push_back (marv);
433 void
434 Editor::get_equivalent_regions (RegionView* basis, vector<RegionView*>& equivalent_regions, PBD::PropertyID property) const
436 mapover_tracks (sigc::bind (sigc::mem_fun (*this, &Editor::mapped_get_equivalent_regions), basis, &equivalent_regions), &basis->get_time_axis_view(), property);
438 /* add clicked regionview since we skipped all other regions in the same track as the one it was in */
440 equivalent_regions.push_back (basis);
443 RegionSelection
444 Editor::get_equivalent_regions (RegionSelection & basis, PBD::PropertyID prop) const
446 RegionSelection equivalent;
448 for (RegionSelection::const_iterator i = basis.begin(); i != basis.end(); ++i) {
450 vector<RegionView*> eq;
452 mapover_tracks (
453 sigc::bind (sigc::mem_fun (*this, &Editor::mapped_get_equivalent_regions), *i, &eq),
454 &(*i)->get_time_axis_view(), prop
457 for (vector<RegionView*>::iterator j = eq.begin(); j != eq.end(); ++j) {
458 equivalent.add (*j);
461 equivalent.add (*i);
464 return equivalent;
469 Editor::get_regionview_count_from_region_list (boost::shared_ptr<Region> region)
471 int region_count = 0;
473 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
475 RouteTimeAxisView* tatv;
477 if ((tatv = dynamic_cast<RouteTimeAxisView*> (*i)) != 0) {
479 boost::shared_ptr<Playlist> pl;
480 vector<boost::shared_ptr<Region> > results;
481 RegionView* marv;
482 boost::shared_ptr<Track> tr;
484 if ((tr = tatv->track()) == 0) {
485 /* bus */
486 continue;
489 if ((pl = (tr->playlist())) != 0) {
490 pl->get_region_list_equivalent_regions (region, results);
493 for (vector<boost::shared_ptr<Region> >::iterator ir = results.begin(); ir != results.end(); ++ir) {
494 if ((marv = tatv->view()->find_view (*ir)) != 0) {
495 region_count++;
502 return region_count;
506 bool
507 Editor::set_selected_regionview_from_click (bool press, Selection::Operation op, bool /*no_track_remove*/)
509 vector<RegionView*> all_equivalent_regions;
510 bool commit = false;
512 if (!clicked_regionview || !clicked_routeview) {
513 return false;
516 if (press) {
517 button_release_can_deselect = false;
520 if (op == Selection::Toggle || op == Selection::Set) {
523 switch (op) {
524 case Selection::Toggle:
525 if (selection->selected (clicked_regionview)) {
526 if (press) {
528 /* whatever was clicked was selected already; do nothing here but allow
529 the button release to deselect it
532 button_release_can_deselect = true;
534 } else {
535 if (button_release_can_deselect) {
537 /* just remove this one region, but only on a permitted button release */
539 selection->remove (clicked_regionview);
540 commit = true;
542 /* no more deselect action on button release till a new press
543 finds an already selected object.
546 button_release_can_deselect = false;
550 } else {
552 if (press) {
554 if (selection->selected (clicked_routeview)) {
555 get_equivalent_regions (clicked_regionview, all_equivalent_regions, ARDOUR::Properties::select.property_id);
556 } else {
557 all_equivalent_regions.push_back (clicked_regionview);
560 /* add all the equivalent regions, but only on button press */
562 if (!all_equivalent_regions.empty()) {
563 commit = true;
566 selection->add (all_equivalent_regions);
569 break;
571 case Selection::Set:
572 if (!selection->selected (clicked_regionview)) {
573 get_equivalent_regions (clicked_regionview, all_equivalent_regions, ARDOUR::Properties::select.property_id);
574 selection->set (all_equivalent_regions);
575 commit = true;
576 } else {
577 /* no commit necessary: clicked on an already selected region */
578 goto out;
580 break;
582 default:
583 /* silly compiler */
584 break;
587 } else if (op == Selection::Extend) {
589 list<Selectable*> results;
590 framepos_t last_frame;
591 framepos_t first_frame;
592 bool same_track = false;
594 /* 1. find the last selected regionview in the track that was clicked in */
596 last_frame = 0;
597 first_frame = max_framepos;
599 for (RegionSelection::iterator x = selection->regions.begin(); x != selection->regions.end(); ++x) {
600 if (&(*x)->get_time_axis_view() == &clicked_regionview->get_time_axis_view()) {
602 if ((*x)->region()->last_frame() > last_frame) {
603 last_frame = (*x)->region()->last_frame();
606 if ((*x)->region()->first_frame() < first_frame) {
607 first_frame = (*x)->region()->first_frame();
610 same_track = true;
614 if (same_track) {
616 /* 2. figure out the boundaries for our search for new objects */
618 switch (clicked_regionview->region()->coverage (first_frame, last_frame)) {
619 case OverlapNone:
620 if (last_frame < clicked_regionview->region()->first_frame()) {
621 first_frame = last_frame;
622 last_frame = clicked_regionview->region()->last_frame();
623 } else {
624 last_frame = first_frame;
625 first_frame = clicked_regionview->region()->first_frame();
627 break;
629 case OverlapExternal:
630 if (last_frame < clicked_regionview->region()->first_frame()) {
631 first_frame = last_frame;
632 last_frame = clicked_regionview->region()->last_frame();
633 } else {
634 last_frame = first_frame;
635 first_frame = clicked_regionview->region()->first_frame();
637 break;
639 case OverlapInternal:
640 if (last_frame < clicked_regionview->region()->first_frame()) {
641 first_frame = last_frame;
642 last_frame = clicked_regionview->region()->last_frame();
643 } else {
644 last_frame = first_frame;
645 first_frame = clicked_regionview->region()->first_frame();
647 break;
649 case OverlapStart:
650 case OverlapEnd:
651 /* nothing to do except add clicked region to selection, since it
652 overlaps with the existing selection in this track.
654 break;
657 } else {
659 /* click in a track that has no regions selected, so extend vertically
660 to pick out all regions that are defined by the existing selection
661 plus this one.
665 first_frame = clicked_regionview->region()->position();
666 last_frame = clicked_regionview->region()->last_frame();
668 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
669 if ((*i)->region()->position() < first_frame) {
670 first_frame = (*i)->region()->position();
672 if ((*i)->region()->last_frame() + 1 > last_frame) {
673 last_frame = (*i)->region()->last_frame();
678 /* 2. find all the tracks we should select in */
680 set<RouteTimeAxisView*> relevant_tracks;
682 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
683 RouteTimeAxisView* r = dynamic_cast<RouteTimeAxisView*> (*i);
684 if (r) {
685 relevant_tracks.insert (r);
689 set<RouteTimeAxisView*> already_in_selection;
691 if (relevant_tracks.empty()) {
693 /* no tracks selected .. thus .. if the
694 regionview we're in isn't selected
695 (i.e. we're about to extend to it), then
696 find all tracks between the this one and
697 any selected ones.
700 if (!selection->selected (clicked_regionview)) {
702 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&clicked_regionview->get_time_axis_view());
704 if (rtv) {
706 /* add this track to the ones we will search */
708 relevant_tracks.insert (rtv);
710 /* find the track closest to this one that
711 already a selected region.
714 RouteTimeAxisView* closest = 0;
715 int distance = INT_MAX;
716 int key = rtv->route()->order_key ("editor");
718 for (RegionSelection::iterator x = selection->regions.begin(); x != selection->regions.end(); ++x) {
720 RouteTimeAxisView* artv = dynamic_cast<RouteTimeAxisView*>(&(*x)->get_time_axis_view());
722 if (artv && artv != rtv) {
724 pair<set<RouteTimeAxisView*>::iterator,bool> result;
726 result = already_in_selection.insert (artv);
728 if (result.second) {
729 /* newly added to already_in_selection */
731 int d = artv->route()->order_key ("editor");
733 d -= key;
735 if (abs (d) < distance) {
736 distance = abs (d);
737 closest = artv;
743 if (closest) {
745 /* now add all tracks between that one and this one */
747 int okey = closest->route()->order_key ("editor");
749 if (okey > key) {
750 swap (okey, key);
753 for (TrackViewList::iterator x = track_views.begin(); x != track_views.end(); ++x) {
754 RouteTimeAxisView* artv = dynamic_cast<RouteTimeAxisView*>(*x);
755 if (artv && artv != rtv) {
757 int k = artv->route()->order_key ("editor");
759 if (k >= okey && k <= key) {
761 /* in range but don't add it if
762 it already has tracks selected.
763 this avoids odd selection
764 behaviour that feels wrong.
767 if (find (already_in_selection.begin(),
768 already_in_selection.end(),
769 artv) == already_in_selection.end()) {
771 relevant_tracks.insert (artv);
781 /* 3. find all selectable objects (regionviews in this case) between that one and the end of the
782 one that was clicked.
785 for (set<RouteTimeAxisView*>::iterator t = relevant_tracks.begin(); t != relevant_tracks.end(); ++t) {
786 (*t)->get_selectables (first_frame, last_frame, -1.0, -1.0, results);
789 /* 4. convert to a vector of regions */
791 vector<RegionView*> regions;
793 for (list<Selectable*>::iterator x = results.begin(); x != results.end(); ++x) {
794 RegionView* arv;
796 if ((arv = dynamic_cast<RegionView*>(*x)) != 0) {
797 regions.push_back (arv);
801 if (!regions.empty()) {
802 selection->add (regions);
803 commit = true;
807 out:
808 return commit;
812 void
813 Editor::set_selected_regionview_from_region_list (boost::shared_ptr<Region> region, Selection::Operation op)
815 vector<RegionView*> all_equivalent_regions;
817 get_regions_corresponding_to (region, all_equivalent_regions);
819 if (all_equivalent_regions.empty()) {
820 return;
823 begin_reversible_command (_("set selected regions"));
825 switch (op) {
826 case Selection::Toggle:
827 /* XXX this is not correct */
828 selection->toggle (all_equivalent_regions);
829 break;
830 case Selection::Set:
831 selection->set (all_equivalent_regions);
832 break;
833 case Selection::Extend:
834 selection->add (all_equivalent_regions);
835 break;
836 case Selection::Add:
837 selection->add (all_equivalent_regions);
838 break;
841 commit_reversible_command () ;
844 bool
845 Editor::set_selected_regionview_from_map_event (GdkEventAny* /*ev*/, StreamView* sv, boost::weak_ptr<Region> weak_r)
847 RegionView* rv;
848 boost::shared_ptr<Region> r (weak_r.lock());
850 if (!r) {
851 return true;
854 if ((rv = sv->find_view (r)) == 0) {
855 return true;
858 /* don't reset the selection if its something other than
859 a single other region.
862 if (selection->regions.size() > 1) {
863 return true;
866 begin_reversible_command (_("set selected regions"));
868 selection->set (rv);
870 commit_reversible_command () ;
872 return true;
875 void
876 Editor::track_selection_changed ()
878 switch (selection->tracks.size()) {
879 case 0:
880 break;
881 default:
882 set_selected_mixer_strip (*(selection->tracks.front()));
883 break;
886 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
888 bool yn = (find (selection->tracks.begin(), selection->tracks.end(), *i) != selection->tracks.end());
890 (*i)->set_selected (yn);
892 TimeAxisView::Children c = (*i)->get_child_list ();
893 for (TimeAxisView::Children::iterator j = c.begin(); j != c.end(); ++j) {
894 (*j)->set_selected (find (selection->tracks.begin(), selection->tracks.end(), j->get()) != selection->tracks.end());
897 if (yn &&
898 ((mouse_mode == MouseRange) ||
899 ((mouse_mode == MouseObject) && (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT)))) {
900 (*i)->reshow_selection (selection->time);
901 } else {
902 (*i)->hide_selection ();
906 ActionManager::set_sensitive (ActionManager::track_selection_sensitive_actions, !selection->tracks.empty());
909 void
910 Editor::time_selection_changed ()
912 if (Profile->get_sae()) {
913 return;
916 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
917 (*i)->hide_selection ();
920 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
921 (*i)->show_selection (selection->time);
924 if (selection->time.empty()) {
925 ActionManager::set_sensitive (ActionManager::time_selection_sensitive_actions, false);
926 } else {
927 ActionManager::set_sensitive (ActionManager::time_selection_sensitive_actions, true);
931 /** Set all region actions to have a given sensitivity */
932 void
933 Editor::sensitize_all_region_actions (bool s)
935 Glib::ListHandle<Glib::RefPtr<Action> > all = _region_actions->get_actions ();
937 for (Glib::ListHandle<Glib::RefPtr<Action> >::iterator i = all.begin(); i != all.end(); ++i) {
938 (*i)->set_sensitive (s);
941 _all_region_actions_sensitized = s;
944 /** Sensitize region-based actions based on the selection ONLY, ignoring the entered_regionview.
945 * This method should be called just before displaying a Region menu. When a Region menu is not
946 * currently being shown, all region actions are sensitized so that hotkey-triggered actions
947 * on entered_regionviews work without having to check sensitivity every time the selection or
948 * entered_regionview changes.
950 * This method also sets up toggle action state as appropriate.
952 void
953 Editor::sensitize_the_right_region_actions ()
955 if ((mouse_mode == MouseRange) || (mouse_mode != MouseObject && _join_object_range_state == JOIN_OBJECT_RANGE_RANGE)) {
956 sensitize_all_region_actions (false);
957 if (!selection->time.empty()) {
958 _region_actions->get_action("split-region")->set_sensitive (true);
960 return;
962 } else if (mouse_mode != MouseObject) {
963 sensitize_all_region_actions (false);
964 return;
967 /* We get here if we are in Object mode */
969 RegionSelection rs = get_regions_from_selection_and_entered ();
970 sensitize_all_region_actions (!rs.empty ());
972 _ignore_region_action = true;
974 /* Look through the regions that are selected and make notes about what we have got */
976 bool have_audio = false;
977 bool have_midi = false;
978 bool have_locked = false;
979 bool have_unlocked = false;
980 bool have_position_lock_style_audio = false;
981 bool have_position_lock_style_music = false;
982 bool have_muted = false;
983 bool have_unmuted = false;
984 bool have_opaque = false;
985 bool have_non_opaque = false;
986 bool have_not_at_natural_position = false;
987 bool have_envelope_visible = false;
988 bool have_envelope_invisible = false;
989 bool have_envelope_active = false;
990 bool have_envelope_inactive = false;
991 bool have_non_unity_scale_amplitude = false;
992 bool have_compound_regions = false;
994 for (list<RegionView*>::const_iterator i = rs.begin(); i != rs.end(); ++i) {
996 boost::shared_ptr<Region> r = (*i)->region ();
997 boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (r);
999 if (ar) {
1000 have_audio = true;
1003 if (boost::dynamic_pointer_cast<MidiRegion> (r)) {
1004 have_midi = true;
1007 if (r->is_compound()) {
1008 have_compound_regions = true;
1011 if (r->locked()) {
1012 have_locked = true;
1013 } else {
1014 have_unlocked = true;
1017 if (r->position_lock_style() == MusicTime) {
1018 have_position_lock_style_music = true;
1019 } else {
1020 have_position_lock_style_audio = true;
1023 if (r->muted()) {
1024 have_muted = true;
1025 } else {
1026 have_unmuted = true;
1029 if (r->opaque()) {
1030 have_opaque = true;
1031 } else {
1032 have_non_opaque = true;
1035 if (!r->at_natural_position()) {
1036 have_not_at_natural_position = true;
1039 if (ar) {
1040 /* its a bit unfortunate that "envelope visible" is a view-only
1041 property. we have to find the regionview to able to check
1042 its current setting.
1045 have_envelope_invisible = true;
1047 if (*i) {
1048 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (*i);
1049 if (arv && arv->envelope_visible()) {
1050 have_envelope_visible = true;
1054 if (ar->envelope_active()) {
1055 have_envelope_active = true;
1056 } else {
1057 have_envelope_inactive = true;
1060 if (ar->scale_amplitude() != 1) {
1061 have_non_unity_scale_amplitude = true;
1066 if (rs.size() > 1) {
1067 _region_actions->get_action("show-region-list-editor")->set_sensitive (false);
1068 _region_actions->get_action("show-region-properties")->set_sensitive (false);
1069 _region_actions->get_action("rename-region")->set_sensitive (false);
1070 if (have_audio) {
1071 _region_actions->get_action("combine-regions")->set_sensitive (true);
1072 } else {
1073 _region_actions->get_action("combine-regions")->set_sensitive (false);
1075 } else if (rs.size() == 1) {
1076 _region_actions->get_action("add-range-markers-from-region")->set_sensitive (false);
1077 _region_actions->get_action("close-region-gaps")->set_sensitive (false);
1078 _region_actions->get_action("combine-regions")->set_sensitive (false);
1081 if (!have_midi) {
1082 _region_actions->get_action("show-region-list-editor")->set_sensitive (false);
1083 _region_actions->get_action("quantize-region")->set_sensitive (false);
1084 _region_actions->get_action("fork-region")->set_sensitive (false);
1085 _region_actions->get_action("transpose-region")->set_sensitive (false);
1088 if (_edit_point == EditAtMouse) {
1089 _region_actions->get_action("set-region-sync-position")->set_sensitive (false);
1090 _region_actions->get_action("trim-front")->set_sensitive (false);
1091 _region_actions->get_action("trim-back")->set_sensitive (false);
1092 _region_actions->get_action("split-region")->set_sensitive (false);
1093 _region_actions->get_action("place-transient")->set_sensitive (false);
1096 if (have_compound_regions) {
1097 _region_actions->get_action("uncombine-regions")->set_sensitive (true);
1098 } else {
1099 _region_actions->get_action("uncombine-regions")->set_sensitive (false);
1102 if (have_audio) {
1104 if (have_envelope_visible && !have_envelope_invisible) {
1105 Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-gain-envelope-visible"))->set_active ();
1106 } else if (have_envelope_visible && have_envelope_invisible) {
1107 // _region_actions->get_action("toggle-region-gain-envelope-visible")->set_inconsistent ();
1110 if (have_envelope_active && !have_envelope_inactive) {
1111 Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-gain-envelope-active"))->set_active ();
1112 } else if (have_envelope_active && have_envelope_inactive) {
1113 // _region_actions->get_action("toggle-region-gain-envelope-active")->set_inconsistent ();
1116 } else {
1118 _region_actions->get_action("analyze-region")->set_sensitive (false);
1119 _region_actions->get_action("reset-region-gain-envelopes")->set_sensitive (false);
1120 _region_actions->get_action("toggle-region-gain-envelope-visible")->set_sensitive (false);
1121 _region_actions->get_action("toggle-region-gain-envelope-active")->set_sensitive (false);
1122 _region_actions->get_action("pitch-shift-region")->set_sensitive (false);
1126 if (!have_non_unity_scale_amplitude || !have_audio) {
1127 _region_actions->get_action("reset-region-scale-amplitude")->set_sensitive (false);
1130 Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-lock"))->set_active (have_locked && !have_unlocked);
1131 if (have_locked && have_unlocked) {
1132 // _region_actions->get_action("toggle-region-lock")->set_inconsistent ();
1135 Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-lock-style"))->set_active (have_position_lock_style_music && !have_position_lock_style_audio);
1137 if (have_position_lock_style_music && have_position_lock_style_audio) {
1138 // _region_actions->get_action("toggle-region-lock-style")->set_inconsistent ();
1141 Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-mute"))->set_active (have_muted && !have_unmuted);
1142 if (have_muted && have_unmuted) {
1143 // _region_actions->get_action("toggle-region-mute")->set_inconsistent ();
1146 Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-opaque-region"))->set_active (have_opaque && !have_non_opaque);
1147 if (have_opaque && have_non_opaque) {
1148 // _region_actions->get_action("toggle-opaque-region")->set_inconsistent ();
1151 if (!have_not_at_natural_position) {
1152 _region_actions->get_action("naturalize-region")->set_sensitive (false);
1155 /* XXX: should also check that there is a track of the appropriate type for the selected region */
1156 if (_edit_point == EditAtMouse || _regions->get_single_selection() == 0 || selection->tracks.empty()) {
1157 _region_actions->get_action("insert-region-from-region-list")->set_sensitive (false);
1158 } else {
1159 _region_actions->get_action("insert-region-from-region-list")->set_sensitive (true);
1162 _ignore_region_action = false;
1164 _all_region_actions_sensitized = false;
1168 void
1169 Editor::region_selection_changed ()
1171 _regions->block_change_connection (true);
1172 editor_regions_selection_changed_connection.block(true);
1174 if (_region_selection_change_updates_region_list) {
1175 _regions->unselect_all ();
1178 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
1179 (*i)->set_selected_regionviews (selection->regions);
1182 if (_region_selection_change_updates_region_list) {
1183 _regions->set_selected (selection->regions);
1186 _regions->block_change_connection (false);
1187 editor_regions_selection_changed_connection.block(false);
1189 if (!_all_region_actions_sensitized) {
1190 /* This selection change might have changed what region actions
1191 are allowed, so sensitize them all in case a key is pressed.
1193 sensitize_all_region_actions (true);
1197 void
1198 Editor::point_selection_changed ()
1200 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
1201 (*i)->set_selected_points (selection->points);
1205 void
1206 Editor::select_all_in_track (Selection::Operation op)
1208 list<Selectable *> touched;
1210 if (!clicked_routeview) {
1211 return;
1214 clicked_routeview->get_selectables (0, max_framepos, 0, DBL_MAX, touched);
1216 switch (op) {
1217 case Selection::Toggle:
1218 selection->add (touched);
1219 break;
1220 case Selection::Set:
1221 selection->set (touched);
1222 break;
1223 case Selection::Extend:
1224 /* meaningless, because we're selecting everything */
1225 break;
1226 case Selection::Add:
1227 selection->add (touched);
1228 break;
1232 void
1233 Editor::select_all_internal_edit (Selection::Operation op)
1235 /* currently limited to MIDI only */
1237 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
1238 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i);
1239 if (mrv) {
1240 mrv->select_all_notes ();
1245 void
1246 Editor::select_all (Selection::Operation op)
1248 list<Selectable *> touched;
1250 if (_internal_editing) {
1251 select_all_internal_edit (op);
1252 return;
1255 for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) {
1256 if ((*iter)->hidden()) {
1257 continue;
1259 (*iter)->get_selectables (0, max_framepos, 0, DBL_MAX, touched);
1261 begin_reversible_command (_("select all"));
1262 switch (op) {
1263 case Selection::Add:
1264 selection->add (touched);
1265 break;
1266 case Selection::Toggle:
1267 selection->add (touched);
1268 break;
1269 case Selection::Set:
1270 selection->set (touched);
1271 break;
1272 case Selection::Extend:
1273 /* meaningless, because we're selecting everything */
1274 break;
1276 commit_reversible_command ();
1278 void
1279 Editor::invert_selection_in_track ()
1281 list<Selectable *> touched;
1283 if (!clicked_routeview) {
1284 return;
1287 clicked_routeview->get_inverted_selectables (*selection, touched);
1288 selection->set (touched);
1291 void
1292 Editor::invert_selection ()
1294 list<Selectable *> touched;
1296 for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) {
1297 if ((*iter)->hidden()) {
1298 continue;
1300 (*iter)->get_inverted_selectables (*selection, touched);
1303 selection->set (touched);
1306 /** @param start Start time in session frames.
1307 * @param end End time in session frames.
1308 * @param top Top (lower) y limit in trackview coordinates (ie 0 at the top of the track view)
1309 * @param bottom Bottom (higher) y limit in trackview coordinates (ie 0 at the top of the track view)
1310 * @param preserve_if_selected true to leave the current selection alone if we're adding to the selection and all of the selectables
1311 * within the region are already selected.
1313 void
1314 Editor::select_all_within (
1315 framepos_t start, framepos_t end, double top, double bot, const TrackViewList& tracklist, Selection::Operation op, bool preserve_if_selected
1318 list<Selectable*> found;
1320 for (TrackViewList::const_iterator iter = tracklist.begin(); iter != tracklist.end(); ++iter) {
1322 if ((*iter)->hidden()) {
1323 continue;
1326 (*iter)->get_selectables (start, end, top, bot, found);
1329 if (found.empty()) {
1330 return;
1333 if (preserve_if_selected && op != Selection::Toggle) {
1334 list<Selectable*>::iterator i = found.begin();
1335 while (i != found.end() && (*i)->get_selected()) {
1336 ++i;
1339 if (i == found.end()) {
1340 return;
1344 begin_reversible_command (_("select all within"));
1345 switch (op) {
1346 case Selection::Add:
1347 selection->add (found);
1348 break;
1349 case Selection::Toggle:
1350 selection->toggle (found);
1351 break;
1352 case Selection::Set:
1353 selection->set (found);
1354 break;
1355 case Selection::Extend:
1356 /* not defined yet */
1357 break;
1360 commit_reversible_command ();
1363 void
1364 Editor::set_selection_from_region ()
1366 if (selection->regions.empty()) {
1367 return;
1370 selection->set (selection->regions.start(), selection->regions.end_frame());
1371 if (!Profile->get_sae()) {
1372 set_mouse_mode (Editing::MouseRange, false);
1376 void
1377 Editor::set_selection_from_punch()
1379 Location* location;
1381 if ((location = _session->locations()->auto_punch_location()) == 0) {
1382 return;
1385 set_selection_from_range (*location);
1388 void
1389 Editor::set_selection_from_loop()
1391 Location* location;
1393 if ((location = _session->locations()->auto_loop_location()) == 0) {
1394 return;
1396 set_selection_from_range (*location);
1399 void
1400 Editor::set_selection_from_range (Location& loc)
1402 begin_reversible_command (_("set selection from range"));
1403 selection->set (loc.start(), loc.end());
1404 commit_reversible_command ();
1406 if (!Profile->get_sae()) {
1407 set_mouse_mode (Editing::MouseRange, false);
1411 void
1412 Editor::select_all_selectables_using_time_selection ()
1414 list<Selectable *> touched;
1416 if (selection->time.empty()) {
1417 return;
1420 framepos_t start = selection->time[clicked_selection].start;
1421 framepos_t end = selection->time[clicked_selection].end;
1423 if (end - start < 1) {
1424 return;
1427 TrackViewList* ts;
1429 if (selection->tracks.empty()) {
1430 ts = &track_views;
1431 } else {
1432 ts = &selection->tracks;
1435 for (TrackViewList::iterator iter = ts->begin(); iter != ts->end(); ++iter) {
1436 if ((*iter)->hidden()) {
1437 continue;
1439 (*iter)->get_selectables (start, end - 1, 0, DBL_MAX, touched);
1442 begin_reversible_command (_("select all from range"));
1443 selection->set (touched);
1444 commit_reversible_command ();
1448 void
1449 Editor::select_all_selectables_using_punch()
1451 Location* location = _session->locations()->auto_punch_location();
1452 list<Selectable *> touched;
1454 if (location == 0 || (location->end() - location->start() <= 1)) {
1455 return;
1459 TrackViewList* ts;
1461 if (selection->tracks.empty()) {
1462 ts = &track_views;
1463 } else {
1464 ts = &selection->tracks;
1467 for (TrackViewList::iterator iter = ts->begin(); iter != ts->end(); ++iter) {
1468 if ((*iter)->hidden()) {
1469 continue;
1471 (*iter)->get_selectables (location->start(), location->end() - 1, 0, DBL_MAX, touched);
1473 begin_reversible_command (_("select all from punch"));
1474 selection->set (touched);
1475 commit_reversible_command ();
1479 void
1480 Editor::select_all_selectables_using_loop()
1482 Location* location = _session->locations()->auto_loop_location();
1483 list<Selectable *> touched;
1485 if (location == 0 || (location->end() - location->start() <= 1)) {
1486 return;
1490 TrackViewList* ts;
1492 if (selection->tracks.empty()) {
1493 ts = &track_views;
1494 } else {
1495 ts = &selection->tracks;
1498 for (TrackViewList::iterator iter = ts->begin(); iter != ts->end(); ++iter) {
1499 if ((*iter)->hidden()) {
1500 continue;
1502 (*iter)->get_selectables (location->start(), location->end() - 1, 0, DBL_MAX, touched);
1504 begin_reversible_command (_("select all from loop"));
1505 selection->set (touched);
1506 commit_reversible_command ();
1510 void
1511 Editor::select_all_selectables_using_cursor (EditorCursor *cursor, bool after)
1513 framepos_t start;
1514 framepos_t end;
1515 list<Selectable *> touched;
1517 if (after) {
1518 begin_reversible_command (_("select all after cursor"));
1519 start = cursor->current_frame;
1520 end = _session->current_end_frame();
1521 } else {
1522 if (cursor->current_frame > 0) {
1523 begin_reversible_command (_("select all before cursor"));
1524 start = 0;
1525 end = cursor->current_frame - 1;
1526 } else {
1527 return;
1532 TrackViewList* ts;
1534 if (selection->tracks.empty()) {
1535 ts = &track_views;
1536 } else {
1537 ts = &selection->tracks;
1540 for (TrackViewList::iterator iter = ts->begin(); iter != ts->end(); ++iter) {
1541 if ((*iter)->hidden()) {
1542 continue;
1544 (*iter)->get_selectables (start, end, 0, DBL_MAX, touched);
1546 selection->set (touched);
1547 commit_reversible_command ();
1550 void
1551 Editor::select_all_selectables_using_edit (bool after)
1553 framepos_t start;
1554 framepos_t end;
1555 list<Selectable *> touched;
1557 if (after) {
1558 begin_reversible_command (_("select all after edit"));
1559 start = get_preferred_edit_position();
1560 end = _session->current_end_frame();
1561 } else {
1562 if ((end = get_preferred_edit_position()) > 1) {
1563 begin_reversible_command (_("select all before edit"));
1564 start = 0;
1565 end -= 1;
1566 } else {
1567 return;
1572 TrackViewList* ts;
1574 if (selection->tracks.empty()) {
1575 ts = &track_views;
1576 } else {
1577 ts = &selection->tracks;
1580 for (TrackViewList::iterator iter = ts->begin(); iter != ts->end(); ++iter) {
1581 if ((*iter)->hidden()) {
1582 continue;
1584 (*iter)->get_selectables (start, end, 0, DBL_MAX, touched);
1586 selection->set (touched);
1587 commit_reversible_command ();
1590 void
1591 Editor::select_all_selectables_between (bool /*within*/)
1593 framepos_t start;
1594 framepos_t end;
1595 list<Selectable *> touched;
1597 if (!get_edit_op_range (start, end)) {
1598 return;
1601 TrackViewList* ts;
1603 if (selection->tracks.empty()) {
1604 ts = &track_views;
1605 } else {
1606 ts = &selection->tracks;
1609 for (TrackViewList::iterator iter = ts->begin(); iter != ts->end(); ++iter) {
1610 if ((*iter)->hidden()) {
1611 continue;
1613 (*iter)->get_selectables (start, end, 0, DBL_MAX, touched);
1616 selection->set (touched);
1619 void
1620 Editor::select_range_between ()
1622 framepos_t start;
1623 framepos_t end;
1625 if (mouse_mode == MouseRange && !selection->time.empty()) {
1626 selection->clear_time ();
1629 if (!get_edit_op_range (start, end)) {
1630 return;
1633 set_mouse_mode (MouseRange);
1634 selection->set (start, end);
1637 bool
1638 Editor::get_edit_op_range (framepos_t& start, framepos_t& end) const
1640 framepos_t m;
1641 bool ignored;
1643 /* in range mode, use any existing selection */
1645 if (mouse_mode == MouseRange && !selection->time.empty()) {
1646 /* we know that these are ordered */
1647 start = selection->time.start();
1648 end = selection->time.end_frame();
1649 return true;
1652 if (!mouse_frame (m, ignored)) {
1653 /* mouse is not in a canvas, try playhead+selected marker.
1654 this is probably most true when using menus.
1657 if (selection->markers.empty()) {
1658 return false;
1661 start = selection->markers.front()->position();
1662 end = _session->audible_frame();
1664 } else {
1666 switch (_edit_point) {
1667 case EditAtPlayhead:
1668 if (selection->markers.empty()) {
1669 /* use mouse + playhead */
1670 start = m;
1671 end = _session->audible_frame();
1672 } else {
1673 /* use playhead + selected marker */
1674 start = _session->audible_frame();
1675 end = selection->markers.front()->position();
1677 break;
1679 case EditAtMouse:
1680 /* use mouse + selected marker */
1681 if (selection->markers.empty()) {
1682 start = m;
1683 end = _session->audible_frame();
1684 } else {
1685 start = selection->markers.front()->position();
1686 end = m;
1688 break;
1690 case EditAtSelectedMarker:
1691 /* use mouse + selected marker */
1692 if (selection->markers.empty()) {
1694 MessageDialog win (_("No edit range defined"),
1695 false,
1696 MESSAGE_INFO,
1697 BUTTONS_OK);
1699 win.set_secondary_text (
1700 _("the edit point is Selected Marker\nbut there is no selected marker."));
1703 win.set_default_response (RESPONSE_CLOSE);
1704 win.set_position (Gtk::WIN_POS_MOUSE);
1705 win.show_all();
1707 win.run ();
1709 return false; // NO RANGE
1711 start = selection->markers.front()->position();
1712 end = m;
1713 break;
1717 if (start == end) {
1718 return false;
1721 if (start > end) {
1722 swap (start, end);
1725 /* turn range into one delimited by start...end,
1726 not start...end-1
1729 end++;
1731 return true;
1734 void
1735 Editor::deselect_all ()
1737 selection->clear ();
1740 long
1741 Editor::select_range_around_region (RegionView* rv)
1743 assert (rv);
1745 selection->set (&rv->get_time_axis_view());
1747 selection->time.clear ();
1748 boost::shared_ptr<Region> r = rv->region ();
1749 return selection->set (r->position(), r->position() + r->length());