First cut of some Pro-tools inspired editing features; linked play/play range
authorCarl Hetherington <carl@carlh.net>
Fri, 1 Jan 2010 22:11:15 +0000 (22:11 +0000)
committerCarl Hetherington <carl@carlh.net>
Fri, 1 Jan 2010 22:11:15 +0000 (22:11 +0000)
and linked object/range modes.

git-svn-id: svn://localhost/ardour2/branches/3.0@6431 d708f5d6-7413-0410-9779-e7cbd77b26cf

18 files changed:
gtk2_ardour/ardour_ui.cc
gtk2_ardour/ardour_ui.h
gtk2_ardour/ardour_ui2.cc
gtk2_ardour/automation_line.cc
gtk2_ardour/automation_line.h
gtk2_ardour/automation_time_axis.h
gtk2_ardour/control_point.cc
gtk2_ardour/editor.cc
gtk2_ardour/editor.h
gtk2_ardour/editor_actions.cc
gtk2_ardour/editor_canvas_events.cc
gtk2_ardour/editor_drag.cc
gtk2_ardour/editor_drag.h
gtk2_ardour/editor_mouse.cc
gtk2_ardour/editor_selection.cc
gtk2_ardour/region_gain_line.cc
gtk2_ardour/region_gain_line.h
libs/surfaces/control_protocol/basic_ui.cc

index 9b70f266e4a3c1de03fcd734c81489abe6767d01..8ab4cf65777fcd50d8baf86294b38d56aa050041 100644 (file)
@@ -1558,9 +1558,14 @@ ARDOUR_UI::transport_roll ()
 
        if (_session->get_play_loop()) {
                _session->request_play_loop (false, true);
-       } else if (_session->get_play_range ()) {
-               _session->request_play_range (false, true);
-       } 
+       } else if (_session->get_play_range () && !join_play_range_button.get_active()) {
+               /* stop playing a range if we currently are */
+               _session->request_play_range (0, true);
+       }
+
+       if (join_play_range_button.get_active()) {
+               _session->request_play_range (&editor->get_selection().time, true);
+       }
 
        if (!rolling) {
                _session->request_transport_speed (1.0f);
@@ -1619,6 +1624,10 @@ ARDOUR_UI::toggle_roll (bool with_abort, bool roll_out_of_bounded_mode)
                if (rolling) {
                        _session->request_stop (with_abort, true);
                } else {
+                       if (join_play_range_button.get_active()) {
+                               _session->request_play_range (&editor->get_selection().time, true);
+                       }
+                       
                        _session->request_transport_speed (1.0f);
                }
        }
