Vkeybd: add a mod-wheel
[ardour.git] / gtk2_ardour / editor_summary.cc
index 72115d61f2f28195745d53ee72d60629dc15ce05..4bb53fb88bb8fd9223babc92e3bcdb9d729f78a6 100644 (file)
@@ -1,26 +1,36 @@
 /*
-    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
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
+ * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2009-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2019 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2012-2013 Colin Fletcher <colin.m.fletcher@googlemail.com>
+ * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2014-2016 Nick Mainsbridge <mainsbridge@gmail.com>
+ * Copyright (C) 2015-2016 Tim Mayberry <mojofunk@gmail.com>
+ * Copyright (C) 2017-2019 Ben Loftis <ben@harrisonconsoles.com>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
 
 #include "ardour/session.h"
 
 #include "canvas/debug.h"
 
+#include <gtkmm/menu.h>
+#include <gtkmm/menuitem.h>
+
+#include "context_menu_helper.h"
 #include "time_axis_view.h"
 #include "streamview.h"
 #include "editor_summary.h"
@@ -35,6 +45,8 @@
 #include "route_time_axis.h"
 #include "ui_config.h"
 
+#include "pbd/i18n.h"
+
 using namespace std;
 using namespace ARDOUR;
 using Gtkmm2ext::Keyboard;
@@ -46,17 +58,13 @@ EditorSummary::EditorSummary (Editor* e)
        : EditorComponent (e),
          _start (0),
          _end (1),
-         _overhang_fraction (0.1),
          _x_scale (1),
          _track_height (16),
          _last_playhead (-1),
-         _begin_dragging (false),
          _move_dragging (false),
-         _moved (false),
          _view_rectangle_x (0, 0),
          _view_rectangle_y (0, 0),
          _zoom_trim_dragging (false),
-         _zoom_dragging (false),
          _old_follow_playhead (false),
          _image (0),
          _background_dirty (true)
@@ -116,6 +124,13 @@ EditorSummary::set_session (Session* s)
                _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
                _editor->selection->RegionsChanged.connect (sigc::mem_fun(*this, &EditorSummary::set_background_dirty));
        }
+
+       UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &EditorSummary::set_colors));
+
+       set_colors();
+
+       _leftmost = max_samplepos;
+       _rightmost = 0;
 }
 
 void
@@ -134,10 +149,26 @@ EditorSummary::render_background_image ()
 
        /* compute start and end points for the summary */
 
-       framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
-       double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
+       std::pair<samplepos_t, samplepos_t> ext = _editor->session_gui_extents();
+       double theoretical_start = ext.first;
+       double theoretical_end = ext.second;
+
+       /* the summary should encompass the full extent of everywhere we've visited since the session was opened */
+       if (_leftmost < theoretical_start)
+               theoretical_start = _leftmost;
+       if (_rightmost > theoretical_end)
+               theoretical_end = _rightmost;
+
+       /* range-check */
        _start = theoretical_start > 0 ? theoretical_start : 0;
-       _end = _session->current_end_frame() + session_length * _overhang_fraction;
+       _end = theoretical_end < max_samplepos ? theoretical_end : max_samplepos;
+
+       /* calculate x scale */
+       if (_end != _start) {
+               _x_scale = static_cast<double> (get_width()) / (_end - _start);
+       } else {
+               _x_scale = 1;
+       }
 
        /* compute track height */
        int N = 0;
@@ -153,13 +184,6 @@ EditorSummary::render_background_image ()
                _track_height = (double) get_height() / N;
        }
 
-       /* calculate x scale */
-       if (_end != _start) {
-               _x_scale = static_cast<double> (get_width()) / (_end - _start);
-       } else {
-               _x_scale = 1;
-       }
-
        /* render tracks and regions */
 
        double y = 0;
@@ -171,11 +195,13 @@ EditorSummary::render_background_image ()
 
                /* paint a non-bg colored strip to represent the track itself */
 
