diff --git a/gtk2_ardour/rec_info_box.cc b/gtk2_ardour/rec_info_box.cc new file mode 100644 index 0000000000..2b11e9b8c4 --- /dev/null +++ b/gtk2_ardour/rec_info_box.cc @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * 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. + */ + +#ifdef WAF_BUILD +#include "gtk2ardour-config.h" +#endif + +#include "ardour/session.h" +#include "ardour/session_route.h" +#include "ardour/track.h" + +#include "gtkmm2ext/utils.h" +#include "temporal/time.h" + +#include "audio_clock.h" +#include "gui_thread.h" +#include "rec_info_box.h" +#include "timers.h" +#include "ui_config.h" + +#include "pbd/i18n.h" + +using namespace ARDOUR; + +RecInfoBox::RecInfoBox () +{ + set_name (X_("RecInfoBox")); + _layout_label = Pango::Layout::create (get_pango_context ()); + _layout_value = Pango::Layout::create (get_pango_context ()); + + UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &RecInfoBox::dpi_reset)); + dpi_reset (); +} + +RecInfoBox::~RecInfoBox () +{ +} + +void +RecInfoBox::dpi_reset () +{ + _layout_label->set_font_description (UIConfiguration::instance ().get_NormalFont ()); + _layout_value->set_font_description (UIConfiguration::instance ().get_LargeMonospaceFont ()); + + int wl, hl, wv, hv; + + _layout_label->set_text (_("Last Capture Duration:")); + _layout_label->get_pixel_size (wl, hl); + + _layout_value->set_text ("00:00:00:00"); + _layout_value->get_pixel_size (wv, hv); + + _width = 8 + std::max (wl, wv); + _height = 4 + 3 * (hl + hv + 8); + + queue_resize (); +} + +void +RecInfoBox::on_size_request (Gtk::Requisition* r) +{ + r->width = _width; + r->height = std::max (150, _height); +} + +void +RecInfoBox::on_size_allocate (Gtk::Allocation& a) +{ + CairoWidget::on_size_allocate (a); +} + +void +RecInfoBox::set_session (Session* s) +{ + SessionHandlePtr::set_session (s); + + if (!_session) { + return; + } + + _session->RecordStateChanged.connect (_session_connections, invalidator (*this), boost::bind (&RecInfoBox::rec_state_changed, this), gui_context()); + _session->UpdateRouteRecordState.connect (_session_connections, invalidator (*this), boost::bind (&RecInfoBox::update, this), gui_context()); + update (); +} + +void +RecInfoBox::rec_state_changed () +{ + if (_session && _session->actively_recording ()) { + if (!_rectime_connection.connected ()) { + _rectime_connection = Timers::rapid_connect (sigc::mem_fun (*this, &RecInfoBox::update)); + } + } else { + _rectime_connection.disconnect (); + } + update (); +} + +void +RecInfoBox::update () +{ + set_dirty (); +} + +void +RecInfoBox::count_recenabled_streams (Route& route) +{ + Track* track = dynamic_cast(&route); + if (track && track->rec_enable_control()->get_value()) { + _rec_enabled_streams += track->n_inputs().n_total(); + } +} + +void +RecInfoBox::render (Cairo::RefPtr const& cr, cairo_rectangle_t* r) +{ + int ww = get_width (); + int hh = get_height (); + + cr->rectangle (r->x, r->y, r->width, r->height); + cr->clip (); + cr->set_operator (Cairo::OPERATOR_OVER); + + bool recording; + if (_session && _session->actively_recording ()) { + Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("alert:red"), .7); + recording = true; + } else { + Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("widget:bg"), .7); + recording = false; + } + + Gtkmm2ext::rounded_rectangle (cr, 1 , 1, ww - 2, hh - 2, /*_height / 4.0 */ 4); + cr->fill (); + + if (!_session) { + return; + } + + Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground")); + + unsigned int xruns = _session->capture_xruns (); + samplecnt_t capture_duration = _session->capture_duration (); + samplecnt_t sample_rate = _session->nominal_sample_rate (); + + /* TODO: cache, calling this at rapid timer intervals when recording is not great */ + boost::optional opt_samples = _session->available_capture_duration (); + + int top = 2; + int w, h; + + if (recording || capture_duration == 0) { + _layout_label->set_text (_("Capture Duration:")); + } else { + _layout_label->set_text (_("Last Capture Duration:")); + } + _layout_label->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), top); + _layout_label->show_in_cairo_context (cr); + top += h + 2; + + if (capture_duration > 0) { + char buf[32]; + AudioClock::print_minsec (capture_duration, buf, sizeof (buf), sample_rate, 1); + _layout_value->set_text (std::string(buf).substr(1)); + } else { + _layout_value->set_text ("--:--:--:-"); + } + _layout_value->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), top); + _layout_value->show_in_cairo_context (cr); + top += h + 6; + + _layout_label->set_text (_("Capture x-runs:")); + _layout_label->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), top); + _layout_label->show_in_cairo_context (cr); + top += h + 2; + + _layout_value->set_text (string_compose ("%1", xruns)); + _layout_value->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), top); + _layout_value->show_in_cairo_context (cr); + top += h + 6; + + /* disk space */ + _layout_label->set_text (_("Available record time:")); + _layout_label->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), top); + _layout_label->show_in_cairo_context (cr); + top += h + 2; + + if (!opt_samples) { + /* Available space is unknown */ + _layout_value->set_text (_("Unknown")); + } else if (opt_samples.value_or (0) == max_samplecnt) { + _layout_value->set_text (_(">24h")); + } else { + _rec_enabled_streams = 0; + _session->foreach_route (this, &RecInfoBox::count_recenabled_streams, false); + + samplecnt_t samples = opt_samples.value_or (0); + + if (_rec_enabled_streams > 0) { + samples /= _rec_enabled_streams; + } + + float remain_sec = samples / (float)sample_rate; + char buf[32]; + + if (remain_sec > 86400) { + _layout_value->set_text (_(">24h")); + } else if (remain_sec > 32400 /* 9 hours */) { + snprintf (buf, sizeof (buf), "%.0f", remain_sec / 3600.f); + _layout_value->set_text (std::string (buf) + S_("hours|h")); + } else if (remain_sec > 5940 /* 99 mins */) { + snprintf (buf, sizeof (buf), "%.1f", remain_sec / 3600.f); + _layout_value->set_text (std::string (buf) + S_("hours|h")); + } else { + snprintf (buf, sizeof (buf), "%.0f", remain_sec / 60.f); + _layout_value->set_text (std::string (buf) + S_("minutes|m")); + } + } + + _layout_value->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), top); + _layout_value->show_in_cairo_context (cr); + top += h + 6; +} diff --git a/gtk2_ardour/rec_info_box.h b/gtk2_ardour/rec_info_box.h new file mode 100644 index 0000000000..76d3168a30 --- /dev/null +++ b/gtk2_ardour/rec_info_box.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * 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 _rec_info_box_h_ +#define _rec_info_box_h_ + +#include "ardour/session_handle.h" + +#include "gtkmm2ext/cairo_widget.h" + +namespace ARDOUR { + class Route; +} + +class RecInfoBox : public CairoWidget, public ARDOUR::SessionHandlePtr +{ +public: + RecInfoBox (); + ~RecInfoBox (); + + void set_session (ARDOUR::Session*); + +protected: + void render (Cairo::RefPtr const&, cairo_rectangle_t*); + void on_size_request (Gtk::Requisition*); + void on_size_allocate (Gtk::Allocation&); + +private: + void dpi_reset (); + void update (); + void rec_state_changed (); + void count_recenabled_streams (ARDOUR::Route&); + + Glib::RefPtr _layout_label; + Glib::RefPtr _layout_value; + + int _width; + int _height; + uint32_t _rec_enabled_streams; + sigc::connection _rectime_connection; +}; + +#endif diff --git a/gtk2_ardour/recorder_ui.cc b/gtk2_ardour/recorder_ui.cc index aa4440272c..153ca1ebc0 100644 --- a/gtk2_ardour/recorder_ui.cc +++ b/gtk2_ardour/recorder_ui.cc @@ -84,6 +84,12 @@ RecorderUI::RecorderUI () load_bindings (); register_actions (); + _transport_ctrl.setup (ARDOUR_UI::instance ()); + _transport_ctrl.map_actions (); + _transport_ctrl.set_no_show_all (); + + signal_tabbed_changed.connect (sigc::mem_fun (*this, &RecorderUI::tabbed_changed)); + _meter_area.set_spacing (0); _meter_area.pack_start (_meter_table, true, true); _meter_area.signal_size_request().connect (sigc::mem_fun (*this, &RecorderUI::meter_area_size_request)); @@ -99,25 +105,30 @@ RecorderUI::RecorderUI () _rec_area.pack_end (_scroller_base, true, true); _rec_area.pack_end (_ruler_sep, false, false, 1); - /* HBox groups | tracks */ + /* HBox [ groups | tracks] */ _rec_group_tabs = new RecorderGroupTabs (this); _rec_groups.pack_start (*_rec_group_tabs, false, false); _rec_groups.pack_start (_rec_area, true, true); - /* vertical scroll, all tracks */ + /* Vertical scroll, all tracks */ _rec_scroller.add (_rec_groups); _rec_scroller.set_shadow_type(SHADOW_NONE); _rec_scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC); - /* HBox, ruler on top */ + /* HBox, ruler on top [ space above headers | time-ruler ] */ _ruler_box.pack_start (_space, false, false); _ruler_box.pack_start (_ruler, true, true); - /* VBox, toplevel of upper pane */ + /* VBox, ruler + scroll-area for tracks */ _rec_container.pack_start (_ruler_box, false, false); _rec_container.pack_start (_rec_scroller, true, true); - _pane.add (_rec_container); + /* HBox, toplevel of upper pane [Info | recarea ] */ + _rec_container.pack_start (_ruler_box, false, false); + _rec_top.pack_start (_rec_info_box, false, false, 4); + _rec_top.pack_start (_rec_container, true, true); + + _pane.add (_rec_top); _pane.add (_meter_scroller); _content.pack_start (_toolbar_sep, false, false, 1); @@ -154,6 +165,7 @@ RecorderUI::RecorderUI () _toolbar.pack_start (_btn_peak_reset, false, false); _toolbar.pack_start (*manage (new ArdourVSpacer), false, false); _toolbar.pack_start (_btn_rec_forget, false, false); + _toolbar.pack_start (_transport_ctrl, false, false); set_tooltip (_btn_rec_all, _("Record enable all tracks")); set_tooltip (_btn_rec_none, _("Disable recording of all tracks")); @@ -172,6 +184,7 @@ RecorderUI::RecorderUI () _rec_groups.show (); _rec_group_tabs->show (); _rec_container.show (); + _rec_top.show (); _meter_table.show (); _meter_area.show (); _meter_scroller.show (); @@ -235,6 +248,16 @@ RecorderUI::use_own_window (bool and_fill_it) return win; } +void +RecorderUI::tabbed_changed (bool tabbed) +{ + if (tabbed) { + _transport_ctrl.hide (); + } else { + _transport_ctrl.show (); + } +} + XMLNode& RecorderUI::get_state () { @@ -268,6 +291,8 @@ RecorderUI::set_session (Session* s) SessionHandlePtr::set_session (s); _ruler.set_session (s); + _rec_info_box.set_session (s); + _transport_ctrl.set_session (s); _rec_group_tabs->set_session (s); update_sensitivity (); diff --git a/gtk2_ardour/recorder_ui.h b/gtk2_ardour/recorder_ui.h index 4e25c83d45..f0642f442c 100644 --- a/gtk2_ardour/recorder_ui.h +++ b/gtk2_ardour/recorder_ui.h @@ -45,6 +45,8 @@ #include "widgets/tabbable.h" #include "input_port_monitor.h" +#include "rec_info_box.h" +#include "transport_control_ui.h" namespace ARDOUR { class SoloMuteRelease; @@ -90,6 +92,7 @@ private: void remove_route (TrackRecordAxis*); void update_rec_table_layout (); void update_spacer_width (Gtk::Allocation&, TrackRecordAxis*); + void tabbed_changed (bool); void set_connections (std::string const&); void port_connected_or_disconnected (std::string, std::string); @@ -116,6 +119,7 @@ private: Gtkmm2ext::Bindings* bindings; Gtk::VBox _content; Gtk::HBox _toolbar; + Gtk::HBox _rec_top; ArdourWidgets::VPane _pane; Gtk::ScrolledWindow _rec_scroller; Gtk::VBox _rec_container; @@ -132,6 +136,8 @@ private: ArdourWidgets::ArdourButton _btn_rec_forget; ArdourWidgets::ArdourButton _btn_new_take; ArdourWidgets::ArdourButton _btn_peak_reset; + RecInfoBox _rec_info_box; + TransportControlUI _transport_ctrl; int _meter_box_width; int _meter_area_cols; diff --git a/gtk2_ardour/transport_control_ui.cc b/gtk2_ardour/transport_control_ui.cc index c3b6fb552a..f7c2a320b3 100644 --- a/gtk2_ardour/transport_control_ui.cc +++ b/gtk2_ardour/transport_control_ui.cc @@ -160,6 +160,8 @@ TransportControlUI::setup (TransportControlProvider* ui) stop_button.set_active (true); + show_all (); + Timers::blink_connect (sigc::mem_fun (*this, &TransportControlUI::blink_rec_enable)); } diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index a3fd7ffed5..684a647ea1 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -226,6 +226,7 @@ gtk2_ardour_sources = [ 'public_editor.cc', 'quantize_dialog.cc', 'rc_option_editor.cc', + 'rec_info_box.cc', 'recorder_group_tabs.cc', 'recorder_ui.cc', 'region_editor.cc',