@@ -1761,12 +1770,14 @@ ARDOUR_UI::map_transport_state ()
 
        if (sp != 0.0) {
 
+               /* we're rolling */
+
                if (_session->get_play_range()) {
 
                        play_selection_button.set_visual_state (1);
                        roll_button.set_visual_state (0);
                        auto_loop_button.set_visual_state (0);
-                       
+
                } else if (_session->get_play_loop ()) {
                        
                        auto_loop_button.set_visual_state (1);
@@ -1780,6 +1791,12 @@ ARDOUR_UI::map_transport_state ()
                        auto_loop_button.set_visual_state (0);
                }
 
+               if (join_play_range_button.get_active()) {
+                       /* light up both roll and play-selection if they are joined */
+                       roll_button.set_visual_state (1);
+                       play_selection_button.set_visual_state (1);
+               }
+
                stop_button.set_visual_state (0);
 
        } else {
index 318d1892517056207aa783d16a79e42948fd6e0a..9cf245fbeb991dee1fee987a87b7becb5991bc63 100644 (file)
@@ -328,6 +328,8 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr
        Gtkmm2ext::TearOff*      transport_tearoff;
        Gtk::Frame               transport_frame;
        Gtk::HBox                transport_tearoff_hbox;
+       Gtk::HBox                play_range_hbox;
+       Gtk::VBox                play_range_vbox;
        Gtk::HBox                transport_hbox;
        Gtk::Fixed               transport_base;
        Gtk::Fixed               transport_button_base;
@@ -385,6 +387,7 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr
        BindableButton auto_loop_button;
        BindableButton play_selection_button;
        BindableButton rec_button;
+       Gtk::ToggleButton join_play_range_button;
 
        void toggle_external_sync ();
        void toggle_time_master ();
index ab772389a78843d3ae159b40bf1e668a84e0315c..b759e022a19137e2602d35e68696b23628afa872 100644 (file)
@@ -178,6 +178,7 @@ ARDOUR_UI::setup_transport ()
        play_selection_button.set_name ("TransportButton");
        rec_button.set_name ("TransportRecButton");
        auto_loop_button.set_name ("TransportButton");
+       join_play_range_button.set_name ("TransportButton");
 
        auto_return_button.set_name ("TransportButton");
        auto_play_button.set_name ("TransportButton");
@@ -221,6 +222,9 @@ ARDOUR_UI::setup_transport ()
        w = manage (new Image (get_icon (X_("transport_loop"))));
        w->show();
        auto_loop_button.add (*w);
+       w = manage (new Image (get_icon (X_("join_tools"))));
+       w->show ();
+       join_play_range_button.add (*w);
 
        RefPtr<Action> act;
 
@@ -361,11 +365,17 @@ ARDOUR_UI::setup_transport ()
 
        transport_tearoff_hbox.pack_start (*svbox, false, false, 3);
 
-       transport_tearoff_hbox.pack_start (auto_loop_button, false, false);
-       if (!Profile->get_sae()) {
-               transport_tearoff_hbox.pack_start (play_selection_button, false, false);
+       if (Profile->get_sae()) {
+               transport_tearoff_hbox.pack_start (auto_loop_button);
+               transport_tearoff_hbox.pack_start (roll_button);
+       } else {
+               transport_tearoff_hbox.pack_start (auto_loop_button, false, false);
+               play_range_hbox.pack_start (play_selection_button, false, false);
+               play_range_hbox.pack_start (roll_button, false, false);
+               play_range_vbox.pack_start (play_range_hbox, false, false);
+               play_range_vbox.pack_start (join_play_range_button, false, false);
+               transport_tearoff_hbox.pack_start (play_range_vbox, false, false);
        }
-       transport_tearoff_hbox.pack_start (roll_button, false, false);
        transport_tearoff_hbox.pack_start (stop_button, false, false);
        transport_tearoff_hbox.pack_start (rec_button, false, false, 6);
 
index 128cfb5148e22ee758dec9d24123456aa6a4b3f9..0c568ee55aac6ff330dd56315cc0bffb570da3b5 100644 (file)
@@ -239,7 +239,7 @@ AutomationLine::modify_point_y (ControlPoint& cp, double y)
 
 
 void
-AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
+AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool keep_x, bool with_push)
 {
        double delta = 0.0;
        uint32_t last_movable = UINT_MAX;
@@ -257,7 +257,7 @@ AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool wi
        y = min (1.0, y);
        y = _height - (y * _height);
 
-       if (cp.can_slide()) {
+       if (cp.can_slide() && !keep_x) {
 
                /* x-coord cannot move beyond adjacent points or the start/end, and is
                   already in frames. it needs to be converted to canvas units.
@@ -362,15 +362,12 @@ AutomationLine::reset_line_coords (ControlPoint& cp)
 }
 
 void
-AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
+AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
 {
-       ControlPoint *p;
-
        update_pending = true;
 
-       for (uint32_t i = start; i <= end; ++i) {
-               p = nth(i);
-               sync_model_with_view_point (*p, false, 0);
+       for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
+               sync_model_with_view_point (**i, did_push, distance);
        }
 }
 
@@ -488,6 +485,14 @@ AutomationLine::determine_visible_control_points (ALPoints& points)
                double tx = points[pi].x;
                double ty = points[pi].y;
 
+               if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
+                       add_visible_control_point (view_index, pi, tx, ty, model, npoints);
+                       prev_rx = this_rx;
+                       prev_ry = this_ry;
+                       ++view_index;
+                       continue;
+               }
+
                if (isnan (tx) || isnan (ty)) {
                        warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
                                                   _name) << endmsg;
@@ -667,66 +672,36 @@ AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
        p[index].y = DBL_MAX;
 }
 
+/** Start dragging a single point.
+ *  @param cp Point to drag.
+ *  @param x Initial x position (frames).
+ *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
+ */
 void
-AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
+AutomationLine::start_drag_single (ControlPoint* cp, nframes_t x, float fraction)
 {
-       if (trackview.editor().session() == 0) { /* how? */
-               return;
-       }
-
-       string str;
-
-       if (cp) {
-               str = _("automation event move");
-       } else {
-               str = _("automation range drag");
-       }
-
-       trackview.editor().session()->begin_reversible_command (str);
+       trackview.editor().session()->begin_reversible_command (_("automation event move"));
        trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
 
-       drag_x = x;
-       drag_distance = 0;
-       first_drag_fraction = fraction;
-       last_drag_fraction = fraction;
-       drags = 0;
-       did_push = false;
-}
-
-void
-AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
-{
-       if (x > drag_x) {
-               drag_distance += (x - drag_x);
-       } else {
-               drag_distance -= (drag_x - x);
-       }
-
-       drag_x = x;
-
-       modify_view_point (cp, x, fraction, with_push);
-
-       if (line_points.size() > 1) {
-               line->property_points() = line_points;
-       }
-
-       drags++;
-       did_push = with_push;
+       _drag_points.clear ();
+       _drag_points.push_back (cp);
+       start_drag_common (x, fraction);
 }
 
+/** Start dragging a line vertically (with no change in x)
+ *  @param i1 Control point index of the `left' point on the line.
+ *  @param i2 Control point index of the `right' point on the line.
+ *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
+ */
 void
-AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
+AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
 {
-       double ydelta = fraction - last_drag_fraction;
-
-       did_push = with_push;
-
-       last_drag_fraction = fraction;
+       trackview.editor().session()->begin_reversible_command (_("automation range move"));
+       trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
 
-       line_drag_cp1 = i1;
-       line_drag_cp2 = i2;
+       _drag_points.clear ();
 
-       //check if one of the control points on the line is in a selected range
+       // check if one of the control points on the line is in a selected range
        bool range_found = false;
        ControlPoint *cp;
 
@@ -740,38 +715,92 @@ AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_p
        if (range_found) {
                for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
                        if ((*i)->selected()) {
-                               modify_view_point (*(*i), trackview.editor().unit_to_frame ((*i)->get_x()), ((_height - (*i)->get_y()) /_height) + ydelta, with_push);
+                               _drag_points.push_back (*i);
                        }
                }
        } else {
-               ControlPoint *cp;
                for (uint32_t i = i1 ; i <= i2; i++) {
-                       cp = nth (i);
-                       modify_view_point (*cp, trackview.editor().unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
+                       _drag_points.push_back (nth (i));
                }
        }
 
+       start_drag_common (0, fraction);
+}
+
+/** Start dragging multiple points (with no change in x)
+ *  @param cp Points to drag.
+ *  @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
+ */
+void
+AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
+{
+       trackview.editor().session()->begin_reversible_command (_("automation range move"));
+       trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), state, 0));
+
+       _drag_points = cp;
+       start_drag_common (0, fraction);
+}
+
+/** Common parts of starting a drag.
+ *  @param d Description of the drag.
+ *  @param x Starting x position in frames, or 0 if x is being ignored.
+ *  @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
+ */
+void
+AutomationLine::start_drag_common (nframes_t x, float fraction)
+{
+       drag_x = x;
+       drag_distance = 0;
+       _last_drag_fraction = fraction;
+       _drag_had_movement = false;
+       did_push = false;
+}
+
+/** Should be called to indicate motion during a drag.
+ *  @param x New x position of the drag in frames, or 0 if x is being ignored.
+ *  @param fraction New y fraction.
+ */
+void
+AutomationLine::drag_motion (nframes_t x, float fraction, bool with_push)
+{
+       int64_t const dx = x - drag_x;
+       drag_distance += dx;
+       drag_x = x;
+
+       double const dy = fraction - _last_drag_fraction;
+
+       _last_drag_fraction = fraction;
+
+       for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
+
+               modify_view_point (
+                       **i,
+                       trackview.editor().unit_to_frame ((*i)->get_x()) + dx,
+                       ((_height - (*i)->get_y()) / _height) + dy,
+                       (x == 0),
+                       with_push
+                       );
+       }
+
        if (line_points.size() > 1) {
                line->property_points() = line_points;
        }
 
-       drags++;
+       _drag_had_movement = true;
+       did_push = with_push;
 }
 
+/** Should be called to indicate the end of a drag */
 void
-AutomationLine::end_drag (ControlPoint* cp)
+AutomationLine::end_drag ()
 {
-       if (!drags) {
+       if (!_drag_had_movement) {
                return;
        }
 
        alist->freeze ();
 
-       if (cp) {
-               sync_model_with_view_point (*cp, did_push, drag_distance);
-       } else {
-               sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
-       }
+       sync_model_with_view_points (_drag_points, did_push, drag_distance);
 
        alist->thaw ();
 
@@ -782,7 +811,6 @@ AutomationLine::end_drag (ControlPoint* cp)
        trackview.editor().session()->set_dirty ();
 }
 
-
 void
 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
 {
@@ -849,7 +877,6 @@ AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int
 
                alist->slide (mr.end, drag_distance);
        }
-
 }
 
 bool
