Add confirm removal on mixer channels (#6691)

* Add confirm removal on mixer channels

Add confirm removal popup when the user calls the action "remove channel" on a mixer channel that is in use (receives audio from other channel or track). Set a config variable to keep track if the user don't want to be asked again. Adding a scroll on settings-general tab because there weren't enough space on the area.

* Core Mixer function channel in use

New core Mixer function to check if a given channel is in use (receives audio)

---------

Co-authored-by: Hyunjin Song <tteu.ingog@gmail.com>
Co-authored-by: saker <sakertooth@Gmail.com>
This commit is contained in:
superpaik
2023-05-10 13:14:24 +02:00
committed by GitHub
parent 87a57db593
commit d551938cbd
6 changed files with 140 additions and 37 deletions

View File

@@ -202,6 +202,10 @@ public:
// rename channels when moving etc. if they still have their original name
void validateChannelName( int index, int oldIndex );
// check if the index channel receives audio from any other channel
// or from any instrument or sample track
bool isChannelInUse(int index);
void toggledSolo();
void activateSolo();
void deactivateSolo();

View File

@@ -95,6 +95,7 @@ public:
// notify the view that a mixer channel was deleted
void deleteChannel(int index);
bool confirmRemoval(int index);
// delete all unused channels
void deleteUnusedChannels();

View File

@@ -82,6 +82,7 @@ private slots:
void toggleLetPreviewsFinish(bool enabled);
void toggleSoloLegacyBehavior(bool enabled);
void toggleTrackDeletionWarning(bool enabled);
void toggleMixerChannelDeletionWarning(bool enabled);
void toggleMMPZ(bool enabled);
void toggleDisableBackup(bool enabled);
void toggleOpenLastProject(bool enabled);
@@ -141,6 +142,7 @@ private:
bool m_letPreviewsFinish;
bool m_soloLegacyBehavior;
bool m_trackDeletionWarning;
bool m_mixerChannelDeletionWarning;
bool m_MMPZ;
bool m_disableBackup;
bool m_openLastProject;

View File

@@ -825,5 +825,41 @@ void Mixer::validateChannelName( int index, int oldIndex )
}
}
bool Mixer::isChannelInUse(int index)
{
// check if the index mixer channel receives audio from any other channel
if (!m_mixerChannels[index]->m_receives.isEmpty())
{
return true;
}
// check if the destination mixer channel on any instrument or sample track is the index mixer channel
TrackContainer::TrackList tracks;
tracks += Engine::getSong()->tracks();
tracks += Engine::patternStore()->tracks();
for (const auto t : tracks)
{
if (t->type() == Track::InstrumentTrack)
{
auto inst = dynamic_cast<InstrumentTrack*>(t);
if (inst->mixerChannelModel()->value() == index)
{
return true;
}
}
else if (t->type() == Track::SampleTrack)
{
auto strack = dynamic_cast<SampleTrack*>(t);
if (strack->mixerChannelModel()->value() == index)
{
return true;
}
}
}
return false;
}
} // namespace lmms

View File

