ardour/gtk2_ardour/track_record_axis.cc
Robin Gareus 699fc983ae
Use transient parent for color dialog, and position it to pointer
This hopefully fixes an odd macOS specific focus issue.
It also moves the dialog to the popup position when
showing the same dialog using the editor-mixer and later
a detached mixer.
2025-08-29 02:50:22 +02:00

906 lines
25 KiB
C++

/*
* Copyright (C) 2020 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 <list>
#include <sigc++/bind.h>
#include "pbd/unwind.h"
#include "ardour/logmeter.h"
#include "ardour/meter.h"
#include "ardour/playlist.h"
#include "ardour/route.h"
#include "ardour/route_group.h"
#include "ardour/selection.h"
#include "ardour/session.h"
#include "ardour/track.h"
#include "ardour/audio_track.h"
#include "ardour/midi_track.h"
#include "gtkmm2ext/colors.h"
#include "gtkmm2ext/gtk_ui.h"
#include "gtkmm2ext/keyboard.h"
#include "gtkmm2ext/rgb_macros.h"
#include "gtkmm2ext/utils.h"
#include "widgets/tooltips.h"
#include "ardour_message.h"
#include "ardour_window.h"
#include "context_menu_helper.h"
#include "editor_cursors.h"
#include "group_tabs.h"
#include "gui_thread.h"
#include "keyboard.h"
#include "level_meter.h"
#include "meter_patterns.h"
#include "public_editor.h"
#include "route_group_menu.h"
#include "timers.h"
#include "ui_config.h"
#include "utils.h"
#include "track_record_axis.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace ArdourMeter;
using namespace ArdourWidgets;
using namespace ARDOUR_UI_UTILS;
using namespace PBD;
using namespace Gtk;
using namespace Gtkmm2ext;
using namespace std;
PBD::Signal<void(TrackRecordAxis*)> TrackRecordAxis::CatchDeletion;
PBD::Signal<void(TrackRecordAxis*, bool)> TrackRecordAxis::EditNextName;
#define PX_SCALE(pxmin, dflt) rint (std::max ((double)pxmin, (double)dflt* UIConfiguration::instance ().get_ui_scale ()))
bool TrackRecordAxis::_size_group_initialized = false;
Glib::RefPtr<Gtk::SizeGroup> TrackRecordAxis::_track_number_size_group;
TrackRecordAxis::TrackRecordAxis (Session* s, std::shared_ptr<ARDOUR::Route> rt)
: SessionHandlePtr (s)
, RouteUI (s)
, _clear_meters (true)
, _route_ops_menu (0)
, _renaming (false)
, _nameentry_ctx (false)
, _input_button (true)
, _playlist_button (S_("RTAV|P"))
, _name_frame (ArdourWidgets::Frame::Horizontal, true)
, _vseparator (1.0)
, _ctrls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
, _monitor_ctrl_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
, _track_summary (rt)
{
if (!_size_group_initialized) {
_size_group_initialized = true;
_track_number_size_group = Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH);
}
RouteUI::set_route (rt);
UI::instance ()->theme_changed.connect (sigc::mem_fun (*this, &TrackRecordAxis::on_theme_changed));
UIConfiguration::instance ().ColorsChanged.connect (sigc::mem_fun (*this, &TrackRecordAxis::on_theme_changed));
UIConfiguration::instance ().DPIReset.connect (sigc::mem_fun (*this, &TrackRecordAxis::on_theme_changed));
UIConfiguration::instance ().ParameterChanged.connect (sigc::mem_fun (*this, &TrackRecordAxis::parameter_changed));
Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&TrackRecordAxis::parameter_changed, this, _1), gui_context ());
s->config.ParameterChanged.connect (*this, invalidator (*this), ui_bind (&TrackRecordAxis::parameter_changed, this, _1), gui_context ());
PublicEditor::instance().playhead_cursor()->PositionChanged.connect (*this, invalidator (*this), std::bind (&TrackSummary::playhead_position_changed, &_track_summary, _1), gui_context());
ResetAllPeakDisplays.connect (sigc::mem_fun (*this, &TrackRecordAxis::reset_peak_display));
ResetRoutePeakDisplays.connect (sigc::mem_fun (*this, &TrackRecordAxis::reset_route_peak_display));
ResetGroupPeakDisplays.connect (sigc::mem_fun (*this, &TrackRecordAxis::reset_group_peak_display));
_number_label.set_name ("tracknumber label");
_number_label.set_elements ((ArdourButton::Element) (ArdourButton::Edge | ArdourButton::Body | ArdourButton::Text | ArdourButton::Inactive));
_number_label.set_alignment (.5, .5);
_number_label.set_fallthrough_to_parent (true);
_number_label.signal_button_press_event().connect (sigc::mem_fun(*this, &TrackRecordAxis::route_ops_click), false);
_playlist_button.set_name ("route button");
_playlist_button.signal_button_press_event().connect (sigc::mem_fun(*this, &TrackRecordAxis::playlist_click), false);
_level_meter = new LevelMeterVBox (s);
_level_meter->set_meter (_route->shared_peak_meter ().get ());
_level_meter->clear_meters ();
_level_meter->setup_meters (120, 10, 3);
name_label.set_name (X_("TrackNameEditor"));
name_label.set_alignment (0.0, 0.5);
name_label.set_padding (4, 0);
name_label.set_width_chars (12);
_namebox.add (name_label);
_namebox.add_events (Gdk::BUTTON_PRESS_MASK);
_namebox.signal_button_press_event ().connect (sigc::mem_fun (*this, &TrackRecordAxis::namebox_button_press));
_name_frame.add (_namebox);
_name_frame.set_edge_color (0x000000ff);
_name_frame.set_border_width (0);
_name_frame.set_padding (0);
_input_button.set_sizing_text ("Capture_8888");
_input_button.set_route (rt, this);
parameter_changed ("editor-stereo-only-meters");
parameter_changed ("time-axis-name-ellipsize-mode");
/* force the track header buttons into a boxy grid-shape */
rec_enable_button->set_tweaks(ArdourButton::Tweaks(ArdourButton::TrackHeader | ArdourButton::ForceBoxy));
mute_button->set_tweaks(ArdourButton::Tweaks(ArdourButton::TrackHeader | ArdourButton::ForceBoxy));
monitor_disk_button->set_tweaks(ArdourButton::Tweaks(ArdourButton::ForceBoxy));
monitor_input_button->set_tweaks(ArdourButton::Tweaks(ArdourButton::ForceBoxy));
_playlist_button.set_tweaks(ArdourButton::Tweaks(ArdourButton::TrackHeader | ArdourButton::ForceBoxy));
_input_button.set_tweaks(ArdourButton::Tweaks(ArdourButton::ForceBoxy));
_number_label.set_tweaks(ArdourButton::Tweaks(ArdourButton::ForceBoxy | ArdourButton::ForceFlat));
_ctrls.attach (*rec_enable_button, 1, 2, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (*mute_button, 2, 3, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_input_button, 3, 4, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_playlist_button, 4, 5, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_name_frame, 5, 6, 0, 1, Gtk::FILL, Gtk::FILL, 0, 0);
_ctrls.attach (*monitor_input_button, 6, 7, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (*monitor_disk_button, 7, 8, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (*_level_meter, 8, 9, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
_ctrls.attach (_number_label, 9, 10, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_vseparator, 10, 11, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_track_summary, 11, 12, 0, 1, Gtk::EXPAND|FILL, Gtk::FILL, 1, 0);
set_tooltip (*mute_button, _("Mute"));
set_tooltip (*rec_enable_button, _("Record"));
set_tooltip (*mute_button, _("Mute"));
set_tooltip (_playlist_button, _("Playlist")); // playlist_tip ()
set_name_label ();
update_sensitivity ();
_track_number_size_group->add_widget (_number_label);
_ctrls_button_size_group->add_widget (*mute_button);
_ctrls_button_size_group->add_widget (_playlist_button);
_monitor_ctrl_size_group->add_widget (*monitor_input_button);
_monitor_ctrl_size_group->add_widget (*monitor_disk_button);
pack_start (_ctrls, false, false);
rec_enable_button->show ();
mute_button->show ();
monitor_input_button->show ();
monitor_disk_button->show ();
mute_button->show ();
_level_meter->show ();
_playlist_button.show();
_number_label.show ();
_name_frame.show ();
_namebox.show ();
name_label.show ();
_input_button.show ();
_track_summary.show ();
_vseparator.show ();
_ctrls.show ();
}
TrackRecordAxis::~TrackRecordAxis ()
{
delete _level_meter;
delete _route_ops_menu;
CatchDeletion (this);
}
void
TrackRecordAxis::self_delete ()
{
delete this;
}
void
TrackRecordAxis::set_session (Session* s)
{
RouteUI::set_session (s);
if (!s) {
return;
}
s->config.ParameterChanged.connect (*this, invalidator (*this), ui_bind (&TrackRecordAxis::parameter_changed, this, _1), gui_context ());
}
void
TrackRecordAxis::route_rec_enable_changed ()
{
if (_route->rec_enable_control()->get_value()) {
/* end renaming when rec-arm engages
* (due to modal grab this can only be triggered by ctrl surfaces) */
end_rename (true);
}
RouteUI::route_rec_enable_changed ();
}
void
TrackRecordAxis::blink_rec_display (bool onoff)
{
RouteUI::blink_rec_display (onoff);
}
std::string
TrackRecordAxis::state_id () const
{
if (_route) {
return string_compose ("recctrl %1", _route->id ().to_s ());
} else {
return string ();
}
}
void
TrackRecordAxis::set_button_names ()
{
mute_button->set_text (S_("Mute|M"));
#if 0
monitor_input_button->set_text (S_("MonitorInput|I"));
monitor_disk_button->set_text (S_("MonitorDisk|D"));
#else
monitor_input_button->set_text (S_("Monitor|In"));
monitor_disk_button->set_text (S_("Monitor|Disk"));
#endif
/* Solo/Listen is N/A */
}
void
TrackRecordAxis::route_property_changed (const PropertyChange& what_changed)
{
if (!what_changed.contains (ARDOUR::Properties::name)) {
return;
}
ENSURE_GUI_THREAD (*this, &TrackRecordAxis::route_property_changed, what_changed);
set_name_label ();
set_tooltip (*_level_meter, _route->name ());
}
void
TrackRecordAxis::route_color_changed ()
{
_number_label.set_fixed_colors (gdk_color_to_rgba (color ()), gdk_color_to_rgba (color ()));
}
void
TrackRecordAxis::on_theme_changed ()
{
}
void
TrackRecordAxis::on_size_request (Gtk::Requisition* r)
{
VBox::on_size_request (r);
}
void
TrackRecordAxis::on_size_allocate (Gtk::Allocation& a)
{
VBox::on_size_allocate (a);
}
void
TrackRecordAxis::parameter_changed (std::string const& p)
{
if (p == "editor-stereo-only-meters") {
#if 0
if (UIConfiguration::instance ().get_editor_stereo_only_meters ()) {
_level_meter->set_max_audio_meter_count (2);
} else {
_level_meter->set_max_audio_meter_count (0);
}
#endif
} else if (p == "time-axis-name-ellipsize-mode") {
set_name_ellipsize_mode ();
}
}
string
TrackRecordAxis::name () const
{
return _route->name ();
}
Gdk::Color
TrackRecordAxis::color () const
{
return RouteUI::route_color ();
}
void
TrackRecordAxis::set_name_label ()
{
string x = _route->name ();
if (x != name_label.get_text ()) {
name_label.set_text (x);
}
set_tooltip (name_label, _route->name ());
const int64_t track_number = _route->track_number ();
assert (track_number > 0);
_number_label.set_text (PBD::to_string (track_number));
}
bool
TrackRecordAxis::namebox_button_press (GdkEventButton* ev)
{
if (_renaming) {
return false;
}
if ((ev->button == 1 && ev->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (ev)) {
if (!start_rename ()) {
ArdourMessageDialog msg (_("Inactive and record-armed tracks cannot be renamed"));
msg.run ();
}
return true;
}
return false;
}
void
TrackRecordAxis::route_active_changed ()
{
RouteUI::route_active_changed ();
update_sensitivity ();
}
void
TrackRecordAxis::map_frozen ()
{
RouteUI::map_frozen ();
switch (track()->freeze_state()) {
case Track::Frozen:
_playlist_button.set_sensitive (false);
break;
default:
_playlist_button.set_sensitive (true);
break;
}
update_sensitivity ();
}
void
TrackRecordAxis::update_sensitivity ()
{
bool en = _route->active ();
monitor_input_button->set_sensitive (en);
monitor_disk_button->set_sensitive (en);
_input_button.set_sensitive (en);
_ctrls.set_sensitive (en);
if (!en) {
end_rename (true);
}
if (!is_track() || track()->mode() != ARDOUR::Normal) {
_playlist_button.set_sensitive (false);
}
}
void
TrackRecordAxis::set_gui_extents (samplepos_t s, samplepos_t e)
{
_track_summary.set_gui_extents (s, e);
}
bool
TrackRecordAxis::rec_extent (samplepos_t& s, samplepos_t& e) const
{
return _track_summary.rec_extent (s, e);
}
int
TrackRecordAxis::summary_xpos () const
{
return _ctrls.get_width () - _track_summary.get_width ();
}
int
TrackRecordAxis::summary_width () const
{
return _track_summary.get_width ();
}
void
TrackRecordAxis::fast_update ()
{
if (_clear_meters) {
_level_meter->clear_meters ();
_clear_meters = false;
}
_level_meter->update_meters ();
}
void
TrackRecordAxis::reset_route_peak_display (Route* route)
{
if (_route && _route.get () == route) {
reset_peak_display ();
}
}
void
TrackRecordAxis::reset_group_peak_display (RouteGroup* group)
{
if (_route && group == _route->route_group ()) {
reset_peak_display ();
}
}
void
TrackRecordAxis::reset_peak_display ()
{
_route->shared_peak_meter ()->reset_max ();
_clear_meters = true;
}
bool
TrackRecordAxis::playlist_click (GdkEventButton* ev)
{
if (ev->button != 1) {
return true;
}
build_playlist_menu ();
_route->session ().selection().select_stripable_and_maybe_group (_route, SelectionSet, false, true, nullptr);
Gtkmm2ext::anchored_menu_popup (playlist_action_menu, &_playlist_button, "", 1, ev->time);
return true;
}
bool
TrackRecordAxis::route_ops_click (GdkEventButton* ev)
{
if (ev->button != 3 ) {
return false;
}
build_route_ops_menu ();
_route->session ().selection().select_stripable_and_maybe_group (_route, SelectionSet, false, true, nullptr);
Gtkmm2ext::anchored_menu_popup (_route_ops_menu, &_number_label, "", 1, ev->time);
return true;
}
void
TrackRecordAxis::build_route_ops_menu ()
{
using namespace Menu_Helpers;
delete _route_ops_menu;
_route_ops_menu = new Menu;
_route_ops_menu->set_name ("ArdourContextMenu");
MenuList& items = _route_ops_menu->items ();
items.push_back (MenuElem (_("Color..."), sigc::bind (sigc::mem_fun (*this, &RouteUI::choose_color), dynamic_cast<Gtk::Window*> (get_toplevel()))));
items.push_back (MenuElem (_("Comments..."), sigc::mem_fun (*this, &RouteUI::open_comment_editor)));
items.push_back (MenuElem (_("Inputs..."), sigc::mem_fun (*this, &RouteUI::edit_input_configuration)));
items.push_back (MenuElem (_("Outputs..."), sigc::mem_fun (*this, &RouteUI::edit_output_configuration)));
items.push_back (SeparatorElem());
items.push_back (MenuElem (_("Rename..."), sigc::mem_fun(*this, &RouteUI::route_rename)));
/* do not allow rename if the track is record-enabled */
items.back().set_sensitive (!is_track() || !track()->rec_enable_control()->get_value());
}
/* ****************************************************************************/
bool
TrackRecordAxis::start_rename ()
{
if (_renaming || _route->rec_enable_control()->get_value() || !_route->active ()) {
return false;
}
assert (_entry_connections.empty ());
GtkRequisition r (name_label.size_request ());
_nameentry.set_size_request (r.width, -1);
_nameentry.set_text (_route->name ());
_namebox.remove ();
_namebox.add (_nameentry);
_nameentry.show ();
_nameentry.grab_focus ();
_nameentry.add_modal_grab ();
_renaming = true;
_entry_connections.push_back (_nameentry.signal_changed().connect (sigc::mem_fun (*this, &TrackRecordAxis::entry_changed)));
_entry_connections.push_back (_nameentry.signal_activate().connect (sigc::mem_fun (*this, &TrackRecordAxis::entry_activated)));
_entry_connections.push_back (_nameentry.signal_key_press_event().connect (sigc::mem_fun (*this, &TrackRecordAxis::entry_key_press), false));
_entry_connections.push_back (_nameentry.signal_key_release_event().connect (sigc::mem_fun (*this, &TrackRecordAxis::entry_key_release), false));
_entry_connections.push_back (_nameentry.signal_button_press_event ().connect (sigc::mem_fun (*this, &TrackRecordAxis::entry_button_press), false));
_entry_connections.push_back (_nameentry.signal_focus_in_event ().connect (sigc::mem_fun (*this, &TrackRecordAxis::entry_focus_in)));
_entry_connections.push_back (_nameentry.signal_focus_out_event ().connect (sigc::mem_fun (*this, &TrackRecordAxis::entry_focus_out)));
_entry_connections.push_back (_nameentry.signal_populate_popup ().connect (sigc::mem_fun (*this, &TrackRecordAxis::entry_populate_popup)));
return true;
}
void
TrackRecordAxis::end_rename (bool ignore_change)
{
if (!_renaming) {
return;
}
std::string result = _nameentry.get_text ();
disconnect_entry_signals ();
_nameentry.remove_modal_grab ();
_namebox.remove ();
_namebox.add (name_label);
name_label.show ();
_renaming = false;
if (ignore_change) {
return;
}
if (verify_new_route_name (result)) {
_route->set_name (result);
}
}
void
TrackRecordAxis::entry_changed ()
{
}
void
TrackRecordAxis::entry_activated ()
{
end_rename (false);
}
void
TrackRecordAxis::entry_populate_popup (Gtk::Menu*)
{
_nameentry_ctx = true;
}
bool
TrackRecordAxis::entry_focus_in (GdkEventFocus*)
{
_nameentry_ctx = false;
return false;
}
bool
TrackRecordAxis::entry_focus_out (GdkEventFocus*)
{
if (!_nameentry_ctx) {
end_rename (false);
}
return false;
}
bool
TrackRecordAxis::entry_button_press (GdkEventButton* ev)
{
if (Keyboard::is_context_menu_event (ev)) {
return false;
} else if (Gtkmm2ext::event_inside_widget_window (_namebox, (GdkEvent*) ev)) {
return false;
} else {
end_rename (false);
return false;
}
}
bool
TrackRecordAxis::entry_key_press (GdkEventKey* ev)
{
switch (ev->keyval) {
case GDK_Escape:
/* fallthrough */
case GDK_ISO_Left_Tab:
/* fallthrough */
case GDK_Tab:
/* fallthrough */
return true;
default:
break;
}
return false;
}
bool
TrackRecordAxis::entry_key_release (GdkEventKey* ev)
{
switch (ev->keyval) {
case GDK_Escape:
end_rename (true);
return true;
case GDK_ISO_Left_Tab:
end_rename (false);
EditNextName (this, false); /* EMIT SIGNAL */
return true;
case GDK_Tab:
end_rename (false);
EditNextName (this, true); /* EMIT SIGNAL */
return true;
default:
break;
}
return false;
}
void
TrackRecordAxis::disconnect_entry_signals ()
{
for (std::list<sigc::connection>::iterator i = _entry_connections.begin(); i != _entry_connections.end(); ++i) {
i->disconnect ();
}
_entry_connections.clear ();
}
/* ****************************************************************************/
TrackRecordAxis::TrackSummary::TrackSummary (std::shared_ptr<ARDOUR::Route> r)
: _start (0)
, _end (480000)
, _xscale (1)
, _last_playhead (0)
, _rec_updating (false)
, _rec_active (false)
{
_track = std::dynamic_pointer_cast<Track> (r);
assert (_track);
_track->PlaylistChanged.connect (_connections, invalidator (*this), std::bind (&TrackSummary::playlist_changed, this), gui_context ());
_track->playlist()->ContentsChanged.connect (_playlist_connections, invalidator (*this), std::bind (&TrackSummary::playlist_contents_changed, this), gui_context ());
_track->presentation_info().PropertyChanged.connect (_connections, invalidator (*this), std::bind (&TrackSummary::property_changed, this, _1), gui_context ());
_track->rec_enable_control()->Changed.connect (_connections, invalidator (*this), std::bind (&TrackSummary::maybe_setup_rec_box, this), gui_context());
_track->session().TransportStateChange.connect (_connections, invalidator (*this), std::bind (&TrackSummary::maybe_setup_rec_box, this), gui_context());
_track->session().TransportLooped.connect (_connections, invalidator (*this), std::bind (&TrackSummary::maybe_setup_rec_box, this), gui_context());
_track->session().RecordStateChanged.connect (_connections, invalidator (*this), std::bind (&TrackSummary::maybe_setup_rec_box, this), gui_context());
}
TrackRecordAxis::TrackSummary::~TrackSummary ()
{
_rec_active = false;
if (_rec_updating) {
_screen_update_connection.disconnect();
}
}
void
TrackRecordAxis::TrackSummary::render (Cairo::RefPtr<Cairo::Context> const& cr, cairo_rectangle_t* r)
{
cr->rectangle (r->x, r->y, r->width, r->height);
cr->clip ();
RouteGroup* g = _track->route_group ();
if (g && g->is_color()) {
Gtkmm2ext::set_source_rgba (cr, GroupTabs::group_color (g));
} else {
Gtkmm2ext::set_source_rgba (cr, _track->presentation_info ().color ());
}
double w = get_width();
double h = get_height();
double ht = h - 2;
double yc = 1 + ht / 2.;
cr->set_line_width (ht);
_track->playlist()->foreach_region(sigc::bind (sigc::mem_fun (*this, &TrackSummary::render_region), cr, yc));
/* Record Boxes */
if (_rec_rects.size () > 0) {
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance().color_mod("recording rect", "recording_rect"));
for (std::vector<RecInfo>::const_iterator i = _rec_rects.begin (); i != _rec_rects.end (); ++i) {
const samplepos_t rs = i->capture_start;
const samplecnt_t re = i->capture_end;
if (re > rs) {
cr->move_to (sample_to_xpos (rs), yc);
cr->line_to (sample_to_xpos (re), yc);
cr->stroke ();
}
}
}
/* top & btm border */
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance().color ("neutral:backgroundest"));
cr->set_line_width (1.0);
cr->move_to (0, 0.5);
cr->line_to (w, 0.5);
cr->stroke ();
cr->move_to (0, h);
cr->line_to (w, h);
cr->stroke ();
/* Playhead */
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance().color ("play head"));
const double phx = sample_to_xpos (PublicEditor::instance().playhead_cursor ()->current_sample());
cr->set_line_width (1.0);
cr->move_to (floor (phx) + .5, 0);
cr->line_to (floor (phx) + .5, h);
cr->stroke ();
_last_playhead = phx;
}
void
TrackRecordAxis::TrackSummary::render_region (std::shared_ptr<ARDOUR::Region> r, Cairo::RefPtr<Cairo::Context> const& cr, double y)
{
const samplepos_t rp = r->position_sample ();
const samplecnt_t rl = r->length_samples ();
if (rp > _start) {
cr->move_to (sample_to_xpos (rp), y);
} else {
cr->move_to (0, y);
}
if (rp + rl > _start) {
cr->line_to (sample_to_xpos (rp + rl), y);
cr->stroke ();
} else {
cr->begin_new_path ();
}
}
void
TrackRecordAxis::TrackSummary::maybe_setup_rec_box ()
{
if (_track->session ().transport_stopped_or_stopping () || !(_track->session ().transport_rolling () || _track->session ().get_record_enabled ())) {
/* stopped, or not roll/rec */
if (_rec_updating) {
_rec_rects.clear ();
_screen_update_connection.disconnect();
_rec_updating = false;
_rec_active = false;
set_dirty ();
}
return;
}
if (!_track->rec_enable_control()->get_value() || !_track->session ().actively_recording ()) {
/* rolling but not (or no longer) recording [yet] */
_rec_active = false;
return;
}
if (!_rec_active) {
const samplepos_t rs = _track->current_capture_start ();
_rec_rects.push_back (RecInfo (rs, rs));
}
_rec_active = true;
if (!_rec_updating) {
_screen_update_connection.disconnect();
_screen_update_connection = Timers::rapid_connect (sigc::mem_fun(*this, &TrackSummary::update_rec_box));
_rec_updating = true;
}
}
void
TrackRecordAxis::TrackSummary::update_rec_box ()
{
if (_rec_active && _rec_rects.size () > 0) {
RecInfo& rect = _rec_rects.back ();
rect.capture_start = _track->current_capture_start ();
rect.capture_end = _track->current_capture_end ();
set_dirty ();
}
}
void
TrackRecordAxis::TrackSummary::playhead_position_changed (samplepos_t p)
{
int const o = _last_playhead;
int const n = sample_to_xpos (p);
if (o != n) {
int a = max (2, min (o, n));
int b = max (o, n);
cairo_rectangle_t r;
r.x = a - 2;
r.y = 0;
r.width = b - a + 4;
r.height = get_height ();
set_dirty (&r);
}
}
void
TrackRecordAxis::TrackSummary::playlist_changed ()
{
_playlist_connections.disconnect ();
_track->playlist()->ContentsChanged.connect (_playlist_connections, invalidator (*this), std::bind (&TrackSummary::playlist_contents_changed, this), gui_context ());
set_dirty ();
}
void
TrackRecordAxis::TrackSummary::playlist_contents_changed ()
{
set_dirty ();
}
void
TrackRecordAxis::TrackSummary::property_changed (PropertyChange const& what_changed)
{
if (what_changed.contains (Properties::color)) {
set_dirty ();
}
}
void
TrackRecordAxis::TrackSummary::on_size_request (Gtk::Requisition* req)
{
req->width = 200;
req->height = 16;
}
void
TrackRecordAxis::TrackSummary::on_size_allocate (Gtk::Allocation& a)
{
CairoWidget::on_size_allocate (a);
if (_end > _start) {
_xscale = static_cast<double> (a.get_width ()) / (_end - _start);
}
}
void
TrackRecordAxis::TrackSummary::set_gui_extents (samplepos_t start, samplepos_t end)
{
if (_start == start && _end == end) {
return;
}
_start = start;
_end = end;
_xscale = static_cast<double> (get_width ()) / (_end - _start);
set_dirty ();
}
bool
TrackRecordAxis::TrackSummary::on_button_press_event (GdkEventButton* ev)
{
if (_track->session ().actively_recording ()) {
return false;
}
// use _start + ev->x / _xscale
_track->session ().request_locate (_start + (double) (_end - _start) * ev->x / get_width ());
return true;
}
bool
TrackRecordAxis::TrackSummary::rec_extent (samplepos_t& start, samplepos_t& end) const
{
if (_rec_rects.size () == 0) {
return false;
}
for (std::vector<RecInfo>::const_iterator i = _rec_rects.begin (); i != _rec_rects.end (); ++i) {
start = std::min (start, i->capture_start);
end = std::max (end, i->capture_end);
}
return true;
}