@@ -1302,7 +1329,7 @@ AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, dou
                control_points[view_index]->set_can_slide(true);
                shape = ControlPoint::Full;
        }
-       
+
        control_points[view_index]->reset (tx, ty, model, view_index, shape);
 
        /* finally, control visibility */
@@ -1316,3 +1343,18 @@ AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, dou
                }
        }
 }
+
+void
+AutomationLine::add_always_in_view (double x)
+{
+       _always_in_view.push_back (x);
+       alist->apply_to_points (*this, &AutomationLine::reset_callback);
+}
+
+void
+AutomationLine::clear_always_in_view ()
+{
+       _always_in_view.clear ();
+       alist->apply_to_points (*this, &AutomationLine::reset_callback);
+}
+
index 35923c08b8927a947f06174001222fd3c4d24fa1..436e5cbb205ac05ff3f7043947364e1b2421b6cf 100644 (file)
@@ -74,10 +74,11 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
        bool control_points_adjacent (double xval, uint32_t& before, uint32_t& after);
 
        /* dragging API */
-       virtual void start_drag (ControlPoint*, nframes_t x, float fraction);
-       virtual void point_drag(ControlPoint&, nframes_t x, float, bool with_push);
-       virtual void end_drag (ControlPoint*);
-       virtual void line_drag(uint32_t i1, uint32_t i2, float, bool with_push);
+       virtual void start_drag_single (ControlPoint*, nframes_t x, float);
+       virtual void start_drag_line (uint32_t, uint32_t, float);
+       virtual void start_drag_multiple (std::list<ControlPoint*>, float, XMLNode *);
+       virtual void drag_motion (nframes_t, float, bool);
+       virtual void end_drag ();
 
        ControlPoint* nth (uint32_t);
        uint32_t npoints() const { return control_points.size(); }
@@ -130,6 +131,9 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
 
        void modify_point_y (ControlPoint&, double);
 
+       void add_always_in_view (double);
+       void clear_always_in_view ();
+
   protected:
 
        std::string    _name;
@@ -164,8 +168,9 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
        static bool invalid_point (ALPoints&, uint32_t index);
 
        void determine_visible_control_points (ALPoints&);
-       void sync_model_with_view_point (ControlPoint&, bool did_push, int64_t distance);
-       void sync_model_with_view_line (uint32_t, uint32_t);
+       void sync_model_with_view_point (ControlPoint&, bool, int64_t);
+       void sync_model_with_view_points (std::list<ControlPoint*>, bool, int64_t);
+       void start_drag_common (nframes_t, float);
 
        virtual void change_model (ARDOUR::AutomationList::iterator, double x, double y);
 
@@ -177,18 +182,17 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
        virtual void add_model_point (ALPoints& tmp_points, double frame, double yfract);
 
   private:
-       uint32_t drags;
-       double   first_drag_fraction;
-       double   last_drag_fraction;
-       uint32_t line_drag_cp1;
-       uint32_t line_drag_cp2;
-       int64_t  drag_x;
-       int64_t  drag_distance;
+       std::list<ControlPoint*> _drag_points; ///< points we are dragging
+       bool _drag_had_movement; ///< true if the drag has seen movement, otherwise false
+       nframes64_t drag_x; ///< last x position of the drag, in frames
+       nframes64_t drag_distance; ///< total x movement of the drag, in frames
+       double _last_drag_fraction; ///< last y position of the drag, as a fraction
+       std::list<double> _always_in_view;
 
        const Evoral::TimeConverter<double, ARDOUR::sframes_t>& _time_converter;
        ARDOUR::AutomationList::InterpolationStyle              _interpolation;
 
-       void modify_view_point (ControlPoint&, double, double, bool with_push);
+       void modify_view_point (ControlPoint&, double, double, bool, bool with_push);
        void reset_line_coords (ControlPoint&);
        void add_visible_control_point (uint32_t, uint32_t, double, double, ARDOUR::AutomationList::iterator, uint32_t);
 
index 05e0746bf2c9982a188ff89ee5a284301c85e616..b1500e51d168d4a0811da53bff3cd6a30955f1a8 100644 (file)
@@ -100,6 +100,10 @@ class AutomationTimeAxisView : public TimeAxisView {
        boost::shared_ptr<ARDOUR::AutomationControl> control()    { return _control; }
        boost::shared_ptr<AutomationController>      controller() { return _controller; }
 
+       ArdourCanvas::Item* base_item () const {
+               return _base_rect;
+       }
+
   protected:
        boost::shared_ptr<ARDOUR::Route> _route; ///< Parent route
        boost::shared_ptr<ARDOUR::AutomationControl> _control; ///< Control
index 78a3dc57d503740f99b266c3ffbcc9129be0f825..781f9f3b31c801040303e6430ed72064af8c6d54 100644 (file)
@@ -155,15 +155,6 @@ void
 ControlPoint::set_size (double sz)
 {
        _size = sz;
-
-#if 0
-       if (_size > 6.0) {
-               item->property_fill() = (gboolean) TRUE;
-       } else {
-               item->property_fill() = (gboolean) FALSE;
-       }
-#endif
-
        move_to (_x, _y, _shape);
 }
 
index 024799bbbfa182d1f9e855eba59a5ae291109751..fd6d8a991f5899fe4bbe56bbbe24794661f2439e 100644 (file)
@@ -220,8 +220,10 @@ show_me_the_size (Requisition* r, const char* what)
 }
 
 Editor::Editor ()
+       : _join_object_range_state (JOIN_OBJECT_RANGE_NONE)
+
          /* time display buttons */
-       : minsec_label (_("Mins:Secs"))
+       , minsec_label (_("Mins:Secs"))
        , bbt_label (_("Bars:Beats"))
        , timecode_label (_("Timecode"))
        , frame_label (_("Samples"))
@@ -2314,6 +2316,10 @@ Editor::set_state (const XMLNode& node, int /*version*/)
                }
        }
 
+       if ((prop = node.property ("join-object-range"))) {
+               join_object_range_button.set_active (string_is_affirmative (prop->value ()));
+       }
+
        if ((prop = node.property ("edit-point"))) {
                set_edit_point_preference ((EditPoint) string_2_enum (prop->value(), _edit_point), true);
        }
@@ -2461,6 +2467,7 @@ Editor::get_state ()
        node->add_property ("region-list-sort-type", enum2str (_regions->sort_type ()));
        node->add_property ("mouse-mode", enum2str(mouse_mode));
        node->add_property ("internal-edit", _internal_editing ? "yes" : "no");