@@ -23,7 +23,9 @@
*/
#include <QCheckBox>
#include <QLayout>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QStyle>
@@ -385,6 +387,12 @@ void MixerView::deleteChannel(int index)
// can't delete master
if( index == 0 ) return;
// if there is no user confirmation, do nothing
if (!confirmRemoval(index))
{
return;
}
// remember selected line
int selLine = m_currentMixerLine->channelIndex();
@@ -397,7 +405,7 @@ void MixerView::deleteChannel(int index)
// delete the view
chLayout->removeWidget(m_mixerChannelViews[index]->m_mixerLine);
m_racksLayout->removeWidget( m_mixerChannelViews[index]->m_rackView );
m_racksLayout->removeWidget(m_mixerChannelViews[index]->m_rackView);
delete m_mixerChannelViews[index]->m_fader;
delete m_mixerChannelViews[index]->m_muteBtn;
delete m_mixerChannelViews[index]->m_soloBtn;
@@ -409,56 +417,74 @@ void MixerView::deleteChannel(int index)
m_channelAreaWidget->adjustSize();
// make sure every channel knows what index it is
for(int i=index + 1; i<m_mixerChannelViews.size(); ++i)
for (int i = index + 1; i < m_mixerChannelViews.size(); ++i)
{
m_mixerChannelViews[i]->m_mixerLine->setChannelIndex(i-1);
m_mixerChannelViews[i]->m_mixerLine->setChannelIndex(i - 1);
}
m_mixerChannelViews.remove(index);
// select the next channel
if( selLine >= m_mixerChannelViews.size() )
if (selLine >= m_mixerChannelViews.size())
{
selLine = m_mixerChannelViews.size()-1;
selLine = m_mixerChannelViews.size() - 1;
}
setCurrentMixerLine(selLine);
updateMaxChannelSelector();
}
bool MixerView::confirmRemoval(int index)
{
// if config variable is set to false, there is no need for user confirmation
bool needConfirm = ConfigManager::inst()->value("ui", "mixerchanneldeletionwarning", "1").toInt();
if (!needConfirm) { return true; }
Mixer* mix = Engine::mixer();
if (!mix->isChannelInUse(index))
{
// is the channel is not in use, there is no need for user confirmation
return true;
}
QString messageRemoveTrack = tr("This Mixer Channel is being used.\n"
"Are you sure you want to remove this channel?\n\n"
"Warning: This operation can not be undone.");
QString messageTitleRemoveTrack = tr("Confirm removal");
QString askAgainText = tr("Don't ask again");
auto askAgainCheckBox = new QCheckBox(askAgainText, nullptr);
connect(askAgainCheckBox, &QCheckBox::stateChanged, [this](int state) {
// Invert button state, if it's checked we *shouldn't* ask again
ConfigManager::inst()->setValue("ui", "mixerchanneldeletionwarning", state ? "0" : "1");
});
QMessageBox mb(this);
mb.setText(messageRemoveTrack);
mb.setWindowTitle(messageTitleRemoveTrack);
mb.setIcon(QMessageBox::Warning);
mb.addButton(QMessageBox::Cancel);
mb.addButton(QMessageBox::Ok);
mb.setCheckBox(askAgainCheckBox);
mb.setDefaultButton(QMessageBox::Cancel);
int answer = mb.exec();
return answer == QMessageBox::Ok;
}
void MixerView::deleteUnusedChannels()
{
TrackContainer::TrackList tracks;
tracks += Engine::getSong()->tracks();
tracks += Engine::patternStore()->tracks();
Mixer* mix = Engine::mixer();
std::vector<bool> inUse(m_mixerChannelViews.size(), false);
//Populate inUse by checking the destination channel for every track
for (Track* t: tracks)
// Check all channels except master, delete those with no incoming sends
for (int i = m_mixerChannelViews.size() - 1; i > 0; --i)
{
//The channel that this track sends to. Since master channel is always in use,
//setting this to 0 is a safe default (for tracks that don't sent to the mixer).
int channel = 0;
if (t->type() == Track::InstrumentTrack)
if (!mix->isChannelInUse(i))
{
auto inst = dynamic_cast<InstrumentTrack*>(t);
channel = inst->mixerChannelModel()->value();
deleteChannel(i);
}
else if (t->type() == Track::SampleTrack)
{
auto strack = dynamic_cast<SampleTrack*>(t);
channel = strack->mixerChannelModel()->value();
}
inUse[channel] = true;
}
//Check all channels except master, delete those with no incoming sends
for(int i = m_mixerChannelViews.size()-1; i > 0; --i)
{
if (!inUse[i] && Engine::mixer()->mixerChannel(i)->m_receives.isEmpty())
{ deleteChannel(i); }
}
}

View File

