pulling trunk
[ardour.git] / gtk2_ardour / editor.cc
index a3a73bc9362464a8ba476a6a0f0fee10a5cb76a3..c7956386ced6848b3058a29d7e9122f12b3c9bbc 100644 (file)
@@ -47,7 +47,8 @@
 #include <ardour/session_route.h>
 #include <ardour/tempo.h>
 #include <ardour/utils.h>
-#include <ardour/control_protocol.h>
+
+#include "control_protocol.h"
 
 #include "ardour_ui.h"
 #include "editor.h"
@@ -89,6 +90,7 @@ using namespace Gtkmm2ext;
 using namespace Editing;
 
 using PBD::internationalize;
+using PBD::atoi;
 
 const double Editor::timebar_height = 15.0;
 
@@ -322,9 +324,10 @@ Editor::Editor (AudioEngine& eng)
        _new_regionviews_show_envelope = false;
        current_timestretch = 0;
        in_edit_group_row_change = false;
-
+       last_canvas_frame = 0;
        edit_cursor = 0;
        playhead_cursor = 0;
+       button_release_can_deselect = true;
 
        location_marker_color = color_map[cLocationMarker];
        location_range_color = color_map[cLocationRange];
@@ -525,6 +528,9 @@ Editor::Editor (AudioEngine& eng)
        edit_group_display.get_column (0)->set_data (X_("colnum"), GUINT_TO_POINTER(0));
        edit_group_display.get_column (1)->set_data (X_("colnum"), GUINT_TO_POINTER(1));
        edit_group_display.get_column (2)->set_data (X_("colnum"), GUINT_TO_POINTER(2));
+       edit_group_display.get_column (0)->set_expand (true);
+       edit_group_display.get_column (1)->set_expand (false);
+       edit_group_display.get_column (2)->set_expand (false);
        edit_group_display.set_headers_visible (true);
 
        /* name is directly editable */
@@ -594,6 +600,7 @@ Editor::Editor (AudioEngine& eng)
        region_list_display.set_model (region_list_model);
        region_list_display.append_column (_("Regions"), region_list_columns.name);
        region_list_display.set_headers_visible (false);
+       region_list_display.set_hover_expand (true);
 
        region_list_display.get_selection()->set_select_function (mem_fun (*this, &Editor::region_list_selection_filter));
        
@@ -810,15 +817,6 @@ Editor::set_entered_track (TimeAxisView* tav)
        }
 }
 
-gint
-Editor::left_track_canvas (GdkEventCrossing *ev)
-{
-       set_entered_track (0);
-       set_entered_regionview (0);
-       return FALSE;
-}
-
-
 void
 Editor::show_window ()
 {
@@ -930,6 +928,14 @@ Editor::reposition_x_origin (jack_nframes_t frame)
 {
        if (frame != leftmost_frame) {
                leftmost_frame = frame;
+               
+               jack_nframes_t rightmost_frame = leftmost_frame + current_page_frames ();
+
+               if (rightmost_frame > last_canvas_frame) {
+                       last_canvas_frame = rightmost_frame;
+                       reset_scrolling_region ();
+               }
+
                horizontal_adjustment.set_value (frame/frames_per_unit);
        }
 }
@@ -1088,24 +1094,6 @@ Editor::session_control_changed (Session::ControlType t)
        }
 }
 