+       node->add_property ("join-object-range", join_object_range_button.get_active () ? "yes" : "no");
 
        Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("show-editor-mixer"));
        if (act) {
@@ -2752,24 +2759,45 @@ Editor::setup_toolbar ()
        mouse_timefx_button.set_relief(Gtk::RELIEF_NONE);
        mouse_audition_button.set_relief(Gtk::RELIEF_NONE);
        // internal_edit_button.set_relief(Gtk::RELIEF_NONE);
+       join_object_range_button.set_relief(Gtk::RELIEF_NONE);
 
        HBox* mode_box = manage(new HBox);
        mode_box->set_border_width (2);
        mode_box->set_spacing(4);
-       mouse_mode_button_box.set_spacing(1);
-       mouse_mode_button_box.pack_start(mouse_move_button, true, true);
-       if (!Profile->get_sae()) {
-               mouse_mode_button_box.pack_start(mouse_select_button, true, true);
+
+       /* table containing mode buttons */
+
+       Table* mouse_mode_button_table = manage (new Table (Profile->get_sae() ? 4 : 6, 2));
+
+       int c = 0;
+
+       if (Profile->get_sae()) {
+               mouse_mode_button_table->attach (mouse_move_button, c, c + 1, 0, 1);
+               ++c;
+       } else {
+               mouse_mode_button_table->attach (mouse_move_button, c, c + 1, 0, 1);
+               mouse_mode_button_table->attach (mouse_select_button, c + 1, c + 2, 0, 1);
+               mouse_mode_button_table->attach (join_object_range_button, c, c + 2, 1, 2);
+               c += 2;
        }
-       mouse_mode_button_box.pack_start(mouse_zoom_button, true, true);
+
+       mouse_mode_button_table->attach (mouse_zoom_button, c, c + 1, 0, 1);
+       ++c;
+       
        if (!Profile->get_sae()) {
-               mouse_mode_button_box.pack_start(mouse_gain_button, true, true);
+               mouse_mode_button_table->attach (mouse_gain_button, c, c + 1, 0, 1);
+               ++c;
        }
-       mouse_mode_button_box.pack_start(mouse_timefx_button, true, true);
-       mouse_mode_button_box.pack_start(mouse_audition_button, true, true);
-       mouse_mode_button_box.pack_start(internal_edit_button, true, true);
-       mouse_mode_button_box.set_homogeneous(true);
-
+       
+       mouse_mode_button_table->attach (mouse_timefx_button, c, c + 1, 0, 1);
+       ++c;
+       
+       mouse_mode_button_table->attach (mouse_audition_button, c, c + 1, 0, 1);
+       ++c;
+       
+       mouse_mode_button_table->attach (internal_edit_button, c, c + 1, 0, 1);
+       ++c;
+       
        vector<string> edit_mode_strings;
        edit_mode_strings.push_back (edit_mode_to_string (Slide));
        if (!Profile->get_sae()) {
@@ -2781,8 +2809,8 @@ Editor::setup_toolbar ()
        set_popdown_strings (edit_mode_selector, edit_mode_strings, true);
        edit_mode_selector.signal_changed().connect (sigc::mem_fun(*this, &Editor::edit_mode_selection_done));
 
-       mode_box->pack_start(edit_mode_selector);
-       mode_box->pack_start(mouse_mode_button_box);
+       mode_box->pack_start (edit_mode_selector);
+       mode_box->pack_start (*mouse_mode_button_table);
 
        mouse_mode_tearoff = manage (new TearOff (*mode_box));
        mouse_mode_tearoff->set_name ("MouseModeBase");
@@ -2807,6 +2835,7 @@ Editor::setup_toolbar ()
        mouse_zoom_button.set_mode (false);
        mouse_timefx_button.set_mode (false);
        mouse_audition_button.set_mode (false);
+       join_object_range_button.set_mode (false);
 
        mouse_move_button.set_name ("MouseModeButton");
        mouse_select_button.set_name ("MouseModeButton");
@@ -2814,8 +2843,8 @@ Editor::setup_toolbar ()
        mouse_zoom_button.set_name ("MouseModeButton");
        mouse_timefx_button.set_name ("MouseModeButton");
        mouse_audition_button.set_name ("MouseModeButton");
-
        internal_edit_button.set_name ("MouseModeButton");
+       join_object_range_button.set_name ("MouseModeButton");
 
        mouse_move_button.unset_flags (CAN_FOCUS);
        mouse_select_button.unset_flags (CAN_FOCUS);
@@ -2824,6 +2853,7 @@ Editor::setup_toolbar ()
        mouse_timefx_button.unset_flags (CAN_FOCUS);
        mouse_audition_button.unset_flags (CAN_FOCUS);
        internal_edit_button.unset_flags (CAN_FOCUS);
+       join_object_range_button.unset_flags (CAN_FOCUS);
 
        /* Zoom */
 
index ca1082af787791e2ed601ff051c2c58500cb59fd..14e6b1b68d7d7a2ff451fc1687e9575259ef6255 100644 (file)
@@ -487,6 +487,19 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
 
        Editing::MouseMode mouse_mode;
        bool _internal_editing;
+       Editing::MouseMode effective_mouse_mode () const;
+
+       enum JoinObjectRangeState {
+               JOIN_OBJECT_RANGE_NONE,
+               /** `join object/range' mode is active and the mouse is over a place where object mode should happen */
+               JOIN_OBJECT_RANGE_OBJECT,
+               /** `join object/range' mode is active and the mouse is over a place where range mode should happen */
+               JOIN_OBJECT_RANGE_RANGE
+       };
+
+       JoinObjectRangeState _join_object_range_state;
+
+       void update_join_object_range_location (double, double);
 
        int  post_maximal_editor_width;
        int  post_maximal_pane_position;
@@ -594,6 +607,8 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
        void collect_new_region_view (RegionView *);
        void collect_and_select_new_region_view (RegionView *);
 
+       void select_range_around_region (RegionView *);
+
        Gtk::Menu track_context_menu;
        Gtk::Menu track_region_context_menu;
        Gtk::Menu track_selection_context_menu;
@@ -1492,7 +1507,6 @@ public:
        Gtk::Table               toolbar_selection_clock_table;
        Gtk::Label               toolbar_selection_cursor_label;
 
-       Gtk::HBox                mouse_mode_button_box;
        Gtkmm2ext::TearOff*      mouse_mode_tearoff;
        Gtk::ToggleButton         mouse_select_button;
        Gtk::ToggleButton         mouse_move_button;
@@ -1500,6 +1514,7 @@ public:
        Gtk::ToggleButton         mouse_zoom_button;
        Gtk::ToggleButton         mouse_timefx_button;
        Gtk::ToggleButton         mouse_audition_button;
+       Gtk::ToggleButton         join_object_range_button;
 
        void                     mouse_mode_toggled (Editing::MouseMode m);
        bool                     ignore_mouse_mode_toggle;
index 6a7e8181357cd636757fdb749383628f8d82fe7f..99177601f6a4929ab9df497d5708ae15517bd497 100644 (file)
@@ -683,7 +683,11 @@ Editor::register_actions ()
        mouse_select_button.set_name ("MouseModeButton");
        mouse_select_button.get_image ()->show ();
 
-       act = ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-gain", _("Gain Tool"), sigc::bind (sigc::mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseGain));
+       join_object_range_button.set_image (*(manage (new Image (::get_icon ("join_tools")))));
+       join_object_range_button.set_name ("MouseModeButton");
+       join_object_range_button.get_image()->show ();
+
+       act = ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-gain", _("Gain Tool"), sigc::bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseGain));
        act->connect_proxy (mouse_gain_button);
        mouse_gain_button.set_image (*(manage (new Image (::get_icon("tool_gain")))));
        mouse_gain_button.set_label ("");
index 24b2e646a6bc78fe1841119335de628a05dde5fd..f3131487cd7502b7bad2b04d7be084fe9072c9a2 100644 (file)
@@ -265,7 +265,6 @@ Editor::canvas_region_view_event (GdkEvent *event, ArdourCanvas::Item* item, Reg
                return false;
        }
 
-
        switch (event->type) {
        case GDK_BUTTON_PRESS:
        case GDK_2BUTTON_PRESS:
index cd9419cffd6576439f2d05c25b2dbab20197f299..4de1722ed8f9399e609d3f4e10aa49588e423810 100644 (file)
@@ -40,6 +40,7 @@
 #include "canvas-note.h"
 #include "selection.h"
 #include "midi_selection.h"
+#include "automation_time_axis.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -2466,15 +2467,12 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
        // the point doesn't 'jump' to the mouse after the first drag
        _time_axis_view_grab_x = _point->get_x();
        _time_axis_view_grab_y = _point->get_y();
+       nframes64_t grab_frame = _editor->pixel_to_frame (_time_axis_view_grab_x);
 
-       _point->line().parent_group().i2w (_time_axis_view_grab_x, _time_axis_view_grab_y);
-       _editor->track_canvas->w2c (_time_axis_view_grab_x, _time_axis_view_grab_y, _time_axis_view_grab_x, _time_axis_view_grab_y);
+       float const fraction = 1 - (_point->get_y() / _point->line().height());
 
-       _time_axis_view_grab_frame = _editor->pixel_to_frame (_time_axis_view_grab_x);
+       _point->line().start_drag_single (_point, grab_frame, fraction);
 
-       _point->line().start_drag (_point, _time_axis_view_grab_frame, 0);
-
-       float fraction = 1.0 - (_point->get_y() / _point->line().height());
        _editor->set_verbose_canvas_cursor (_point->line().get_verbose_cursor_string (fraction),
                                            event->button.x + 10, event->button.y + 10);
 
@@ -2492,18 +2490,16 @@ ControlPointDrag::motion (GdkEvent* event, bool)
                dy *= 0.1;
        }
 
+       /* coordinate in TimeAxisView's space */
        double cx = _time_axis_view_grab_x + _cumulative_x_drag + dx;
        double cy = _time_axis_view_grab_y + _cumulative_y_drag + dy;
 
        // calculate zero crossing point. back off by .01 to stay on the
        // positive side of zero
-       double _unused = 0;
-       double zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
-       _point->line().parent_group().i2w(_unused, zero_gain_y);
+       double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
 
        // make sure we hit zero when passing through
-       if ((cy < zero_gain_y and (cy - dy) > zero_gain_y)
-                       or (cy > zero_gain_y and (cy - dy) < zero_gain_y)) {
+       if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
                cy = zero_gain_y;
        }
 
@@ -2517,13 +2513,10 @@ ControlPointDrag::motion (GdkEvent* event, bool)
        _cumulative_x_drag = cx - _time_axis_view_grab_x;
        _cumulative_y_drag = cy - _time_axis_view_grab_y;
 
-       _point->line().parent_group().w2i (cx, cy);
-
        cx = max (0.0, cx);
        cy = max (0.0, cy);
        cy = min ((double) _point->line().height(), cy);
 
-       //translate cx to frames
        nframes64_t cx_frames = _editor->unit_to_frame (cx);
 
        if (!_x_constrained) {
@@ -2534,7 +2527,7 @@ ControlPointDrag::motion (GdkEvent* event, bool)
 
        bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
 
-       _point->line().point_drag (*_point, cx_frames, fraction, push);
+       _point->line().drag_motion (cx_frames, fraction, push);
 
        _editor->set_verbose_canvas_cursor_text (_point->line().get_verbose_cursor_string (fraction));
 }
@@ -2553,7 +2546,7 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
        } else {
                motion (event, false);
        }
-       _point->line().end_drag (_point);
+       _point->line().end_drag ();
 }
 
 bool
@@ -2594,7 +2587,10 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        nframes64_t const frame_within_region = (nframes64_t) floor (cx * _editor->frames_per_unit);
 
-       if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
+       uint32_t before;
+       uint32_t after;
+       
+       if (!_line->control_points_adjacent (frame_within_region, before, after)) {
                /* no adjacent points */
                return;
        }
@@ -2608,7 +2604,7 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        double fraction = 1.0 - (cy / _line->height());
 
-       _line->start_drag (0, grab_frame(), fraction);
+       _line->start_drag_line (before, after, fraction);
 
        _editor->set_verbose_canvas_cursor (_line->get_verbose_cursor_string (fraction),
                                            event->button.x + 10, event->button.y + 10);
@@ -2642,7 +2638,8 @@ LineDrag::motion (GdkEvent* event, bool)
                push = true;
        }
 
