13
0
livetrax/gtk2_ardour/meterbridge.cc
Robin Gareus 4050ca5633
Update GPL boilerplate and (C)
Copyright-holder and year information is extracted from git log.

git history begins in 2005. So (C) from 1998..2005 is lost. Also some
(C) assignment of commits where the committer didn't use --author.
2019-08-03 15:53:15 +02:00

774 lines
19 KiB
C++

/*
* Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
* Copyright (C) 2014-2017 Tim Mayberry <mojofunk@gmail.com>
* Copyright (C) 2015-2016 Paul Davis <paul@linuxaudiosystems.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.
*/
#ifdef WAF_BUILD
#include "gtk2ardour-config.h"
#endif
#include <map>
#include <sigc++/bind.h>
#include <gtkmm/accelmap.h>
#include <gtkmm/comboboxtext.h>
#include <glibmm/threads.h>
#include <gtkmm2ext/gtk_ui.h>
#include <gtkmm2ext/utils.h>
#include <gtkmm2ext/window_title.h>
#include "ardour/debug.h"
#include "ardour/midi_track.h"
#include "ardour/route_group.h"
#include "ardour/session.h"
#include "ardour/audio_track.h"
#include "ardour/midi_track.h"
#include "meterbridge.h"
#include "keyboard.h"
#include "monitor_section.h"
#include "public_editor.h"
#include "ardour_ui.h"
#include "utils.h"
#include "route_sorter.h"
#include "actions.h"
#include "gui_thread.h"
#include "meter_patterns.h"
#include "timers.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace ARDOUR_UI_UTILS;
using namespace PBD;
using namespace Gtk;
using namespace Glib;
using namespace Gtkmm2ext;
using namespace std;
using namespace ArdourMeter;
using PBD::atoi;
Meterbridge* Meterbridge::_instance = 0;
Meterbridge*
Meterbridge::instance ()
{
if (!_instance) {
_instance = new Meterbridge;
}
return _instance;
}
Meterbridge::Meterbridge ()
: Window (Gtk::WINDOW_TOPLEVEL)
, VisibilityTracker (*((Gtk::Window*) this))
, _visible (false)
, _show_busses (false)
, metrics_left (1, MeterPeak)
, metrics_right (2, MeterPeak)
, cur_max_width (-1)
{
set_name ("Meter Bridge");
m_width = default_width;
m_height = default_height;
m_root_x = 1;
m_root_y = 1;
update_title ();
set_wmclass (X_("ardour_mixer"), PROGRAM_NAME);
#ifdef __APPLE__
set_type_hint (Gdk::WINDOW_TYPE_HINT_DIALOG);
#else
if (UIConfiguration::instance().get_all_floating_windows_are_dialogs()) {
set_type_hint (Gdk::WINDOW_TYPE_HINT_DIALOG);
} else {
set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
}
#endif
Gdk::Geometry geom;
geom.max_width = 1<<16;
geom.max_height = max_height;
geom.min_width = 40;
geom.min_height = -1;
geom.height_inc = 16;
geom.width_inc = 1;
assert(max_height % 16 == 0);
set_geometry_hints(*((Gtk::Window*) this), geom, Gdk::HINT_MIN_SIZE | Gdk::HINT_MAX_SIZE | Gdk::HINT_RESIZE_INC);
set_border_width (0);
metrics_vpacker_left.pack_start (metrics_left, true, true);
metrics_vpacker_left.pack_start (metrics_spacer_left, false, false);
metrics_spacer_left.set_size_request(-1, 0);
metrics_spacer_left.set_spacing(0);
metrics_vpacker_right.pack_start (metrics_right, true, true);
metrics_vpacker_right.pack_start (metrics_spacer_right, false, false);
metrics_spacer_right.set_size_request(-1, 0);
metrics_spacer_right.set_spacing(0);
signal_delete_event().connect (sigc::mem_fun (*this, &Meterbridge::hide_window));
signal_configure_event().connect (sigc::mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
MeterStrip::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&Meterbridge::remove_strip, this, _1), gui_context());
MeterStrip::MetricChanged.connect (*this, invalidator (*this), boost::bind(&Meterbridge::sync_order_keys, this), gui_context());
MeterStrip::ConfigurationChanged.connect (*this, invalidator (*this), boost::bind(&Meterbridge::queue_resize, this), gui_context());
PresentationInfo::Change.connect (*this, invalidator (*this), boost::bind (&Meterbridge::resync_order, this, _1), gui_context());
/* work around ScrolledWindowViewport alignment mess Part one */
Gtk::HBox * yspc = manage (new Gtk::HBox());
yspc->set_size_request(-1, 1);
Gtk::VBox * xspc = manage (new Gtk::VBox());
xspc->pack_start(meterarea, true, true);
xspc->pack_start(*yspc, false, false);
yspc->show();
xspc->show();
meterarea.set_spacing(0);
scroller.set_shadow_type(Gtk::SHADOW_NONE);
scroller.set_border_width(0);
scroller.add (*xspc);
scroller.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER);
global_hpacker.pack_start (metrics_vpacker_left, false, false);
global_hpacker.pack_start (scroller, true, true);
global_hpacker.pack_start (metrics_vpacker_right, false, false);
global_vpacker.pack_start (global_hpacker, true, true);
add (global_vpacker);
metrics_left.show();
metrics_right.show();
metrics_vpacker_left.show();
metrics_spacer_left.show();
metrics_vpacker_right.show();
metrics_spacer_right.show();
meterarea.show();
global_vpacker.show();
global_hpacker.show();
scroller.show();
/* the return of the ScrolledWindowViewport mess:
* remove shadow from scrollWindow's viewport
* see http://www.mail-archive.com/gtkmm-list@gnome.org/msg03509.html
*/
Gtk::Viewport* viewport = (Gtk::Viewport*) scroller.get_child();
viewport->set_shadow_type(Gtk::SHADOW_NONE);
viewport->set_border_width(0);
UI::instance()->theme_changed.connect (sigc::mem_fun(*this, &Meterbridge::on_theme_changed));
UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Meterbridge::on_theme_changed));
UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &Meterbridge::on_theme_changed));
}
Meterbridge::~Meterbridge ()
{
while (_metrics.size() > 0) {
delete (_metrics.back());
_metrics.pop_back();
}
}
void
Meterbridge::show_window ()
{
present();
if (!_visible) {
set_window_pos_and_size ();
}
_visible = true;
}
/* code duplicated from gtk2_ardour/mixer_ui.cc Mixer_UI::update_title() */
void
Meterbridge::update_title ()
{
if (_session) {
string n;
if (_session->snap_name() != _session->name()) {
n = _session->snap_name ();
} else {
n = _session->name ();
}
if (_session->dirty ()) {
n = "*" + n;
}
WindowTitle title (n);
title += S_("Window|Meterbridge");
title += Glib::get_application_name ();
set_title (title.get_string());
} else {
WindowTitle title (S_("Window|Meterbridge"));
title += Glib::get_application_name ();
set_title (title.get_string());
}
}
void
Meterbridge::set_window_pos_and_size ()
{
resize (m_width, m_height);
if (m_root_x >= 0 && m_root_y >= 0) {
move (m_root_x, m_root_y);
}
}
void
Meterbridge::get_window_pos_and_size ()
{
get_position(m_root_x, m_root_y);
get_size(m_width, m_height);
}
bool
Meterbridge::hide_window (GdkEventAny *ev)
{
if (!_visible) return 0;
get_window_pos_and_size();
_visible = false;
return just_hide_it(ev, static_cast<Gtk::Window *>(this));
}
bool
Meterbridge::on_key_press_event (GdkEventKey* ev)
{
if (gtk_window_propagate_key_event (GTK_WINDOW(gobj()), ev)) {
return true;
}
return relay_key_press (ev, this);
}
bool
Meterbridge::on_key_release_event (GdkEventKey* ev)
{
if (gtk_window_propagate_key_event (GTK_WINDOW(gobj()), ev)) {
return true;
}
/* don't forward releases */
return true;
}
bool
Meterbridge::on_scroll_event (GdkEventScroll* ev)
{
switch (ev->direction) {
case GDK_SCROLL_LEFT:
scroll_left ();
return true;
case GDK_SCROLL_UP:
if (ev->state & Keyboard::TertiaryModifier) {
scroll_left ();
return true;
}
return false;
case GDK_SCROLL_RIGHT:
scroll_right ();
return true;
case GDK_SCROLL_DOWN:
if (ev->state & Keyboard::TertiaryModifier) {
scroll_right ();
return true;
}
return false;
}
return false;
}
void
Meterbridge::scroll_left ()
{
if (!scroller.get_hscrollbar()) return;
Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
/* stupid GTK: can't rely on clamping across versions */
scroller.get_hscrollbar()->set_value (max (adj->get_lower(), adj->get_value() - adj->get_step_increment()));
}
void
Meterbridge::scroll_right ()
{
if (!scroller.get_hscrollbar()) return;
Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
/* stupid GTK: can't rely on clamping across versions */
scroller.get_hscrollbar()->set_value (min (adj->get_upper(), adj->get_value() + adj->get_step_increment()));
}
void
Meterbridge::on_size_request (Gtk::Requisition* r)
{
meter_clear_pattern_cache(3);
Gtk::Window::on_size_request(r);
Gdk::Geometry geom;
Gtk::Requisition mr = meterarea.size_request();
geom.max_width = mr.width + metrics_left.get_width() + metrics_right.get_width();
geom.max_width = std::max(50, geom.max_width);
geom.max_height = max_height;
if (cur_max_width != geom.max_width) {
cur_max_width = geom.max_width;
/* height resizes are 'heavy' since the metric areas and meter-patterns
* are re-generated. limit to 16px steps. */
geom.height_inc = 16;
geom.width_inc = 1;
geom.min_width = 40;
geom.min_height = -1;
set_geometry_hints(*((Gtk::Window*) this), geom, Gdk::HINT_MIN_SIZE | Gdk::HINT_MAX_SIZE | Gdk::HINT_RESIZE_INC);
}
}
void
Meterbridge::on_size_allocate (Gtk::Allocation& a)
{
const Gtk::Scrollbar * hsc = scroller.get_hscrollbar();
/* switch left/right edge patterns depending on horizontal scroll-position */
if (scroller.get_hscrollbar_visible() && hsc) {
if (!scroll_connection.connected()) {
scroll_connection = scroller.get_hscrollbar()->get_adjustment()->signal_value_changed().connect(sigc::mem_fun (*this, &Meterbridge::on_scroll));
scroller.get_hscrollbar()->get_adjustment()->signal_changed().connect(sigc::mem_fun (*this, &Meterbridge::on_scroll));
}
gint scrollbar_spacing;
gtk_widget_style_get (GTK_WIDGET (scroller.gobj()),
"scrollbar-spacing", &scrollbar_spacing, NULL);
const int h = hsc->get_height() + scrollbar_spacing + 1;
metrics_spacer_left.set_size_request(-1, h);
metrics_spacer_right.set_size_request(-1, h);
} else {
metrics_spacer_left.set_size_request(-1, 0);
metrics_spacer_right.set_size_request(-1, 0);
}
Gtk::Window::on_size_allocate(a);
}
void
Meterbridge::on_scroll()
{
if (!scroller.get_hscrollbar()) return;
Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
int leftend = adj->get_value();
int rightend = scroller.get_width() + leftend;
int mm_left = _mm_left;
int mm_right = _mm_right;
ARDOUR::MeterType mt_left = _mt_left;
ARDOUR::MeterType mt_right = _mt_right;
for (unsigned int i = 0; i < _metrics.size(); ++i) {
int sx, dx = 0, dy = 0;
int mm = _metrics[i]->get_metric_mode();
sx = (mm & 2) ? _metrics[i]->get_width() : 0;
_metrics[i]->translate_coordinates(meterarea, sx, 0, dx, dy);
if (dx < leftend && !(mm&2)) {
mm_left = mm;
mt_left = _metrics[i]->meter_type();
}
if (dx > rightend && (mm&2)) {
mm_right = mm;
mt_right = _metrics[i]->meter_type();
break;
}
}
metrics_left.set_metric_mode(mm_left, mt_left);
metrics_right.set_metric_mode(mm_right, mt_right);
}
void
Meterbridge::set_session (Session* s)
{
SessionHandlePtr::set_session (s);
if (!_session) {
return;
}
metrics_left.set_session(s);
metrics_right.set_session(s);
XMLNode* node = _session->instant_xml(X_("Meterbridge"));
if (node) {
set_state (*node);
}
update_title ();
_show_busses = _session->config.get_show_busses_on_meterbridge();
_show_master = _session->config.get_show_master_on_meterbridge();
_show_midi = _session->config.get_show_midi_on_meterbridge();
RouteList copy = _session->get_routelist ();
copy.sort (Stripable::Sorter (true));
add_strips (copy);
_session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&Meterbridge::add_strips, this, _1), gui_context());
_session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&Meterbridge::update_title, this), gui_context());
_session->StateSaved.connect (_session_connections, invalidator (*this), boost::bind (&Meterbridge::update_title, this), gui_context());
_session->config.ParameterChanged.connect (*this, invalidator (*this), ui_bind (&Meterbridge::parameter_changed, this, _1), gui_context());
Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&Meterbridge::parameter_changed, this, _1), gui_context());
if (_visible) {
show_window();
present ();
}
start_updating ();
}
void
Meterbridge::session_going_away ()
{
ENSURE_GUI_THREAD (*this, &Meterbridge::session_going_away);
for (list<MeterBridgeStrip>::iterator i = strips.begin(); i != strips.end(); ++i) {
delete ((*i).s);
}
strips.clear ();
stop_updating ();
SessionHandlePtr::session_going_away ();
_session = 0;
update_title ();
}
int
Meterbridge::set_state (const XMLNode& node)
{
XMLNode* geometry;
m_width = default_width;
m_height = default_height;
m_root_x = 1;
m_root_y = 1;
if ((geometry = find_named_node (node, "geometry")) != 0) {
geometry->get_property ("x-size", m_width);
geometry->get_property ("y-size", m_height);
geometry->get_property ("x-pos", m_root_x);
geometry->get_property ("y-pos", m_root_y);
}
set_window_pos_and_size ();
node.get_property ("show-meterbridge", _visible);
return 0;
}
XMLNode&
Meterbridge::get_state (void)
{
XMLNode* node = new XMLNode ("Meterbridge");
if (is_realized() && _visible) {
get_window_pos_and_size ();
}
XMLNode* geometry = new XMLNode ("geometry");
geometry->set_property(X_("x-size"), m_width);
geometry->set_property(X_("y-size"), m_height);
geometry->set_property(X_("x-pos"), m_root_x);
geometry->set_property(X_("y-pos"), m_root_y);
node->add_child_nocopy (*geometry);
node->set_property ("show-meterbridge", _visible);
return *node;
}
gint
Meterbridge::start_updating ()
{
fast_screen_update_connection = Timers::super_rapid_connect (sigc::mem_fun(*this, &Meterbridge::fast_update_strips));
return 0;
}
gint
Meterbridge::stop_updating ()
{
fast_screen_update_connection.disconnect();
return 0;
}
void
Meterbridge::fast_update_strips ()
{
if (!is_mapped () || !_session) {
return;
}
for (list<MeterBridgeStrip>::iterator i = strips.begin(); i != strips.end(); ++i) {
if (!(*i).visible) continue;
(*i).s->fast_update ();
}
}
void
Meterbridge::add_strips (RouteList& routes)
{
MeterStrip* strip;
for (RouteList::iterator x = routes.begin(); x != routes.end(); ++x) {
boost::shared_ptr<Route> route = (*x);
if (route->is_auditioner()) {
continue;
}
if (route->is_monitor()) {
continue;
}
strip = new MeterStrip (_session, route);
strips.push_back (MeterBridgeStrip(strip));
route->active_changed.connect (*this, invalidator (*this), boost::bind (&Meterbridge::sync_order_keys, this), gui_context ());
meterarea.pack_start (*strip, false, false);
strip->show();
}
resync_order();
}
void
Meterbridge::remove_strip (MeterStrip* strip)
{
if (_session && _session->deletion_in_progress()) {
return;
}
list<MeterBridgeStrip>::iterator i;
for (list<MeterBridgeStrip>::iterator i = strips.begin(); i != strips.end(); ++i) {
if ( (*i).s == strip) {
strips.erase (i);
break;
}
}
resync_order();
}
void
Meterbridge::sync_order_keys ()
{
Glib::Threads::Mutex::Lock lm (_resync_mutex);
MeterOrderRouteSorter sorter;
strips.sort(sorter);
int pos = 0;
int vis = 0;
MeterStrip * last = 0;
unsigned int metrics = 0;
MeterType lmt = MeterPeak;
bool have_midi = false;
metrics_left.set_metric_mode(1, lmt);
for (list<MeterBridgeStrip>::iterator i = strips.begin(); i != strips.end(); ++i) {
if (! (*i).s->route()->active()) {
(*i).s->hide();
(*i).visible = false;
}
else if ((*i).s->route()->is_master()) {
if (_show_master) {
(*i).s->show();
(*i).visible = true;
vis++;
} else {
(*i).s->hide();
(*i).visible = false;
}
}
else if (boost::dynamic_pointer_cast<AudioTrack>((*i).s->route()) == 0
&& boost::dynamic_pointer_cast<MidiTrack>((*i).s->route()) == 0
) {
/* non-master bus */
if (_show_busses) {
(*i).s->show();
(*i).visible = true;
vis++;
} else {
(*i).s->hide();
(*i).visible = false;
}
}
else if (boost::dynamic_pointer_cast<MidiTrack>((*i).s->route())) {
if (_show_midi) {
(*i).s->show();
(*i).visible = true;
vis++;
} else {
(*i).s->hide();
(*i).visible = false;
}
}
else {
(*i).s->show();
(*i).visible = true;
vis++;
}
(*i).s->set_tick_bar(0);
MeterType nmt = (*i).s->meter_type();
if (nmt == MeterKrms) nmt = MeterPeak; // identical metrics
if (vis == 1) {
(*i).s->set_tick_bar(1);
}
if ((*i).visible && nmt != lmt && vis == 1) {
lmt = nmt;
metrics_left.set_metric_mode(1, lmt);
} else if ((*i).visible && nmt != lmt) {
if (last) {
last->set_tick_bar(last->get_tick_bar() | 2);
}
(*i).s->set_tick_bar((*i).s->get_tick_bar() | 1);
if (_metrics.size() <= metrics) {
_metrics.push_back(new MeterStrip(have_midi ? 2 : 3, lmt));
meterarea.pack_start (*_metrics[metrics], false, false);
_metrics[metrics]->set_session(_session);
_metrics[metrics]->show();
} else {
_metrics[metrics]->set_metric_mode(have_midi ? 2 : 3, lmt);
}
meterarea.reorder_child(*_metrics[metrics], pos++);
metrics++;
lmt = nmt;
if (_metrics.size() <= metrics) {
_metrics.push_back(new MeterStrip(1, lmt));
meterarea.pack_start (*_metrics[metrics], false, false);
_metrics[metrics]->set_session(_session);
_metrics[metrics]->show();
} else {
_metrics[metrics]->set_metric_mode(1, lmt);
}
meterarea.reorder_child(*_metrics[metrics], pos++);
metrics++;
have_midi = false;
}
if ((*i).visible && (*i).s->has_midi()) {
have_midi = true;
}
meterarea.reorder_child(*((*i).s), pos++);
if ((*i).visible) {
last = (*i).s;
}
}
if (last) {
last->set_tick_bar(last->get_tick_bar() | 2);
}
metrics_right.set_metric_mode(have_midi ? 2 : 3, lmt);
while (_metrics.size() > metrics) {
meterarea.remove(*_metrics.back());
delete (_metrics.back());
_metrics.pop_back();
}
_mm_left = metrics_left.get_metric_mode();
_mt_left = metrics_left.meter_type();
_mm_right = metrics_right.get_metric_mode();
_mt_right = metrics_right.meter_type();
on_scroll();
queue_resize();
}
void
Meterbridge::resync_order (PropertyChange what_changed)
{
if (what_changed.contains (ARDOUR::Properties::order)) {
sync_order_keys();
}
}
void
Meterbridge::parameter_changed (std::string const & p)
{
if (p == "show-busses-on-meterbridge") {
_show_busses = _session->config.get_show_busses_on_meterbridge();
resync_order();
}
else if (p == "show-master-on-meterbridge") {
_show_master = _session->config.get_show_master_on_meterbridge();
resync_order();
}
else if (p == "show-midi-on-meterbridge") {
_show_midi = _session->config.get_show_midi_on_meterbridge();
resync_order();
}
else if (p == "meter-line-up-level") {
meter_clear_pattern_cache();
}
else if (p == "show-rec-on-meterbridge") {
scroller.queue_resize();
}
else if (p == "show-mute-on-meterbridge") {
scroller.queue_resize();
}
else if (p == "show-solo-on-meterbridge") {
scroller.queue_resize();
}
else if (p == "show-name-on-meterbridge") {
scroller.queue_resize();
}
else if (p == "meterbridge-label-height") {
scroller.queue_resize();
}
else if (p == "show-monitor-on-meterbridge") {
scroller.queue_resize();
}
else if (p == "track-name-number") {
scroller.queue_resize();
}
}
void
Meterbridge::on_theme_changed ()
{
meter_clear_pattern_cache();
}