File indexing completed on 2022-09-27 14:35:05

0001 /*
0002  * KMix -- KDE's full featured mini mixer
0003  *
0004  * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken <esken@kde.org>
0005  *
0006  * This program is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU Library General Public
0008  * License as published by the Free Software Foundation; either
0009  * version 2 of the License, or (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014  * Library General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU Library General Public
0017  * License along with this program; if not, write to the Free
0018  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0019  */
0020 
0021 #include "kmixwindow.h"
0022 
0023 // include files for Qt
0024 #include <QApplication>
0025 #include <QMenuBar>
0026 #include <QTabWidget>
0027 #include <QPointer>
0028 #include <QHash>
0029 #include <QTimer>
0030 #include <QDBusInterface>
0031 #include <QDBusPendingCall>
0032 
0033 // include files for KDE
0034 #include <kxmlgui_version.h>
0035 #include <kglobalaccel.h>
0036 #include <kmessagebox.h>
0037 #include <klocalizedstring.h>
0038 #include <kstandardaction.h>
0039 #include <kxmlguifactory.h>
0040 #include <KProcess>
0041 
0042 // KMix
0043 #include "kmix_debug.h"
0044 #include "core/mixertoolbox.h"
0045 #include "core/kmixdevicemanager.h"
0046 #include "gui/kmixerwidget.h"
0047 #include "gui/kmixdockwidget.h"
0048 #include "gui/kmixtoolbox.h"
0049 #include "gui/dialogaddview.h"
0050 #include "gui/dialogselectmaster.h"
0051 #include "dbus/dbusmixsetwrapper.h"
0052 #include "settings.h"
0053 
0054 #ifdef HAVE_CANBERRA
0055 #include "volumefeedback.h"
0056 #endif
0057 
0058 
0059 /* KMixWindow
0060  * Constructs a mixer window (KMix main window)
0061  */
0062 
0063 KMixWindow::KMixWindow(bool invisible, bool reset) :
0064         KXmlGuiWindow(nullptr, Qt::WindowFlags(KDE_DEFAULT_WINDOWFLAGS|Qt::WindowContextHelpButtonHint)),
0065         m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident
0066         m_autouseMultimediaKeys(true),
0067         m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false)
0068 {
0069     setObjectName(QStringLiteral("KMixWindow"));
0070     // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in
0071     setAttribute(Qt::WA_DeleteOnClose, false);
0072 
0073     initActions(); // init actions first, so we can use them in the loadConfig() already
0074     loadAndInitConfig(reset); // Load config before initMixer(), e.g. due to "MultiDriver" keyword
0075     initActionsLate(); // init actions that require a loaded config
0076     // TODO: Port to KF5
0077     //KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls"));
0078     initWidgets();
0079     initPrefDlg();
0080     DBusMixSetWrapper::initialize(this, QStringLiteral("/Mixers"));
0081     MixerToolBox::initMixer(m_multiDriverMode, m_backendFilter, true);
0082     KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance();
0083     initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s).
0084 
0085     recreateGUI(false, reset);
0086     if (m_wsMixers->count() < 1)
0087     {
0088         // Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder
0089         recreateGUI(false, QString(), true, reset);
0090     }
0091 
0092     if (!qApp->isSessionRestored() ) // done by the session manager otherwise
0093         setInitialSize();
0094 
0095     fixConfigAfterRead();
0096     connect(theKMixDeviceManager, &KMixDeviceManager::plugged, this, &KMixWindow::plugged);
0097     connect(theKMixDeviceManager, &KMixDeviceManager::unplugged, this, &KMixWindow::unplugged);
0098     theKMixDeviceManager->initHotplug();
0099 
0100     if (m_startVisible && !invisible) show();   // Started visible
0101 
0102     connect(qApp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) );
0103 
0104     ControlManager::instance().addListener(
0105             QString(), // All mixers (as the Global master Mixer might change)
0106             ControlManager::ControlList|ControlManager::MasterChanged, this,
0107             "KMixWindow");
0108 #ifdef HAVE_CANBERRA
0109     VolumeFeedback::instance()->init();     // set up for volume feedback
0110 #endif
0111     // Send an initial volume refresh (otherwise all volumes are 0 until the next change)
0112     ControlManager::instance().announce(QString(), ControlManager::Volume, "Startup");
0113 }
0114 
0115 KMixWindow::~KMixWindow()
0116 {
0117     ControlManager::instance().removeListener(this);
0118 
0119     delete m_dsm;
0120 
0121     // -1- Cleanup Memory: clearMixerWidgets
0122     while (m_wsMixers->count() != 0)
0123     {
0124         QWidget *mw = m_wsMixers->widget(0);
0125         m_wsMixers->removeTab(0);
0126         delete mw;
0127     }
0128 
0129     // -2- Mixer HW
0130     MixerToolBox::deinitMixer();
0131 
0132     // -3- Action collection (just to please Valgrind)
0133     actionCollection()->clear();
0134 
0135     // GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which
0136     // means main window and potentially also in the tray popup (at least we might do so in the future).
0137     // This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup.
0138     // Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic.
0139 
0140     GUIProfile::clearCache();
0141 
0142 }
0143 
0144 void KMixWindow::controlsChange(ControlManager::ChangeType changeType)
0145 {
0146     switch (changeType)
0147     {
0148     case ControlManager::ControlList:
0149     case ControlManager::MasterChanged:
0150         updateDocking();
0151         break;
0152 
0153     default:
0154         ControlManager::warnUnexpectedChangeType(changeType, this);
0155         break;
0156     }
0157 
0158 }
0159 
0160 
0161 void KMixWindow::initActions()
0162 {
0163     // file menu
0164     KStandardAction::quit(this, SLOT(quit()), actionCollection());
0165 
0166     // settings menu
0167     _actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()), actionCollection());
0168     KStandardAction::preferences(this, SLOT(showSettings()), actionCollection());
0169 
0170     KStandardAction::keyBindings(guiFactory(), &KXMLGUIFactory::showConfigureShortcutsDialog, actionCollection());
0171 
0172     QAction* action = actionCollection()->addAction(QStringLiteral("launch_kdesoundsetup"));
0173     action->setText(i18n("Audio Setup..."));
0174     connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec()));
0175 
0176     action = actionCollection()->addAction(QStringLiteral("hide_kmixwindow"));
0177     action->setText(i18n("Hide Mixer Window"));
0178     connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose()));
0179     actionCollection()->setDefaultShortcut(action, Qt::Key_Escape);
0180 
0181     action = actionCollection()->addAction(QStringLiteral("toggle_channels_currentview"));
0182     action->setText(i18n("Configure &Channels..."));
0183     connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView()));
0184 
0185     action = actionCollection()->addAction(QStringLiteral("select_master"));
0186     action->setText(i18n("Select Master Channel..."));
0187     connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster()));
0188 
0189     action = actionCollection()->addAction(QStringLiteral("save_1"));
0190     actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_1);
0191     action->setText(i18n("Save volume profile 1"));
0192     connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1()));
0193 
0194     action = actionCollection()->addAction(QStringLiteral("save_2"));
0195     actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_2);
0196     action->setText(i18n("Save volume profile 2"));
0197     connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2()));
0198 
0199     action = actionCollection()->addAction(QStringLiteral("save_3"));
0200     actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_3);
0201     action->setText(i18n("Save volume profile 3"));
0202     connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3()));
0203 
0204     action = actionCollection()->addAction(QStringLiteral("save_4"));
0205     actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_4);
0206     action->setText(i18n("Save volume profile 4"));
0207     connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4()));
0208 
0209     action = actionCollection()->addAction(QStringLiteral("load_1"));
0210     actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_1);
0211     action->setText(i18n("Load volume profile 1"));
0212     connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1()));
0213 
0214     action = actionCollection()->addAction(QStringLiteral("load_2"));
0215     actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_2);
0216     action->setText(i18n("Load volume profile 2"));
0217     connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2()));
0218 
0219     action = actionCollection()->addAction(QStringLiteral("load_3"));
0220     actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_3);
0221     action->setText(i18n("Load volume profile 3"));
0222     connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3()));
0223 
0224     action = actionCollection()->addAction(QStringLiteral("load_4"));
0225     actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_4);
0226     action->setText(i18n("Load volume profile 4"));
0227     connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4()));
0228 
0229     createGUI(QLatin1String("kmixui.rc"));
0230 }
0231 
0232 void KMixWindow::initActionsLate()
0233 {
0234     if (m_autouseMultimediaKeys)
0235     {
0236         QAction* globalAction = actionCollection()->addAction(QStringLiteral("increase_volume"));
0237         globalAction->setText(i18n("Increase Volume"));
0238 
0239         KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeUp);
0240 
0241         connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume()));
0242 
0243         globalAction = actionCollection()->addAction(QStringLiteral("decrease_volume"));
0244         globalAction->setText(i18n("Decrease Volume"));
0245         KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeDown);
0246         connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume()));
0247 
0248         globalAction = actionCollection()->addAction(QStringLiteral("mute"));
0249         globalAction->setText(i18n("Mute"));
0250         KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeMute);
0251         connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute()));
0252     }
0253 }
0254 
0255 void KMixWindow::initActionsAfterInitMixer()
0256 {
0257     // Only show the new tab widget if Pulseaudio is not used. Hint: The Pulseaudio backend always
0258     // runs with 4 fixed Tabs.
0259     if (!Mixer::pulseaudioPresent())
0260     {
0261         QPushButton* _cornerLabelNew = new QPushButton();
0262         _cornerLabelNew->setIcon(QIcon::fromTheme("tab-new"));
0263         _cornerLabelNew->setToolTip(i18n("Add new view"));
0264         m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner);
0265         connect(_cornerLabelNew, SIGNAL(clicked()), SLOT(newView()));
0266     }
0267 }
0268 
0269 
0270 //  This needs to done on initialisation, so that the
0271 //  signal can be connected.
0272 void KMixWindow::initPrefDlg()
0273 {
0274     KMixPrefDlg *prefDlg = KMixPrefDlg::instance(this);
0275     connect(prefDlg, &KMixPrefDlg::kmixConfigHasChanged, this, &KMixWindow::applyPrefs);
0276 }
0277 
0278 
0279 void KMixWindow::initWidgets()
0280 {
0281     m_wsMixers = new QTabWidget();
0282     m_wsMixers->setDocumentMode(true);
0283     setCentralWidget(m_wsMixers);
0284     m_wsMixers->setTabsClosable(false);
0285     connect(m_wsMixers, SIGNAL(tabCloseRequested(int)), SLOT(saveAndCloseView(int)));
0286 
0287     connect(m_wsMixers, SIGNAL(currentChanged(int)), SLOT(newMixerShown(int)));
0288 
0289     // show menubar if the actions says so (or if the action does not exist)
0290     menuBar()->setVisible((_actionShowMenubar == 0) || _actionShowMenubar->isChecked());
0291 }
0292 
0293 void KMixWindow::setInitialSize()
0294 {
0295     // HACK: QTabWidget will bound its sizeHint to 200x200 unless scrollbuttons
0296     // are disabled, so we disable them, get a decent sizehint and enable them
0297     // back
0298     m_wsMixers->setUsesScrollButtons(false);
0299     QSize defSize = sizeHint();
0300     m_wsMixers->setUsesScrollButtons(true);
0301     QSize size = Settings::size();
0302     if (size.isNull()) size = defSize;
0303     if (!size.isNull()) resize(size);
0304 
0305     QPoint pos = Settings::position();
0306     if (!pos.isNull()) move(pos);
0307 }
0308 
0309 void KMixWindow::removeDock()
0310 {
0311     if (m_dockWidget)
0312     {
0313         m_dockWidget->deleteLater();
0314         m_dockWidget = 0;
0315     }
0316 }
0317 
0318 /**
0319  * Creates or deletes the KMixDockWidget, depending on whether there is a Mixer instance available.
0320  *
0321  * @returns true, if the docking succeeded. Failure usually means that there
0322  *    was no suitable mixer control selected.
0323  */
0324 bool KMixWindow::updateDocking()
0325 {
0326     if (!Settings::showDockWidget() || Mixer::mixers().isEmpty())
0327     {
0328         removeDock();
0329         return false;
0330     }
0331     if (!m_dockWidget)
0332     {
0333         m_dockWidget = new KMixDockWidget(this);
0334     }
0335     return true;
0336 }
0337 void KMixWindow::saveConfig()
0338 {
0339     saveBaseConfig();
0340     saveViewConfig();
0341     saveVolumes();
0342 
0343     // TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!!
0344     KSharedConfig::openConfig()->sync();
0345     qCDebug(KMIX_LOG)
0346     << "Saved config ... sync finished";
0347 }
0348 
0349 void KMixWindow::saveBaseConfig()
0350 {
0351     Settings::setConfigVersion(KMIX_CONFIG_VERSION);
0352 
0353     Settings::setSize(size());
0354     Settings::setPosition(pos());
0355     // Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden.
0356     // (Please note that the problem was only there when quitting via Systray - esken).
0357     // Using it again, as internal behaviour has changed with KDE4
0358     Settings::setVisible(isVisible());
0359     Settings::setMenubar(_actionShowMenubar->isChecked());
0360 
0361     // TODO: check whether the next line is needed
0362     //Settings::setMixersForSoundMenu(GlobalConfig::instance().getMixersForSoundmenu().values());
0363     Settings::setDefaultCardOnStart(m_defaultCardOnStart);
0364     Settings::setAutoUseMultimediaKeys(m_autouseMultimediaKeys);
0365 
0366     const MasterControl &master = Mixer::getGlobalMasterPreferred(false);
0367     Settings::setMasterMixer(master.getCard());
0368     Settings::setMasterMixerDevice(master.getControl());
0369 
0370     const QString mixerIgnoreExpression = MixerToolBox::mixerIgnoreExpression();
0371     Settings::setMixerIgnoreExpression(mixerIgnoreExpression);
0372 
0373     Settings::self()->save();
0374     qCDebug(KMIX_LOG) << "Base configuration saved";
0375 }
0376 
0377 void KMixWindow::saveViewConfig()
0378 {
0379     QMap<QString, QStringList> mixerViews;
0380 
0381     // The following loop is necessary for the case that the user has hidden all views for a Mixer instance.
0382     // Otherwise we would not save the Meta information (step -2- below for that mixer.
0383     // We also do not save dynamic mixers (e.g. PulseAudio)
0384     for (const Mixer *mixer : std::as_const(Mixer::mixers()))
0385     {
0386         mixerViews[mixer->id()];        // just insert a map entry
0387     }
0388 
0389 // -1- Save the views themselves
0390     for (int i = 0; i < m_wsMixers->count(); ++i)
0391     {
0392         QWidget *w = m_wsMixers->widget(i);
0393         KMixerWidget *mw = qobject_cast<KMixerWidget *>(w);
0394         if (mw!=nullptr)
0395         {
0396             // Here also Views are saved. even for Mixers that are closed. This is necessary when unplugging cards.
0397             // Otherwise the user will be confused afer re-plugging the card (as the config was not saved).
0398             mw->saveConfig(Settings::self()->config());
0399             // add the view to the corresponding mixer list, so we can save a views-per-mixer list below
0400 //          if (!mw->mixer()->isDynamic())
0401 //          {
0402                 QStringList& qsl = mixerViews[mw->mixer()->id()];
0403                 qsl.append(mw->getGuiprof()->getId());
0404 //          }
0405         }
0406     }
0407 
0408     // -2- Save Meta-Information (which views, and in which order). views-per-mixer list
0409     KConfigGroup pconfig(KSharedConfig::openConfig(), "Profiles");
0410     QMap<QString, QStringList>::const_iterator itEnd = mixerViews.constEnd();
0411     for (QMap<QString, QStringList>::const_iterator it = mixerViews.constBegin(); it != itEnd; ++it)
0412     {
0413         const QString& mixerProfileKey = it.key(); // this is actually some mixer->id()
0414         const QStringList& qslProfiles = it.value();
0415         pconfig.writeEntry(mixerProfileKey, qslProfiles);
0416         qCDebug(KMIX_LOG)
0417         << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count();
0418     }
0419 
0420     qCDebug(KMIX_LOG)
0421     << "View configuration saved";
0422 }
0423 
0424 /**
0425  * Stores the volumes of all mixers  Can be restored via loadVolumes() or
0426  * the kmixctrl application.
0427  */
0428 void KMixWindow::saveVolumes()
0429 {
0430     saveVolumes(QString());
0431 }
0432 
0433 void KMixWindow::saveVolumes(const QString &postfix)
0434 {
0435     const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
0436     KConfig *cfg = new KConfig(kmixctrlRcFilename);
0437     for (int i = 0; i < Mixer::mixers().count(); ++i)
0438     {
0439         Mixer *mixer = (Mixer::mixers())[i];
0440         if (mixer->isOpen())
0441         { // protect from unplugged devices (better do *not* save them)
0442             mixer->volumeSave(cfg);
0443         }
0444     }
0445     cfg->sync();
0446     delete cfg;
0447     qCDebug(KMIX_LOG)
0448     << "Volume configuration saved";
0449 }
0450 
0451 QString KMixWindow::getKmixctrlRcFilename(const QString &postfix)
0452 {
0453     QString kmixctrlRcFilename("kmixctrlrc");
0454     if (!postfix.isEmpty())
0455     {
0456         kmixctrlRcFilename.append(".").append(postfix);
0457     }
0458     return kmixctrlRcFilename;
0459 }
0460 
0461 
0462 void KMixWindow::loadAndInitConfig(bool reset)
0463 {
0464     if (!reset) loadBaseConfig();
0465 }
0466 
0467 
0468 void KMixWindow::loadBaseConfig()
0469 {
0470     m_startVisible = Settings::visible();
0471     m_multiDriverMode = Settings::multiDriver();
0472     m_defaultCardOnStart = Settings::defaultCardOnStart();
0473     m_configVersion = Settings::configVersion();
0474     // WARNING Don't overwrite m_configVersion with the "correct" value, before having it
0475     // evaluated. Better only write that in saveBaseConfig()
0476     m_autouseMultimediaKeys = Settings::autoUseMultimediaKeys();
0477     QString mixerMasterCard = Settings::masterMixer();
0478     QString masterDev = Settings::masterMixerDevice();
0479     Mixer::setGlobalMaster(mixerMasterCard, masterDev, true);
0480 
0481     QString mixerIgnoreExpression = Settings::mixerIgnoreExpression();
0482     if (!mixerIgnoreExpression.isEmpty()) MixerToolBox::setMixerIgnoreExpression(mixerIgnoreExpression);
0483 
0484     // The global volume step setting.
0485     const int volumePercentageStep = Settings::volumePercentageStep();
0486     if (volumePercentageStep>0) Volume::setVolumeStep(volumePercentageStep);
0487 
0488     // The following log is very helpful in bug reports. Please keep it.
0489     m_backendFilter = Settings::backends();
0490     qCDebug(KMIX_LOG) << "Backends from settings" << m_backendFilter;
0491 
0492     // show/hide menu bar
0493     bool showMenubar = Settings::menubar();
0494     if (_actionShowMenubar!=nullptr) _actionShowMenubar->setChecked(showMenubar);
0495 }
0496 
0497 /**
0498  * Loads the volumes of all mixers from kmixctrlrc.
0499  * In other words:
0500  * Restores the default volumes as stored via saveVolumes() or the
0501  * execution of "kmixctrl --save"
0502  */
0503 
0504 void KMixWindow::loadVolumes()
0505 {
0506     loadVolumes(QString());
0507 }
0508 
0509 void KMixWindow::loadVolumes(QString postfix)
0510 {
0511     qCDebug(KMIX_LOG)
0512     << "About to load config (Volume)";
0513     const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
0514 
0515     KConfig *cfg = new KConfig(kmixctrlRcFilename);
0516     for (int i = 0; i < Mixer::mixers().count(); ++i)
0517     {
0518         Mixer *mixer = (Mixer::mixers())[i];
0519         mixer->volumeLoad(cfg);
0520     }
0521     delete cfg;
0522 }
0523 
0524 void KMixWindow::recreateGUIwithSavingView()
0525 {
0526     recreateGUI(true, false);
0527 }
0528 
0529 void KMixWindow::recreateGUI(bool saveConfig, bool reset)
0530 {
0531     recreateGUI(saveConfig, QString(), false, reset);
0532 }
0533 
0534 /**
0535  * Create or recreate the Mixer GUI elements
0536  *
0537  * @param saveConfig  Whether to save all View configurations before recreating
0538  * @param forceNewTab To enforce opening a new tab, even when the profileList in the kmixrc is empty.
0539  *                    It should only be set to "true" in case of a Hotplug (because then the user definitely expects a new Tab to show).
0540  */
0541 void KMixWindow::recreateGUI(bool saveConfig, const QString& mixerId, bool forceNewTab, bool reset)
0542 {
0543     // -1- Remember which of the tabs is currently selected for restoration for re-insertion
0544     int oldTabPosition = m_wsMixers->currentIndex();
0545 
0546     if (!reset && saveConfig)
0547         saveViewConfig();  // save the state before recreating
0548 
0549     // -2- RECREATE THE ALREADY EXISTING TABS **********************************
0550     QHash<const Mixer *, bool> mixerHasProfile;
0551 
0552 // -2a- Build a list of all active profiles in the main window (that means: from all tabs)
0553     QList<GUIProfile*> activeGuiProfiles;
0554     for (int i = 0; i < m_wsMixers->count(); ++i)
0555     {
0556         KMixerWidget* kmw = dynamic_cast<KMixerWidget*>(m_wsMixers->widget(i));
0557         if (kmw)
0558         {
0559             activeGuiProfiles.append(kmw->getGuiprof());
0560         }
0561     }
0562 
0563     for (const GUIProfile *guiprof : std::as_const(activeGuiProfiles))
0564     {
0565         const Mixer *mixer = Mixer::findMixer(guiprof->getMixerId());
0566         if (mixer==nullptr)
0567         {
0568             qCCritical(KMIX_LOG) << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId();
0569             continue;
0570         }
0571         mixerHasProfile[mixer] = true;
0572 
0573         KMixerWidget* kmw = findKMWforTab(guiprof->getId());
0574         if ( kmw == 0 )
0575         {
0576             // does not yet exist => create
0577             addMixerWidget(mixer->id(), guiprof->getId(), -1);
0578         }
0579         else
0580         {
0581             // did exist => remove and insert new guiprof at old position
0582             int indexOfTab = m_wsMixers->indexOf(kmw);
0583             if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab);
0584             delete kmw;
0585             addMixerWidget(mixer->id(), guiprof->getId(), indexOfTab);
0586         }
0587     } // Loop over all GUIProfile's
0588 
0589 
0590 
0591     // -3- ADD TABS FOR Mixer instances that have no tab yet **********************************
0592     KConfigGroup pconfig(KSharedConfig::openConfig(), "Profiles");
0593     for (const Mixer *mixer : std::as_const(Mixer::mixers()))
0594     {
0595         if ( mixerHasProfile.contains(mixer))
0596         {
0597             continue;  // OK, this mixer already has a profile => skip it
0598         }
0599 
0600 
0601         // =========================================================================================
0602         // No TAB YET => This should mean KMix is just started, or the user has just plugged in a card
0603 
0604         {
0605             GUIProfile* guiprof = 0;
0606             if (reset)
0607             {
0608                 guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ###
0609             }
0610 
0611             if ( guiprof != 0 )
0612             {
0613                 guiprof->setDirty();  // All fallback => dirty
0614                 addMixerWidget(mixer->id(), guiprof->getId(), -1);
0615                 continue;
0616             }
0617         }
0618 
0619 
0620         // =========================================================================================
0621         // The trivial cases have not added anything => Look at [Profiles] in config file
0622 
0623         QStringList profileList = pconfig.readEntry( mixer->id(), QStringList() );
0624         bool allProfilesRemovedByUser = pconfig.hasKey(mixer->id()) && profileList.isEmpty();
0625         if (allProfilesRemovedByUser)
0626         {
0627             continue; // User has explicitly hidden the views => do no further checks
0628         }
0629 
0630         {
0631             bool atLeastOneProfileWasAdded = false;
0632 
0633             for (const QString &profileId : std::as_const(profileList))
0634             {
0635                 // This handles the profileList form the kmixrc
0636                 qCDebug(KMIX_LOG) << "Searching for GUI profile" << profileId;
0637                 GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ###
0638 
0639                 if (guiprof==nullptr)
0640                 {
0641                     qCWarning(KMIX_LOG) << "Cannot load profile" << profileId;
0642                     if (profileId.startsWith(QLatin1String("MPRIS2.")))
0643                     {
0644                         const QString fallbackProfileId = "MPRIS2.default";
0645                         qCDebug(KMIX_LOG) << "For MPRIS2 falling back to" << fallbackProfileId;
0646                         guiprof = GUIProfile::find(mixer, fallbackProfileId, true, false);
0647                     }
0648                 }
0649 
0650                 if (guiprof!=nullptr)
0651                 {
0652                     addMixerWidget(mixer->id(), guiprof->getId(), -1);
0653                     atLeastOneProfileWasAdded = true;
0654                 }
0655             }
0656 
0657             if (atLeastOneProfileWasAdded)
0658             {
0659                 // Continue
0660                 continue;
0661             }
0662         }
0663 
0664         // =========================================================================================
0665         // Neither trivial cases have added something, nor the anything => Look at [Profiles] in config file
0666 
0667         // The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code.
0668         bool mixerIdMatch = mixerId.isEmpty() || (mixer->id() == mixerId);
0669         bool thisMixerShouldBeForced = forceNewTab && mixerIdMatch;
0670         bool we_need_a_fallback = mixerIdMatch && thisMixerShouldBeForced;
0671         if ( we_need_a_fallback )
0672         {
0673             // The profileList was empty or nothing could be loaded
0674             //     (Hint: This means the user cannot hide a device completely
0675 
0676             // Lets try a bunch of fallback strategies:
0677             qCDebug(KMIX_LOG) << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id();
0678             GUIProfile* guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ###
0679             if ( guiprof == 0 )
0680             {
0681                 qCDebug(KMIX_LOG) << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id();
0682                 guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ###
0683             }
0684             if ( guiprof == 0)
0685             {
0686                 qCDebug(KMIX_LOG) << "Using fallback GUI Profile for the mixer " << mixer->id();
0687                 // This means there is neither card specific nor card unspecific profile
0688                 // This is the case for some backends (as they don't ship profiles).
0689                 guiprof = GUIProfile::fallbackProfile(mixer);
0690             }
0691 
0692             if ( guiprof != 0 )
0693             {
0694                 guiprof->setDirty();  // All fallback => dirty
0695                 addMixerWidget(mixer->id(), guiprof->getId(), -1);
0696             }
0697             else
0698             {
0699                 qCCritical(KMIX_LOG) << "Cannot use ANY profile (including Fallback) for mixer " << mixer->id() << " . This is impossible, and thus this mixer can NOT be used.";
0700             }
0701 
0702         }
0703     }
0704     mixerHasProfile.clear();
0705 
0706     // -4- FINALIZE **********************************
0707     if (m_wsMixers->count() > 0)
0708     {
0709         if (oldTabPosition >= 0)
0710         {
0711             m_wsMixers->setCurrentIndex(oldTabPosition);
0712         }
0713         bool dockingSucceded = updateDocking();
0714         if (!dockingSucceded && !Mixer::mixers().empty())
0715         {
0716             show(); // avoid invisible and inaccessible main window
0717         }
0718     }
0719     else
0720     {
0721         // No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards.
0722         updateDocking();  // -<- removes the DockIcon
0723         hide();
0724     }
0725 
0726 }
0727 
0728 KMixerWidget*
0729 KMixWindow::findKMWforTab(const QString& kmwId)
0730 {
0731     for (int i = 0; i < m_wsMixers->count(); ++i)
0732     {
0733         KMixerWidget *kmw = qobject_cast<KMixerWidget *>(m_wsMixers->widget(i));
0734         if (kmw->getGuiprof()->getId() == kmwId)
0735         {
0736             return kmw;
0737         }
0738     }
0739     return 0;
0740 }
0741 
0742 void KMixWindow::newView()
0743 {
0744     if (Mixer::mixers().empty())
0745     {
0746         qCCritical(KMIX_LOG) << "Trying to create a View, but no Mixer exists";
0747         return; // should never happen
0748     }
0749 
0750     Mixer *mixer = Mixer::mixers()[0];
0751     QPointer<DialogAddView> dav = new DialogAddView(this, mixer);
0752     int ret = dav->exec();
0753 
0754     // TODO: it is pointless using a smart pointer for the dialogue
0755     // (which is good practice) here and then not checking it!
0756     if (QDialog::Accepted == ret)
0757     {
0758         QString profileName = dav->getresultViewName();
0759         QString mixerId = dav->getresultMixerId();
0760         mixer = Mixer::findMixer(mixerId);
0761         qCDebug(KMIX_LOG)
0762         << ">>> mixer = " << mixerId << " -> " << mixer;
0763 
0764         GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false);
0765         if (guiprof == nullptr)
0766         {
0767             guiprof = GUIProfile::find(mixer, profileName, false, true);
0768         }
0769 
0770         if (guiprof == nullptr)
0771         {
0772             KMessageBox::error(this, i18n("Cannot add view - GUIProfile is invalid."), i18n("Error"));
0773         }
0774         else
0775         {
0776             bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1);
0777             if (!ret)
0778             {
0779                 KMessageBox::error(this, i18n("Cannot add view - View already exists."), i18n("Error"));
0780             }
0781         }
0782 
0783         delete dav;
0784     }
0785 
0786     //qCDebug(KMIX_LOG) << "Exit";
0787 }
0788 
0789 /**
0790  * Save the view and close it
0791  *
0792  * @arg idx The index in the TabWidget
0793  */
0794 void KMixWindow::saveAndCloseView(int idx)
0795 {
0796     qCDebug(KMIX_LOG)
0797     << "Enter";
0798     QWidget *w = m_wsMixers->widget(idx);
0799     KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
0800     if (kmw)
0801     {
0802         kmw->saveConfig(Settings::self()->config()); // -<- This alone is not enough, as I need to save the META information as well. Thus use saveViewConfig() below
0803         m_wsMixers->removeTab(idx);
0804         updateTabsClosable();
0805         saveViewConfig();
0806         delete kmw;
0807     }
0808     qCDebug(KMIX_LOG)
0809     << "Exit";
0810 }
0811 
0812 void KMixWindow::fixConfigAfterRead()
0813 {
0814     unsigned int configVersion = Settings::configVersion();
0815     if (configVersion < 3)
0816     {
0817         // Fix the "double Base" bug, by deleting all groups starting with "View.Base.Base.".
0818         // The group has been copied over by KMixToolBox::loadView() for all soundcards, so
0819         // we should be fine now
0820         QStringList cfgGroups = KSharedConfig::openConfig()->groupList();
0821         QStringListIterator it(cfgGroups);
0822         while (it.hasNext())
0823         {
0824             QString groupName = it.next();
0825             if (groupName.indexOf("View.Base.Base") == 0)
0826             {
0827                 qCDebug(KMIX_LOG) << "Fixing group " << groupName;
0828                 KConfigGroup buggyDevgrpCG(KSharedConfig::openConfig(), groupName);
0829                 buggyDevgrpCG.deleteGroup();
0830             } // remove buggy group
0831         } // for all groups
0832     } // if config version < 3
0833 }
0834 
0835 
0836 void KMixWindow::plugged(const char *driverName, const QString &udi, int dev)
0837 {
0838     qCDebug(KMIX_LOG) << "dev" << dev << "driver" << driverName << "udi" << udi;
0839     Mixer *mixer = new Mixer(QString::fromLocal8Bit(driverName), dev);
0840     if (mixer!=nullptr)
0841     {
0842         if (MixerToolBox::possiblyAddMixer(mixer))
0843         {
0844             qCDebug(KMIX_LOG) << "adding mixer id" << mixer->id() << "name" << mixer->readableName();
0845             recreateGUI(true, mixer->id(), true, false);
0846         }
0847         else qCWarning(KMIX_LOG) << "Cannot add mixer to GUI";
0848     }
0849 }
0850 
0851 
0852 void KMixWindow::unplugged(const QString &udi)
0853 {
0854     qCDebug(KMIX_LOG) << "udi" << udi;
0855     for (int i = 0; i < Mixer::mixers().count(); ++i)
0856     {
0857         Mixer *mixer = (Mixer::mixers())[i];
0858         // qCDebug(KMIX_LOG) << "Try Match with:" << mixer->udi();
0859         if (mixer->udi() == udi)
0860         {
0861             qCDebug(KMIX_LOG) << "Removing mixer";
0862             bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer());
0863 
0864             // Part 1: Remove tab from GUI
0865             for (int i = 0; i < m_wsMixers->count(); ++i)
0866             {
0867                 QWidget *w = m_wsMixers->widget(i);
0868                 KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
0869                 if (kmw && kmw->mixer() == mixer)
0870                 {
0871                     saveAndCloseView(i);
0872                     i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() )
0873                 }
0874             }
0875 
0876             // Part 2: Remove mixer from known list
0877             MixerToolBox::removeMixer(mixer);
0878 
0879             // Part 3: Check whether the Global Master disappeared,
0880             // and select a new one if necessary
0881             shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
0882             if (globalMasterMixerDestroyed || md.get() == 0)
0883             {
0884                 // We don't know what the global master should be now.
0885                 // So lets play stupid, and just select the recommended master of the first device
0886                 if (Mixer::mixers().count() > 0)
0887                 {
0888                     shared_ptr<MixDevice> master = ((Mixer::mixers())[0])->getLocalMasterMD();
0889                     if (master.get() != 0)
0890                     {
0891                         QString localMaster = master->id();
0892                         Mixer::setGlobalMaster(((Mixer::mixers())[0])->id(), localMaster, false);
0893 
0894                         QString text;
0895                         text =
0896                                 i18n(
0897                                         "The soundcard containing the master device was unplugged. Changing to control %1 on card %2.",
0898                                         master->readableName(), ((Mixer::mixers())[0])->readableName());
0899                         KMixToolBox::notification("MasterFallback", text);
0900                     }
0901                 }
0902             }
0903             if (Mixer::mixers().count() == 0)
0904             {
0905                 QString text;
0906                 text = i18n("The last soundcard was unplugged.");
0907                 KMixToolBox::notification("MasterFallback", text);
0908             }
0909             recreateGUI(true, false);
0910             break;
0911         }
0912     }
0913 
0914 }
0915 
0916 
0917 /**
0918  *
0919  */
0920 bool KMixWindow::profileExists(QString guiProfileId)
0921 {
0922     for (int i = 0; i < m_wsMixers->count(); ++i)
0923     {
0924         KMixerWidget* kmw = dynamic_cast<KMixerWidget*>(m_wsMixers->widget(i));
0925         if (kmw && kmw->getGuiprof()->getId() == guiProfileId)
0926             return true;
0927     }
0928     return false;
0929 }
0930 
0931 bool KMixWindow::addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition)
0932 {
0933     qCDebug(KMIX_LOG)
0934     << "Add " << guiprofId;
0935     GUIProfile* guiprof = GUIProfile::find(guiprofId);
0936     if (guiprof != 0 && profileExists(guiprof->getId())) // TODO Bad place. Should be checked in the add-tab-dialog
0937         return false; // already present => don't add again
0938     Mixer *mixer = Mixer::findMixer(mixer_ID);
0939     if (mixer == 0)
0940         return false; // no such Mixer
0941 
0942     //       qCDebug(KMIX_LOG) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added";
0943     ViewBase::ViewFlags vflags = ViewBase::HasMenuBar;
0944     if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked())
0945         vflags |= ViewBase::MenuBarVisible;
0946     KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId, actionCollection());
0947     /* A newly added mixer will automatically added at the top
0948      * and thus the window title is also set appropriately */
0949 
0950     /*
0951      * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am
0952      * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the
0953      * card ID. This means you cannot distinguish between cards with an identical name.
0954      */
0955 //  QString tabLabel = guiprof->getName();
0956 //  if (tabLabel.isEmpty())
0957 //    QString tabLabel = kmw->mixer()->readableName(true);
0958     QString tabLabel = kmw->mixer()->readableName(true);
0959 
0960     m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart
0961 
0962     if (insertPosition == -1)
0963         m_wsMixers->addTab(kmw, tabLabel);
0964     else
0965         m_wsMixers->insertTab(insertPosition, kmw, tabLabel);
0966 
0967     if (kmw->getGuiprof()->getId() == m_defaultCardOnStart)
0968     {
0969         m_wsMixers->setCurrentWidget(kmw);
0970     }
0971 
0972     updateTabsClosable();
0973     m_dontSetDefaultCardOnStart = false;
0974 
0975     kmw->loadConfig(Settings::self()->config());
0976     // Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly
0977     // obsolete, as the backend should take care of updating itself.
0978     kmw->mixer()->readSetFromHWforceUpdate();
0979     return true;
0980 }
0981 
0982 void KMixWindow::updateTabsClosable()
0983 {
0984     // Pulseaudio runs with 4 fixed tabs - don't allow to close them.
0985     // Also do not allow to close the last view
0986     m_wsMixers->setTabsClosable(!Mixer::pulseaudioPresent() && m_wsMixers->count() > 1);
0987 }
0988 
0989 bool KMixWindow::queryClose()
0990 {
0991     if (Settings::showDockWidget() && !qApp->isSavingSession())
0992     {
0993         // Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process.
0994         hide();
0995         return false;
0996     }
0997     else
0998     {
0999         // Accept the close, if:
1000         //     The user has disabled docking
1001         // or  SessionSaving() is running
1002         //         qCDebug(KMIX_LOG) << "close";
1003         return true;
1004     }
1005 }
1006 
1007 void KMixWindow::hideOrClose()
1008 {
1009     if (Settings::showDockWidget() && m_dockWidget!=nullptr)
1010     {
1011         // we can hide if there is a dock widget
1012         hide();
1013     }
1014     else
1015     {
1016         //  if there is no dock widget, we will quit
1017         quit();
1018     }
1019 }
1020 
1021 // internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume
1022 void KMixWindow::increaseOrDecreaseVolume(bool increase)
1023 {
1024     Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below
1025     if (mixer == 0)
1026         return; // e.g. when no soundcard is available
1027     shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1028     if (md.get() == 0)
1029         return; // shouldn't happen, but lets play safe
1030 
1031     Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture;
1032     md->increaseOrDecreaseVolume(!increase, volumeType);
1033     md->mixer()->commitVolumeChange(md);
1034 
1035     showVolumeDisplay();
1036 }
1037 
1038 void KMixWindow::slotIncreaseVolume()
1039 {
1040     increaseOrDecreaseVolume(true);
1041 }
1042 
1043 void KMixWindow::slotDecreaseVolume()
1044 {
1045     increaseOrDecreaseVolume(false);
1046 }
1047 
1048 void KMixWindow::showVolumeDisplay()
1049 {
1050     Mixer* mixer = Mixer::getGlobalMasterMixer();
1051     if (mixer == 0)
1052         return; // e.g. when no soundcard is available
1053     shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1054     if (md.get() == 0)
1055         return; // shouldn't happen, but lets play safe
1056 
1057     if (Settings::showOSD())
1058     {
1059         QDBusMessage msg = QDBusMessage::createMethodCall(
1060             "org.kde.plasmashell",
1061             "/org/kde/osdService",
1062             "org.kde.osdService",
1063             "volumeChanged"
1064         );
1065 
1066         int currentVolume = 0;
1067         if (!md->isMuted()) {
1068             currentVolume = md->playbackVolume().getAvgVolumePercent(Volume::MALL);
1069         }
1070 
1071         msg.setArguments(QList<QVariant>() << currentVolume);
1072 
1073         QDBusConnection::sessionBus().asyncCall(msg);
1074     }
1075 }
1076 
1077 /**
1078  * Mutes the global master. (SLOT)
1079  */
1080 void KMixWindow::slotMute()
1081 {
1082     Mixer* mixer = Mixer::getGlobalMasterMixer();
1083     if (mixer == 0)
1084         return; // e.g. when no soundcard is available
1085     shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1086     if (md.get() == 0)
1087         return; // shouldn't happen, but lets play safe
1088     md->toggleMute();
1089     mixer->commitVolumeChange(md);
1090     showVolumeDisplay();
1091 }
1092 
1093 void KMixWindow::quit()
1094 {
1095     //     qCDebug(KMIX_LOG) << "quit";
1096     qApp->quit();
1097 }
1098 
1099 /**
1100  * Shows the configuration dialog, with the "General" tab opened.
1101  */
1102 void KMixWindow::showSettings()
1103 {
1104     KMixPrefDlg::instance()->showAtPage(KMixPrefDlg::PageGeneral);
1105 }
1106 
1107 
1108 void KMixWindow::showHelp()
1109 {
1110     actionCollection()->action("help_contents")->trigger();
1111 }
1112 
1113 void KMixWindow::showAbout()
1114 {
1115     actionCollection()->action("help_about_app")->trigger();
1116 }
1117 
1118 
1119 /**
1120  * Apply the Preferences from the preferences dialog. Depending on what has been changed,
1121  * the corresponding announcements are made.
1122  */
1123 void KMixWindow::applyPrefs(KMixPrefDlg::PrefChanges changes)
1124 {
1125     qCDebug(KMIX_LOG) << "changes" << changes;
1126 
1127     if (changes & KMixPrefDlg::ChangedControls)
1128     {
1129         // These might need a complete relayout => announce a ControlList change to rebuild everything
1130         ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("Preferences Dialog"));
1131     }
1132     else if (changes & KMixPrefDlg::ChangedMaster)
1133     {
1134         // This announce was originally made in KMixPrefDlg::updateSettings().
1135         // It is treated as equivalent to ControlManager::ControlList by
1136         // the system tray popup, hence the 'else' here.
1137         ControlManager::instance().announce(QString(), ControlManager::MasterChanged, QString("Select Backends Dialog"));
1138     }
1139     if (changes & KMixPrefDlg::ChangedGui)
1140     {
1141         ControlManager::instance().announce(QString(), ControlManager::GUI, QString("Preferences Dialog"));
1142     }
1143 
1144     //this->repaint(); // make KMix look fast (saveConfig() often uses several seconds)
1145     qApp->processEvents();
1146 
1147     // Remove saveConfig() IF aa changes have been migrated to GlobalConfig.
1148     // Currently there is still stuff like "show menu bar".
1149     saveConfig();
1150 }
1151 
1152 
1153 void KMixWindow::toggleMenuBar()
1154 {
1155     menuBar()->setVisible(_actionShowMenubar->isChecked());
1156 }
1157 
1158 void KMixWindow::slotKdeAudioSetupExec()
1159 {
1160     forkExec(QStringList() << "kcmshell5" << "kcm_pulseaudio");
1161 }
1162 
1163 void KMixWindow::forkExec(const QStringList& args)
1164 {
1165    int pid = KProcess::startDetached(args);
1166    if (pid == 0)
1167    {
1168        KMessageBox::error(this, i18n("The helper application is either not installed or not working.\n\n%1",
1169                          args.join(QLatin1String(" "))));
1170    }
1171 }
1172 
1173 void KMixWindow::slotConfigureCurrentView()
1174 {
1175     KMixerWidget *mw = qobject_cast<KMixerWidget *>(m_wsMixers->currentWidget());
1176     ViewBase* view = 0;
1177     if (mw)
1178         view = mw->currentView();
1179     if (view)
1180         view->configureView();
1181 }
1182 
1183 void KMixWindow::slotSelectMasterClose(QObject*)
1184 {
1185     m_dsm = 0;
1186 }
1187 
1188 void KMixWindow::slotSelectMaster()
1189 {
1190     const Mixer *mixer = Mixer::getGlobalMasterMixer();
1191     if (mixer!=nullptr)
1192     {
1193         if (!m_dsm)
1194         {
1195             m_dsm = new DialogSelectMaster(mixer, this);
1196             connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*)));
1197             m_dsm->setAttribute(Qt::WA_DeleteOnClose, true);
1198             m_dsm->show();
1199         }
1200         m_dsm->raise();
1201         m_dsm->activateWindow();
1202     }
1203     else
1204     {
1205         KMessageBox::error(nullptr, KMixToolBox::noDevicesWarningString());
1206     }
1207 }
1208 
1209 void KMixWindow::newMixerShown(int /*tabIndex*/)
1210 {
1211     KMixerWidget *kmw = qobject_cast<KMixerWidget *>(m_wsMixers->currentWidget());
1212     if (kmw!=nullptr)
1213     {
1214         // I am using the app name as a PREFIX, as KMix is a single window app, and it is
1215         // more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic
1216         // soundcard name like "HDA ATI SB".
1217         // Reformatted for KF5 so as to not say "KDE"
1218         // and so that there are not two different dashes.
1219         setWindowTitle(i18n("Mixer (%1)", kmw->mixer()->readableName()));
1220         if (!m_dontSetDefaultCardOnStart)
1221             m_defaultCardOnStart = kmw->getGuiprof()->getId();
1222         // As switching the tab does NOT mean switching the master card, we do not need to update dock icon here.
1223         // It would lead to unnecesary flickering of the (complete) dock area.
1224 
1225         // We only show the "Configure Channels..." menu item if the mixer is not dynamic
1226         ViewBase* view = kmw->currentView();
1227         QAction* action = actionCollection()->action("toggle_channels_currentview");
1228         if (view && action)
1229             action->setVisible(!view->isDynamic());
1230     }
1231 }