-       _line->line_drag (_before, _after, fraction, push);
+       /* we are ignoring x position for this drag, so we can just pass in 0 */
+       _line->drag_motion (0, fraction, push);
 
        _editor->set_verbose_canvas_cursor_text (_line->get_verbose_cursor_string (fraction));
 }
@@ -2651,7 +2648,7 @@ void
 LineDrag::finished (GdkEvent* event, bool)
 {
        motion (event, false);
-       _line->end_drag (0);
+       _line->end_drag ();
 }
 
 void
@@ -2850,6 +2847,8 @@ SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
        : Drag (e, i)
        , _operation (o)
        , _copy (false)
+       , _original_pointer_time_axis (-1)
+       , _last_pointer_time_axis (-1)
 {
 
 }
@@ -2909,6 +2908,8 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
        } else {
                _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
        }
+
+       _original_pointer_time_axis = _editor->trackview_by_y_position (current_pointer_y ()).first->order ();
 }
 
 void
@@ -2918,13 +2919,16 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        nframes64_t end = 0;
        nframes64_t length;
 
+       pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (current_pointer_y ());
+       if (pending_time_axis.first == 0) {
+               return;
+       }
+       
        nframes64_t const pending_position = adjusted_current_frame (event);
 
-       /* only alter selection if the current frame is
-          different from the last frame position (adjusted)
-        */
+       /* only alter selection if things have changed */
 
