diff options
-rw-r--r-- | gtk2_ardour/ardour_ui.h | 3 | ||||
-rw-r--r-- | gtk2_ardour/ardour_ui2.cc | 4 | ||||
-rw-r--r-- | gtk2_ardour/ardour_ui_dialogs.cc | 2 | ||||
-rw-r--r-- | gtk2_ardour/mini_timeline.cc | 486 | ||||
-rw-r--r-- | gtk2_ardour/mini_timeline.h | 89 | ||||
-rw-r--r-- | gtk2_ardour/wscript | 1 |
6 files changed, 584 insertions, 1 deletions
diff --git a/gtk2_ardour/ardour_ui.h b/gtk2_ardour/ardour_ui.h index f1d6167525..33dd9edf3a 100644 --- a/gtk2_ardour/ardour_ui.h +++ b/gtk2_ardour/ardour_ui.h @@ -128,7 +128,7 @@ class SessionDialog; class SessionOptionEditorWindow; class ShuttleControl; class Splash; -class TimeInfoBox; +class MiniTimeline; class Meterbridge; class LuaWindow; class MidiTracer; @@ -253,6 +253,7 @@ public: MainClock* secondary_clock; void focus_on_clock (); AudioClock* big_clock; + MiniTimeline* mini_timeline; VideoTimeLine *video_timeline; diff --git a/gtk2_ardour/ardour_ui2.cc b/gtk2_ardour/ardour_ui2.cc index d7f46b97b5..5371376c31 100644 --- a/gtk2_ardour/ardour_ui2.cc +++ b/gtk2_ardour/ardour_ui2.cc @@ -54,6 +54,7 @@ #include "utils.h" #include "theme_manager.h" #include "midi_tracer.h" +#include "mini_timeline.h" #include "shuttle_control.h" #include "global_port_matrix.h" #include "location_ui.h" @@ -396,6 +397,7 @@ ARDOUR_UI::setup_transport () punch_button_size_group->add_widget (punch_out_button); shuttle_box = manage (new ShuttleControl); + mini_timeline = manage (new MiniTimeline); /* and now the layout... */ @@ -489,6 +491,8 @@ ARDOUR_UI::setup_transport () transport_table.attach (editor_visibility_button, 14, 15, 0, 1 , FILL, SHRINK, 1, 0); transport_table.attach (mixer_visibility_button, 14, 15, 1, 2 , FILL, SHRINK, 1, 0); + transport_table.attach (*mini_timeline, 15, 16, 0, 2, EXPAND|FILL, EXPAND|FILL, 2, 0); + /* desensitize */ feedback_alert_button.set_sensitive (false); diff --git a/gtk2_ardour/ardour_ui_dialogs.cc b/gtk2_ardour/ardour_ui_dialogs.cc index 90cc890743..b11f932e0a 100644 --- a/gtk2_ardour/ardour_ui_dialogs.cc +++ b/gtk2_ardour/ardour_ui_dialogs.cc @@ -56,6 +56,7 @@ #include "meter_patterns.h" #include "monitor_section.h" #include "midi_tracer.h" +#include "mini_timeline.h" #include "mixer_ui.h" #include "public_editor.h" #include "processor_box.h" @@ -114,6 +115,7 @@ ARDOUR_UI::set_session (Session *s) primary_clock->set_session (s); secondary_clock->set_session (s); big_clock->set_session (s); + mini_timeline->set_session (s); video_timeline->set_session (s); /* sensitize menu bar options that are now valid */ diff --git a/gtk2_ardour/mini_timeline.cc b/gtk2_ardour/mini_timeline.cc new file mode 100644 index 0000000000..f9b1904873 --- /dev/null +++ b/gtk2_ardour/mini_timeline.cc @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2016 Robin Gareus <robin@gareus.org> + * + * 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/audioengine.h" +#include "ardour/session.h" +#include "ardour/tempo.h" + +#include "gtkmm2ext/gui_thread.h" +#include "gtkmm2ext/keyboard.h" + +#include "canvas/colors.h" + +#include "ardour_ui.h" +#include "public_editor.h" +#include "main_clock.h" +#include "mini_timeline.h" +#include "timers.h" +#include "ui_config.h" + +#include "pbd/i18n.h" + +#define BBT_BAR_CHAR "|" + +using namespace ARDOUR; + +MiniTimeline::MiniTimeline () + : _last_update_frame (-1) + , _clock_mode (AudioClock::Timecode) + , _time_width (0) + , _time_height (0) +{ + _layout = Pango::Layout::create (get_pango_context()); + + UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &MiniTimeline::set_colors)); + UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &MiniTimeline::on_name_changed)); + UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &MiniTimeline::on_name_changed)); + + set_name ("minitimeline"); + + Location::name_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ()); + Location::end_changed.connect (marker_connection, invalidator (*this), boost::bind (&MiniTimeline::update_minitimeline, this), gui_context ()); + 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 ()); + +} + +MiniTimeline::~MiniTimeline () +{ +} + +void +MiniTimeline::session_going_away () +{ + super_rapid_connection.disconnect (); + SessionHandlePtr::session_going_away (); + _jumplist.clear (); +} + +void +MiniTimeline::set_session (Session* s) +{ + SessionHandlePtr::set_session (s); + if (!s) { + return; + } + + assert (!super_rapid_connection.connected ()); + super_rapid_connection = Timers::super_rapid_connect ( + sigc::mem_fun (*this, &MiniTimeline::super_rapid_update) + ); + _jumplist.clear (); +} + +void +MiniTimeline::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style) +{ + CairoWidget::on_style_changed (old_style); + set_colors (); + calculate_time_width (); +} + +void +MiniTimeline::on_name_changed () +{ + set_colors (); + calculate_time_width (); + + if (is_realized()) { + queue_resize (); + } +} + +void +MiniTimeline::set_colors () +{ + // TODO UIConfiguration::instance().color & font +} + +void +MiniTimeline::on_size_request (Gtk::Requisition* req) +{ + req->width = req->height = 0; + CairoWidget::on_size_request (req); + + req->width = std::max( req->width, 1); + req->height = std::max (req->height, 20); +} + +void +MiniTimeline::super_rapid_update () +{ + if (!_session || !_session->engine().running()) { + return; + } + framepos_t const frame = PublicEditor::instance().playhead_cursor_sample (); + AudioClock::Mode m = ARDOUR_UI::instance()->primary_clock->mode(); + + bool change = _last_update_frame != frame; + + if (m != _clock_mode) { + _clock_mode = m; + calculate_time_width (); + change = true; + } + + if (change) { + _last_update_frame = frame; + update_minitimeline (); + } +} + +void +MiniTimeline::update_minitimeline () +{ + CairoWidget::set_dirty (); +} + +void +MiniTimeline::calculate_time_width () +{ + switch (_clock_mode) { + case AudioClock::Timecode: + _layout->set_text (" 88:88:88,888 "); + break; + case AudioClock::BBT: + _layout->set_text ("888|88|8888"); + break; + case AudioClock::MinSec: + _layout->set_text ("88:88:88,88"); + break; + case AudioClock::Frames: + _layout->set_text ("8888888888"); + break; + } + _layout->get_pixel_size (_time_width, _time_height); +} + +void +MiniTimeline::format_time (framepos_t when) +{ + switch (_clock_mode) { + case AudioClock::Timecode: + { + Timecode::Time TC; + _session->timecode_time (when, TC); + // chop of leading space or minus. + _layout->set_text (Timecode::timecode_format_time (TC).substr(1)); + } + break; + case AudioClock::BBT: + { + char buf[64]; + Timecode::BBT_Time BBT = _session->tempo_map().bbt_at_frame (when); + snprintf (buf, sizeof (buf), "%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, + BBT.bars, BBT.beats, BBT.ticks); + _layout->set_text (buf); + } + break; + case AudioClock::MinSec: + { + char buf[32]; + AudioClock::print_minsec (when, buf, sizeof (buf), _session->frame_rate()); + _layout->set_text (std::string(buf).substr(1)); + } + break; + case AudioClock::Frames: + { + char buf[32]; + snprintf (buf, sizeof (buf), "%" PRId64, when); + _layout->set_text (buf); + } + break; + } +} + +void +MiniTimeline::draw_dots (cairo_t* cr, int left, int right, int y) +{ + if (left + 1 >= right) { + return; + } + cairo_move_to (cr, left + .5, y + .5); + cairo_line_to (cr, right - .5, y + .5); + cairo_set_source_rgb (cr, 0, .5, 0); // tc color + const double dashes[] = { 0, 1 }; + cairo_set_dash (cr, dashes, 2, 1); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); + cairo_set_dash (cr, 0, 0, 0); +} + +int +MiniTimeline::draw_mark (cairo_t* cr, int x0, int x1, int h, const std::string& label) +{ + /* ArdourMarker shape + * MH = 13 + * + * Mark: + * + * (0,0) -- (6,0) + * | | + * | | + * (0,MH*.4) (6,MH*.4) + * \ / + * (3,MH) + */ + + int y = 3; + int w2 = (h - 1) / 4; + double h0 = h * .4; + double h1 = h - h0; + + int lw, lh; + _layout->set_text (label); + _layout->get_pixel_size (lw, lh); + + // 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 { + cairo_save (cr); + cairo_rectangle (cr, x0, y, rw - x0, h); + cairo_set_source_rgba (cr, r, g, b, 0.5); + cairo_fill_preserve (cr); + cairo_clip (cr); + + // marker label + cairo_move_to (cr, x0 + w2, y + .5 * (h - lh)); + cairo_set_source_rgb (cr, 0, 0, 0); + pango_cairo_show_layout (cr, _layout->gobj()); + cairo_restore (cr); + } + + // draw marker on top + cairo_move_to (cr, x0 - .5, y + .5); + cairo_rel_line_to (cr, -w2 , 0 ); + cairo_rel_line_to (cr, 0, h0); + cairo_rel_line_to (cr, w2, h1); + cairo_rel_line_to (cr, w2, -h1); + cairo_rel_line_to (cr, 0, -h0); + cairo_close_path (cr); + cairo_set_source_rgba (cr, r, g, b, 1.0); + cairo_set_line_width (cr, 1.0); + cairo_stroke_preserve (cr); + cairo_fill (cr); + + return rw; +} + +void +MiniTimeline::render (cairo_t* cr, cairo_rectangle_t*) +{ + int n_labels = floor (get_width () / (_time_width * 1.15)); + + if (n_labels == 0) { + return; + } + + Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), 4); + cairo_set_source_rgba (cr, 0, 0, 0, 1); + cairo_fill_preserve (cr); + cairo_clip (cr); + + if (_session == 0) { + return; + } + + + /* time */ + const framepos_t time_span = 60; /* left+right: 2 minutes */ + const framepos_t time_span_samples = time_span * _session->nominal_frame_rate (); + const framepos_t time_granularity = _session->nominal_frame_rate () * ceil (2. * time_span / n_labels); + const framepos_t p = _last_update_frame; + const framepos_t lower = (std::max ((framepos_t)0, (p - time_span_samples)) / time_granularity) * time_granularity; + const double px_per_sample = get_width () / (2. * time_span_samples); + + int dot_left = get_width() * .5 + (lower - p) * px_per_sample; + for (int i = 0; i < 2 + n_labels; ++i) { + framepos_t when = lower + i * time_granularity; + double xpos = get_width() * .5 + (when - p) * px_per_sample; + + // TODO round to nearest display TC in +/- 1px + // prefer to display BBT |0 or .0 + + int lw, lh; + format_time (when); + _layout->get_pixel_size (lw, lh); + + int x0 = xpos - lw / 2.0; + int y0 = get_height() - 3 - _time_height; + + draw_dots (cr, dot_left, x0, y0 + _time_height * .5); + +#if 1 // border around TC + Gtkmm2ext::rounded_rectangle (cr, x0, y0, lw, _time_height, 4); + cairo_set_source_rgba (cr, 0, 1, 0, .5); // tc color, shaded + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); +#endif + + cairo_move_to (cr, x0, y0); + cairo_set_source_rgb (cr, 0, 1, 0); // tc color + pango_cairo_show_layout (cr, _layout->gobj()); + dot_left = x0 + lw; + } + draw_dots (cr, dot_left, get_width(), get_height() - 3 - _time_height * .5); + + /* locations */ + framepos_t lmin = std::max ((framepos_t)0, (p - time_span_samples)); + framepos_t lmax = p + time_span_samples; + + const int mh = std::min (.4f * get_height(), get_height() - _time_height - 8.f); + assert (mh > 4); + const int mw = (mh - 1) / 4; + + lmin -= mw / px_per_sample; + lmax += mw / px_per_sample; + + struct LocationMarker { + LocationMarker (const std::string& l, framepos_t w) + : label (l), when (w) {} + std::string label; + framepos_t when; + }; + + struct LocationMarkerSort { + bool operator() (const LocationMarker& a, const LocationMarker& b) { + return (a.when < b.when); + } + } location_marker_sort; + + std::vector<LocationMarker> lm; + + const Locations::LocationList& ll (_session->locations ()->list ()); + for (Locations::LocationList::const_iterator l = ll.begin(); l != ll.end(); ++l) { + if ((*l)->is_session_range ()) { + framepos_t when = (*l)->start (); + if (when >= lmin && when <= lmax) { + lm.push_back (LocationMarker(_("start"), when)); + } + when = (*l)->end (); + if (when >= lmin && when <= lmax) { + lm.push_back (LocationMarker(_("end"), when)); + } + continue; + } + + if (!(*l)->is_mark () || (*l)->name().substr (0, 4) == "xrun") { + continue; + } + + framepos_t when = (*l)->start (); + if (when < lmin || when > lmax) { + continue; + } + lm.push_back (LocationMarker((*l)->name(), when)); + } + + _jumplist.clear (); + std::sort (lm.begin(), lm.end(), location_marker_sort); + + for (std::vector<LocationMarker>::const_iterator l = lm.begin(); l != lm.end();) { + framepos_t when = (*l).when; + int x0 = floor (get_width() * .5 + (when - p) * px_per_sample); + int x1 = get_width(); + const std::string& label = (*l).label; + 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)); + } + + /* playhead on top */ + int xc = get_width () * 0.5f; + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgb (cr, 1, 0, 0); // playhead color + cairo_move_to (cr, xc - .5, 0); + cairo_rel_line_to (cr, 0, get_height ()); + cairo_stroke (cr); + cairo_move_to (cr, xc - .5, get_height ()); + cairo_rel_line_to (cr, -3, 0); + cairo_rel_line_to (cr, 3, -4); + cairo_rel_line_to (cr, 3, 4); + cairo_close_path (cr); + cairo_fill (cr); +} + +bool +MiniTimeline::on_button_release_event (GdkEventButton *ev) +{ + if (!_session) { return true; } + if (ev->y < get_height () * .5) { + 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) { + _session->request_locate (i->to, _session->transport_rolling ()); + } + break; + } + } + } else if (ev->button == 1) { + // copy from ::render() // TODO consolidate + const framepos_t time_span = 60; /* left+right: 2 minutes */ + const framepos_t time_span_samples = time_span * _session->nominal_frame_rate (); + const framepos_t p = _last_update_frame; + const double px_per_sample = get_width () / (2. * time_span_samples); + + framepos_t when = p + (ev->x - get_width() * .5) / px_per_sample; + _session->request_locate (std::max ((framepos_t)0, when), _session->transport_rolling ()); + } + return true; +} + +bool +MiniTimeline::on_scroll_event (GdkEventScroll *ev) +{ + if (!_session) { return true; } + framepos_t when = _session->audible_frame (); + double scale = 2.0; + + if (ev->state & Gtkmm2ext::Keyboard::GainFineScaleModifier) { + if (ev->state & Gtkmm2ext::Keyboard::GainExtraFineScaleModifier) { + scale = 0.1; + } else { + scale = 0.5; + } + } + + switch (ev->direction) { + case GDK_SCROLL_UP: + when += scale * _session->nominal_frame_rate (); + break; + case GDK_SCROLL_DOWN: + when -= scale * _session->nominal_frame_rate (); + break; + default: + return true; + break; + } + _session->request_locate (std::max ((framepos_t)0, when), _session->transport_rolling ()); + return true; +} diff --git a/gtk2_ardour/mini_timeline.h b/gtk2_ardour/mini_timeline.h new file mode 100644 index 0000000000..23071c4ce2 --- /dev/null +++ b/gtk2_ardour/mini_timeline.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 Robin Gareus <robin@gareus.org> + * + * 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. + */ + +#ifndef __gtkardour_mini_timeline_h__ +#define __gtkardour_mini_timeline_h__ + +#include <list> +#include <pangomm.h> + +#include "pbd/signals.h" + +#include "ardour/ardour.h" +#include "ardour/session_handle.h" + +#include "gtkmm2ext/cairo_widget.h" +#include "gtkmm2ext/utils.h" + +#include "audio_clock.h" + +namespace ARDOUR { + class Session; +} + +class MiniTimeline : public CairoWidget, public ARDOUR::SessionHandlePtr, public PBD::ScopedConnectionList +{ + +public: + MiniTimeline (); + ~MiniTimeline (); + + void set_session (ARDOUR::Session *); + +private: + void session_going_away (); + void super_rapid_update (); + + void on_size_request (Gtk::Requisition*); + void on_style_changed (const Glib::RefPtr<Gtk::Style>&); + void on_name_changed (); + void set_colors (); + + void calculate_time_width (); + void update_minitimeline (); + void draw_dots (cairo_t*, int left, int right, int y); + int draw_mark (cairo_t*, int x0, int x1, int h, const std::string&); + + void render (cairo_t*, cairo_rectangle_t*); + void format_time (framepos_t when); + + bool on_button_release_event (GdkEventButton *ev); + bool on_scroll_event (GdkEventScroll*); + + Glib::RefPtr<Pango::Layout> _layout; + sigc::connection super_rapid_connection; + PBD::ScopedConnectionList marker_connection; + + framepos_t _last_update_frame; + AudioClock::Mode _clock_mode; + int _time_width; + int _time_height; + + struct JumpRange { + JumpRange (int l, int r, framepos_t t) + : left (l), right (r), to (t) {} + int left; + int right; + framepos_t to; + }; + + typedef std::list <JumpRange> JumpList; + JumpList _jumplist; +}; + +#endif diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 4b48bc9837..1d4fd4b34c 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -152,6 +152,7 @@ gtk2_ardour_sources = [ 'midi_time_axis.cc', 'midi_tracer.cc', 'midi_velocity_dialog.cc', + 'mini_timeline.cc', 'missing_file_dialog.cc', 'missing_plugin_dialog.cc', 'mixer_group_tabs.cc', |