mp4chaps Lua script: don't clutter global environment
[ardour.git] / gtk2_ardour / editor_selection.cc
index 56cedd5fa09c10b6afe990974605d1ffb3f85f30..b5a0be1c7869aa3e19c71a5a83c8ef282ac08bdb 100644 (file)
 #include <cstdlib>
 
 #include "pbd/stacktrace.h"
+#include "pbd/unwind.h"
 
+#include "ardour/control_protocol_manager.h"
 #include "ardour/midi_region.h"
 #include "ardour/playlist.h"
 #include "ardour/profile.h"
 #include "ardour/route_group.h"
+#include "ardour/selection.h"
 #include "ardour/session.h"
+#include "ardour/vca.h"
 
-#include "control_protocol/control_protocol.h"
-
-#include "editor_drag.h"
 #include "editor.h"
+#include "editor_drag.h"
+#include "editor_routes.h"
 #include "actions.h"
 #include "audio_time_axis.h"
 #include "audio_region_view.h"
@@ -88,10 +91,6 @@ Editor::extend_selection_to_track (TimeAxisView& view)
 
        sorted.sort (cmp);
 
-       if (!selection->selected (&view)) {
-               to_be_added.push_back (&view);
-       }
-
        /* figure out if we should go forward or backwards */
 
        for (TrackViewList::iterator i = sorted.begin(); i != sorted.end(); ++i) {
@@ -157,6 +156,10 @@ Editor::extend_selection_to_track (TimeAxisView& view)
                }
        }
 
+       if (!selection->selected (&view)) {
+               to_be_added.push_back (&view);
+       }
+
        if (!to_be_added.empty()) {
                selection->add (to_be_added);
                return true;
@@ -174,6 +177,7 @@ Editor::select_all_tracks ()
                        visible_views.push_back (*i);
                }
        }
+       PBD::Unwinder<bool> uw (_track_selection_change_without_scroll, true);
        selection->set (visible_views);
 }
 
@@ -265,9 +269,7 @@ Editor::set_selected_track (TimeAxisView& view, Selection::Operation op, bool no
                break;
 
        case Selection::Add:
-               if (!selection->selected (&view)) {
-                       selection->add (&view);
-               }
+               selection->add (&view);
                break;
 
        case Selection::Set:
@@ -891,6 +893,30 @@ out:
        return commit;
 }
 
+void
+Editor::set_selection (std::list<Selectable*> s, Selection::Operation op)
+{
+       if (s.empty()) {
+               return;
+       }
+       begin_reversible_selection_op (X_("set selection"));
+       switch (op) {
+               case Selection::Toggle:
+                       selection->toggle (s);
+                       break;
+               case Selection::Set:
+                       selection->set (s);
+                       break;
+               case Selection::Extend:
+                       selection->add (s);
+                       break;
+               case Selection::Add:
+                       selection->add (s);
+                       break;
+       }
+
+       commit_reversible_selection_op () ;
+}
 
 void
 Editor::set_selected_regionview_from_region_list (boost::shared_ptr<Region> region, Selection::Operation op)
@@ -955,81 +981,175 @@ Editor::set_selected_regionview_from_map_event (GdkEventAny* /*ev*/, StreamView*
        return true;
 }
 
+struct SelectionOrderSorter {
+       bool operator() (TimeAxisView const * const a, TimeAxisView const * const b) const  {
+               boost::shared_ptr<Stripable> sa = a->stripable ();
+               boost::shared_ptr<Stripable> sb = b->stripable ();
+               if (!sa && !sb) {
+                       return a < b;
+               }
+               if (!sa) {
+                       return false;
+               }
+               if (!sb) {
+                       return true;
+               }
+               return sa->presentation_info().selection_cnt() < sb->presentation_info().selection_cnt();
+       }
+};
+
 void
