diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 13047dc5dc3..2b37dc07480 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -651,7 +651,7 @@ lmms--gui--MixerChannelView { color: #e0e0e0; qproperty-backgroundActive: qlineargradient(spread:reflect, x1:0, y1:0, x2:1, y2:0, stop:0 #7b838d, stop:1 #6b7581 ); - qproperty-strokeOuterActive: rgb( 0, 0, 0 ); + qproperty-strokeOuterActive: rgb( 0, 255, 0 ); qproperty-strokeOuterInactive: rgba( 0, 0, 0, 50 ); qproperty-strokeInnerActive: rgba( 255, 255, 255, 100 ); qproperty-strokeInnerInactive: rgba( 255, 255, 255, 50 ); diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 184bc9b5682..b3d018da881 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -691,7 +691,7 @@ lmms--gui--MixerChannelView { qproperty-backgroundActive: #3B424A; qproperty-strokeOuterActive: #262B30; qproperty-strokeOuterInactive: #262B30; - qproperty-strokeInnerActive: #0C0D0F; + qproperty-strokeInnerActive: #0bd556; qproperty-strokeInnerInactive: #0C0D0F; } diff --git a/include/MixerChannelView.h b/include/MixerChannelView.h index 6716aee094b..b0dfed1c578 100644 --- a/include/MixerChannelView.h +++ b/include/MixerChannelView.h @@ -34,11 +34,14 @@ #include "EffectRackView.h" #include "Fader.h" +#include "GuiApplication.h" #include "Knob.h" #include "LcdWidget.h" #include "PixmapButton.h" #include "SendButtonIndicator.h" +#include + namespace lmms { class MixerChannel; } @@ -82,21 +85,42 @@ class MixerChannelView : public QWidget QColor strokeInnerInactive() const { return m_strokeInnerInactive; } void setStrokeInnerInactive(const QColor& c) { m_strokeInnerInactive = c; } + static const std::set& selectedChannels() + { + return s_selectedChannels; + } + static void select(MixerChannelView* mcv) + { + s_selectedChannels.insert(mcv); + } + static void deselect(MixerChannelView* mcv) + { + s_selectedChannels.erase(mcv); + } + static void deselectAll() + { + s_selectedChannels.clear(); + } + static void sanitizeSelection(); + + public slots: void renameChannel(); void resetColor(); void selectColor(); void randomizeColor(); + void toggledSolo(); + void toggledMute(); private slots: void renameFinished(); - void removeChannel(); - void removeUnusedChannels(); - void moveChannelLeft(); - void moveChannelRight(); + static void removeSelectedChannels(); + static void removeUnusedChannels(); + static void moveChannelLeft(); + static void moveChannelRight(); private: - bool confirmRemoval(int index); + static bool confirmRemoval(int index); QString elideName(const QString& name); MixerChannel* mixerChannel() const; auto isMasterChannel() const -> bool { return m_channelIndex == 0; } @@ -128,6 +152,8 @@ private slots: QColor m_strokeInnerActive; QColor m_strokeInnerInactive; + static std::set s_selectedChannels; + friend class MixerView; }; } // namespace lmms::gui diff --git a/include/MixerView.h b/include/MixerView.h index 89315f93ab7..bb228c107e2 100644 --- a/include/MixerView.h +++ b/include/MixerView.h @@ -67,8 +67,12 @@ class LMMS_EXPORT MixerView : public QWidget, public ModelView, } - void setCurrentMixerChannel(MixerChannelView* channel); - void setCurrentMixerChannel(int channel); + void setCurrentMixerChannel(MixerChannelView* channel, bool keepSelection = false, bool rangeSelect = false); + void setCurrentMixerChannel(int channel, bool keepSelection = false, bool rangeSelect = false); + + void selectMixerChannelsInRange(int index1, int index2); + + void sanitizeSelection(); void clear(); @@ -101,17 +105,9 @@ public slots: private slots: void updateFaders(); - // TODO This should be improved. Currently the solo and mute models are connected via - // the MixerChannelView's constructor with the MixerView. It would already be an improvement - // if the MixerView connected itself to each new MixerChannel that it creates/handles. - void toggledSolo(); - void toggledMute(); private: Mixer* getMixer() const; - void updateAllMixerChannels(); - void connectToSoloAndMute(int channelIndex); - void disconnectFromSoloAndMute(int channelIndex); private: QVector m_mixerChannelViews; diff --git a/src/gui/MixerChannelView.cpp b/src/gui/MixerChannelView.cpp index 315dd8bf071..d7db47a93ee 100644 --- a/src/gui/MixerChannelView.cpp +++ b/src/gui/MixerChannelView.cpp @@ -25,6 +25,7 @@ #include "MixerChannelView.h" #include +#include #include #include #include @@ -44,6 +45,9 @@ #include "Song.h" namespace lmms::gui { + +std::set MixerChannelView::s_selectedChannels; + MixerChannelView::MixerChannelView(QWidget* parent, MixerView* mixerView, int channelIndex) : QWidget(parent) , m_mixerView(mixerView) @@ -120,6 +124,8 @@ MixerChannelView::MixerChannelView(QWidget* parent, MixerView* mixerView, int ch m_soloButton->setInactiveGraphic(embed::getIconPixmap("led_off")); m_soloButton->setCheckable(true); m_soloButton->setToolTip(tr("Solo this channel")); + connect(m_muteButton, &PixmapButton::toggled, this, &MixerChannelView::toggledMute); + connect(m_soloButton, &PixmapButton::toggled, this, &MixerChannelView::toggledSolo); auto soloMuteLayout = new QVBoxLayout(); soloMuteLayout->setContentsMargins(0, 0, 0, 0); @@ -151,6 +157,11 @@ MixerChannelView::MixerChannelView(QWidget* parent, MixerView* mixerView, int ch connect(m_renameLineEdit, &QLineEdit::editingFinished, this, &MixerChannelView::renameFinished); } +void MixerChannelView::sanitizeSelection() +{ + std::erase_if(MixerChannelView::s_selectedChannels, [](auto m){ return !getGUI()->mixerView()->m_mixerChannelViews.contains(m); }); +} + void MixerChannelView::contextMenuEvent(QContextMenuEvent*) { auto contextMenu = new CaptionMenu(mixerChannel()->m_name, this); @@ -167,7 +178,7 @@ void MixerChannelView::contextMenuEvent(QContextMenuEvent*) if (!isMasterChannel()) // no remove-option in master { contextMenu->addAction( - embed::getIconPixmap("cancel"), tr("R&emove channel"), this, &MixerChannelView::removeChannel); + embed::getIconPixmap("cancel"), tr("R&emove selected channels"), this, &MixerChannelView::removeSelectedChannels); contextMenu->addSeparator(); } @@ -192,6 +203,7 @@ void MixerChannelView::paintEvent(QPaintEvent*) static constexpr auto outerBorderSize = 1; const auto channel = mixerChannel(); + const auto isSelected = selectedChannels().contains(this); const auto isActive = m_mixerView->currentMixerChannel() == this; const auto width = rect().width(); const auto height = rect().height(); @@ -199,9 +211,9 @@ void MixerChannelView::paintEvent(QPaintEvent*) if (channel->color().has_value() && !channel->m_muteModel.value()) { - painter.fillRect(rect(), channel->color()->darker(isActive ? 120 : 150)); + painter.fillRect(rect(), channel->color()->darker(isSelected ? 120 : 150)); } - else { painter.fillRect(rect(), isActive ? backgroundActive().color() : painter.background().color()); } + else { painter.fillRect(rect(), isSelected ? backgroundActive().color() : painter.background().color()); } // inner border painter.setPen(isActive ? strokeInnerActive() : strokeInnerInactive()); @@ -212,9 +224,13 @@ void MixerChannelView::paintEvent(QPaintEvent*) painter.drawRect(0, 0, width - outerBorderSize, height - outerBorderSize); } -void MixerChannelView::mousePressEvent(QMouseEvent*) +void MixerChannelView::mousePressEvent(QMouseEvent* me) { - if (m_mixerView->currentMixerChannel() != this) { m_mixerView->setCurrentMixerChannel(this); } + bool keepSelection = me->modifiers() & Qt::ControlModifier || me->modifiers() & Qt::ShiftModifier; + bool rangeSelect = me->modifiers() & Qt::ShiftModifier; + if (m_mixerView->currentMixerChannel() != this) { + m_mixerView->setCurrentMixerChannel(this, keepSelection, rangeSelect); + } } void MixerChannelView::mouseDoubleClickEvent(QMouseEvent*) @@ -293,9 +309,12 @@ void MixerChannelView::renameFinished() void MixerChannelView::resetColor() { - mixerChannel()->setColor(std::nullopt); + for (auto mcv : selectedChannels()) + { + mcv->mixerChannel()->setColor(std::nullopt); + mcv->update(); + } Engine::getSong()->setModified(); - update(); } void MixerChannelView::selectColor() @@ -308,20 +327,63 @@ void MixerChannelView::selectColor() if (!newColor.isValid()) { return; } - channel->setColor(newColor); - + for (auto mcv : selectedChannels()) + { + mcv->mixerChannel()->setColor(newColor); + mcv->update(); + } Engine::getSong()->setModified(); - update(); } void MixerChannelView::randomizeColor() { - auto channel = mixerChannel(); - channel->setColor(ColorChooser::getPalette(ColorChooser::Palette::Mixer)[rand() % 48]); + const QColor randomColor = ColorChooser::getPalette(ColorChooser::Palette::Mixer)[rand() % 48]; + for (auto mcv : selectedChannels()) + { + mcv->mixerChannel()->setColor(randomColor); + mcv->update(); + } Engine::getSong()->setModified(); - update(); } +void MixerChannelView::toggledMute() +{ + if (selectedChannels().contains(this)) + { + for (auto mcv : selectedChannels()) + { + getGUI()->mixerView()->getMixer()->mixerChannel(mcv->channelIndex())->m_muteModel.setValue(Engine::mixer()->mixerChannel(channelIndex())->m_muteModel.value()); + mcv->update(); + } + } + else + { + update(); + } +} + +// TODO PLEAE FIX +void MixerChannelView::toggledSolo() +{ + getGUI()->mixerView()->getMixer()->toggledSolo(); + if (selectedChannels().contains(this)) + { + for (auto mcv : selectedChannels()) + { + if (mcv != this) + { + getGUI()->mixerView()->getMixer()->mixerChannel(mcv->channelIndex())->m_muteModel.setValue(!getGUI()->mixerView()->getMixer()->mixerChannel(mcv->channelIndex())->m_muteBeforeSolo); + } + mcv->update(); + } + } + else + { + update(); + } +} + + bool MixerChannelView::confirmRemoval(int index) { // if config variable is set to false, there is no need for user confirmation @@ -331,9 +393,9 @@ bool MixerChannelView::confirmRemoval(int index) // is the channel is not in use, there is no need for user confirmation if (!getGUI()->mixerView()->getMixer()->isChannelInUse(index)) { return true; } - QString messageRemoveTrack = tr("This Mixer Channel is being used.\n" + QString messageRemoveTrack = tr("Mixer Channel %1 is being used.\n" "Are you sure you want to remove this channel?\n\n" - "Warning: This operation can not be undone."); + "Warning: This operation can not be undone.").arg(index); QString messageTitleRemoveTrack = tr("Confirm removal"); QString askAgainText = tr("Don't ask again"); @@ -357,11 +419,16 @@ bool MixerChannelView::confirmRemoval(int index) return answer == QMessageBox::Ok; } -void MixerChannelView::removeChannel() +void MixerChannelView::removeSelectedChannels() { - if (!confirmRemoval(m_channelIndex)) { return; } auto mix = getGUI()->mixerView(); - mix->deleteChannel(m_channelIndex); + std::set tempSelectedChannels = selectedChannels(); + for (auto mcv : tempSelectedChannels) + { + if (!confirmRemoval(mcv->channelIndex())) { continue; } + mix->deleteChannel(mcv->channelIndex()); + MixerChannelView::deselect(mcv); + } } void MixerChannelView::removeUnusedChannels() @@ -373,13 +440,39 @@ void MixerChannelView::removeUnusedChannels() void MixerChannelView::moveChannelLeft() { auto mix = getGUI()->mixerView(); - mix->moveChannelLeft(m_channelIndex); + int oldSelectedIndex = mix->currentMixerChannel()->channelIndex(); + auto min_channel = *std::min_element(selectedChannels().begin(), selectedChannels().end(), [](auto mcv1, auto mcv2){ return mcv1->channelIndex() < mcv2->channelIndex(); }); + // Don't shift if it would collide with master + if (min_channel->channelIndex() <= 1) { return; } + + std::set tempSelectedChannels = selectedChannels(); + std::set newSelectedChannels; + for (auto it = tempSelectedChannels.begin(); it != tempSelectedChannels.end(); ++it) + { + mix->moveChannelLeft((*it)->channelIndex()); + newSelectedChannels.insert(mix->m_mixerChannelViews[(*it)->channelIndex() - 1]); + } + mix->setCurrentMixerChannel(oldSelectedIndex - 1, true); + s_selectedChannels = newSelectedChannels; } void MixerChannelView::moveChannelRight() { auto mix = getGUI()->mixerView(); - mix->moveChannelRight(m_channelIndex); + int oldSelectedIndex = mix->currentMixerChannel()->channelIndex(); + auto max_channel = *std::max_element(selectedChannels().begin(), selectedChannels().end(), [](auto mcv1, auto mcv2){ return mcv1->channelIndex() < mcv2->channelIndex(); }); + // Don't shift if it would go over the end + if (max_channel->channelIndex() >= mix->m_mixerChannelViews.size() - 1) { return; } + + std::set tempSelectedChannels = selectedChannels(); + std::set newSelectedChannels; + for (auto it = tempSelectedChannels.rbegin(); it != tempSelectedChannels.rend(); ++it) + { + mix->moveChannelRight((*it)->channelIndex()); + newSelectedChannels.insert(mix->m_mixerChannelViews[(*it)->channelIndex() + 1]); + } + mix->setCurrentMixerChannel(oldSelectedIndex + 1, true); + s_selectedChannels = newSelectedChannels; } QString MixerChannelView::elideName(const QString& name) diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 0ba893be062..50c009df5e2 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -101,7 +101,6 @@ MixerView::MixerView(Mixer* mixer) : // add master channel m_mixerChannelViews.resize(mixer->numChannels()); MixerChannelView * masterView = new MixerChannelView(this, this, 0); - connectToSoloAndMute(0); m_mixerChannelViews[0] = masterView; m_racksLayout->addWidget(m_mixerChannelViews[0]->m_effectRackView); @@ -114,7 +113,6 @@ MixerView::MixerView(Mixer* mixer) : for (int i = 1; i < m_mixerChannelViews.size(); ++i) { m_mixerChannelViews[i] = new MixerChannelView(m_channelAreaWidget, this, i); - connectToSoloAndMute(i); chLayout->addWidget(m_mixerChannelViews[i]); } @@ -189,7 +187,6 @@ int MixerView::addNewChannel() int newChannelIndex = mix->createChannel(); m_mixerChannelViews.push_back(new MixerChannelView(m_channelAreaWidget, this, newChannelIndex)); - connectToSoloAndMute(newChannelIndex); chLayout->addWidget(m_mixerChannelViews[newChannelIndex]); m_racksLayout->addWidget(m_mixerChannelViews[newChannelIndex]->m_effectRackView); @@ -206,9 +203,6 @@ void MixerView::refreshDisplay() // delete all views and re-add them for (int i = 1; iremoveWidget(mixerChannelView); m_racksLayout->removeWidget(mixerChannelView->m_effectRackView); @@ -222,7 +216,6 @@ void MixerView::refreshDisplay() for (int i = 1; i < m_mixerChannelViews.size(); ++i) { m_mixerChannelViews[i] = new MixerChannelView(m_channelAreaWidget, this, i); - connectToSoloAndMute(i); chLayout->addWidget(m_mixerChannelViews[i]); m_racksLayout->addWidget(m_mixerChannelViews[i]->m_effectRackView); @@ -281,56 +274,17 @@ void MixerView::loadSettings(const QDomElement& domElement) MainWindow::restoreWidgetState(this, domElement); } - - - - -void MixerView::toggledSolo() -{ - getMixer()->toggledSolo(); - - updateAllMixerChannels(); -} - - -void MixerView::toggledMute() -{ - updateAllMixerChannels(); -} - Mixer* MixerView::getMixer() const { return m_mixer; } -void MixerView::updateAllMixerChannels() -{ - for (int i = 0; i < m_mixerChannelViews.size(); ++i) - { - m_mixerChannelViews[i]->update(); - } -} - -void MixerView::connectToSoloAndMute(int channelIndex) -{ - auto * mixerChannel = getMixer()->mixerChannel(channelIndex); - - connect(&mixerChannel->m_muteModel, &BoolModel::dataChanged, this, &MixerView::toggledMute, Qt::DirectConnection); - connect(&mixerChannel->m_soloModel, &BoolModel::dataChanged, this, &MixerView::toggledSolo, Qt::DirectConnection); -} - -void MixerView::disconnectFromSoloAndMute(int channelIndex) -{ - auto * mixerChannel = getMixer()->mixerChannel(channelIndex); - - disconnect(&mixerChannel->m_muteModel, &BoolModel::dataChanged, this, &MixerView::toggledMute); - disconnect(&mixerChannel->m_soloModel, &BoolModel::dataChanged, this, &MixerView::toggledSolo); -} - - -void MixerView::setCurrentMixerChannel(MixerChannelView* channel) +void MixerView::setCurrentMixerChannel(MixerChannelView* channel, bool keepSelection, bool rangeSelect) { // select + if (!keepSelection) { MixerChannelView::deselectAll(); } + if (rangeSelect) { selectMixerChannelsInRange(m_currentMixerChannel->channelIndex(), channel->channelIndex()); } + MixerChannelView::select(channel); m_currentMixerChannel = channel; m_racksLayout->setCurrentWidget(m_mixerChannelViews[channel->channelIndex()]->m_effectRackView); @@ -341,6 +295,13 @@ void MixerView::setCurrentMixerChannel(MixerChannelView* channel) } } +void MixerView::selectMixerChannelsInRange(int index1, int index2) +{ + for (int i = std::min(index1,index2); i < std::max(index1,index2); i++) + { + MixerChannelView::select(m_mixerChannelViews[i]); + } +} void MixerView::updateMixerChannel(int index) { @@ -385,12 +346,6 @@ void MixerView::deleteChannel(int index) // can't delete master if (index == 0) return; - // Disconnect from the solo/mute models of the channel we are about to delete - disconnectFromSoloAndMute(index); - - // remember selected line - int selLine = m_currentMixerChannel->channelIndex(); - Mixer* mixer = getMixer(); // in case the deleted channel is soloed or the remaining // channels will be left in a muted state @@ -413,13 +368,7 @@ void MixerView::deleteChannel(int index) } m_mixerChannelViews.remove(index); - // select the next channel - if (selLine >= m_mixerChannelViews.size()) - { - selLine = m_mixerChannelViews.size() - 1; - } - setCurrentMixerChannel(selLine); - + setCurrentMixerChannel(std::clamp(0, m_currentMixerChannel->channelIndex(), m_mixerChannelViews.size() - 1), true); updateMaxChannelSelector(); } @@ -484,7 +433,7 @@ void MixerView::keyPressEvent(QKeyEvent * e) switch(e->key()) { case Qt::Key_Delete: - deleteChannel(m_currentMixerChannel->channelIndex()); + MixerChannelView::removeSelectedChannels(); break; case Qt::Key_Left: if (e->modifiers() & Qt::AltModifier) @@ -539,11 +488,11 @@ void MixerView::closeEvent(QCloseEvent * ce) -void MixerView::setCurrentMixerChannel(int channel) +void MixerView::setCurrentMixerChannel(int channel, bool keepSelection, bool rangeSelect) { if (channel >= 0 && channel < m_mixerChannelViews.size()) { - setCurrentMixerChannel(m_mixerChannelViews[channel]); + setCurrentMixerChannel(m_mixerChannelViews[channel], keepSelection, rangeSelect); } } diff --git a/src/gui/SendButtonIndicator.cpp b/src/gui/SendButtonIndicator.cpp index 4fb20cc315e..0964347d6a5 100644 --- a/src/gui/SendButtonIndicator.cpp +++ b/src/gui/SendButtonIndicator.cpp @@ -29,11 +29,29 @@ void SendButtonIndicator::mousePressEvent(QMouseEvent* e) { // not sending. create a mixer send. mix->createChannelSend(from, to); + // create mixer sends for all other selected channels if they don't have them already + for (auto mcv : MixerChannelView::selectedChannels()) + { + if (mcv->channelIndex() != from && mcv->channelIndex() != to + && mix->channelSendModel(mcv->channelIndex(), to) == nullptr) + { + mix->createChannelSend(mcv->channelIndex(), to); + } + } } else { // sending. delete the mixer send. mix->deleteChannelSend(from, to); + // delete mixer sends for all other selected channels if they have them + for (auto mcv : MixerChannelView::selectedChannels()) + { + if (mcv->channelIndex() != from && mcv->channelIndex() != to + && mix->channelSendModel(mcv->channelIndex(), to) != nullptr) + { + mix->deleteChannelSend(mcv->channelIndex(), to); + } + } } m_mv->updateMixerChannel(m_parent->channelIndex());