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:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user