-       if (pending_position == last_pointer_frame()) {
+       if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
                return;
        }
 
@@ -2969,8 +2973,36 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                                _editor->clicked_selection = _editor->selection->set (start, end);
                        }
                }
-               break;
+
+               /* select the track that we're in */
+               if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
+                       _editor->selection->add (pending_time_axis.first);
+                       _added_time_axes.push_back (pending_time_axis.first);
+               }
+
+               /* deselect any tracks that this drag no longer includes, being careful to only deselect
+                  tracks that we selected in the first place.
+               */
+               
+               int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
+               int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
+
+               list<TimeAxisView*>::iterator i = _added_time_axes.begin();
+               while (i != _added_time_axes.end()) {
+
+                       list<TimeAxisView*>::iterator tmp = i;
+                       ++tmp;
+                       
+                       if ((*i)->order() < min_order || (*i)->order() > max_order) {
+                               _editor->selection->remove (*i);
+                               _added_time_axes.remove (*i);
+                       }
+
+                       i = tmp;
+               }
+
        }
+       break;
 
        case SelectionStartTrim:
 
@@ -3490,3 +3522,96 @@ NoteDrag::finished (GdkEvent* ev, bool moved)
                region->note_dropped (cnote, drag_delta_x, drag_delta_note);
        }
 }
+
+AutomationRangeDrag::AutomationRangeDrag (Editor* e, ArdourCanvas::Item* i, list<AudioRange> const & r)
+       : Drag (e, i)
+       , _ranges (r)
+       , _nothing_to_drag (false)
+{
+       _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
+       assert (_atav);
+
+       _line = _atav->line ();
+}
+
+void
+AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
+
+       list<ControlPoint*> points;
+
+       XMLNode* state = &_line->get_state ();
+       
+       if (_ranges.empty()) {
+               
+               uint32_t const N = _line->npoints ();
+               for (uint32_t i = 0; i < N; ++i) {
+                       points.push_back (_line->nth (i));
+               }
+               
+       } else {
+
+               boost::shared_ptr<AutomationList> the_list = _line->the_list ();
+               for (list<AudioRange>::const_iterator j = _ranges.begin(); j != _ranges.end(); ++j) {
+                       the_list->add (j->start, the_list->eval (j->start));
+                       _line->add_always_in_view (j->start);
+                       the_list->add (j->start + 1, the_list->eval (j->start + 1));
+                       _line->add_always_in_view (j->start + 1);
+                       the_list->add (j->end - 1, the_list->eval (j->end - 1));
+                       _line->add_always_in_view (j->end - 1);
+                       the_list->add (j->end, the_list->eval (j->end));
+                       _line->add_always_in_view (j->end);
+               }
+
+               uint32_t const N = _line->npoints ();
+               AutomationList::const_iterator j = the_list->begin ();
+               for (uint32_t i = 0; i < N; ++i) {
+
+                       ControlPoint* p = _line->nth (i);
+
+                       list<AudioRange>::const_iterator k = _ranges.begin ();
+                       while (k != _ranges.end() && (k->start >= (*j)->when || k->end <= (*j)->when)) {
+                               ++k;
+                       }
+
+                       if (k != _ranges.end()) {
+                               points.push_back (p);
+                       }
+
+                       ++j;
+               }
+       }
+
+       if (points.empty()) {
+               _nothing_to_drag = true;
+               return;
+       }
+
+       _line->start_drag_multiple (points, 1 - (current_pointer_y() / _line->height ()), state);
+}
+
+void
+AutomationRangeDrag::motion (GdkEvent* event, bool first_move)
+{
+       if (_nothing_to_drag) {
+               return;
+       }
+       
+       float const f = 1 - (current_pointer_y() / _line->height());
+
+       /* we are ignoring x position for this drag, so we can just pass in 0 */
+       _line->drag_motion (0, f, false);
+}
+
+void
+AutomationRangeDrag::finished (GdkEvent* event, bool)
+{
+       if (_nothing_to_drag) {
+               return;
+       }
+       
+       motion (event, false);
+       _line->end_drag ();
+       _line->clear_always_in_view ();
+}
index 1d9ac151d8f7a150678a2448e8d30940ceb0b4ac..9d7215d93f689a7b5d12834c155db06c9c918209 100644 (file)
@@ -474,7 +474,6 @@ private:
        ControlPoint* _point;
        double _time_axis_view_grab_x;
        double _time_axis_view_grab_y;
-       nframes64_t _time_axis_view_grab_frame;
        double _cumulative_x_drag;
        double _cumulative_y_drag;
        static double const _zero_gain_fraction;
@@ -537,7 +536,7 @@ public:
        void finished (GdkEvent *, bool);
 };
 
-/** Drag in range select(gc_owner.get()) moAutomatable */
+/** Drag in range select mode */
 class SelectionDrag : public Drag
 {
 public:
@@ -557,6 +556,9 @@ public:
 private:
        Operation _operation;
        bool _copy;
+       int _original_pointer_time_axis;
+       int _last_pointer_time_axis;
+       std::list<TimeAxisView*> _added_time_axes;
 };
 
 /** Range marker drag */
@@ -583,16 +585,33 @@ private:
        bool _copy;
 };
 
-/* Drag of rectangle to set zoom */
+/** Drag of rectangle to set zoom */
 class MouseZoomDrag : public Drag
 {
 public:
-       MouseZoomDrag (Editor *e, ArdourCanvas::Item *i) : Drag (e, i) {}
+       MouseZoomDrag (Editore, ArdourCanvas::Item *i) : Drag (e, i) {}
 
        void start_grab (GdkEvent *, Gdk::Cursor* c = 0);
        void motion (GdkEvent *, bool);
        void finished (GdkEvent *, bool);
 };
 
+/** Drag of a range of automation data, changing value but not position */
+class AutomationRangeDrag : public Drag
+{
+public:
+       AutomationRangeDrag (Editor *, ArdourCanvas::Item *, std::list<ARDOUR::AudioRange> const &);
+
+       void start_grab (GdkEvent *, Gdk::Cursor* c = 0);
+       void motion (GdkEvent *, bool);
+       void finished (GdkEvent *, bool);
+
+private:
+       std::list<ARDOUR::AudioRange> _ranges;
+       AutomationTimeAxisView* _atav;
+       boost::shared_ptr<AutomationLine> _line;
+       bool _nothing_to_drag;
+};
+
 #endif /* __gtk2_ardour_editor_drag_h_ */
 
