Update Russian translation
[ardour.git] / gtk2_ardour / mini_timeline.cc
index c17ccea0071984320698b06ed906e7dabbb82458..653c3c40b04af4e91e708270ddb49324f53171b4 100644 (file)
 #include "main_clock.h"
 #include "mini_timeline.h"
 #include "timers.h"
+#include "tooltips.h"
 #include "ui_config.h"
 
 #include "pbd/i18n.h"
 
+#define PADDING 3
 #define BBT_BAR_CHAR "|"
 
 using namespace ARDOUR;
@@ -48,7 +50,13 @@ MiniTimeline::MiniTimeline ()
        , _px_per_sample (0)
        , _time_granularity (0)
        , _time_span_samples (0)
+       , _marker_height (0)
+       , _pointer_x (-1)
+       , _pointer_y (-1)
+       , _minitl_context_menu (0)
 {
+       add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::POINTER_MOTION_MASK|Gdk::SCROLL_MASK);
+
        _layout = Pango::Layout::create (get_pango_context());
 
        UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &MiniTimeline::set_colors));
@@ -62,10 +70,17 @@ MiniTimeline::MiniTimeline ()
        Location::start_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ());
        Location::flags_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ());
 
+       ARDOUR_UI_UTILS::set_tooltip (*this,
+                       string_compose (_("<b>Navigation Timeline</b>. Use left-click to locate to time position or marker; scroll-wheel to jump, hold %1 for fine gained and %2 + %3 for extra-fine grained control. Right-click to set display range. The display unit is defined by the primary clock."),
+                               Gtkmm2ext::Keyboard::primary_modifier_name(),
+                               Gtkmm2ext::Keyboard::primary_modifier_name (),
+                               Gtkmm2ext::Keyboard::secondary_modifier_name ()));
 }
 
 MiniTimeline::~MiniTimeline ()
 {
+       delete _minitl_context_menu;
+       _minitl_context_menu = 0;
 }
 
 void
@@ -75,6 +90,8 @@ MiniTimeline::session_going_away ()
        session_connection.drop_connections ();
        SessionHandlePtr::session_going_away ();
        _jumplist.clear ();
+       delete _minitl_context_menu;
+       _minitl_context_menu = 0;
 }
 
 void
@@ -163,10 +180,23 @@ MiniTimeline::on_size_allocate (Gtk::Allocation& alloc)
        calculate_time_spacing ();
 }
 