-               cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
-               cairo_set_line_width (cr, _track_height - 1);
-               cairo_move_to (cr, 0, y + _track_height / 2);
-               cairo_line_to (cr, get_width(), y + _track_height / 2);
-               cairo_stroke (cr);
+               if (_track_height > 4) {
+                       cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
+                       cairo_set_line_width (cr, _track_height - 1);
+                       cairo_move_to (cr, 0, y + _track_height / 2);
+                       cairo_line_to (cr, get_width(), y + _track_height / 2);
+                       cairo_stroke (cr);
+               }
 
                StreamView* s = (*i)->view ();
 
@@ -183,10 +209,10 @@ EditorSummary::render_background_image ()
                        cairo_set_line_width (cr, _track_height * 0.8);
 
                        s->foreach_regionview (sigc::bind (
-                                                      sigc::mem_fun (*this, &EditorSummary::render_region),
-                                                      cr,
-                                                      y + _track_height / 2
-                                                      ));
+                                                          sigc::mem_fun (*this, &EditorSummary::render_region),
+                                                          cr,
+                                                          y + _track_height / 2
+                                                         ));
                }
 
                y += _track_height;
@@ -197,11 +223,11 @@ EditorSummary::render_background_image ()
        cairo_set_line_width (cr, 1);
        cairo_set_source_rgb (cr, 1, 1, 0);
 
-       const double p = (_session->current_start_frame() - _start) * _x_scale;
+       const double p = (_session->current_start_sample() - _start) * _x_scale;
        cairo_move_to (cr, p, 0);
        cairo_line_to (cr, p, get_height());
 
-       double const q = (_session->current_end_frame() - _start) * _x_scale;
+       double const q = (_session->current_end_sample() - _start) * _x_scale;
        cairo_move_to (cr, q, 0);
        cairo_line_to (cr, q, get_height());
        cairo_stroke (cr);
@@ -221,6 +247,19 @@ EditorSummary::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle
                return;
        }
 
+       /* maintain the leftmost and rightmost locations that we've ever reached */
+       samplecnt_t const leftmost = _editor->leftmost_sample ();
+       if (leftmost < _leftmost) {
+               _leftmost = leftmost;
+               _background_dirty = true;
+       }
+       samplecnt_t const rightmost = leftmost + _editor->current_page_samples();
+       if (rightmost > _rightmost) {
+               _rightmost = rightmost;
+               _background_dirty = true;
+       }
+
+       /* draw the background (regions, markers, etc) if they've changed */
        if (!_image || _background_dirty) {
                render_background_image ();
                _background_dirty = false;
@@ -245,7 +284,6 @@ EditorSummary::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle
 
        int32_t width = _view_rectangle_x.second - _view_rectangle_x.first;
        std::min(8, width);
-       int32_t height = _view_rectangle_y.second - _view_rectangle_y.first;
        cairo_rectangle (cr, _view_rectangle_x.first, 0, width, get_height ());
        cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
        cairo_fill (cr);
@@ -259,10 +297,11 @@ EditorSummary::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle
        /* Playhead */
 
        cairo_set_line_width (cr, 1);
-       /* XXX: colour should be set from configuration file */
-       cairo_set_source_rgba (cr, 1, 0, 0, 1);
 
-       const double ph= playhead_frame_to_position (_editor->playhead_cursor->current_frame());
+       double r,g,b,a;  Gtkmm2ext::color_to_rgba(_phead_color, r,g,b,a);
+       cairo_set_source_rgb (cr, r,g,b); // playhead color
+
+       const double ph= playhead_sample_to_position (_editor->playhead_cursor->current_sample());
        cairo_move_to (cr, ph, 0);
        cairo_line_to (cr, ph, get_height());
        cairo_stroke (cr);
@@ -272,6 +311,14 @@ EditorSummary::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle
 
 }
 
+void
+EditorSummary::set_colors ()
+{
+       _phead_color = UIConfiguration::instance().color ("play head");
+}
+
+
+
 /** Render a region for the summary.
  *  @param r Region view.
  *  @param cr Cairo context.
@@ -280,6 +327,13 @@ EditorSummary::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle
 void
 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
 {
+       /*NOTE:  you can optimize this operation by coalescing adjacent regions into a single line stroke.
+        * In a session with a single track ~1,000 regions, this reduced render time from 14ms to 11 ms.
+        * However, you lose a lot of visual information.  The current method preserves a sense of separation between regions.
+        * The current method shows the current selection (red regions), which needs to be preserved if this is optimized.
+        * I think it's not worth it for now,  but we might choose to revisit this someday.
+        */ 
+
        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);
 