index 5fbd76ed497a2523f6d4218948bbf50c035166ff..1b91947d409eb02f805989c614c140ac8adc7c48 100644 (file)
@@ -134,13 +134,11 @@ Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
        case GDK_BUTTON_PRESS:
        case GDK_2BUTTON_PRESS:
        case GDK_3BUTTON_PRESS:
-
                *pcx = event->button.x;
                *pcy = event->button.y;
                _trackview_group->w2i(*pcx, *pcy);
                break;
        case GDK_MOTION_NOTIFY:
-
                *pcx = event->motion.x;
                *pcy = event->motion.y;
                _trackview_group->w2i(*pcx, *pcy);
@@ -254,6 +252,17 @@ Editor::set_canvas_cursor ()
                }
        }
 
+       switch (_join_object_range_state) {
+       case JOIN_OBJECT_RANGE_NONE:
+               break;
+       case JOIN_OBJECT_RANGE_OBJECT:
+               current_canvas_cursor = which_grabber_cursor ();
+               break;
+       case JOIN_OBJECT_RANGE_RANGE:
+               current_canvas_cursor = selector_cursor;
+               break;
+       }
+
        if (is_drawable()) {
                track_canvas->get_window()->set_cursor(*current_canvas_cursor);
        }
@@ -315,9 +324,9 @@ Editor::mouse_mode_toggled (MouseMode m)
 
        instant_save ();
 