@@ -112,6 +112,8 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) :
"app", "sololegacybehavior", "0").toInt()),
m_trackDeletionWarning(ConfigManager::inst()->value(
"ui", "trackdeletionwarning", "1").toInt()),
m_mixerChannelDeletionWarning(ConfigManager::inst()->value(
"ui", "mixerchanneldeletionwarning", "1").toInt()),
m_MMPZ(!ConfigManager::inst()->value(
"app", "nommpz").toInt()),
m_disableBackup(!ConfigManager::inst()->value(
@@ -198,6 +200,18 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) :
general_layout->setContentsMargins(0, 0, 0, 0);
labelWidget(general_w, tr("General"));
// General scroll area.
auto generalScroll = new QScrollArea(general_w);
generalScroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
generalScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// General controls widget.
auto generalControls = new QWidget(general_w);
// Path selectors layout.
auto generalControlsLayout = new QVBoxLayout;
generalControlsLayout->setSpacing(10);
auto addLedCheckBox = [&XDelta, &YDelta, this](const QString& ledText, TabWidget* tw, int& counter,
bool initialState, const char* toggledSlot, bool showRestartWarning) {
auto checkBox = new LedCheckBox(ledText, tw);
@@ -214,7 +228,7 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) :
int counter = 0;
// GUI tab.
auto gui_tw = new TabWidget(tr("Graphical user interface (GUI)"), general_w);
auto gui_tw = new TabWidget(tr("Graphical user interface (GUI)"), generalControls);
addLedCheckBox(tr("Display volume as dBFS "), gui_tw, counter,
m_displaydBFS, SLOT(toggleDisplaydBFS(bool)), true);
@@ -236,14 +250,19 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) :
m_soloLegacyBehavior, SLOT(toggleSoloLegacyBehavior(bool)), false);
addLedCheckBox(tr("Show warning when deleting tracks"), gui_tw, counter,
m_trackDeletionWarning, SLOT(toggleTrackDeletionWarning(bool)), false);
addLedCheckBox(tr("Show warning when deleting a mixer channel that is in use"), gui_tw, counter,
m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false);
gui_tw->setFixedHeight(YDelta + YDelta * counter);
generalControlsLayout->addWidget(gui_tw);
generalControlsLayout->addSpacing(10);
counter = 0;
// Projects tab.
auto projects_tw = new TabWidget(tr("Projects"), general_w);
auto projects_tw = new TabWidget(tr("Projects"), generalControls);
addLedCheckBox(tr("Compress project files by default"), projects_tw, counter,
m_MMPZ, SLOT(toggleMMPZ(bool)), true);
@@ -254,8 +273,12 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) :
projects_tw->setFixedHeight(YDelta + YDelta * counter);
generalControlsLayout->addWidget(projects_tw);
generalControlsLayout->addSpacing(10);
// Language tab.
auto lang_tw = new TabWidget(tr("Language"), general_w);
auto lang_tw = new TabWidget(tr("Language"), generalControls);
lang_tw->setFixedHeight(48);
auto changeLang = new QComboBox(lang_tw);
changeLang->move(XDelta, 20);
@@ -310,11 +333,15 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) :
connect(changeLang, SIGNAL(currentIndexChanged(int)),
this, SLOT(showRestartWarning()));
generalControlsLayout->addWidget(lang_tw);
generalControlsLayout->addSpacing(10);
// General layout ordering.
general_layout->addWidget(gui_tw);
general_layout->addWidget(projects_tw);
general_layout->addWidget(lang_tw);
generalControlsLayout->addStretch();
generalControls->setLayout(generalControlsLayout);
generalScroll->setWidget(generalControls);
generalScroll->setWidgetResizable(true);
general_layout->addWidget(generalScroll);
general_layout->addStretch();
@@ -896,6 +923,8 @@ void SetupDialog::accept()
QString::number(m_soloLegacyBehavior));
ConfigManager::inst()->setValue("ui", "trackdeletionwarning",
QString::number(m_trackDeletionWarning));
ConfigManager::inst()->setValue("ui", "mixerchanneldeletionwarning",
QString::number(m_mixerChannelDeletionWarning));
ConfigManager::inst()->setValue("app", "nommpz",
QString::number(!m_MMPZ));
ConfigManager::inst()->setValue("app", "disablebackup",
@@ -1017,6 +1046,11 @@ void SetupDialog::toggleTrackDeletionWarning(bool enabled)
m_trackDeletionWarning = enabled;
}
void SetupDialog::toggleMixerChannelDeletionWarning(bool enabled)
{
m_mixerChannelDeletionWarning = enabled;
}
void SetupDialog::toggleMMPZ(bool enabled)
{