+void
+MiniTimeline::set_span (framecnt_t ts)
+{
+       assert (_session);
+       if (_session->config.get_minitimeline_span () == ts) {
+               return;
+       }
+
+       _session->config.set_minitimeline_span (ts);
+       calculate_time_spacing ();
+       update_minitimeline ();
+}
+
 void
 MiniTimeline::super_rapid_update ()
 {
-       if (!_session || !_session->engine().running()) {
+       if (!_session || !_session->engine().running() || !is_mapped ()) {
                return;
        }
        framepos_t const frame = PublicEditor::instance().playhead_cursor_sample ();
@@ -183,6 +213,11 @@ MiniTimeline::super_rapid_update ()
                change = true;
        }
 
+       if (_clock_mode == AudioClock::BBT) {
+               // TODO check if tempo-map changed
+               change = true;
+       }
+
        if (change) {
                _last_update_frame = frame;
                update_minitimeline ();
@@ -224,7 +259,7 @@ MiniTimeline::calculate_time_spacing ()
                return;
        }
 
-       const framepos_t time_span = _session->config.get_minitimeline_span () / 2;
+       const framecnt_t time_span = _session->config.get_minitimeline_span () / 2;
        _time_span_samples = time_span * _session->nominal_frame_rate ();
        _time_granularity = _session->nominal_frame_rate () * ceil (2. * time_span / _n_labels);
        _px_per_sample = get_width () / (2. * _time_span_samples);
@@ -287,8 +322,9 @@ MiniTimeline::draw_dots (cairo_t* cr, int left, int right, int y, ArdourCanvas::
 }
 
 int
-MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, int h, const std::string& label)
+MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, const std::string& label, bool& prelight)
 {
+       int h = _marker_height;
        /* ArdourMarker shape
         * MH = 13
         *
@@ -302,7 +338,7 @@ MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, int h, const std::string&
         *        (3,MH)
         */
 
-       int y = 3;
+       const int y = PADDING;
        int w2 = (h - 1) / 4;
        double h0 = h * .4;
        double h1 = h - h0;
@@ -310,13 +346,19 @@ MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, int h, const std::string&
        int lw, lh;
        _layout->set_text (label);
        _layout->get_pixel_size (lw, lh);
+       int rw = std::min (x1, x0 + w2 + lw + 2);
+
+       if (_pointer_y >= 0 && _pointer_y <= y + h && _pointer_x >= x0 && _pointer_x <= rw) {
+               prelight = true;
+       }
+
+       // TODO cache in set_colors()
+       uint32_t color = UIConfiguration::instance().color (
+                       prelight ? "entered marker" : "location marker");
 
-       // TODO cache, set_colors()
-       uint32_t color = UIConfiguration::instance().color ("location marker");
        double r, g, b, a;
        ArdourCanvas::color_to_rgba (color, r, g, b, a);
 
-       int rw = std::min (x1, x0 + w2 + lw + 2);
        if (rw < x0) {
                rw = x1;
        } else {
@@ -377,7 +419,7 @@ MiniTimeline::render (cairo_t* cr, cairo_rectangle_t*)
        ArdourCanvas::set_source_rgba(cr, base);
        cairo_fill (cr);
 
-       Gtkmm2ext::rounded_rectangle (cr, 3, 3, get_width()-6, get_height()-6, 4);
+       Gtkmm2ext::rounded_rectangle (cr, PADDING, PADDING, get_width() - PADDING - PADDING, get_height() - PADDING - PADDING, 4);
        cairo_clip (cr);
 
        if (_session == 0) {
@@ -402,7 +444,7 @@ MiniTimeline::render (cairo_t* cr, cairo_rectangle_t*)
                _layout->get_pixel_size (lw, lh);
 
                int x0 = xpos - lw / 2.0;
-               int y0 = get_height() - 3 - _time_height;
+               int y0 = get_height() - PADDING - _time_height;
 
                draw_dots (cr, dot_left, x0, y0 + _time_height * .5, text);
 
@@ -411,7 +453,7 @@ MiniTimeline::render (cairo_t* cr, cairo_rectangle_t*)
                pango_cairo_show_layout (cr, _layout->gobj());
                dot_left = x0 + lw;
        }
-       draw_dots (cr, dot_left, get_width(), get_height() - 3 - _time_height * .5, text);
+       draw_dots (cr, dot_left, get_width(), get_height() - PADDING - _time_height * .5, text);
 
        /* locations */
        framepos_t lmin = std::max ((framepos_t)0, (p - _time_span_samples));
@@ -421,9 +463,9 @@ MiniTimeline::render (cairo_t* cr, cairo_rectangle_t*)
        _layout->set_text (X_("Marker@"));
        _layout->get_pixel_size (tw, th);
 
-       const int mh = th + 2;
-       assert (mh > 4);
-       const int mw = (mh - 1) / 4;
+       _marker_height = th + 2;
+       assert (_marker_height > 4);
+       const int mw = (_marker_height - 1) / 4;
 
        lmin -= mw / _px_per_sample;
        lmax += mw / _px_per_sample;
@@ -460,7 +502,8 @@ MiniTimeline::render (cairo_t* cr, cairo_rectangle_t*)
        LocationMarkerSort location_marker_sort;
        std::sort (lm.begin(), lm.end(), location_marker_sort);
 
-       for (std::vector<LocationMarker>::const_iterator l = lm.begin(); l != lm.end();) {
+       int id = 0;
+       for (std::vector<LocationMarker>::const_iterator l = lm.begin(); l != lm.end(); ++id) {
                framepos_t when = (*l).when;
                int x0 = floor (get_width() * .5 + (when - p) * _px_per_sample);
                int x1 = get_width();
@@ -468,8 +511,9 @@ MiniTimeline::render (cairo_t* cr, cairo_rectangle_t*)
                if (++l != lm.end()) {
                        x1 = floor (get_width() * .5 + ((*l).when - p) * _px_per_sample) - 1 - mw;
                }
-               x1 = draw_mark (cr, x0, x1, mh, label);
-               _jumplist.push_back (JumpRange (x0 - mw, x1, when));
+               bool prelight = false;
+               x1 = draw_mark (cr, x0, x1, label, prelight);
+               _jumplist.push_back (JumpRange (x0 - mw, x1, when, prelight));
        }
 
        /* playhead on top */
@@ -487,19 +531,73 @@ MiniTimeline::render (cairo_t* cr, cairo_rectangle_t*)
        cairo_fill (cr);
 }
 
+void
+MiniTimeline::build_minitl_context_menu ()
+{
+       using namespace Gtk;
+       using namespace Gtk::Menu_Helpers;
+
+       assert (_session);
+
+       const framecnt_t time_span = _session->config.get_minitimeline_span ();
+
+       _minitl_context_menu = new Gtk::Menu();
+       MenuList& items = _minitl_context_menu->items();
+
+       // ideally this would have a heading (or rather be a sub-menu to "Visible Time")
+       std::map<framecnt_t, std::string> spans;
+       spans[30]   = _("30 sec");
+       spans[60]   = _("1 min");
+       spans[120]  = _("2 mins");
+       spans[300]  = _("5 mins");
+       spans[600]  = _("10 mins");
+       spans[1200] = _("20 mins");
+
+       RadioMenuItem::Group span_group;
+       for (std::map<framecnt_t, std::string>::const_iterator i = spans.begin (); i != spans.end (); ++i) {
+               items.push_back (RadioMenuElem (span_group, i->second, sigc::bind (sigc::mem_fun (*this, &MiniTimeline::set_span), i->first)));
+               if (time_span == i->first) {
+                       static_cast<RadioMenuItem*>(&items.back())->set_active ();
+               }
+       }
+}
+
+void
+MiniTimeline::show_minitl_context_menu ()
+{
+       if (_minitl_context_menu == 0) {
+               build_minitl_context_menu ();
+       }
+       _minitl_context_menu->popup (1, gtk_get_current_event_time());
+}
+
+bool
+MiniTimeline::on_button_press_event (GdkEventButton *ev)
+{
+       if (Gtkmm2ext::Keyboard::is_context_menu_event (ev)) {
+               if (_session) {
+                       show_minitl_context_menu ();
+               }
+               return true;
+       }
+       return true;
+}
+
 bool
 MiniTimeline::on_button_release_event (GdkEventButton *ev)
 {
        if (!_session) { return true; }
+       if (_session->actively_recording ()) { return true; }
+       if (ev->y < 0 || ev->y > get_height () || ev->x < 0 || ev->x > get_width ()) {
+               return true;
+       }
 
-       for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) {
-               if (i->left < ev->x && ev->x < i->right) {
-                       if (ev->button == 3) {
-                               PublicEditor::instance().center_screen (i->to);
-                       } else if (ev->button == 1) {
+       if (ev->y <= PADDING + _marker_height) {
+               for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) {
+                       if (i->left < ev->x && ev->x < i->right) {
                                _session->request_locate (i->to, _session->transport_rolling ());
+                               return true;
                        }
-                       return true;
                }
        }
 
@@ -511,12 +609,60 @@ MiniTimeline::on_button_release_event (GdkEventButton *ev)
        return true;
 }
 
+bool
+MiniTimeline::on_motion_notify_event (GdkEventMotion *ev)
+{
+       if (!_session) { return true; }
+       if (_session->actively_recording ()) { return true; }
+
+       _pointer_x = ev->x;
+       _pointer_y = ev->y;
+
+       bool need_expose = false;
+
+       for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) {
+               if (i->left < ev->x && ev->x < i->right && ev->y <= PADDING + _marker_height) {
+                       if (!(*i).prelight) {
+                               need_expose = true;
+                               break;
+                       }
+               } else {
+                       if ((*i).prelight) {
+                               need_expose = true;
+                               break;
+                       }
+               }
+       }
+       if (need_expose) {
+               update_minitimeline ();
+       }
+
+       return true;
+}
+
+bool
+MiniTimeline::on_leave_notify_event (GdkEventCrossing *ev)
+{
+       CairoWidget::on_leave_notify_event (ev);
+       _pointer_x = _pointer_y = -1;
+       for (JumpList::const_iterator i = _jumplist.begin (); i != _jumplist.end(); ++i) {
+               if ((*i).prelight) {
+                       update_minitimeline ();
+                       break;
+               }
+       }
+       return true;
+}
+
 bool
 MiniTimeline::on_scroll_event (GdkEventScroll *ev)
 {
        if (!_session) { return true; }
+       if (_session->actively_recording ()) { return true; }
+       const framecnt_t time_span = _session->config.get_minitimeline_span ();
        framepos_t when = _session->audible_frame ();
-       double scale = 2.0;
+
+       double scale = time_span / 60.0;
 
        if (ev->state & Gtkmm2ext::Keyboard::GainFineScaleModifier) {
                if (ev->state & Gtkmm2ext::Keyboard::GainExtraFineScaleModifier) {
@@ -528,9 +674,11 @@ MiniTimeline::on_scroll_event (GdkEventScroll *ev)
 
        switch (ev->direction) {
                case GDK_SCROLL_UP:
+               case GDK_SCROLL_RIGHT:
                        when += scale * _session->nominal_frame_rate ();
                        break;
                case GDK_SCROLL_DOWN:
+               case GDK_SCROLL_LEFT:
                        when -= scale * _session->nominal_frame_rate ();
                        break;
                default: