proper 1px track separator
[ardour.git] / gtk2_ardour / editor_canvas.cc
index 8ad2abdfad3ca77f82c10887b189d96d4fb28472..66caeb7bc6d72ab55899873d6bb4f22320070943 100644 (file)
@@ -35,6 +35,7 @@
 #include "canvas/debug.h"
 
 #include "ardour_ui.h"
+#include "automation_time_axis.h"
 #include "editor.h"
 #include "global_signals.h"
 #include "editing.h"
@@ -55,6 +56,7 @@
 
 using namespace std;
 using namespace ARDOUR;
+using namespace ARDOUR_UI_UTILS;
 using namespace PBD;
 using namespace Gtk;
 using namespace Glib;
@@ -67,6 +69,11 @@ Editor::initialize_canvas ()
        _track_canvas_viewport = new ArdourCanvas::GtkCanvasViewport (horizontal_adjustment, vertical_adjustment);
        _track_canvas = _track_canvas_viewport->canvas ();
 
+       /* scroll group for items that should not automatically scroll
+        *  (e.g verbose cursor). It shares the canvas coordinate space.
+       */
+       no_scroll_group = new ArdourCanvas::Container (_track_canvas->root());
+
        ArdourCanvas::ScrollGroup* hsg; 
        ArdourCanvas::ScrollGroup* hg;
        ArdourCanvas::ScrollGroup* vg;
@@ -100,7 +107,7 @@ Editor::initialize_canvas ()
        }
 
        /*a group to hold global rects like punch/loop indicators */
-       global_rect_group = new ArdourCanvas::Group (hv_scroll_group);
+       global_rect_group = new ArdourCanvas::Container (hv_scroll_group);
        CANVAS_DEBUG_NAME (global_rect_group, "global rect group");
 
         transport_loop_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX));
@@ -112,10 +119,10 @@ Editor::initialize_canvas ()
        transport_punch_range_rect->hide();
 
        /*a group to hold time (measure) lines */
-       time_line_group = new ArdourCanvas::Group (hv_scroll_group);
+       time_line_group = new ArdourCanvas::Container (hv_scroll_group);
        CANVAS_DEBUG_NAME (time_line_group, "time line group");
 
-       _trackview_group = new ArdourCanvas::Group (hv_scroll_group);
+       _trackview_group = new ArdourCanvas::Container (hv_scroll_group);
        CANVAS_DEBUG_NAME (_trackview_group, "Canvas TrackViews");
        
        // used to show zoom mode active zooming
@@ -130,30 +137,30 @@ Editor::initialize_canvas ()
        /* a group to hold stuff while it gets dragged around. Must be the
         * uppermost (last) group with hv_scroll_group as a parent
         */
-       _drag_motion_group = new ArdourCanvas::Group (hv_scroll_group);                                                                                                                                     
+       _drag_motion_group = new ArdourCanvas::Container (hv_scroll_group);                                                                                                                                     
         CANVAS_DEBUG_NAME (_drag_motion_group, "Canvas Drag Motion");
 
        /* TIME BAR CANVAS */
        
-       _time_markers_group = new ArdourCanvas::Group (h_scroll_group);
+       _time_markers_group = new ArdourCanvas::Container (h_scroll_group);
        CANVAS_DEBUG_NAME (_time_markers_group, "time bars");
 
-       cd_marker_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, 0.0));
+       cd_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, 0.0));
        CANVAS_DEBUG_NAME (cd_marker_group, "cd marker group");
        /* the vide is temporarily placed a the same location as the
           cd_marker_group, but is moved later.
        */
-       videotl_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple(0.0, 0.0));
+       videotl_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple(0.0, 0.0));
        CANVAS_DEBUG_NAME (videotl_group, "videotl group");
-       marker_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height + 1.0));
+       marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height + 1.0));
        CANVAS_DEBUG_NAME (marker_group, "marker group");
-       transport_marker_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 2.0) + 1.0));
+       transport_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 2.0) + 1.0));
        CANVAS_DEBUG_NAME (transport_marker_group, "transport marker group");
-       range_marker_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 3.0) + 1.0));
+       range_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 3.0) + 1.0));
        CANVAS_DEBUG_NAME (range_marker_group, "range marker group");
-       tempo_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 4.0) + 1.0));
+       tempo_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 4.0) + 1.0));
        CANVAS_DEBUG_NAME (tempo_group, "tempo group");
-       meter_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 5.0) + 1.0));
+       meter_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 5.0) + 1.0));
        CANVAS_DEBUG_NAME (meter_group, "meter group");
 
        meter_bar = new ArdourCanvas::Rectangle (meter_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height));
@@ -412,7 +419,7 @@ Editor::drop_paths_part_two (const vector<string>& paths, framepos_t frame, doub
        }
 
 