-void
-Editor::fake_handle_new_audio_region (AudioRegion *region)
-{
-       Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun(*this, &Editor::handle_new_audio_region), region));
-}
-
-void
-Editor::fake_handle_audio_region_removed (AudioRegion *region)
-{
-       Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun(*this, &Editor::handle_audio_region_removed), region));
-}
-
-void
-Editor::fake_handle_new_duration ()
-{
-       Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &Editor::handle_new_duration));
-}
-
 void
 Editor::start_scrolling ()
 {
@@ -1163,12 +1151,16 @@ Editor::center_screen_internal (jack_nframes_t frame, float page)
 void
 Editor::handle_new_duration ()
 {
-       reset_scrolling_region ();
+       ENSURE_GUI_THREAD (mem_fun (*this, &Editor::handle_new_duration));
 
-       if (session) {
-               cerr << "Set upper #2 to " << horizontal_adjustment.get_upper () << endl;
-               horizontal_adjustment.set_value (leftmost_frame/frames_per_unit);
+       jack_nframes_t new_end = session->get_maximum_extent() + (jack_nframes_t) floorf (current_page_frames() * 0.10f);
+                                 
+       if (new_end > last_canvas_frame) {
+               last_canvas_frame = new_end;
+               reset_scrolling_region ();
        }
+
+       horizontal_adjustment.set_value (leftmost_frame/frames_per_unit);
 }
 
 void
@@ -1229,9 +1221,9 @@ Editor::connect_to_session (Session *t)
        session_connections.push_back (session->TransportStateChange.connect (mem_fun(*this, &Editor::map_transport_state)));
        session_connections.push_back (session->PositionChanged.connect (mem_fun(*this, &Editor::map_position_change)));
        session_connections.push_back (session->RouteAdded.connect (mem_fun(*this, &Editor::handle_new_route_p)));
-       session_connections.push_back (session->AudioRegionAdded.connect (mem_fun(*this, &Editor::fake_handle_new_audio_region)));
-       session_connections.push_back (session->AudioRegionRemoved.connect (mem_fun(*this, &Editor::fake_handle_audio_region_removed)));
-       session_connections.push_back (session->DurationChanged.connect (mem_fun(*this, &Editor::fake_handle_new_duration)));
+       session_connections.push_back (session->AudioRegionAdded.connect (mem_fun(*this, &Editor::handle_new_audio_region)));
+       session_connections.push_back (session->AudioRegionRemoved.connect (mem_fun(*this, &Editor::handle_audio_region_removed)));
+       session_connections.push_back (session->DurationChanged.connect (mem_fun(*this, &Editor::handle_new_duration)));
        session_connections.push_back (session->edit_group_added.connect (mem_fun(*this, &Editor::add_edit_group)));
        session_connections.push_back (session->edit_group_removed.connect (mem_fun(*this, &Editor::edit_groups_changed)));
        session_connections.push_back (session->NamedSelectionAdded.connect (mem_fun(*this, &Editor::handle_new_named_selection)));
@@ -1337,7 +1329,7 @@ Editor::connect_to_session (Session *t)
        update_crossfade_model ();
        update_layering_model ();
 
-       reset_scrolling_region ();
+       handle_new_duration ();
 
        redisplay_regions ();
        redisplay_named_selections ();
@@ -2200,7 +2192,7 @@ Editor::set_state (const XMLNode& node)
        }
 
        if ((prop = node.property ("zoom"))) {
-               set_frames_per_unit (atof (prop->value()));
+               set_frames_per_unit (PBD::atof (prop->value()));
        }
 
        if ((prop = node.property ("snap-to"))) {
@@ -2839,101 +2831,6 @@ Editor::setup_toolbar ()
        toolbar_frame.add (toolbar_base);
 }
 
-gint
-Editor::_autoscroll_canvas (void *arg)
-{
-       return ((Editor *) arg)->autoscroll_canvas ();
-}
-
-gint
-Editor::autoscroll_canvas ()
-{
-       jack_nframes_t new_frame;
-       bool keep_calling = true;
-
-       if (autoscroll_direction < 0) {
-               if (leftmost_frame < autoscroll_distance) {
-                       new_frame = 0;
-               } else {
-                       new_frame = leftmost_frame - autoscroll_distance;
-               }
-       } else {
-               if (leftmost_frame > max_frames - autoscroll_distance) {
-                       new_frame = max_frames;
-               } else {
-                       new_frame = leftmost_frame + autoscroll_distance;
-               }
-       }
-
-       if (new_frame != leftmost_frame) {
-               reposition_x_origin (new_frame);
-       }
-
-       if (new_frame == 0 || new_frame == max_frames) {
-               /* we are done */
-               return FALSE;
-       }
-
-       autoscroll_cnt++;
-
-       if (autoscroll_cnt == 1) {
-
-               /* connect the timeout so that we get called repeatedly */
-               
-               autoscroll_timeout_tag = gtk_timeout_add (100, _autoscroll_canvas, this);
-               keep_calling = false;
-
-       } else if (autoscroll_cnt > 10 && autoscroll_cnt < 20) {
-               
-               /* after about a while, speed up a bit by changing the timeout interval */
-
-               autoscroll_timeout_tag = gtk_timeout_add (50, _autoscroll_canvas, this);
-               keep_calling = false;
-               
-       } else if (autoscroll_cnt >= 20 && autoscroll_cnt < 30) {
-
-               /* after about another while, speed up some more */
-
-               autoscroll_timeout_tag = gtk_timeout_add (25, _autoscroll_canvas, this);
-               keep_calling = false;
-
-       } else if (autoscroll_cnt >= 30) {
-
-               /* we've been scrolling for a while ... crank it up */
-
-               autoscroll_distance = 10 * (jack_nframes_t) floor (canvas_width * frames_per_unit);
-       }
-
-       return keep_calling;
-}
-
-void
-Editor::start_canvas_autoscroll (int dir)
-{
-       if (!session) {
-               return;
-       }
-
-       stop_canvas_autoscroll ();
-
-       autoscroll_direction = dir;
-       autoscroll_distance = (jack_nframes_t) floor ((canvas_width * frames_per_unit)/10.0);
-       autoscroll_cnt = 0;
-       
-       /* do it right now, which will start the repeated callbacks */
-       
-       autoscroll_canvas ();
-}
-
-void
-Editor::stop_canvas_autoscroll ()
-{
-       if (autoscroll_timeout_tag >= 0) {
-               gtk_timeout_remove (autoscroll_timeout_tag);
-               autoscroll_timeout_tag = -1;
-       }
-}
-
 int
 Editor::convert_drop_to_paths (vector<ustring>& paths, 
                               const RefPtr<Gdk::DragContext>& context,
@@ -3003,7 +2900,7 @@ Editor::convert_drop_to_paths (vector<ustring>& paths,
        for (vector<ustring>::iterator i = uris.begin(); i != uris.end(); ++i) {
                if ((*i).substr (0,7) == "file://") {
                        string p = *i;
-                       url_decode (p);
+                        PBD::url_decode (p);
                        paths.push_back (p.substr (7));
                }
        }
@@ -3086,31 +2983,31 @@ Editor::commit_reversible_command ()
        }
 }
 
-void
-Editor::set_selected_track_from_click (Selection::Operation op, bool with_undo, bool no_remove)
+bool
+Editor::set_selected_track_from_click (bool press, Selection::Operation op, bool with_undo, bool no_remove)
 {
+       bool commit = false;
+
        if (!clicked_trackview) {
-               return;
+               return false;
        }
 
-       if (with_undo) {
-               begin_reversible_command (_("set selected trackview"));
-       }
-       
        switch (op) {
        case Selection::Toggle:
                if (selection->selected (clicked_trackview)) {
                        if (!no_remove) {
                                selection->remove (clicked_trackview);
+                               commit = true;
                        }
                } else {
-                       selection->toggle (clicked_trackview);
+                       selection->add (clicked_trackview);
+                       commit = false;
                }
                break;
+
        case Selection::Set:
                if (selection->selected (clicked_trackview) && selection->tracks.size() == 1) {
                        /* no commit necessary */
-                       return;
                } 
 
                selection->set (clicked_trackview);
@@ -3120,41 +3017,33 @@ Editor::set_selected_track_from_click (Selection::Operation op, bool with_undo,
                /* not defined yet */
                break;
        }
-       
-       if (with_undo) {
-               commit_reversible_command ();
-       }
+
+       return commit;
 }
 
-void
-Editor::set_selected_control_point_from_click (Selection::Operation op, bool with_undo, bool no_remove)
+bool
+Editor::set_selected_control_point_from_click (bool press, Selection::Operation op, bool with_undo, bool no_remove)
 {
        if (!clicked_control_point) {
-               return;
+               return false;
        }
 
-       if (with_undo) {
-               begin_reversible_command (_("set selected control point"));
-       }
+       /* select this point and any others that it represents */
 
-       switch (op) {
-       case Selection::Set:
-               break;
-       case Selection::Toggle:
-               break;
-       case Selection::Extend:
-               break;
-       }
-       if (with_undo) {
-               commit_reversible_command ();
-       }
+       double y1, y2;
+       jack_nframes_t x1, x2;
+
+       x1 = pixel_to_frame (clicked_control_point->get_x() - 10);
+       x2 = pixel_to_frame (clicked_control_point->get_x() + 10);
+       y1 = clicked_control_point->get_x() - 10;
+       y2 = clicked_control_point->get_y() + 10;
+
+       return select_all_within (x1, x2, y1, y2, op);
 }
 
 void
-Editor::mapover_audio_tracks (slot<void,AudioTimeAxisView&,uint32_t> sl)
+Editor::get_relevant_audio_tracks (AudioTimeAxisView& base, set<AudioTimeAxisView*>& relevant_tracks)
 {
-       set<AudioTimeAxisView*> relevant_tracks;
-
        /* step one: get all selected tracks and all tracks in the relevant edit groups */
 
        for (TrackSelection::iterator ti = selection->tracks.begin(); ti != selection->tracks.end(); ++ti) {
@@ -3187,12 +3076,22 @@ Editor::mapover_audio_tracks (slot<void,AudioTimeAxisView&,uint32_t> sl)
 
                        /* no active group, or no group */
 
-                       relevant_tracks.insert (atv);
+                       relevant_tracks.insert (&base);
                }
 
        }
+}
+
+void
+Editor::mapover_audio_tracks (slot<void,AudioTimeAxisView&,uint32_t> sl)
+{
+       set<AudioTimeAxisView*> relevant_tracks;
+
+       if (!clicked_audio_trackview) {
+               return;
+       }
 
-       /* step two: apply operation to each track */
+       get_relevant_audio_tracks (*clicked_audio_trackview, relevant_tracks);
 
        uint32_t sz = relevant_tracks.size();
        
@@ -3209,11 +3108,17 @@ Editor::mapped_set_selected_regionview_from_click (AudioTimeAxisView& atv, uint3
        vector<AudioRegion*> results;
        AudioRegionView* marv;
        DiskStream* ds;
-       
+
        if ((ds = atv.get_diskstream()) == 0) {
                /* bus */
                return;
        }
+
+       if (&atv == &basis->get_time_axis_view()) {
+               /* looking in same track as the original */
+               return;
+       }
+
        
        if ((pl = ds->playlist()) != 0) {
                pl->get_equivalent_regions (basis->region, results);
@@ -3226,70 +3131,192 @@ Editor::mapped_set_selected_regionview_from_click (AudioTimeAxisView& atv, uint3
        }
 }
 
-void
-Editor::set_selected_regionview_from_click (Selection::Operation op, bool no_track_remove)
+bool
+Editor::set_selected_regionview_from_click (bool press, Selection::Operation op, bool no_track_remove)
 {
-       cerr << "In SSRfC\n";
-
        vector<AudioRegionView*> all_equivalent_regions;
+       bool commit = false;
 
-       if (!clicked_regionview) {
-               return;
+       if (!clicked_regionview || !clicked_audio_trackview) {
+               return false;
        }
 
-       mapover_audio_tracks (bind (mem_fun (*this, &Editor::mapped_set_selected_regionview_from_click), 
-                                   clicked_regionview, &all_equivalent_regions));
-       
+       if (op == Selection::Toggle || op == Selection::Set) {
+               
+               mapover_audio_tracks (bind (mem_fun (*this, &Editor::mapped_set_selected_regionview_from_click), 
+                                           clicked_regionview, &all_equivalent_regions));
+               
+               
+               /* add clicked regionview since we skipped all other regions in the same track as the one it was in */
+               
+               all_equivalent_regions.push_back (clicked_regionview);
+               
+               switch (op) {
+               case Selection::Toggle:
+                       
+                       if (clicked_regionview->get_selected()) {
+                               if (press) {
 
-       cerr << "mapover done\n";
+                                       /* whatever was clicked was selected already; do nothing here but allow
+                                          the button release to deselect it
+                                       */
 
-       begin_reversible_command (_("set selected regionview"));
+                                       button_release_can_deselect = true;
+
+                               } else {
+
+                                       if (button_release_can_deselect) {
+
+                                               /* just remove this one region, but only on a permitted button release */
+
+                                               selection->remove (clicked_regionview);
+                                               commit = true;
+
+                                               /* no more deselect action on button release till a new press
+                                                  finds an already selected object.
+                                               */
+
+                                               button_release_can_deselect = false;
+                                       }
+                               } 
 
-       switch (op) {
-       case Selection::Toggle:
-               selection->toggle (clicked_regionview);
-#if 0
-               if (clicked_regionview->get_selected()) {
-                       if (/* group && group->is_active() && */ selection->audio_regions.size() > 1) {
-                               /* reduce selection down to just the one clicked */
-                               selection->set (clicked_regionview);
                        } else {
-                               selection->remove (clicked_regionview);
+
+                               if (press) {
+                                       /* add all the equivalent regions, but only on button press */
+                                       
+                                       if (!all_equivalent_regions.empty()) {
+                                               commit = true;
+                                       }
+                                       
+                                       for (vector<AudioRegionView*>::iterator i = all_equivalent_regions.begin(); i != all_equivalent_regions.end(); ++i) {
+                                               selection->add (*i);
+                                       }
+                               } 
                        }
-               } else {
-                       selection->add (all_equivalent_regions);
+                       break;
+                       
+               case Selection::Set:
+                       if (!clicked_regionview->get_selected()) {
+                               selection->set (all_equivalent_regions);
+                               commit = true;
+                       } else {
+                               /* no commit necessary: clicked on an already selected region */
+                               goto out;
+                       }
+                       break;
+
+               default:
+                       /* silly compiler */
+                       break;
                }
-#endif
-               set_selected_track_from_click (op, false, no_track_remove);
-               break;
 
-       case Selection::Set:
-               // karsten wiese suggested these two lines to make
-               // a selected region rise to the top. but this
-               // leads to a mismatch between actual layering
-               // and visual layering. resolution required ....
-               //
-               // gnome_canvas_item_raise_to_top (clicked_regionview->get_canvas_group());
-               // gnome_canvas_item_raise_to_top (clicked_regionview->get_time_axis_view().canvas_display);
-
-               if (clicked_regionview->get_selected()) {
-                       /* no commit necessary: we are the one selected. */
-                       return;
+       } else if (op == Selection::Extend) {
 
-               } else {
-                       
-                       selection->set (all_equivalent_regions);
-                       set_selected_track_from_click (op, false, false);
+               list<Selectable*> results;
+               jack_nframes_t last_frame;
+               jack_nframes_t first_frame;
+
+               /* 1. find the last selected regionview in the track that was clicked in */
+
+               last_frame = 0;
+               first_frame = max_frames;
+
+               for (AudioRegionSelection::iterator x = selection->audio_regions.begin(); x != selection->audio_regions.end(); ++x) {
+                       if (&(*x)->get_time_axis_view() == &clicked_regionview->get_time_axis_view()) {
+
+                               if ((*x)->region.last_frame() > last_frame) {
+                                       last_frame = (*x)->region.last_frame();
+                               }
+
+                               if ((*x)->region.first_frame() < first_frame) {
+                                       first_frame = (*x)->region.first_frame();
+                               }
+                       }
                }
-               break;
 
-       case Selection::Extend:
-               /* not defined yet */
-               break;
+               /* 2. figure out the boundaries for our search for new objects */
+
+               switch (clicked_regionview->region.coverage (first_frame, last_frame)) {
+               case OverlapNone:
+                       cerr << "no overlap, first = " << first_frame << " last = " << last_frame << " region = " 
+                            << clicked_regionview->region.first_frame() << " .. " << clicked_regionview->region.last_frame() << endl;
+
+                       if (last_frame < clicked_regionview->region.first_frame()) {
+                               first_frame = last_frame;
+                               last_frame = clicked_regionview->region.last_frame();
+                       } else {
+                               last_frame = first_frame;
+                               first_frame = clicked_regionview->region.first_frame();
+                       }
+                       break;
+
+               case OverlapExternal:
+                       cerr << "external overlap, first = " << first_frame << " last = " << last_frame << " region = " 
+                            << clicked_regionview->region.first_frame() << " .. " << clicked_regionview->region.last_frame() << endl;
+
+                       if (last_frame < clicked_regionview->region.first_frame()) {
+                               first_frame = last_frame;
+                               last_frame = clicked_regionview->region.last_frame();
+                       } else {
+                               last_frame = first_frame;
+                               first_frame = clicked_regionview->region.first_frame();
+                       }
+                       break;
+
+               case OverlapInternal:
+                       cerr << "internal overlap, first = " << first_frame << " last = " << last_frame << " region = " 
+                            << clicked_regionview->region.first_frame() << " .. " << clicked_regionview->region.last_frame() << endl;
+
+                       if (last_frame < clicked_regionview->region.first_frame()) {
+                               first_frame = last_frame;
+                               last_frame = clicked_regionview->region.last_frame();
+                       } else {
+                               last_frame = first_frame;
+                               first_frame = clicked_regionview->region.first_frame();
+                       }
+                       break;
+
+               case OverlapStart:
+               case OverlapEnd:
+                       /* nothing to do except add clicked region to selection, since it
+                          overlaps with the existing selection in this track.
+                       */
+                       break;
+               }
+
+               /* 2. find all selectable objects (regionviews in this case) between that one and the end of the
+                     one that was clicked.
+               */
+
+               set<AudioTimeAxisView*> relevant_tracks;
+               
+               get_relevant_audio_tracks (*clicked_audio_trackview, relevant_tracks);
+               
+               for (set<AudioTimeAxisView*>::iterator t = relevant_tracks.begin(); t != relevant_tracks.end(); ++t) {
+                       (*t)->get_selectables (first_frame, last_frame, -1.0, -1.0, results);
+               }
+               
+               /* 3. convert to a vector of audio regions */
+
+               vector<AudioRegionView*> audio_regions;
+               
+               for (list<Selectable*>::iterator x = results.begin(); x != results.end(); ++x) {
+                       AudioRegionView* arv;
+
+                       if ((arv = dynamic_cast<AudioRegionView*>(*x)) != 0) {
+                               audio_regions.push_back (arv);
+                       }
+               }
+
+               if (!audio_regions.empty()) {
+                       selection->add (audio_regions);
+                       commit = true;
+               }
        }
-       cerr << "case done\n";
 
-       commit_reversible_command () ;
+  out:
+       return commit;
 }
 
 void
@@ -3336,13 +3363,13 @@ Editor::set_selected_regionview_from_region_list (Region& r, Selection::Operatio
        switch (op) {
        case Selection::Toggle:
                /* XXX this is not correct */
-               selection->add (all_equivalent_regions);
+               selection->toggle (all_equivalent_regions);
                break;
        case Selection::Set:
                selection->set (all_equivalent_regions);
                break;
        case Selection::Extend:
-               /* not defined yet */
+               selection->add (all_equivalent_regions);
                break;
        }