use image cursors for left/right trim cursors
[ardour.git] / gtk2_ardour / editor_summary.cc
index 762749eecaa3fdc7394928a26fda81a34a5e0e9a..ed72799a89443008982d23c9d536693e150e0e94 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2009 Paul Davis 
+    Copyright (C) 2009 Paul Davis
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 #include "keyboard.h"
 
 using namespace std;
-using namespace sigc;
 using namespace ARDOUR;
+using Gtkmm2ext::Keyboard;
 
 /** Construct an EditorSummary.
  *  @param e Editor to represent.
  */
 EditorSummary::EditorSummary (Editor* e)
        : EditorComponent (e),
+         _start (0),
+         _end (1),
+         _overhang_fraction (0.1),
          _x_scale (1),
-         _y_scale (1),
+         _track_height (16),
          _last_playhead (-1),
          _move_dragging (false),
          _moved (false),
          _zoom_dragging (false)
-         
+
 {
-       
+       Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
+       _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
 }
 
 /** Connect to a session.
  *  @param s Session.
  */
 void
-EditorSummary::connect_to_session (Session* s)
+EditorSummary::set_session (Session* s)
 {
-       EditorComponent::connect_to_session (s);
+       EditorComponent::set_session (s);
 
-       Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
+       set_dirty ();
 
-       _session_connections.push_back (_session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty))));
-       _session_connections.push_back (_session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty)));
-       _session_connections.push_back (_session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty)));
-       _editor->playhead_cursor->PositionChanged.connect (mem_fun (*this, &EditorSummary::playhead_position_changed));
+       /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
+        * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
+        * emitted when a cut region is added to the `cutlist' playlist.
+        */
 
-       set_dirty ();
+       if (_session) {
+               _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
+               _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
+       }
 }
 
 /** Handle an expose event.
@@ -80,11 +87,11 @@ EditorSummary::on_expose_event (GdkEventExpose* event)
        cairo_t* cr = gdk_cairo_create (get_window()->gobj());
 
        /* Render the view rectangle */
-       
+
        pair<double, double> x;
        pair<double, double> y;
        get_editor (&x, &y);
-       
+
        cairo_move_to (cr, x.first, y.first);
        cairo_line_to (cr, x.second, y.first);
        cairo_line_to (cr, x.second, y.second);
@@ -102,14 +109,14 @@ EditorSummary::on_expose_event (GdkEventExpose* event)
        /* XXX: colour should be set from configuration file */
        cairo_set_source_rgba (cr, 1, 0, 0, 1);
 
-       double const p = _editor->playhead_cursor->current_frame * _x_scale;
+       double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
        cairo_move_to (cr, p, 0);
        cairo_line_to (cr, p, _height);
        cairo_stroke (cr);
        _last_playhead = p;
 
        cairo_destroy (cr);
-       
+
        return true;
 }
 
@@ -120,7 +127,7 @@ void
 EditorSummary::render (cairo_t* cr)
 {
        /* background */
-       
+
        cairo_set_source_rgb (cr, 0, 0, 0);
        cairo_rectangle (cr, 0, 0, _width, _height);
        cairo_fill (cr);
@@ -129,64 +136,103 @@ EditorSummary::render (cairo_t* cr)
                return;
        }
 
-       /* compute total height of all tracks */
+       /* compute start and end points for the summary */
        
-       int h = 0;
-       int max_height = 0;
-       for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
-               int const t = (*i)->effective_height ();
-               h += t;
-               max_height = max (max_height, t);
+       nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
+       double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
+       _start = theoretical_start > 0 ? theoretical_start : 0;
+       _end = _session->current_end_frame() + session_length * _overhang_fraction;
+
+       /* compute track height */
+       int N = 0;
+       for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
+               if (!(*i)->hidden()) {
+                       ++N;
+               }
+       }
+       
+       if (N == 0) {
+               _track_height = 16;
+       } else {
+               _track_height = (double) _height / N;
        }
 
-       nframes_t const start = _session->current_start_frame ();
-       _x_scale = static_cast<double> (_width) / (_session->current_end_frame() - start);
-       _y_scale = static_cast<double> (_height) / h;
-
-       /* tallest a region should ever be in the summary, in pixels */
-       int const tallest_region_pixels = 4;
-
-       if (max_height * _y_scale > tallest_region_pixels) {
-               _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
-
+       /* calculate x scale */
+       if (_end != _start) {
+               _x_scale = static_cast<double> (_width) / (_end - _start);
+       } else {
+               _x_scale = 1;
        }
 
-       /* render regions */
+       /* render tracks and regions */
 
        double y = 0;
-       for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
+       for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
+
+               if ((*i)->hidden()) {
+                       continue;
+               }
+
+               cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
+               cairo_set_line_width (cr, _track_height - 2);
+               cairo_move_to (cr, 0, y + _track_height / 2);
+               cairo_line_to (cr, _width, y + _track_height / 2);
+               cairo_stroke (cr);
+               
                StreamView* s = (*i)->view ();
 
                if (s) {
-                       double const h = (*i)->effective_height () * _y_scale;
-                       cairo_set_line_width (cr, h);
+                       cairo_set_line_width (cr, _track_height * 0.6);
 
-                       s->foreach_regionview (bind (
-                                                      mem_fun (*this, &EditorSummary::render_region),
+                       s->foreach_regionview (sigc::bind (
+                                                      sigc::mem_fun (*this, &EditorSummary::render_region),
                                                       cr,
-                                                      start,
-                                                      y + h / 2
+                                                      y + _track_height / 2
                                                       ));
-                       y += h;
                }
+               
+               y += _track_height;
        }
 
+       /* start and end markers */
+
+       cairo_set_line_width (cr, 1);
+       cairo_set_source_rgb (cr, 1, 1, 0);
+
+       double const p = (_session->current_start_frame() - _start) * _x_scale;
+       cairo_move_to (cr, p, 0);
+       cairo_line_to (cr, p, _height);
+       cairo_stroke (cr);
+
+       double const q = (_session->current_end_frame() - _start) * _x_scale;
+       cairo_move_to (cr, q, 0);
+       cairo_line_to (cr, q, _height);
+       cairo_stroke (cr);
 }
 
 /** Render a region for the summary.
  *  @param r Region view.
  *  @param cr Cairo context.
- *  @param start Frame offset that the summary starts at.
  *  @param y y coordinate to render at.
  */
 void
-EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
+EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
 {
        uint32_t const c = r->get_fill_color ();
        cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
-                       
-       cairo_move_to (cr, (r->region()->position() - start) * _x_scale, y);
-       cairo_line_to (cr, ((r->region()->position() - start + r->region()->length())) * _x_scale, y);
+
+       if (r->region()->position() > _start) {
+               cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
+       } else {
+               cairo_move_to (cr, 0, y);
+       }
+
+       if ((r->region()->position() + r->region()->length()) > _start) {
+               cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
+       } else {
+               cairo_line_to (cr, 0, y);
+       }
+
        cairo_stroke (cr);
 }
 
@@ -194,7 +240,7 @@ EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, doubl
 void
 EditorSummary::set_overlays_dirty ()
 {
-       ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_overlays_dirty));
+       ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
        queue_draw ();
 }
 
@@ -218,12 +264,9 @@ EditorSummary::centre_on_click (GdkEventButton* ev)
        get_editor (&xr, &yr);
 
        double const w = xr.second - xr.first;
-       double const h = yr.second - yr.first;
-       
+
        xr.first = ev->x - w / 2;
        xr.second = ev->x + w / 2;
-       yr.first = ev->y - h / 2;
-       yr.second = ev->y + h / 2;
 
        if (xr.first < 0) {
                xr.first = 0;
@@ -233,15 +276,13 @@ EditorSummary::centre_on_click (GdkEventButton* ev)
                xr.first = _width - w;
        }
 
-       if (yr.first < 0) {
-               yr.first = 0;
-               yr.second = h;
-       } else if (yr.second > _height) {
-               yr.second = _height;
-               yr.first = _height - h;
+       double ey = summary_y_to_editor (ev->y);
+       ey -= (_editor->canvas_height() - _editor->get_canvas_timebars_vsize ()) / 2;
+       if (ey < 0) {
+               ey = 0;
        }
-
-       set_editor (xr, yr);
+       
+       set_editor (xr, editor_y_to_summary (ey));
 }
 
 /** Handle a button press.
@@ -259,18 +300,34 @@ EditorSummary::on_button_press_event (GdkEventButton* ev)
                _start_editor_x = xr;
                _start_editor_y = yr;
                _start_mouse_x = ev->x;
-               _start_mouse_y = ev->y;                 
-               
+               _start_mouse_y = ev->y;
+
+               if (
+                       _start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second &&
+                       _start_editor_y.first <= _start_mouse_y && _start_mouse_y <= _start_editor_y.second
+                       ) {
+
+                       _start_position = IN_VIEWBOX;
+
+               } else if (_start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second) {
+
+                       _start_position = BELOW_OR_ABOVE_VIEWBOX;
+
+               } else {
+
+                       _start_position = TO_LEFT_OR_RIGHT_OF_VIEWBOX;
+               }
+
                if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
 
                        /* primary-modifier-click: start a zoom drag */
-                       
+
                        double const hx = (xr.first + xr.second) * 0.5;
                        _zoom_left = ev->x < hx;
                        _zoom_dragging = true;
                        _editor->_dragging_playhead = true;
-               
-                       
+
+
                        /* In theory, we could support vertical dragging, which logically
                           might scale track heights in order to make the editor reflect
                           the dragged viewbox.  However, having tried this:
@@ -278,43 +335,43 @@ EditorSummary::on_button_press_event (GdkEventButton* ev)
                           b) it's quite slow
                           c) it doesn't seem particularly useful, especially with the
                           limited height of the summary
-                          
+
                           So at the moment we don't support that...
-                       */                      
+                       */
+
 
-                               
                } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
-                       
+
                        /* secondary-modifier-click: locate playhead */
                        if (_session) {
-                               _session->request_locate (ev->x / _x_scale + _session->current_start_frame());
+                               _session->request_locate (ev->x / _x_scale + _start);
                        }
 
                } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
 
                        centre_on_click (ev);
-                       
+
                } else {
-                               
+
                        /* ordinary click: start a move drag */
-                       
+
                        _move_dragging = true;
                        _moved = false;
                        _editor->_dragging_playhead = true;
                }
        }
-       
+
        return true;
 }
 
 void
 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
 {
-       x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
+       x->first = (_editor->leftmost_position () - _start) * _x_scale;
        x->second = x->first + _editor->current_page_frames() * _x_scale;
 
-       y->first = _editor->vertical_adjustment.get_value() * _y_scale;
-       y->second = y->first + _editor->canvas_height () * _y_scale;
+       y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
+       y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
 }
 
 bool
@@ -322,17 +379,33 @@ EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
 {
        pair<double, double> xr = _start_editor_x;
        pair<double, double> yr = _start_editor_y;
-       
+       double y = _start_editor_y.first;
+
        if (_move_dragging) {
 
                _moved = true;
 
-               xr.first += ev->x - _start_mouse_x;
-               xr.second += ev->x - _start_mouse_x;
-               yr.first += ev->y - _start_mouse_y;
-               yr.second += ev->y - _start_mouse_y;
-               
-               set_editor (xr, yr);
+               /* don't alter x if we clicked outside and above or below the viewbox */
+               if (_start_position == IN_VIEWBOX || _start_position == TO_LEFT_OR_RIGHT_OF_VIEWBOX) {
+                       xr.first += ev->x - _start_mouse_x;
+                       xr.second += ev->x - _start_mouse_x;
+               }
+
+               /* don't alter y if we clicked outside and to the left or right of the viewbox */
+               if (_start_position == IN_VIEWBOX || _start_position == BELOW_OR_ABOVE_VIEWBOX) {
+                       y += ev->y - _start_mouse_y;
+               }
+
+               if (xr.first < 0) {
+                       xr.second -= xr.first;
+                       xr.first = 0;
+               }
+
+               if (y < 0) {
+                       y = 0;
+               }
+
+               set_editor (xr, y);
 
        } else if (_zoom_dragging) {
 
@@ -344,9 +417,9 @@ EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
                        xr.second += dx;
                }
 
-               set_editor (xr, yr);
+               set_editor (xr, y);
        }
-               
+
        return true;
 }
 
@@ -363,75 +436,157 @@ bool
 EditorSummary::on_scroll_event (GdkEventScroll* ev)
 {
        /* mouse wheel */
-       
+
        pair<double, double> xr;
        pair<double, double> yr;
        get_editor (&xr, &yr);
+       double y = yr.first;
+
+       double amount = 8;
+
+       if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
+               amount = 64;
+       } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
+               amount = 1;
+       }
 
-       double const amount = 8;
-               
        if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
-               
+
+               /* primary-wheel == left-right scrolling */
+
                if (ev->direction == GDK_SCROLL_UP) {
                        xr.first += amount;
                        xr.second += amount;
-               } else {
+               } else if (ev->direction == GDK_SCROLL_DOWN) {
                        xr.first -= amount;
                        xr.second -= amount;
                }
 
        } else {
-               
+
                if (ev->direction == GDK_SCROLL_DOWN) {
-                       yr.first += amount;
-                       yr.second += amount;
-               } else {
-                       yr.first -= amount;
-                       yr.second -= amount;
+                       y += amount;
+               } else if (ev->direction == GDK_SCROLL_UP) {
+                       y -= amount;
+               } else if (ev->direction == GDK_SCROLL_LEFT) {
+                       xr.first -= amount;
+                       xr.second -= amount;
+               } else if (ev->direction == GDK_SCROLL_RIGHT) {
+                       xr.first += amount;
+                       xr.second += amount;
                }
-               
        }
-       
-       set_editor (xr, yr);
+
+       set_editor (xr, y);
        return true;
 }
 
+/** Set the editor to display a given x range and a y range with the top at a given position.
+ *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
+ *  x and y parameters are specified in summary coordinates.
+ */
 void
-EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
+EditorSummary::set_editor (pair<double,double> const & x, double const y)
 {
-       if (_editor->pending_visual_change.idle_handler_id < 0) {
+       if (_editor->pending_visual_change.idle_handler_id >= 0) {
 
                /* As a side-effect, the Editor's visual change idle handler processes
                   pending GTK events.  Hence this motion notify handler can be called
                   in the middle of a visual change idle handler, and if this happens,
                   the queue_visual_change calls below modify the variables that the
-                  idle handler is working with.  This causes problems.  Hence the
-                  check above.  It ensures that we won't modify the pending visual change
+                  idle handler is working with.  This causes problems.  Hence this
+                  check.  It ensures that we won't modify the pending visual change
                   while a visual change idle handler is in progress.  It's not perfect,
                   as it also means that we won't change these variables if an idle handler
                   is merely pending but not executing.  But c'est la vie.
                */
 
-               _editor->reset_x_origin (x.first / _x_scale);
-               _editor->reset_y_origin (y.first / _y_scale);
-
-               double const nx = (
-                       ((x.second - x.first) / _x_scale) /
-                       _editor->frame_to_unit (_editor->current_page_frames())
-                       );
+               return;
+       }
+       
+       double y1 = summary_y_to_editor (y);
+       double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
+       double y2 = y1 + eh;
+       
+       double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
 
-               if (nx != _editor->get_current_zoom ()) {
-                       _editor->reset_zoom (nx);
-               }
+       if (y2 > full_editor_height) {
+               y1 -= y2 - full_editor_height;
+       }
+       
+       if (y1 < 0) {
+               y1 = 0;
        }
+
+       _editor->reset_x_origin (x.first / _x_scale + _start);
+       _editor->reset_y_origin (y1);
+       
+       double const nx = (
+               ((x.second - x.first) / _x_scale) /
+               _editor->frame_to_unit (_editor->current_page_frames())
+               );
+       
+       if (nx != _editor->get_current_zoom ()) {
+               _editor->reset_zoom (nx);
+       }       
 }
 
 void
 EditorSummary::playhead_position_changed (nframes64_t p)
 {
-       if (int (p * _x_scale) != int (_last_playhead)) {
+       if (_session && int (p * _x_scale) != int (_last_playhead)) {
                set_overlays_dirty ();
        }
 }
 
-       
+double
+EditorSummary::summary_y_to_editor (double y) const
+{
+       double ey = 0;
+       TrackViewList::const_iterator i = _editor->track_views.begin ();
+       while (i != _editor->track_views.end()) {
+               
+               if ((*i)->hidden()) {
+                       ++i;
+                       continue;
+               }
+               
+               double const h = (*i)->effective_height ();
+               if (y < _track_height) {
+                       /* in this track */
+                       return ey + y * h / _track_height;
+               }
+
+               ey += h;
+               y -= _track_height;
+               ++i;
+       }
+
+       return ey;
+}
+
+double
+EditorSummary::editor_y_to_summary (double y) const
+{
+       double sy = 0;
+       TrackViewList::const_iterator i = _editor->track_views.begin ();
+       while (i != _editor->track_views.end()) {
+               
+               if ((*i)->hidden()) {
+                       ++i;
+                       continue;
+               }
+
+               double const h = (*i)->effective_height ();
+               if (y < h) {
+                       /* in this track */
+                       return sy + y * _track_height / h;
+               }
+
+               sy += _track_height;
+               y -= h;
+               ++i;
+       }
+
+       return sy;
+}