-       std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos);
+       std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (ypos, false);
        if (tvp.first == 0) {
 
                /* drop onto canvas background: create new tracks */
@@ -798,26 +805,48 @@ Editor::entered_track_canvas (GdkEventCrossing */*ev*/)
 }
 
 void
-Editor::_ensure_time_axis_view_is_visible (const TimeAxisView& tav, bool at_top)
+Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top)
 {
-       double begin = tav.y_position();
-       double v = vertical_adjustment.get_value ();
-
-       if (!at_top && (begin < v || begin + tav.current_height() > v + _visible_canvas_height)) {
-               /* try to put the TimeAxisView roughly central */
-               if (begin >= _visible_canvas_height/2.0) {
-                       begin -= _visible_canvas_height/2.0;
-               }
+       if (track.hidden()) {
+               return;
        }
 
-       /* Clamp the y pos so that we do not extend beyond the canvas full
-        * height. 
+       /* compute visible area of trackview group, as offsets from top of
+        * trackview group.
         */
-       if (_full_canvas_height - begin < _visible_canvas_height){
-               begin = _full_canvas_height - _visible_canvas_height;
+
+       double const current_view_min_y = vertical_adjustment.get_value();
+       double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size();
+
+       double const track_min_y = track.y_position ();
+       double const track_max_y = track.y_position () + track.effective_height ();
+
+       if (!at_top && 
+           (track_min_y >= current_view_min_y &&
+            track_max_y < current_view_max_y)) {
+               /* already visible, and caller did not ask to place it at the
+                * top of the track canvas
+                */
+               return;
        }
 
-       vertical_adjustment.set_value (begin);
+       double new_value;
+
+       if (at_top) {
+               new_value = track_min_y;
+       } else {
+               if (track_min_y < current_view_min_y) {
+                       // Track is above the current view
+                       new_value = track_min_y;
+               } else if (track_max_y > current_view_max_y) {
+                       // Track is below the current view
+                       new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size();
+               } else {
+                       new_value = track_min_y;
+               }
+       }
+
+       vertical_adjustment.set_value(new_value);
 }
 
 /** Called when the main vertical_adjustment has changed */
@@ -861,7 +890,6 @@ Editor::color_handler()
        bbt_ruler->set_outline_color (text);
        
        playhead_cursor->set_color (ARDOUR_UI::config()->get_canvasvar_PlayHead());
-       _verbose_cursor->set_color (ARDOUR_UI::config()->get_canvasvar_VerboseCanvasCursor());
 
        meter_bar->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MeterBar());
        meter_bar->set_outline_color (ARDOUR_UI::config()->get_canvasvar_MarkerBarSeparator());
@@ -926,6 +954,52 @@ Editor::horizontal_position () const
        return sample_to_pixel (leftmost_frame);
 }
 
+bool
+Editor::track_canvas_key_press (GdkEventKey*)
+{
+       /* XXX: event does not report the modifier key pressed down, AFAICS, so use the Keyboard object instead */
+       if (mouse_mode == Editing::MouseZoom && Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
+               set_canvas_cursor (_cursors->zoom_out, true);
+       }
+
+       return false;
+}
+
+bool
+Editor::track_canvas_key_release (GdkEventKey*)
+{
+       if (mouse_mode == Editing::MouseZoom && !Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
+               set_canvas_cursor (_cursors->zoom_in, true);
+       }
+
+       return false;
+}
+
+double
+Editor::clamp_verbose_cursor_x (double x)
+{
+       if (x < 0) {
+               x = 0;
+       } else {
+               x = min (_visible_canvas_width - 200.0, x);
+       }
+       return x;
+}
+
+double
+Editor::clamp_verbose_cursor_y (double y)
+{
+       y = max (0.0, y);
+       y = min (_visible_canvas_height - 50, y);
+       return y;
+}
+
+ArdourCanvas::GtkCanvasViewport*
+Editor::get_track_canvas() const
+{
+       return _track_canvas_viewport;
+}
+
 void
 Editor::set_canvas_cursor (Gdk::Cursor* cursor, bool save)
 {
@@ -935,8 +1009,8 @@ Editor::set_canvas_cursor (Gdk::Cursor* cursor, bool save)
 
        Glib::RefPtr<Gdk::Window> win = _track_canvas->get_window();
 
-       if (win) {
-               _track_canvas->get_window()->set_cursor (*cursor);
+       if (win && cursor) {
+               win->set_cursor (*cursor);
        }
 }
 
@@ -959,48 +1033,332 @@ Editor::pop_canvas_cursor ()
        }
 }
 