@@ -317,9 +371,9 @@ EditorSummary::set_overlays_dirty ()
 
 /** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
 void
-EditorSummary::set_overlays_dirty (int x, int y, int w, int h)
+EditorSummary::set_overlays_dirty_rect (int x, int y, int w, int h)
 {
-       ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
+       ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty_rect);
        queue_draw_area (x, y, w, h);
 }
 
@@ -376,11 +430,14 @@ EditorSummary::on_key_press_event (GdkEventKey* key)
 {
        gint x, y;
        GtkAccelKey set_playhead_accel;
+
+       /* XXXX this is really ugly and should be using our own action maps and bindings */
+
        if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
                if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
                        if (_session) {
                                get_pointer (x, y);
-                               _session->request_locate (_start + (framepos_t) x / _x_scale, _session->transport_rolling());
+                               _session->request_locate (_start + (samplepos_t) x / _x_scale, _session->transport_rolling());
                                return true;
                        }
                }
@@ -394,6 +451,9 @@ EditorSummary::on_key_release_event (GdkEventKey* key)
 {
 
        GtkAccelKey set_playhead_accel;
+
+       /* XXXX this is really ugly and should be using our own action maps and bindings */
+
        if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
                if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
                        return true;
@@ -402,6 +462,8 @@ EditorSummary::on_key_release_event (GdkEventKey* key)
        return false;
 }
 
+#include "gtkmm2ext/utils.h"
+
 /** Handle a button press.
  *  @param ev GTK event.
  */
@@ -410,6 +472,16 @@ EditorSummary::on_button_press_event (GdkEventButton* ev)
 {
        _old_follow_playhead = _editor->follow_playhead ();
 
+       if (ev->button == 3) { // right-click:  show the reset menu action
+               using namespace Gtk::Menu_Helpers;
+               Gtk::Menu* m = ARDOUR_UI_UTILS::shared_popup_menu ();
+               MenuList& items = m->items ();
+               items.push_back(MenuElem(_("Reset Summary to Extents"),
+                       sigc::mem_fun(*this, &EditorSummary::reset_to_extents)));
+               m->popup (ev->button, ev->time);
+               return true;
+       }
+
        if (ev->button != 1) {
                return true;
        }
@@ -449,9 +521,22 @@ EditorSummary::on_button_press_event (GdkEventButton* ev)
 
        } else {
 
-               /* start a move or zoom drag */
-               /* won't know which one until the mouse moves */
-               _begin_dragging = true;
+               /* start a move+zoom drag */
+               get_editor (&_pending_editor_x, &_pending_editor_y);
+               _pending_editor_changed = false;
+               _editor->_dragging_playhead = true;
+               _editor->set_follow_playhead (false);
+
+               _move_dragging = true;
+
+               _last_mx = ev->x;
+               _last_my = ev->y;
+               _last_dx = 0;
+               _last_dy = 0;
+               _last_y_delta = 0;
+
+               get_window()->set_cursor (*_editor->_cursors->expand_left_right);
+
        }
 
        return true;
@@ -463,7 +548,7 @@ EditorSummary::on_button_press_event (GdkEventButton* ev)
 bool
 EditorSummary::suspending_editor_updates () const
 {
-       return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_dragging || _zoom_trim_dragging || _move_dragging));
+       return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_trim_dragging || _move_dragging));
 }
 
 /** Fill in x and y with the editor's current viewable area in summary coordinates */
@@ -521,6 +606,18 @@ EditorSummary::get_position (double x, double y) const
        }
 }
 
+void
+EditorSummary::reset_to_extents()
+{
+       /* reset as if the user never went anywhere outside the extents */
+       _leftmost = max_samplepos;
+       _rightmost = 0;
+
+       _editor->temporal_zoom_extents ();
+       set_background_dirty ();
+}
+
+
 void
 EditorSummary::set_cursor (Position p)
 {
@@ -535,7 +632,7 @@ EditorSummary::set_cursor (Position p)
                get_window()->set_cursor (*_editor->_cursors->move);
                break;
        case TO_LEFT_OR_RIGHT:
-               get_window()->set_cursor (*_editor->_cursors->expand_left_right);
+               get_window()->set_cursor (*_editor->_cursors->move);
                break;
        default:
                assert (0);
@@ -544,51 +641,101 @@ EditorSummary::set_cursor (Position p)
        }
 }
 