-       if (mouse_mode != MouseRange) {
+       if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
 
-               /* in all modes except range, hide the range selection,
+               /* in all modes except range and joined object/range, hide the range selection,
                   show the object (region) selection.
                */
 
@@ -331,7 +340,7 @@ Editor::mouse_mode_toggled (MouseMode m)
        } else {
 
                /*
-                  in range mode, show the range selection.
+                  in range or object/range mode, show the range selection.
                */
 
                for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
@@ -416,6 +425,7 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
        */
 
        if (((mouse_mode != MouseObject) &&
+            (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
             (mouse_mode != MouseAudition || item_type != RegionItem) &&
             (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
             (mouse_mode != MouseGain) &&
@@ -445,16 +455,21 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
 
        switch (item_type) {
        case RegionItem:
-               if (mouse_mode != MouseRange) {
+               if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
                        set_selected_regionview_from_click (press, op, true);
                } else if (event->type == GDK_BUTTON_PRESS) {
-                       set_selected_track_as_side_effect ();
+                       selection->clear_tracks ();
+                       set_selected_track_as_side_effect (true);
+               }
+               if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
+                       select_range_around_region (selection->regions.front());
                }
+                       
                break;
 
        case RegionViewNameHighlight:
        case RegionViewName:
-               if (mouse_mode != MouseRange) {
+               if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
                        set_selected_regionview_from_click (press, op, true);
                } else if (event->type == GDK_BUTTON_PRESS) {
                        set_selected_track_as_side_effect ();
@@ -466,7 +481,7 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
        case FadeInItem:
        case FadeOutHandleItem:
        case FadeOutItem:
-               if (mouse_mode != MouseRange) {
+               if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
                        set_selected_regionview_from_click (press, op, true);
                } else if (event->type == GDK_BUTTON_PRESS) {
                        set_selected_track_as_side_effect ();
@@ -475,7 +490,7 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
 
        case ControlPointItem:
                set_selected_track_as_side_effect ();
-               if (mouse_mode != MouseRange) {
+               if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
                        set_selected_control_point_from_click (op, false);
                }
                break;
@@ -597,7 +612,9 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                break;
        }
 
-       switch (mouse_mode) {
+       Editing::MouseMode eff = effective_mouse_mode ();
+
+       switch (eff) {
        case MouseRange:
                switch (item_type) {
                case StartSelectionTrimItem:
@@ -623,11 +640,21 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                _drag = new SelectionDrag (this, item, SelectionDrag::SelectionMove);
                                _drag->start_grab (event);
                        } else {
-                               /* this was debated, but decided the more common action was to
-                                  make a new selection */
-                               assert (_drag == 0);
-                               _drag = new SelectionDrag (this, item, SelectionDrag::CreateSelection);
-                               _drag->start_grab (event);
+                               double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
+                               pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
+                               if (tvp.first) {
+                                       AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
+                                       assert (_drag == 0);
+                                       if (join_object_range_button.get_active() && atv) {
+                                               /* smart "join" mode: drag automation */
+                                               _drag = new AutomationRangeDrag (this, atv->base_item(), selection->time);
+                                       } else {
+                                               /* this was debated, but decided the more common action was to
+                                                  make a new selection */
+                                               _drag = new SelectionDrag (this, item, SelectionDrag::CreateSelection);
+                                       }
+                                       _drag->start_grab (event);
+                               }
                        }
                        break;
 
@@ -734,14 +761,46 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                        _drag = new RegionCreateDrag (this, item, clicked_axisview);
                                        _drag->start_grab (event);
                                        return true;
+                               } else {
+                                       assert (_drag == 0);
+                                       _drag = new RubberbandSelectDrag (this, item);
+                                       _drag->start_grab (event);
                                }
-                               /* fallthru */
+                               break;
+                               
                        case AutomationTrackItem:
                                assert (_drag == 0);
-                               _drag = new RubberbandSelectDrag (this, item);
+                               
+                               if (join_object_range_button.get_active()) {
+                                       /* smart "join" mode: drag automation */
+                                       _drag = new AutomationRangeDrag (this, item, selection->time);
+                               } else {
+                                       /* otherwise: rubberband drag to select automation points */
+                                       _drag = new RubberbandSelectDrag (this, item);
+                               }
+                               
                                _drag->start_grab (event);
                                break;
 
+                       case SelectionItem:
+                       {
+                               if (join_object_range_button.get_active()) {
+                                       /* we're in "smart" joined mode, and we've clicked on a Selection; if we're
+                                        * over an automation track, start a drag of its data */
+                                       double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
+                                       pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
+                                       if (tvp.first) {
+                                               AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
+                                               if (atv) {
+                                                       assert (_drag == 0);
+                                                       _drag = new AutomationRangeDrag (this, atv->base_item(), selection->time);
+                                                       _drag->start_grab (event);
+                                               }
+                                       }
+                               }
+                               break;
+                       }
+
 #ifdef WITH_CMT
                        case ImageFrameHandleStartItem:
                                imageframe_start_handle_op(item, event) ;
@@ -878,7 +937,8 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 bool
 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
-       switch (mouse_mode) {
+       Editing::MouseMode const eff = effective_mouse_mode ();
+       switch (eff) {
        case MouseObject:
                switch (item_type) {
                case RegionItem:
@@ -1152,6 +1212,8 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 
        /* delete events get handled here */
 
+       Editing::MouseMode const eff = effective_mouse_mode ();
+
        if (_drag == 0 && Keyboard::is_delete_event (&event->button)) {
 
                switch (item_type) {
@@ -1168,13 +1230,13 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                        break;
 
                case RegionItem:
-                       if (mouse_mode == MouseObject) {
+                       if (eff == MouseObject) {
                                remove_clicked_region ();
                        }
                        break;
 
                case ControlPointItem:
-                       if (mouse_mode == MouseGain) {
+                       if (eff == MouseGain) {
                                remove_gain_control_point (item, event);
                        } else {
                                remove_control_point (item, event);
@@ -1233,7 +1295,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                        break;
                }
 
-               switch (mouse_mode) {
+               switch (eff) {
                case MouseObject:
                        switch (item_type) {
                        case AutomationTrackItem:
@@ -1305,7 +1367,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 
 
        case 2:
-               switch (mouse_mode) {
+               switch (eff) {
 
                case MouseObject:
                        switch (item_type) {
@@ -1348,7 +1410,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 }
 
 bool
-Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType item_type)
+Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
        ControlPoint* cp;
        Marker * marker;
@@ -1544,7 +1606,7 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
 }
 
 bool
-Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType item_type)
+Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
        AutomationLine* al;
        ControlPoint* cp;
@@ -1661,6 +1723,10 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                break;
        }
 
+       if (item_type == RegionItem) {
+               update_join_object_range_location (event->crossing.x, event->crossing.y);
+       }
+               
        return false;
 }
 
@@ -1782,6 +1848,12 @@ Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from
                return true;
        }
 
+       JoinObjectRangeState const old = _join_object_range_state;
+       update_join_object_range_location (event->motion.x, event->motion.y);
+       if (_join_object_range_state != old) {
+               set_canvas_cursor ();
+       }
+
        bool handled = false;
        if (_drag) {
                handled = _drag->motion_handler (event, from_autoscroll);
@@ -2598,3 +2670,52 @@ Editor::set_internal_edit (bool yn)
 
 
 }
+
+/** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
+ *  used by the `join object/range' tool mode.
+ */
+void
+Editor::update_join_object_range_location (double x, double y)
+{
+       if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
+               _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
+               return;
+       }
+       
+       if (entered_regionview) {
+
+               double cx = x;
+               double cy = y;
+               entered_regionview->get_canvas_group()->w2i (cx, cy);
+               
+               double x1;
+               double y1;
+               double x2;
+               double y2;
+               entered_regionview->get_canvas_group()->get_bounds (x1, y1, x2, y2);
+               
+               bool const top_half = cy < (y2 - y1) / 2;
+               
+               _join_object_range_state = top_half ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
+               
+       } else {
+
+               if (mouse_mode == MouseObject) {
+                       _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
+               } else if (mouse_mode == MouseRange) {
+                       _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
+               }
+       }
+}
+
+Editing::MouseMode
+Editor::effective_mouse_mode () const
+{
+       if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
+               return MouseObject;
+       } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
+               return MouseRange;
+       }
+
+       return mouse_mode;
+}
index 4e66847ba2fd40609a1c39d0341ac61047b85dbb..59be95713827bd1a30f0b14fb19f83bdcfe6649c 100644 (file)
@@ -171,6 +171,9 @@ Editor::select_all_tracks ()
        selection->set (visible_views);
 }
 
+/** Select clicked_routeview, unless there are no currently selected
+ *  tracks, in which case nothing will happen unless `force' is true.
+ */
 void
 Editor::set_selected_track_as_side_effect (bool force)
 {
@@ -1392,4 +1395,12 @@ Editor::deselect_all ()
        selection->clear ();
 }
 
-
+void
+Editor::select_range_around_region (RegionView* rv)
+{
+       selection->set (&rv->get_time_axis_view());
+       
+       selection->time.clear ();
+       boost::shared_ptr<Region> r = rv->region ();
+       selection->set (r->position(), r->position() + r->length());
+}
index 00352c96df7992b8418dca1e97ed137d4c8bfa2e..a2c5cffa5af57866e6bf6b6c7ba2c529fad64ab2 100644 (file)
@@ -50,9 +50,10 @@ AudioRegionGainLine::AudioRegionGainLine (const string & name, AudioRegionView&
 }
 
 void
-AudioRegionGainLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
+AudioRegionGainLine::start_drag_single (ControlPoint* cp, nframes_t x, float fraction)
 {
-       AutomationLine::start_drag (cp, x, fraction);
+       AutomationLine::start_drag_single (cp, x, fraction);
+       
        if (!rv.audio_region()->envelope_active()) {
                trackview.session()->add_command(new MementoCommand<AudioRegion>(*(rv.audio_region().get()), &rv.audio_region()->get_state(), 0));
                rv.audio_region()->set_envelope_active(false);
@@ -85,13 +86,13 @@ AudioRegionGainLine::remove_point (ControlPoint& cp)
 }
 
 void
-AudioRegionGainLine::end_drag (ControlPoint* cp)
+AudioRegionGainLine::end_drag ()
 {
        if (!rv.audio_region()->envelope_active()) {
                rv.audio_region()->set_envelope_active(true);
                trackview.session()->add_command(new MementoCommand<AudioRegion>(*(rv.audio_region().get()), 0, &rv.audio_region()->get_state()));
        }
 
-       AutomationLine::end_drag(cp);
+       AutomationLine::end_drag ();
 }
 
index d54c962294ddebe56f47acc3d846fc99168b9aff..0f163722876574f313f2f15c57272e76b8cc0980 100644 (file)
@@ -38,8 +38,8 @@ class AudioRegionGainLine : public AutomationLine
   public:
        AudioRegionGainLine (const std::string & name, AudioRegionView&, ArdourCanvas::Group& parent, boost::shared_ptr<ARDOUR::AutomationList>);
 
-       void start_drag (ControlPoint*, nframes_t x, float fraction);
-       void end_drag (ControlPoint*);
+       void start_drag_single (ControlPoint*, nframes_t x, float fraction);
+       void end_drag ();
 
        void remove_point (ControlPoint&);
 
index a21da95bb1b57bb9e3d4a2a11b81653bbcb16d23..8bb353edd4530ab34218b7b2154a115b6307e4ad 100644 (file)
@@ -126,7 +126,7 @@ BasicUI::transport_play (bool from_last_start)
        } 
 
        if (session->get_play_range ()) {
-               session->request_play_range (false);
+               session->request_play_range (0);
        }
        
        if (from_last_start && rolling) {