-bool
-Editor::track_canvas_key_press (GdkEventKey*)
+Gdk::Cursor*
+Editor::which_grabber_cursor () const
 {
-       /* XXX: event does not report the modifier key pressed down, AFAICS, so use the Keyboard object instead */
-       if (mouse_mode == Editing::MouseZoom && Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
-               set_canvas_cursor (_cursors->zoom_out, true);
+       Gdk::Cursor* c = _cursors->grabber;
+
+       if (_internal_editing) {
+               switch (mouse_mode) {
+               case MouseDraw:
+                       c = _cursors->midi_pencil;
+                       break;
+
+               case MouseObject:
+                       c = _cursors->grabber_note;
+                       break;
+
+               case MouseTimeFX:
+                       c = _cursors->midi_resize;
+                       break;
+                       
+               case MouseRange:
+                       c = _cursors->grabber_note;
+                       break;
+
+               default:
+                       break;
+               }
+
+       } else {
+
+               switch (_edit_point) {
+               case EditAtMouse:
+                       c = _cursors->grabber_edit_point;
+                       break;
+               default:
+                       boost::shared_ptr<Movable> m = _movable.lock();
+                       if (m && m->locked()) {
+                               c = _cursors->speaker;
+                       }
+                       break;
+               }
        }
 
-       return false;
+       return c;
+}
+
+Gdk::Cursor*
+Editor::which_trim_cursor (bool left) const
+{
+       if (!entered_regionview) {
+               return 0;
+       }
+
+       Trimmable::CanTrim ct = entered_regionview->region()->can_trim ();
+               
+       if (left) {
+               
+               if (ct & Trimmable::FrontTrimEarlier) {
+                       return _cursors->left_side_trim;
+               } else {
+                       return _cursors->left_side_trim_right_only;
+               }
+       } else {
+               if (ct & Trimmable::EndTrimLater) {
+                       return _cursors->right_side_trim;
+               } else {
+                       return _cursors->right_side_trim_left_only;
+               }
+       }
+}
+
+Gdk::Cursor*
+Editor::which_mode_cursor () const
+{
+       Gdk::Cursor* mode_cursor = 0;
+
+       switch (mouse_mode) {
+       case MouseRange:
+               mode_cursor = _cursors->selector;
+               if (_internal_editing) {
+                       mode_cursor = which_grabber_cursor();
+               }
+               break;
+
+       case MouseCut:
+               mode_cursor = _cursors->scissors;
+               break;
+                       
+       case MouseObject:
+               /* don't use mode cursor, pick a grabber cursor based on the item */
+               break;
+
+       case MouseDraw:
+               mode_cursor = _cursors->midi_pencil;
+               break;
+
+       case MouseGain:
+               mode_cursor = _cursors->cross_hair;
+               break;
+
+       case MouseZoom:
+               if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
+                       mode_cursor = _cursors->zoom_out;
+               } else {
+                       mode_cursor = _cursors->zoom_in;
+               }
+               break;
+
+       case MouseTimeFX:
+               mode_cursor = _cursors->time_fx; // just use playhead
+               break;
+
+       case MouseAudition:
+               mode_cursor = _cursors->speaker;
+               break;
+       }
+
+       /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
+       if (!_internal_editing && get_smart_mode() ) {
+
+               double x, y;
+               get_pointer_position (x, y);
+
+               if (x >= 0 && y >= 0) {
+                       
+                       vector<ArdourCanvas::Item const *> items;
+
+                       /* Note how we choose a specific scroll group to get
+                        * items from. This could be problematic.
+                        */
+                       
+                       hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items);
+                       
+                       // first item will be the upper most 
+                       
+                       if (!items.empty()) {
+                               const ArdourCanvas::Item* i = items.front();
+                               
+                               if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) {
+                                       pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y);
+                                       if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
+                                               mode_cursor = _cursors->up_down;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return mode_cursor;
+}
+
+Gdk::Cursor*
+Editor::which_track_cursor () const
+{
+       Gdk::Cursor* cursor = 0;
+
+       assert (mouse_mode == MouseObject || get_smart_mode());
+
+       if (!_internal_editing) {
+               switch (_join_object_range_state) {
+               case JOIN_OBJECT_RANGE_NONE:
+               case JOIN_OBJECT_RANGE_OBJECT:
+                       cursor = which_grabber_cursor ();
+                       break;
+               case JOIN_OBJECT_RANGE_RANGE:
+                       cursor = _cursors->selector;
+                       break;
+               }
+       }
+
+       return cursor;
 }
 
 bool
-Editor::track_canvas_key_release (GdkEventKey*)
+Editor::reset_canvas_cursor ()
 {
-       if (mouse_mode == Editing::MouseZoom && !Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
-               set_canvas_cursor (_cursors->zoom_in, true);
+       if (!is_drawable()) {
+               return false;
+       }
+
+       Gdk::Cursor* cursor = which_mode_cursor ();
+
+       if (cursor) {
+               set_canvas_cursor (cursor);
+               return true;
        }
 
        return false;
 }
 
-double
-Editor::clamp_verbose_cursor_x (double x)
+void
+Editor::choose_canvas_cursor_on_entry (GdkEventCrossing* /*event*/, ItemType type)
 {
-       if (x < 0) {
-               x = 0;
-       } else {
-               x = min (_visible_canvas_width - 200.0, x);
+       Gdk::Cursor* cursor = 0;
+
+       if (_drags->active()) {
+               return;
+       }
+
+       cursor = which_mode_cursor ();
+
+       if (mouse_mode == MouseObject || get_smart_mode ()) {
+
+               /* find correct cursor to use in object/smart mode */
+
+               switch (type) {
+               case RegionItem:
+               case RegionViewNameHighlight:
+               case RegionViewName:
+               case WaveItem:
+               case StreamItem:
+               case AutomationTrackItem:
+                       cursor = which_track_cursor ();
+                       break;
+               case PlayheadCursorItem:
+                       switch (_edit_point) {
+                       case EditAtMouse:
+                               cursor = _cursors->grabber_edit_point;
+                               break;
+                       default:
+                               cursor = _cursors->grabber;
+                               break;
+                       }
+                       break;
+               case SelectionItem:
+                       cursor = _cursors->selector;
+                       break;
+               case ControlPointItem:
+                       cursor = _cursors->fader;
+                       break;
+               case GainLineItem:
+                       cursor = _cursors->fader;
+                       break;
+               case AutomationLineItem:
+                       cursor = _cursors->cross_hair;
+                       break;
+               case StartSelectionTrimItem:
+                       cursor = _cursors->left_side_trim;
+                       break;
+               case EndSelectionTrimItem:
+                       cursor = _cursors->right_side_trim;
+                       break;
+               case FadeInItem:
+                       cursor = _cursors->fade_in;
+                       break;
+               case FadeInHandleItem:
+                       cursor = _cursors->fade_in;
+                       break;
+               case FadeInTrimHandleItem:
+                       cursor = _cursors->fade_in;
+                       break;
+               case FadeOutItem:
+                       cursor = _cursors->fade_out;
+                       break;
+               case FadeOutHandleItem:
+                       cursor = _cursors->fade_out;
+                       break;
+               case FadeOutTrimHandleItem:
+                       cursor = _cursors->fade_out;
+                       break;
+               case NoteItem:
+                       cursor = which_grabber_cursor();
+                       break;
+               case FeatureLineItem:
+                       cursor = _cursors->cross_hair;
+                       break;
+               case LeftFrameHandle:
+                       if ( effective_mouse_mode() == MouseObject )  // (smart mode): if the user is in the top half, override the trim cursor, since they are in the range zone
+                               cursor = which_trim_cursor (true);  //alternatively, one could argue that we _should_ allow trims here, and disallow range selection
+                       break;
+               case RightFrameHandle:
+                       if ( effective_mouse_mode() == MouseObject )  //see above
+                               cursor = which_trim_cursor (false);
+                       break;
+               case StartCrossFadeItem:
+                       cursor = _cursors->fade_in;
+                       break;
+               case EndCrossFadeItem:
+                       cursor = _cursors->fade_out;
+                       break;
+               case CrossfadeViewItem:
+                       cursor = _cursors->cross_hair;
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       switch (type) {
+               /* These items use the timebar cursor at all times */
+       case TimecodeRulerItem:
+       case MinsecRulerItem:
+       case BBTRulerItem:
+       case SamplesRulerItem:
+               cursor = _cursors->timebar;
+               break;
+
+               /* These items use the grabber cursor at all times */
+       case MeterMarkerItem:
+       case TempoMarkerItem:
+       case MeterBarItem:
+       case TempoBarItem:
+       case MarkerItem:
+       case MarkerBarItem:
+       case RangeMarkerBarItem:
+       case CdMarkerBarItem:
+       case VideoBarItem:
+       case TransportMarkerBarItem:
+       case DropZoneItem:
+               cursor = which_grabber_cursor();
+               break;
+
+       default:
+               break;
+       }
+
+       if (cursor) {
+               set_canvas_cursor (cursor, false);
        }
-       return x;
 }
 
 double
-Editor::clamp_verbose_cursor_y (double y)
+Editor::trackviews_height() const
 {
-       y = max (0.0, y);
-       y = min (_visible_canvas_height - 50, y);
-       return y;
-}
+       if (!_trackview_group) {
+               return 0;
+       }
 
-ArdourCanvas::GtkCanvasViewport*
-Editor::get_track_canvas() const
-{
-       return _track_canvas_viewport;
+       return _visible_canvas_height - _trackview_group->canvas_origin().y;
 }