+void
+EditorSummary::summary_zoom_step (int steps /* positive steps to zoom "out" , negative steps to zoom "in" */  )
+{
+       pair<double, double> xn;
+
+       get_editor (&xn);
+
+       xn.first -= steps;
+       xn.second += steps;
+
+       /* for now, disallow really close zooming-in from the scroomer. (Currently it
+        * causes the start-offset to 'walk' because of integer limitations.
+        * To fix this, probably need to maintain float throught the get/set_editor() path.)
+        */
+       if (steps<0) {
+      if ((xn.second - xn.first) < 2)
+               return;
+       }
+
+       set_overlays_dirty ();
+       set_editor_x (xn);
+}
+
+
 bool
 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
 {
-       pair<double, double> xr = _start_editor_x;
-       double x = _start_editor_x.first;
-
        if (_move_dragging) {
 
-               _moved = true;
+               /* To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
+                * we use screen coordinates for this, not canvas-based grab_x */
+               double mx = ev->x;
+               double dx = mx - _last_mx;
+               double my = ev->y;
+               double dy = my - _last_my;
 
-               assert (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT);
-               x += ev->x - _start_mouse_x;
+               /* do zooming in windowed "steps" so it feels more reversible ? */
+               const int stepsize = 2;
+               int y_delta = _start_mouse_y - my;
+               y_delta = y_delta / stepsize;
 
-               if (x < 0) {
-                       x = 0;
+               /* do the zoom? */
+               const float zscale = 3;
+               if ((dx == 0) && (_last_dx == 0) && (y_delta != _last_y_delta)) {
+
+                       summary_zoom_step (dy * zscale);
+
+                       /* after the zoom we must re-calculate x-pos grabs */
+                       pair<double, double> xr;
+                       get_editor (&xr);
+                       _start_editor_x = xr;
+                       _start_mouse_x = ev->x;
+
+                       _last_y_delta = y_delta;
                }
 
-               set_editor (x);
+               /* always track horizontal movement, if any */
+               if (dx != 0) {
 
-       } else if (_zoom_dragging) {
+                       double x = _start_editor_x.first;
+                       x += ev->x - _start_mouse_x;
 
-               //ToDo: refactor into summary_zoom_in/out(
-               //ToDo:  protect the case where the editor position is small, and results in offsetting the position
+                       if (x < 0) {
+                               x = 0;
+                       }
 
-               double const dy = ev->y - _zoom_last_y;
-               
-               pair<double, double> xn;
-               get_editor (&xn);
+                       /* zoom-behavior-tweaks: protect the right edge from expanding beyond the end */
+                       pair<double, double> xr;
+                       get_editor (&xr);
+                       double w = xr.second - xr.first;
+                       if (x + w < get_width()) {
+                               set_editor (x);
+                       }
+               }
+
+               _last_my = my;
+               _last_mx = mx;
+               _last_dx = dx;
+               _last_dy = dy;
 
-               xn.first -= dy;
-               xn.second += dy;
-       
-               set_overlays_dirty ();
-               set_editor_x (xn);
-       
-               _zoom_last_y = ev->y;
-                       
        } else if (_zoom_trim_dragging) {
 
+               pair<double, double> xr = _start_editor_x;
+
                double const dx = ev->x - _start_mouse_x;
 
                if (_zoom_trim_position == LEFT) {
                        xr.first += dx;
                } else if (_zoom_trim_position == RIGHT) {
-                       xr.second += dx;
+
+                       /* zoom-behavior-tweaks: protect the right edge from expanding beyond the edge */
+                       if ((xr.second + dx) < get_width()) {
+                               xr.second += dx;
+                       }
+
                } else {
                        assert (0);
                        xr.first = -1; /* do not change */
@@ -598,49 +745,8 @@ EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
                set_cursor (_zoom_trim_position);
                set_editor (xr);
 
-       } else if (_begin_dragging) {
-
-               double const dx = ev->x - _start_mouse_x;
-               double const dy = ev->y - _start_mouse_y;
-
-               if ( fabs(dx) > fabs(dy) ) {
-                       
-                       /* initiate a move drag */
-
-                       /* get the editor's state in case we are suspending updates */
-                       get_editor (&_pending_editor_x, &_pending_editor_y);
-                       _pending_editor_changed = false;
-
-                       _move_dragging = true;
-                       _moved = false;
-                       _editor->_dragging_playhead = true;
-                       _editor->set_follow_playhead (false);
-
-                       get_window()->set_cursor (*_editor->_cursors->expand_left_right);
-
-                       _begin_dragging = false;
-               
-               } else if ( fabs(dy) > fabs(dx) ) {
-               
-                       /* initiate a zoom drag */
-
-                       /* get the editor's state in case we are suspending updates */
-                       get_editor (&_pending_editor_x, &_pending_editor_y);
-                       _pending_editor_changed = false;
-
-                       //_zoom_position = get_position (ev->x, ev->y);
-                       _zoom_dragging = true;
-                       _zoom_last_y = ev->y;
-                       _editor->_dragging_playhead = true;
-                       _editor->set_follow_playhead (false);
-
-                       get_window()->set_cursor (*_editor->_cursors->expand_up_down);
-
-                       _begin_dragging = false;
-               }
-               
        } else {
-               set_cursor ( INSIDE );
+               set_cursor (get_position (ev->x, ev->y));
        }
 
        return true;
@@ -653,7 +759,6 @@ EditorSummary::on_button_release_event (GdkEventButton*)
 
        _move_dragging = false;
        _zoom_trim_dragging = false;
-       _zoom_dragging = false;
        _editor->_dragging_playhead = false;
        _editor->set_follow_playhead (_old_follow_playhead, false);
 
@@ -674,33 +779,19 @@ EditorSummary::on_scroll_event (GdkEventScroll* ev)
 
        switch (ev->direction) {
                case GDK_SCROLL_UP: {
-                       //ToDo:  use function summary_zoom_in/out
-                       
-                       pair<double, double> xn;
-                       get_editor (&xn);
-
-                       xn.first -= 2;
-                       xn.second += 2;
-               
-                       set_overlays_dirty ();
-                       set_editor_x (xn);
-               
+
+                       summary_zoom_step (-4);
+
                        return true;
                } break;
-               
+
                case GDK_SCROLL_DOWN: {
-                       pair<double, double> xn;
-                       get_editor (&xn);
-
-                       xn.first += 2;
-                       xn.second -= 2;
-               
-                       set_overlays_dirty ();
-                       set_editor_x (xn);
-               
+
+                       summary_zoom_step (4);
+
                        return true;
                } break;
-               
+
                case GDK_SCROLL_LEFT:
                        if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
                                _editor->temporal_zoom_step (false);
@@ -833,14 +924,14 @@ EditorSummary::set_editor_x (pair<double, double> x)
 }
 
 void
-EditorSummary::playhead_position_changed (framepos_t p)
+EditorSummary::playhead_position_changed (samplepos_t p)
 {
        int const o = int (_last_playhead);
-       int const n = int (playhead_frame_to_position (p));
+       int const n = int (playhead_sample_to_position (p));
        if (_session && o != n) {
                int a = max(2, min (o, n));
                int b = max (o, n);
-               set_overlays_dirty (a - 2, 0, b + 2, get_height ());
+               set_overlays_dirty_rect (a - 2, 0, b + 2, get_height ());
        }
 }
 
@@ -891,13 +982,13 @@ EditorSummary::route_gui_changed (PBD::PropertyChange const& what_changed)
 }
 
 double
-EditorSummary::playhead_frame_to_position (framepos_t t) const
+EditorSummary::playhead_sample_to_position (samplepos_t t) const
 {
        return (t - _start) * _x_scale;
 }
 
-framepos_t
-EditorSummary::position_to_playhead_frame_to_position (double pos) const
+samplepos_t
+EditorSummary::position_to_playhead_sample_to_position (double pos) const
 {
        return _start  + (pos * _x_scale);
 }