-Editor::track_selection_changed ()
+Editor::presentation_info_changed (PropertyChange const & what_changed)
 {
-       switch (selection->tracks.size()) {
-       case 0:
-               break;
-       default:
-               /* last element in selection list is the most recently
-                * selected, because we always append to that list.
-                */
-               set_selected_mixer_strip (*(selection->tracks.back()));
-               ensure_time_axis_view_is_visible (*(selection->tracks.back()), false);
-               break;
+       uint32_t n_tracks = 0;
+       uint32_t n_busses = 0;
+       uint32_t n_vcas = 0;
+       uint32_t n_routes = 0;
+       uint32_t n_stripables = 0;
+
+       /* We cannot ensure ordering of the handlers for
+        * PresentationInfo::Changed, so we have to do everything in order
+        * here, as a single handler.
+        */
+
+       if (what_changed.contains (Properties::selected)) {
+               for (TrackViewList::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
+                       (*i)->set_selected (false);
+                       (*i)->hide_selection ();
+               }
        }
 
-       RouteNotificationListPtr routes (new RouteNotificationList);
-       StripableNotificationListPtr stripables (new StripableNotificationList);
+       /* STEP 1: set the GUI selection state (in which TimeAxisViews for the
+        * currently selected stripable/controllable duples are found and added
+        */
 
-       for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+       selection->core_selection_changed (what_changed);
 
-               bool yn = (find (selection->tracks.begin(), selection->tracks.end(), *i) != selection->tracks.end());
+       /* STEP 2: update TimeAxisView's knowledge of their selected state
+        */
 
-               (*i)->set_selected (yn);
+       if (what_changed.contains (Properties::selected)) {
 
-               TimeAxisView::Children c = (*i)->get_child_list ();
-               for (TimeAxisView::Children::iterator j = c.begin(); j != c.end(); ++j) {
-                       (*j)->set_selected (find (selection->tracks.begin(), selection->tracks.end(), j->get()) != selection->tracks.end());
-               }
+               StripableNotificationListPtr stripables (new StripableNotificationList);
 
-               if (yn) {
-                       (*i)->reshow_selection (selection->time);
-               } else {
-                       (*i)->hide_selection ();
+               switch (selection->tracks.size()) {
+               case 0:
+                       break;
+               default:
+                       set_selected_mixer_strip (*(selection->tracks.back()));
+                       if (!_track_selection_change_without_scroll) {
+                               ensure_time_axis_view_is_visible (*(selection->tracks.back()), false);
+                       }
+                       break;
                }
 
+               CoreSelection::StripableAutomationControls sc;
+               _session->selection().get_stripables (sc);
+
+               for (CoreSelection::StripableAutomationControls::const_iterator i = sc.begin(); i != sc.end(); ++i) {
+
+                       AxisView* av = axis_view_by_stripable ((*i).stripable);
 
-               if (yn) {
-                       RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
-                       if (rtav) {
-                               routes->push_back (rtav->route());
-                               stripables->push_back (rtav->route());
+                       if (!av) {
+                               continue;
                        }
-               }
-       }
 
-       ActionManager::set_sensitive (ActionManager::track_selection_sensitive_actions, !selection->tracks.empty());
+                       n_stripables++;
 
-       /* notify control protocols */
+                       if (boost::dynamic_pointer_cast<Track> ((*i).stripable)) {
+                               n_tracks++;
+                               n_routes++;
+                       } else if (boost::dynamic_pointer_cast<Route> ((*i).stripable)) {
+                               n_busses++;
+                               n_routes++;
+                       } else if (boost::dynamic_pointer_cast<VCA> ((*i).stripable)) {
+                               n_vcas++;
+                       }
 
-       ControlProtocol::StripableSelectionChanged (stripables);
+                       TimeAxisView* tav = dynamic_cast<TimeAxisView*> (av);
 
-       if (sfbrowser && _session && !_session->deletion_in_progress()) {
-               uint32_t audio_track_cnt = 0;
-               uint32_t midi_track_cnt = 0;
+                       if (!tav) {
+                               assert (0);
+                               continue; /* impossible */
+                       }
 
-               for (TrackSelection::iterator x = selection->tracks.begin(); x != selection->tracks.end(); ++x) {
-                       AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(*x);
+                       if (!(*i).controllable) {
 
-                       if (atv) {
-                               if (atv->is_audio_track()) {
-                                       audio_track_cnt++;
-                               }
+                               /* "parent" track selected */
+                               tav->set_selected (true);
+                               tav->reshow_selection (selection->time);
 
                        } else {
-                               MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(*x);
 
-                               if (mtv) {
-                                       if (mtv->is_midi_track()) {
-                                               midi_track_cnt++;
+                               /* possibly a child */
+
+                               TimeAxisView::Children c = tav->get_child_list ();
+
+                               for (TimeAxisView::Children::iterator j = c.begin(); j != c.end(); ++j) {
+
+                                       boost::shared_ptr<AutomationControl> control = (*j)->control ();
+
+                                       if (control != (*i).controllable) {
+                                               continue;
+                                       }
+
+                                       (*j)->set_selected (true);
+                                       (*j)->reshow_selection (selection->time);
+                               }
+                       }
+
+                       stripables->push_back ((*i).stripable);
+               }
+
+               ActionManager::set_sensitive (ActionManager::stripable_selection_sensitive_actions, (n_stripables > 0));
+               ActionManager::set_sensitive (ActionManager::track_selection_sensitive_actions, (n_tracks > 0));
+               ActionManager::set_sensitive (ActionManager::bus_selection_sensitive_actions, (n_busses > 0));
+               ActionManager::set_sensitive (ActionManager::route_selection_sensitive_actions, (n_routes > 0));
+               ActionManager::set_sensitive (ActionManager::vca_selection_sensitive_actions, (n_vcas > 0));
+
+               sensitize_the_right_region_actions (false);
+
+               /* STEP 4: notify control protocols */
+
+               ControlProtocolManager::instance().stripable_selection_changed (stripables);
+
+               if (sfbrowser && _session && !_session->deletion_in_progress()) {
+                       uint32_t audio_track_cnt = 0;
+                       uint32_t midi_track_cnt = 0;
+
+                       for (TrackSelection::iterator x = selection->tracks.begin(); x != selection->tracks.end(); ++x) {
+                               AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(*x);
+
+                               if (atv) {
+                                       if (atv->is_audio_track()) {
+                                               audio_track_cnt++;
+                                       }
+
+                               } else {
+                                       MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(*x);
+
+                                       if (mtv) {
+                                               if (mtv->is_midi_track()) {
+                                                       midi_track_cnt++;
+                                               }
                                        }
                                }
                        }
+
+                       sfbrowser->reset (audio_track_cnt, midi_track_cnt);
                }
+       }
+
+       /* STEP 4: update EditorRoutes treeview */
+
+       PropertyChange soh;
 
-               sfbrowser->reset (audio_track_cnt, midi_track_cnt);
+       soh.add (Properties::selected);
+       soh.add (Properties::order);
+       soh.add (Properties::hidden);
+
+       if (what_changed.contains (soh)) {
+               _routes->sync_treeview_from_presentation_info (what_changed);
        }
 }
 
@@ -1086,22 +1206,87 @@ Editor::sensitize_all_region_actions (bool s)
        _all_region_actions_sensitized = s;
 }
 
-/** Sensitize region-based actions based on the selection ONLY, ignoring the entered_regionview.
- *  This method should be called just before displaying a Region menu.  When a Region menu is not
- *  currently being shown, all region actions are sensitized so that hotkey-triggered actions
- *  on entered_regionviews work without having to check sensitivity every time the selection or
- *  entered_regionview changes.
+/** Sensitize region-based actions.
  *
- *  This method also sets up toggle action state as appropriate.
+ *  This method is called from whenever we leave the canvas, either by moving
+ *  the pointer out of it, or by popping up a context menu. See
+ *  Editor::{entered,left}_track_canvas() for details there.
  */
 void
-Editor::sensitize_the_right_region_actions ()
+Editor::sensitize_the_right_region_actions (bool because_canvas_crossing)
 {
-       RegionSelection rs = get_regions_from_selection_and_entered ();
-       sensitize_all_region_actions (!rs.empty ());
+       bool have_selection = false;
+       bool have_entered = false;
+       bool have_edit_point = false;
+       RegionSelection rs;
+
+       // std::cerr << "STRRA: crossing ? " << because_canvas_crossing << " within ? " << within_track_canvas
+       // << std::endl;
+
+       if (!selection->regions.empty()) {
+               have_selection = true;
+               rs = selection->regions;
+       }
+
+       if (entered_regionview) {
+               have_entered = true;
+               rs.add (entered_regionview);
+       }
+
+       if (rs.empty() && !selection->tracks.empty()) {
+
+               /* no selected regions, but some selected tracks.
+                */
+
+               if (_edit_point == EditAtMouse) {
+                       if (!within_track_canvas) {
+                               /* pointer is not in canvas, so edit point is meaningless */
+                               have_edit_point = false;
+                       } else {
+                               /* inside canvas. we don't know where the edit
+                                  point will be when an action is invoked, but
+                                  assume it could intersect with a region.
+                               */
+                               have_edit_point = true;
+                       }
+               } else {
+                       RegionSelection at_edit_point;
+                       framepos_t const where = get_preferred_edit_position (Editing::EDIT_IGNORE_NONE, false, !within_track_canvas);
+                       get_regions_at (at_edit_point, where, selection->tracks);
+                       if (!at_edit_point.empty()) {
+                               have_edit_point = true;
+                       }
+                       if (rs.empty()) {
+                               rs.insert (rs.end(), at_edit_point.begin(), at_edit_point.end());
+                       }
+               }
+       }
+
+       //std::cerr << "\tfinal have selection: " << have_selection
+       // << " have entered " << have_entered
+       // << " have edit point " << have_edit_point
+       // << " EP = " << enum_2_string (_edit_point)
+       // << std::endl;
+
+       typedef std::map<std::string,RegionAction> RegionActionMap;
 
        _ignore_region_action = true;
 
+       for (RegionActionMap::iterator x = region_action_map.begin(); x != region_action_map.end(); ++x) {
+               RegionActionTarget tgt = x->second.target;
+               bool sensitive = false;
+
+               if ((tgt & SelectedRegions) && have_selection) {
+                       sensitive = true;
+               } else if ((tgt & EnteredRegions) && have_entered) {
+                       sensitive = true;
+               } else if ((tgt & EditPointRegions) && have_edit_point) {
+                       sensitive = true;
+               }
+
+               x->second.action->set_sensitive (sensitive);
+       }
+
        /* Look through the regions that are selected and make notes about what we have got */
 
        bool have_audio = false;
@@ -1251,12 +1436,7 @@ Editor::sensitize_the_right_region_actions ()
                /* others were already marked sensitive */
        }
 
-       if (_edit_point == EditAtMouse) {
-               _region_actions->get_action("set-region-sync-position")->set_sensitive (false);
-               _region_actions->get_action("trim-front")->set_sensitive (false);
-               _region_actions->get_action("trim-back")->set_sensitive (false);
-               _region_actions->get_action("place-transient")->set_sensitive (false);
-       }
+       /* ok, moving along... */
 
        if (have_compound_regions) {
                _region_actions->get_action("uncombine-regions")->set_sensitive (true);
@@ -1301,19 +1481,13 @@ Editor::sensitize_the_right_region_actions ()
        }
 
        a = Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-lock-style"));
-
-       CheckMenuItem* cm = dynamic_cast<CheckMenuItem*> (
-               ActionManager::get_widget (X_("/Main/RegionMenu/RegionMenuPosition/toggle-region-lock-style")));
-
-       if (cm) {
-               cm->set_inconsistent (false);
-       }
-
        a->set_active (have_position_lock_style_music && !have_position_lock_style_audio);
 
-       if (have_position_lock_style_music && have_position_lock_style_audio) {
-               if (cm) {
-                       cm->set_inconsistent (true);
+       vector<Widget*> proxies = a->get_proxies();
+       for (vector<Widget*>::iterator p = proxies.begin(); p != proxies.end(); ++p) {
+               Gtk::CheckMenuItem* cmi = dynamic_cast<Gtk::CheckMenuItem*> (*p);
+               if (cmi) {
+                       cmi->set_inconsistent (have_position_lock_style_music && have_position_lock_style_audio);
                }
        }
 
@@ -1368,7 +1542,6 @@ Editor::sensitize_the_right_region_actions ()
        _all_region_actions_sensitized = false;
 }
 
-
 void
 Editor::region_selection_changed ()
 {
@@ -1390,16 +1563,7 @@ Editor::region_selection_changed ()
        _regions->block_change_connection (false);
        editor_regions_selection_changed_connection.block(false);
 
-       if (selection->regions.empty()) {
-               sensitize_all_region_actions (false);
-       } else {
-               if (!_all_region_actions_sensitized) {
-                       /* This selection change might have changed what region actions
-                          are allowed, so sensitize them all in case a key is pressed.
-                       */
-                       sensitize_all_region_actions (true);
-               }
-       }
+       sensitize_the_right_region_actions (false);
 
        /* propagate into backend */
 
@@ -1479,28 +1643,32 @@ Editor::select_all_objects (Selection::Operation op)
 {
        list<Selectable *> touched;
 
-       TrackViewList ts  = track_views;
-
        if (internal_editing() && select_all_internal_edit(op)) {
                return;  // Selected notes
        }
 
+       TrackViewList ts;
+
+       if (selection->tracks.empty()) {
+               ts = track_views;
+       } else {
+               ts = selection->tracks;
+       }
+
        for (TrackViewList::iterator iter = ts.begin(); iter != ts.end(); ++iter) {
                if ((*iter)->hidden()) {
                        continue;
                }
                (*iter)->get_selectables (0, max_framepos, 0, DBL_MAX, touched);
-               selection->add (*iter);
        }
 
-
        begin_reversible_selection_op (X_("select all"));
        switch (op) {
        case Selection::Add:
                selection->add (touched);
                break;
        case Selection::Toggle:
-               selection->add (touched);
+               selection->toggle (touched);
                break;
        case Selection::Set:
                selection->set (touched);
@@ -1637,7 +1805,9 @@ Editor::set_selection_from_region ()
 
        selection->set (tvl);
 
-       set_mouse_mode (Editing::MouseRange, false);
+       if (!get_smart_mode () || !mouse_mode == Editing::MouseObject) {
+               set_mouse_mode (Editing::MouseRange, false);
+       }
 }
 
 void
@@ -1670,7 +1840,9 @@ Editor::set_selection_from_range (Location& loc)
        selection->set (loc.start(), loc.end());
        commit_reversible_selection_op ();
 
-       set_mouse_mode (Editing::MouseRange, false);
+       if (!get_smart_mode () || mouse_mode != Editing::MouseObject) {
+               set_mouse_mode (MouseRange, false);
+       }
 }
 
 void
@@ -1929,8 +2101,11 @@ Editor::select_range_between ()
                return;
        }
 
+       if (!get_smart_mode () || mouse_mode != Editing::MouseObject) {
+               set_mouse_mode (MouseRange, false);
+       }
+
        begin_reversible_selection_op (X_("Select Range Between"));
-       set_mouse_mode (MouseRange);
        selection->set (start, end);
        commit_reversible_selection_op ();
 }