Skip to content

Commit

Permalink
Add confirm removal on mixer channels (LMMS#6691)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: saker <[email protected]>
  • Loading branch information
3 people authored May 10, 2023
1 parent 87a57db commit d551938
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 37 deletions.
4 changes: 4 additions & 0 deletions include/Mixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ class LMMS_EXPORT Mixer : public Model, public JournallingObject
// 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();
Expand Down
1 change: 1 addition & 0 deletions include/MixerView.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class LMMS_EXPORT MixerView : public QWidget, public ModelView,

// notify the view that a mixer channel was deleted
void deleteChannel(int index);
bool confirmRemoval(int index);

// delete all unused channels
void deleteUnusedChannels();
Expand Down
2 changes: 2 additions & 0 deletions include/SetupDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -141,6 +142,7 @@ private slots:
bool m_letPreviewsFinish;
bool m_soloLegacyBehavior;
bool m_trackDeletionWarning;
bool m_mixerChannelDeletionWarning;
bool m_MMPZ;
bool m_disableBackup;
bool m_openLastProject;
Expand Down
36 changes: 36 additions & 0 deletions src/core/Mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
88 changes: 57 additions & 31 deletions src/gui/MixerView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
*/


#include <QCheckBox>
#include <QLayout>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QStyle>
Expand Down Expand Up @@ -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();

Expand All @@ -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;
Expand All @@ -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)
{
auto inst = dynamic_cast<InstrumentTrack*>(t);
channel = inst->mixerChannelModel()->value();
}
else if (t->type() == Track::SampleTrack)
if (!mix->isChannelInUse(i))
{
auto strack = dynamic_cast<SampleTrack*>(t);
channel = strack->mixerChannelModel()->value();
deleteChannel(i);
}
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); }
}
}

Expand Down
46 changes: 40 additions & 6 deletions src/gui/modals/SetupDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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();


Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
{
Expand Down

0 comments on commit d551938

Please sign in to comment.