diff --git a/apps/kmix.cpp b/apps/kmix.cpp index db06efe2..bf286be9 100644 --- a/apps/kmix.cpp +++ b/apps/kmix.cpp @@ -1,1300 +1,1287 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "apps/kmix.h" // include files for Qt #include #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include // KMix #include "gui/guiprofile.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/MasterControl.h" #include "core/MediaController.h" #include "core/mixertoolbox.h" #include "core/kmixdevicemanager.h" #include "gui/kmixerwidget.h" #include "gui/kmixprefdlg.h" #include "gui/kmixdockwidget.h" #include "gui/kmixtoolbox.h" #include "core/version.h" #include "gui/viewdockareapopup.h" #include "gui/dialogaddview.h" #include "gui/dialogselectmaster.h" #include "dbus/dbusmixsetwrapper.h" #include "kmix_debug.h" #include #include #include /* KMixWindow * Constructs a mixer window (KMix main window) */ KMixWindow::KMixWindow(bool invisible, bool reset) : KXmlGuiWindow(0, Qt::WindowFlags( KDE_DEFAULT_WINDOWFLAGS | Qt::WindowContextHelpButtonHint)), m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident m_autouseMultimediaKeys(true), m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false) { setObjectName(QStringLiteral("KMixWindow")); // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in setAttribute(Qt::WA_DeleteOnClose, false); initActions(); // init actions first, so we can use them in the loadConfig() already loadAndInitConfig(reset); // Load config before initMixer(), e.g. due to "MultiDriver" keyword initActionsLate(); // init actions that require a loaded config // TODO: Port to KF5 //KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls")); initWidgets(); initPrefDlg(); DBusMixSetWrapper::initialize(this, QStringLiteral("/Mixers")); MixerToolBox::initMixer(m_multiDriverMode, m_backendFilter, true); KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance(); initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s). recreateGUI(false, reset); if (m_wsMixers->count() < 1) { // Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder recreateGUI(false, QString(), true, reset); } if (!qApp->isSessionRestored() ) // done by the session manager otherwise setInitialSize(); fixConfigAfterRead(); connect(theKMixDeviceManager, &KMixDeviceManager::plugged, this, &KMixWindow::plugged); connect(theKMixDeviceManager, &KMixDeviceManager::unplugged, this, &KMixWindow::unplugged); theKMixDeviceManager->initHotplug(); if (m_startVisible && !invisible) show(); // Started visible connect(qApp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) ); ControlManager::instance().addListener( QString(), // All mixers (as the Global master Mixer might change) ControlManager::ControlList|ControlManager::MasterChanged, this, "KMixWindow"); // Send an initial volume refresh (otherwise all volumes are 0 until the next change) ControlManager::instance().announce(QString(), ControlManager::Volume, "Startup"); } KMixWindow::~KMixWindow() { ControlManager::instance().removeListener(this); delete m_dsm; // -1- Cleanup Memory: clearMixerWidgets while (m_wsMixers->count() != 0) { QWidget *mw = m_wsMixers->widget(0); m_wsMixers->removeTab(0); delete mw; } // -2- Mixer HW MixerToolBox::deinitMixer(); // -3- Action collection (just to please Valgrind) actionCollection()->clear(); // GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which // means main window and potentially also in the tray popup (at least we might do so in the future). // This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup. // Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic. GUIProfile::clearCache(); } void KMixWindow::controlsChange(ControlManager::ChangeType changeType) { switch (changeType) { case ControlManager::ControlList: case ControlManager::MasterChanged: updateDocking(); break; default: ControlManager::warnUnexpectedChangeType(changeType, this); break; } } void KMixWindow::initActions() { // file menu KStandardAction::quit(this, SLOT(quit()), actionCollection()); // settings menu _actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()), actionCollection()); //actionCollection()->addAction(QStringLiteral( a->objectName()), a ); KStandardAction::preferences(this, SLOT(showSettings()), actionCollection()); KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); QAction* action = actionCollection()->addAction(QStringLiteral("launch_kdesoundsetup")); action->setText(i18n("Audio Setup...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec())); action = actionCollection()->addAction(QStringLiteral("hide_kmixwindow")); action->setText(i18n("Hide Mixer Window")); connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose())); actionCollection()->setDefaultShortcut(action, Qt::Key_Escape); action = actionCollection()->addAction(QStringLiteral("toggle_channels_currentview")); action->setText(i18n("Configure &Channels...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView())); action = actionCollection()->addAction(QStringLiteral("select_master")); action->setText(i18n("Select Master Channel...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster())); action = actionCollection()->addAction(QStringLiteral("save_1")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_1); action->setText(i18n("Save volume profile 1")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1())); action = actionCollection()->addAction(QStringLiteral("save_2")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_2); action->setText(i18n("Save volume profile 2")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2())); action = actionCollection()->addAction(QStringLiteral("save_3")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_3); action->setText(i18n("Save volume profile 3")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3())); action = actionCollection()->addAction(QStringLiteral("save_4")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_4); action->setText(i18n("Save volume profile 4")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4())); action = actionCollection()->addAction(QStringLiteral("load_1")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_1); action->setText(i18n("Load volume profile 1")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1())); action = actionCollection()->addAction(QStringLiteral("load_2")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_2); action->setText(i18n("Load volume profile 2")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2())); action = actionCollection()->addAction(QStringLiteral("load_3")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_3); action->setText(i18n("Load volume profile 3")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3())); action = actionCollection()->addAction(QStringLiteral("load_4")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_4); action->setText(i18n("Load volume profile 4")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4())); createGUI(QLatin1String("kmixui.rc")); } void KMixWindow::initActionsLate() { if (m_autouseMultimediaKeys) { QAction* globalAction = actionCollection()->addAction(QStringLiteral("increase_volume")); globalAction->setText(i18n("Increase Volume")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeUp); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume())); globalAction = actionCollection()->addAction(QStringLiteral("decrease_volume")); globalAction->setText(i18n("Decrease Volume")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeDown); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume())); globalAction = actionCollection()->addAction(QStringLiteral("mute")); globalAction->setText(i18n("Mute")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeMute); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute())); } } void KMixWindow::initActionsAfterInitMixer() { // Only show the new tab widget if Pulseaudio is not used. Hint: The Pulseaudio backend always // runs with 4 fixed Tabs. if (!Mixer::pulseaudioPresent()) { QPushButton* _cornerLabelNew = new QPushButton(); _cornerLabelNew->setIcon(QIcon::fromTheme("tab-new")); _cornerLabelNew->setToolTip(i18n("Add new view")); //cornerLabelNew->setSizePolicy(QSizePolicy()); m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner); connect(_cornerLabelNew, SIGNAL(clicked()), SLOT(newView())); } } void KMixWindow::initPrefDlg() { KMixPrefDlg* prefDlg = KMixPrefDlg::createInstance(this, GlobalConfig::instance()); connect(prefDlg, SIGNAL(kmixConfigHasChanged()), SLOT(applyPrefs())); } void KMixWindow::initWidgets() { m_wsMixers = new QTabWidget(); m_wsMixers->setDocumentMode(true); setCentralWidget(m_wsMixers); m_wsMixers->setTabsClosable(false); connect(m_wsMixers, SIGNAL(tabCloseRequested(int)), SLOT(saveAndCloseView(int))); connect(m_wsMixers, SIGNAL(currentChanged(int)), SLOT(newMixerShown(int))); // show menubar if the actions says so (or if the action does not exist) menuBar()->setVisible((_actionShowMenubar == 0) || _actionShowMenubar->isChecked()); } void KMixWindow::setInitialSize() { KConfigGroup config(KSharedConfig::openConfig(), "Global"); // HACK: QTabWidget will bound its sizeHint to 200x200 unless scrollbuttons // are disabled, so we disable them, get a decent sizehint and enable them // back m_wsMixers->setUsesScrollButtons(false); QSize defSize = sizeHint(); m_wsMixers->setUsesScrollButtons(true); QSize size = config.readEntry("Size", defSize); if (!size.isEmpty()) resize(size); QPoint defPos = pos(); QPoint pos = config.readEntry("Position", defPos); move(pos); } void KMixWindow::removeDock() { if (m_dockWidget) { m_dockWidget->deleteLater(); m_dockWidget = 0; } } /** * Creates or deletes the KMixDockWidget, depending on whether there is a Mixer instance available. * * @returns true, if the docking succeeded. Failure usually means that there * was no suitable mixer control selected. */ bool KMixWindow::updateDocking() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (!gcd.showDockWidget || Mixer::mixers().isEmpty()) { removeDock(); return false; } if (!m_dockWidget) { m_dockWidget = new KMixDockWidget(this); } return true; } void KMixWindow::saveConfig() { saveBaseConfig(); saveViewConfig(); saveVolumes(); #ifdef __GNUC_ #warn We must Sync here, or we will lose configuration data. The reson for that is unknown. #endif // TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!! KSharedConfig::openConfig()->sync(); qCDebug(KMIX_LOG) << "Saved config ... sync finished"; } void KMixWindow::saveBaseConfig() { GlobalConfig::instance().save(); KConfigGroup config(KSharedConfig::openConfig(), "Global"); config.writeEntry("Size", size()); config.writeEntry("Position", pos()); // Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden. // (Please note that the problem was only there when quitting via Systray - esken). // Using it again, as internal behaviour has changed with KDE4 config.writeEntry("Visible", isVisible()); config.writeEntry("Menubar", _actionShowMenubar->isChecked()); config.writeEntry("Soundmenu.Mixers", GlobalConfig::instance().getMixersForSoundmenu().values()); config.writeEntry("DefaultCardOnStart", m_defaultCardOnStart); config.writeEntry("ConfigVersion", KMIX_CONFIG_VERSION); config.writeEntry("AutoUseMultimediaKeys", m_autouseMultimediaKeys); MasterControl& master = Mixer::getGlobalMasterPreferred(false); config.writeEntry("MasterMixer", master.getCard()); config.writeEntry("MasterMixerDevice", master.getControl()); QString mixerIgnoreExpression = MixerToolBox::mixerIgnoreExpression(); config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression); qCDebug(KMIX_LOG) << "Base configuration saved"; } void KMixWindow::saveViewConfig() { QMap mixerViews; // The following loop is necessary for the case that the user has hidden all views for a Mixer instance. // Otherwise we would not save the Meta information (step -2- below for that mixer. // We also do not save dynamic mixers (e.g. PulseAudio) foreach ( Mixer* mixer, Mixer::mixers() ) { mixerViews[mixer->id()]; // just insert a map entry } // -1- Save the views themselves for (int i = 0; i < m_wsMixers->count(); ++i) { QWidget *w = m_wsMixers->widget(i); KMixerWidget *mw = qobject_cast(w); if (mw!=nullptr) { // Here also Views are saved. even for Mixers that are closed. This is necessary when unplugging cards. // Otherwise the user will be confused afer re-plugging the card (as the config was not saved). mw->saveConfig(KSharedConfig::openConfig().data()); // add the view to the corresponding mixer list, so we can save a views-per-mixer list below // if (!mw->mixer()->isDynamic()) // { QStringList& qsl = mixerViews[mw->mixer()->id()]; qsl.append(mw->getGuiprof()->getId()); // } } } // -2- Save Meta-Information (which views, and in which order). views-per-mixer list KConfigGroup pconfig(KSharedConfig::openConfig(), "Profiles"); QMap::const_iterator itEnd = mixerViews.constEnd(); for (QMap::const_iterator it = mixerViews.constBegin(); it != itEnd; ++it) { const QString& mixerProfileKey = it.key(); // this is actually some mixer->id() const QStringList& qslProfiles = it.value(); pconfig.writeEntry(mixerProfileKey, qslProfiles); qCDebug(KMIX_LOG) << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count(); } qCDebug(KMIX_LOG) << "View configuration saved"; } /** * Stores the volumes of all mixers Can be restored via loadVolumes() or * the kmixctrl application. */ void KMixWindow::saveVolumes() { saveVolumes(QString()); } void KMixWindow::saveVolumes(const QString &postfix) { const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix); KConfig *cfg = new KConfig(kmixctrlRcFilename); for (int i = 0; i < Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; if (mixer->isOpen()) { // protect from unplugged devices (better do *not* save them) mixer->volumeSave(cfg); } } cfg->sync(); delete cfg; qCDebug(KMIX_LOG) << "Volume configuration saved"; } QString KMixWindow::getKmixctrlRcFilename(const QString &postfix) { QString kmixctrlRcFilename("kmixctrlrc"); if (!postfix.isEmpty()) { kmixctrlRcFilename.append(".").append(postfix); } return kmixctrlRcFilename; } void KMixWindow::loadAndInitConfig(bool reset) { if (!reset) { loadBaseConfig(); } //loadViewConfig(); // mw->loadConfig() explicitly called always after creating mw. //loadVolumes(); // not in use // create an initial snapshot, so we have a reference of the state before changes through the preferences dialog configDataSnapshot = GlobalConfig::instance().data; } void KMixWindow::loadBaseConfig() { KConfigGroup config(KSharedConfig::openConfig(), "Global"); GlobalConfigData& gcd = GlobalConfig::instance().data; QList preferredMixersInSoundMenu; preferredMixersInSoundMenu = config.readEntry("Soundmenu.Mixers", preferredMixersInSoundMenu); GlobalConfig::instance().setMixersForSoundmenu(preferredMixersInSoundMenu.toSet()); m_startVisible = config.readEntry("Visible", false); m_multiDriverMode = config.readEntry("MultiDriver", false); m_defaultCardOnStart = config.readEntry("DefaultCardOnStart", ""); m_configVersion = config.readEntry("ConfigVersion", 0); // WARNING Don't overwrite m_configVersion with the "correct" value, before having it // evaluated. Better only write that in saveBaseConfig() m_autouseMultimediaKeys = config.readEntry("AutoUseMultimediaKeys", true); QString mixerMasterCard = config.readEntry("MasterMixer", ""); QString masterDev = config.readEntry("MasterMixerDevice", ""); Mixer::setGlobalMaster(mixerMasterCard, masterDev, true); QString mixerIgnoreExpression = config.readEntry("MixerIgnoreExpression", "Modem"); MixerToolBox::setMixerIgnoreExpression(mixerIgnoreExpression); // --- Advanced options, without GUI: START ------------------------------------- QString volumePercentageStepString = config.readEntry("VolumePercentageStep"); if (!volumePercentageStepString.isNull()) { float volumePercentageStep = volumePercentageStepString.toFloat(); if (volumePercentageStep > 0 && volumePercentageStep <= 100) Volume::VOLUME_STEP_DIVISOR = (100 / volumePercentageStep); } // --- Advanced options, without GUI: END ------------------------------------- // The following log is very helpful in bug reports. Please keep it. m_backendFilter = config.readEntry<>("Backends", QList()); qCDebug(KMIX_LOG) << "Backends: " << m_backendFilter; // show/hide menu bar bool showMenubar = config.readEntry("Menubar", true); if (_actionShowMenubar) _actionShowMenubar->setChecked(showMenubar); } /** * Loads the volumes of all mixers from kmixctrlrc. * In other words: * Restores the default volumes as stored via saveVolumes() or the * execution of "kmixctrl --save" */ void KMixWindow::loadVolumes() { loadVolumes(QString()); } void KMixWindow::loadVolumes(QString postfix) { qCDebug(KMIX_LOG) << "About to load config (Volume)"; const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix); KConfig *cfg = new KConfig(kmixctrlRcFilename); for (int i = 0; i < Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; mixer->volumeLoad(cfg); } delete cfg; } void KMixWindow::recreateGUIwithSavingView() { recreateGUI(true, false); } void KMixWindow::recreateGUI(bool saveConfig, bool reset) { recreateGUI(saveConfig, QString(), false, reset); } /** * Create or recreate the Mixer GUI elements * * @param saveConfig Whether to save all View configurations before recreating * @param forceNewTab To enforce opening a new tab, even when the profileList in the kmixrc is empty. * It should only be set to "true" in case of a Hotplug (because then the user definitely expects a new Tab to show). */ void KMixWindow::recreateGUI(bool saveConfig, const QString& mixerId, bool forceNewTab, bool reset) { // -1- Remember which of the tabs is currently selected for restoration for re-insertion int oldTabPosition = m_wsMixers->currentIndex(); if (!reset && saveConfig) saveViewConfig(); // save the state before recreating // -2- RECREATE THE ALREADY EXISTING TABS ********************************** QHash mixerHasProfile; // -2a- Build a list of all active profiles in the main window (that means: from all tabs) QList activeGuiProfiles; for (int i = 0; i < m_wsMixers->count(); ++i) { KMixerWidget* kmw = dynamic_cast(m_wsMixers->widget(i)); if (kmw) { activeGuiProfiles.append(kmw->getGuiprof()); } } foreach ( GUIProfile* guiprof, activeGuiProfiles) { Mixer *mixer = Mixer::findMixer( guiprof->getMixerId() ); if ( mixer == 0 ) { qCCritical(KMIX_LOG) << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId(); continue; } mixerHasProfile[mixer] = true; KMixerWidget* kmw = findKMWforTab(guiprof->getId()); if ( kmw == 0 ) { // does not yet exist => create addMixerWidget(mixer->id(), guiprof->getId(), -1); } else { // did exist => remove and insert new guiprof at old position int indexOfTab = m_wsMixers->indexOf(kmw); if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab); delete kmw; addMixerWidget(mixer->id(), guiprof->getId(), indexOfTab); } } // Loop over all GUIProfile's // -3- ADD TABS FOR Mixer instances that have no tab yet ********************************** KConfigGroup pconfig(KSharedConfig::openConfig(), "Profiles"); foreach ( Mixer *mixer, Mixer::mixers()) { if ( mixerHasProfile.contains(mixer)) { continue; // OK, this mixer already has a profile => skip it } // ========================================================================================= // No TAB YET => This should mean KMix is just started, or the user has just plugged in a card { GUIProfile* guiprof = 0; if (reset) { guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ### } if ( guiprof != 0 ) { guiprof->setDirty(); // All fallback => dirty addMixerWidget(mixer->id(), guiprof->getId(), -1); continue; } } // ========================================================================================= // The trivial cases have not added anything => Look at [Profiles] in config file QStringList profileList = pconfig.readEntry( mixer->id(), QStringList() ); bool allProfilesRemovedByUser = pconfig.hasKey(mixer->id()) && profileList.isEmpty(); if (allProfilesRemovedByUser) { continue; // User has explicitly hidden the views => do no further checks } { bool atLeastOneProfileWasAdded = false; foreach ( QString profileId, profileList) { // This handles the profileList form the kmixrc qCDebug(KMIX_LOG) << "Searching for GUI profile" << profileId; GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ### if (guiprof==nullptr) { qCWarning(KMIX_LOG) << "Cannot load profile" << profileId; if (profileId.startsWith(QLatin1String("MPRIS2."))) { profileId = "MPRIS2.default"; qCDebug(KMIX_LOG) << "For MPRIS2 falling back to" << profileId; guiprof = GUIProfile::find(mixer, profileId, true, false); } } if (guiprof!=nullptr) { addMixerWidget(mixer->id(), guiprof->getId(), -1); atLeastOneProfileWasAdded = true; } } if (atLeastOneProfileWasAdded) { // Continue continue; } } // ========================================================================================= // Neither trivial cases have added something, nor the anything => Look at [Profiles] in config file // The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code. bool mixerIdMatch = mixerId.isEmpty() || (mixer->id() == mixerId); bool thisMixerShouldBeForced = forceNewTab && mixerIdMatch; bool we_need_a_fallback = mixerIdMatch && thisMixerShouldBeForced; if ( we_need_a_fallback ) { // The profileList was empty or nothing could be loaded // (Hint: This means the user cannot hide a device completely // Lets try a bunch of fallback strategies: qCDebug(KMIX_LOG) << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id(); GUIProfile* guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ### if ( guiprof == 0 ) { qCDebug(KMIX_LOG) << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id(); guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ### } if ( guiprof == 0) { qCDebug(KMIX_LOG) << "Using fallback GUI Profile for the mixer " << mixer->id(); // This means there is neither card specific nor card unspecific profile // This is the case for some backends (as they don't ship profiles). guiprof = GUIProfile::fallbackProfile(mixer); } if ( guiprof != 0 ) { guiprof->setDirty(); // All fallback => dirty addMixerWidget(mixer->id(), guiprof->getId(), -1); } else { qCCritical(KMIX_LOG) << "Cannot use ANY profile (including Fallback) for mixer " << mixer->id() << " . This is impossible, and thus this mixer can NOT be used."; } } } mixerHasProfile.clear(); // -4- FINALIZE ********************************** if (m_wsMixers->count() > 0) { if (oldTabPosition >= 0) { m_wsMixers->setCurrentIndex(oldTabPosition); } bool dockingSucceded = updateDocking(); if (!dockingSucceded && !Mixer::mixers().empty()) { show(); // avoid invisible and inaccessible main window } } else { // No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards. updateDocking(); // -<- removes the DockIcon hide(); } } KMixerWidget* KMixWindow::findKMWforTab(const QString& kmwId) { for (int i = 0; i < m_wsMixers->count(); ++i) { KMixerWidget *kmw = qobject_cast(m_wsMixers->widget(i)); if (kmw->getGuiprof()->getId() == kmwId) { return kmw; } } return 0; } void KMixWindow::newView() { if (Mixer::mixers().empty()) { qCCritical(KMIX_LOG) << "Trying to create a View, but no Mixer exists"; return; // should never happen } Mixer *mixer = Mixer::mixers()[0]; QPointer dav = new DialogAddView(this, mixer); int ret = dav->exec(); if (QDialog::Accepted == ret) { QString profileName = dav->getresultViewName(); QString mixerId = dav->getresultMixerId(); mixer = Mixer::findMixer(mixerId); qCDebug(KMIX_LOG) << ">>> mixer = " << mixerId << " -> " << mixer; GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false); if (guiprof == nullptr) { guiprof = GUIProfile::find(mixer, profileName, false, true); } if (guiprof == nullptr) { KMessageBox::sorry(this, i18n("Cannot add view - GUIProfile is invalid."), i18n("Error")); } else { bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1); if (!ret) { KMessageBox::sorry(this, i18n("Cannot add view - View already exists."), i18n("Error")); } } delete dav; } //qCDebug(KMIX_LOG) << "Exit"; } /** * Save the view and close it * * @arg idx The index in the TabWidget */ void KMixWindow::saveAndCloseView(int idx) { qCDebug(KMIX_LOG) << "Enter"; QWidget *w = m_wsMixers->widget(idx); KMixerWidget* kmw = ::qobject_cast(w); if (kmw) { kmw->saveConfig(KSharedConfig::openConfig().data()); // -<- This alone is not enough, as I need to save the META information as well. Thus use saveViewConfig() below m_wsMixers->removeTab(idx); updateTabsClosable(); saveViewConfig(); delete kmw; } qCDebug(KMIX_LOG) << "Exit"; } void KMixWindow::fixConfigAfterRead() { KConfigGroup grp(KSharedConfig::openConfig(), "Global"); unsigned int configVersion = grp.readEntry("ConfigVersion", 0); if (configVersion < 3) { // Fix the "double Base" bug, by deleting all groups starting with "View.Base.Base.". // The group has been copied over by KMixToolBox::loadView() for all soundcards, so // we should be fine now QStringList cfgGroups = KSharedConfig::openConfig()->groupList(); QStringListIterator it(cfgGroups); while (it.hasNext()) { QString groupName = it.next(); if (groupName.indexOf("View.Base.Base") == 0) { qCDebug(KMIX_LOG) << "Fixing group " << groupName; KConfigGroup buggyDevgrpCG(KSharedConfig::openConfig(), groupName); buggyDevgrpCG.deleteGroup(); } // remove buggy group } // for all groups } // if config version < 3 } void KMixWindow::plugged(const char *driverName, const QString &udi, int dev) { qCDebug(KMIX_LOG) << "dev" << dev << "driver" << driverName << "udi" << udi; Mixer *mixer = new Mixer(QString::fromLocal8Bit(driverName), dev); if (mixer!=nullptr) { if (MixerToolBox::possiblyAddMixer(mixer)) { qCDebug(KMIX_LOG) << "adding mixer id" << mixer->id() << "name" << mixer->readableName(); recreateGUI(true, mixer->id(), true, false); } else qCWarning(KMIX_LOG) << "Cannot add mixer to GUI"; } } void KMixWindow::unplugged(const QString &udi) { qCDebug(KMIX_LOG) << "udi" << udi; for (int i = 0; i < Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; // qCDebug(KMIX_LOG) << "Try Match with:" << mixer->udi(); if (mixer->udi() == udi) { qCDebug(KMIX_LOG) << "Removing mixer"; bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer()); // Part 1: Remove tab from GUI for (int i = 0; i < m_wsMixers->count(); ++i) { QWidget *w = m_wsMixers->widget(i); KMixerWidget* kmw = ::qobject_cast(w); if (kmw && kmw->mixer() == mixer) { saveAndCloseView(i); i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() ) } } // Part 2: Remove mixer from known list MixerToolBox::removeMixer(mixer); // Part 3: Check whether the Global Master disappeared, // and select a new one if necessary shared_ptr md = Mixer::getGlobalMasterMD(); if (globalMasterMixerDestroyed || md.get() == 0) { // We don't know what the global master should be now. // So lets play stupid, and just select the recommended master of the first device if (Mixer::mixers().count() > 0) { shared_ptr master = ((Mixer::mixers())[0])->getLocalMasterMD(); if (master.get() != 0) { QString localMaster = master->id(); Mixer::setGlobalMaster(((Mixer::mixers())[0])->id(), localMaster, false); QString text; text = i18n( "The soundcard containing the master device was unplugged. Changing to control %1 on card %2.", master->readableName(), ((Mixer::mixers())[0])->readableName()); KMixToolBox::notification("MasterFallback", text); } } } if (Mixer::mixers().count() == 0) { QString text; text = i18n("The last soundcard was unplugged."); KMixToolBox::notification("MasterFallback", text); } recreateGUI(true, false); break; } } } -/** - * Create a widget with an error message - * This widget shows an error message like "no mixers detected. - void KMixWindow::setErrorMixerWidget() - { - QString s = i18n("Please plug in your soundcard. No soundcard found. Probably you have not set it up or are missing soundcard drivers. Please check your operating system manual for installing your soundcard."); // !! better text - m_errorLabel = new QLabel( s,this ); - m_errorLabel->setAlignment( Qt::AlignCenter ); - m_errorLabel->setWordWrap(true); - m_errorLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - m_wsMixers->addTab( m_errorLabel, i18n("No soundcard found") ); - } - */ /** * */ bool KMixWindow::profileExists(QString guiProfileId) { for (int i = 0; i < m_wsMixers->count(); ++i) { KMixerWidget* kmw = dynamic_cast(m_wsMixers->widget(i)); if (kmw && kmw->getGuiprof()->getId() == guiProfileId) return true; } return false; } bool KMixWindow::addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition) { qCDebug(KMIX_LOG) << "Add " << guiprofId; GUIProfile* guiprof = GUIProfile::find(guiprofId); if (guiprof != 0 && profileExists(guiprof->getId())) // TODO Bad place. Should be checked in the add-tab-dialog return false; // already present => don't add again Mixer *mixer = Mixer::findMixer(mixer_ID); if (mixer == 0) return false; // no such Mixer // qCDebug(KMIX_LOG) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added"; ViewBase::ViewFlags vflags = ViewBase::HasMenuBar; if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked()) vflags |= ViewBase::MenuBarVisible; KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId, actionCollection()); /* A newly added mixer will automatically added at the top * and thus the window title is also set appropriately */ /* * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the * card ID. This means you cannot distinguish between cards with an identical name. */ // QString tabLabel = guiprof->getName(); // if (tabLabel.isEmpty()) // QString tabLabel = kmw->mixer()->readableName(true); QString tabLabel = kmw->mixer()->readableName(true); m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart if (insertPosition == -1) m_wsMixers->addTab(kmw, tabLabel); else m_wsMixers->insertTab(insertPosition, kmw, tabLabel); if (kmw->getGuiprof()->getId() == m_defaultCardOnStart) { m_wsMixers->setCurrentWidget(kmw); } updateTabsClosable(); m_dontSetDefaultCardOnStart = false; kmw->loadConfig(KSharedConfig::openConfig().data()); // Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly // obsolete, as the backend should take care of updating itself. kmw->mixer()->readSetFromHWforceUpdate(); return true; } void KMixWindow::updateTabsClosable() { // Pulseaudio runs with 4 fixed tabs - don't allow to close them. // Also do not allow to close the last view m_wsMixers->setTabsClosable(!Mixer::pulseaudioPresent() && m_wsMixers->count() > 1); } bool KMixWindow::queryClose() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (gcd.showDockWidget && !qApp->isSavingSession() ) { // Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process. hide(); return false; } else { // Accept the close, if: // The user has disabled docking // or SessionSaving() is running // qCDebug(KMIX_LOG) << "close"; return true; } } void KMixWindow::hideOrClose() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (gcd.showDockWidget && m_dockWidget != 0) { // we can hide if there is a dock widget hide(); } else { // if there is no dock widget, we will quit quit(); } } // internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume void KMixWindow::increaseOrDecreaseVolume(bool increase) { Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture; md->increaseOrDecreaseVolume(!increase, volumeType); md->mixer()->commitVolumeChange(md); showVolumeDisplay(); } void KMixWindow::slotIncreaseVolume() { increaseOrDecreaseVolume(true); } void KMixWindow::slotDecreaseVolume() { increaseOrDecreaseVolume(false); } void KMixWindow::showVolumeDisplay() { Mixer* mixer = Mixer::getGlobalMasterMixer(); if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe if (GlobalConfig::instance().data.showOSD) { QDBusMessage msg = QDBusMessage::createMethodCall( "org.kde.plasmashell", "/org/kde/osdService", "org.kde.osdService", "volumeChanged" ); int currentVolume = 0; if (!md->isMuted()) { currentVolume = md->playbackVolume().getAvgVolumePercent(Volume::MALL); } msg.setArguments(QList() << currentVolume); QDBusConnection::sessionBus().asyncCall(msg); } } /** * Mutes the global master. (SLOT) */ void KMixWindow::slotMute() { Mixer* mixer = Mixer::getGlobalMasterMixer(); if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe md->toggleMute(); mixer->commitVolumeChange(md); showVolumeDisplay(); } void KMixWindow::quit() { // qCDebug(KMIX_LOG) << "quit"; qApp->quit(); } /** * Shows the configuration dialog, with the "general" tab opened. */ void KMixWindow::showSettings() { KMixPrefDlg::getInstance()->switchToPage(KMixPrefDlg::PrefGeneral); KMixPrefDlg::getInstance()->show(); } void KMixWindow::showHelp() { actionCollection()->action("help_contents")->trigger(); } void KMixWindow::showAbout() { actionCollection()->action("help_about_app")->trigger(); } /** * Apply the Preferences from the preferences dialog. Depending on what has been changed, * the corresponding announcements are made. */ void KMixWindow::applyPrefs() { // -1- Determine what has changed ------------------------------------------------------------------ GlobalConfigData& config = GlobalConfig::instance().data; GlobalConfigData& configBefore = configDataSnapshot; bool labelsHasChanged = config.showLabels ^ configBefore.showLabels; bool ticksHasChanged = config.showTicks ^ configBefore.showTicks; bool dockwidgetHasChanged = config.showDockWidget ^ configBefore.showDockWidget; bool toplevelOrientationHasChanged = config.getToplevelOrientation() != configBefore.getToplevelOrientation(); bool traypopupOrientationHasChanged = config.getTraypopupOrientation() != configBefore.getTraypopupOrientation(); qCDebug(KMIX_LOG) << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged << ", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation(); qCDebug(KMIX_LOG) << "trayOrientationHasChanged=" << traypopupOrientationHasChanged << ", config=" << config.getTraypopupOrientation() << ", configBefore=" << configBefore.getTraypopupOrientation(); // -2- Determine what effect the changes have ------------------------------------------------------------------ if (dockwidgetHasChanged || toplevelOrientationHasChanged || traypopupOrientationHasChanged) { // These might need a complete relayout => announce a ControlList change to rebuild everything ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("Preferences Dialog")); } else if (labelsHasChanged || ticksHasChanged) { ControlManager::instance().announce(QString(), ControlManager::GUI, QString("Preferences Dialog")); } // showOSD does not require any information. It reads on-the-fly from GlobalConfig. // -3- Apply all changes ------------------------------------------------------------------ // this->repaint(); // make KMix look fast (saveConfig() often uses several seconds) qApp->processEvents(); configDataSnapshot = GlobalConfig::instance().data; // create a new snapshot as all current changes are applied now // Remove saveConfig() IF aa changes have been migrated to GlobalConfig. // Currently there is still stuff like "show menu bar". saveConfig(); } void KMixWindow::toggleMenuBar() { menuBar()->setVisible(_actionShowMenubar->isChecked()); } void KMixWindow::slotKdeAudioSetupExec() { forkExec(QStringList() << "kcmshell5" << "kcm_phonon"); } void KMixWindow::forkExec(const QStringList& args) { int pid = KProcess::startDetached(args); if (pid == 0) { KMessageBox::error(this, i18n("The helper application is either not installed or not working.\n\n%1", args.join(QLatin1String(" ")))); } } void KMixWindow::slotConfigureCurrentView() { KMixerWidget *mw = qobject_cast(m_wsMixers->currentWidget()); ViewBase* view = 0; if (mw) view = mw->currentView(); if (view) view->configureView(); } void KMixWindow::slotSelectMasterClose(QObject*) { m_dsm = 0; } void KMixWindow::slotSelectMaster() { - Mixer *mixer = Mixer::getGlobalMasterMixer(); - if (mixer != 0) + const Mixer *mixer = Mixer::getGlobalMasterMixer(); + if (mixer!=nullptr) { if (!m_dsm) { - m_dsm = new DialogSelectMaster(Mixer::getGlobalMasterMixer(), this); + m_dsm = new DialogSelectMaster(mixer, this); connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*))); m_dsm->setAttribute(Qt::WA_DeleteOnClose, true); m_dsm->show(); } m_dsm->raise(); m_dsm->activateWindow(); } else { - KMessageBox::error(0, i18n("No sound card is installed or currently plugged in.")); + KMessageBox::error(nullptr, KMixToolBox::noDevicesWarningString()); } } void KMixWindow::newMixerShown(int /*tabIndex*/) { KMixerWidget *kmw = qobject_cast(m_wsMixers->currentWidget()); if (kmw!=nullptr) { // I am using the app name as a PREFIX, as KMix is a single window app, and it is // more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic // soundcard name like "HDA ATI SB". // Reformatted for KF5 so as to not say "KDE" // and so that there are not two different dashes. setWindowTitle(i18n("Mixer (%1)", kmw->mixer()->readableName())); if (!m_dontSetDefaultCardOnStart) m_defaultCardOnStart = kmw->getGuiprof()->getId(); // As switching the tab does NOT mean switching the master card, we do not need to update dock icon here. // It would lead to unnecesary flickering of the (complete) dock area. // We only show the "Configure Channels..." menu item if the mixer is not dynamic ViewBase* view = kmw->currentView(); QAction* action = actionCollection()->action("toggle_channels_currentview"); if (view && action) action->setVisible(!view->isDynamic()); } } diff --git a/core/mixset.cpp b/core/mixset.cpp index d1b5c256..b774a2f2 100644 --- a/core/mixset.cpp +++ b/core/mixset.cpp @@ -1,107 +1,107 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //KMix #include "mixset.h" #include "core/mixdevice.h" #include "kmix_debug.h" // KDE #include #include // Qt #include MixSet::~MixSet() { clear(); } bool MixSet::read( KConfig *config, const QString& grp ) { qCDebug(KMIX_LOG) << "MixSet::read() of group " << grp; KConfigGroup group = config->group(grp); m_name = group.readEntry( "name", m_name ); bool have_success = false, have_fail = false; foreach ( shared_ptr md, *this) { if ( md->read( config, grp ) ) have_success = true; else have_fail = true; } return have_success && !have_fail; } bool MixSet::write( KConfig *config, const QString& grp ) { qCDebug(KMIX_LOG) << "MixSet::write() of group " << grp; KConfigGroup conf = config->group(grp); conf.writeEntry( "name", m_name ); bool have_success = false, have_fail = false; foreach ( shared_ptr md, *this) { if ( md->write( config, grp ) ) have_success = true; else have_fail = true; } return have_success && !have_fail; } void MixSet::setName( const QString &name ) { m_name = name; } -shared_ptr MixSet::get(const QString &id) +shared_ptr MixSet::get(const QString &id) const { shared_ptr mdRet; foreach ( shared_ptr md, *this) { if ( md->id() == id ) { mdRet = md; break; } } return mdRet; } void MixSet::removeById(const QString &id) { for (int i=0; i < count() ; i++ ) { shared_ptr md = operator[](i); if ( md->id() == id ) { removeAt(i); break; } } } diff --git a/core/mixset.h b/core/mixset.h index f0138dc3..a7415e79 100644 --- a/core/mixset.h +++ b/core/mixset.h @@ -1,48 +1,48 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MixSet_h #define MixSet_h #include #include "core/mixdevice.h" #include "kmixcore_export.h" class KMIXCORE_EXPORT MixSet : public QList > { public: ~MixSet(); bool read( KConfig *config, const QString& grp ); bool write( KConfig *config, const QString& grp ); QString name() const { return m_name; } void setName( const QString &name ); - shared_ptr get(const QString &id); + shared_ptr get(const QString &id) const; void removeById(const QString &id); private: QString m_name; }; #endif diff --git a/gui/dialogaddview.cpp b/gui/dialogaddview.cpp index b7d44801..489eecc4 100644 --- a/gui/dialogaddview.cpp +++ b/gui/dialogaddview.cpp @@ -1,225 +1,223 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gui/dialogaddview.h" #include #include #include #include #include #include #include "core/mixer.h" +#include "gui/kmixtoolbox.h" static QStringList viewNames; static QStringList viewIds; DialogAddView::DialogAddView(QWidget *parent, const Mixer *mixer) - : DialogBase( parent ) + : DialogBase(parent) { // TODO 000 Adding View for MPRIS2 is broken. We need at least a dummy XML GUI Profile. Also the // fixed list below is plain wrong. Actually we should get the Profile list from either the XML files or // from the backend. The latter is probably easier for now. if ( viewNames.isEmpty() ) { - // initialize static list. Later this list could be generated from the actually installed profiles + // Initialize static list. + // Maybe this list could be generated from the actually installed profiles. viewNames.append(i18n("All controls")); viewNames.append(i18n("Only playback controls")); viewNames.append(i18n("Only capture controls")); viewIds.append("default"); viewIds.append("playback"); viewIds.append("capture"); } - setWindowTitle( i18n( "Add View" ) ); - if ( Mixer::mixers().count() > 0 ) - setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); - else { - setButtons( QDialogButtonBox::Cancel ); - } - - m_listForChannelSelector = nullptr; + setWindowTitle(i18n("Add View")); + if (!Mixer::mixers().isEmpty()) setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); + else setButtons(QDialogButtonBox::Cancel); - createWidgets(mixer); // for the specified 'mixer' + m_listForChannelSelector = nullptr; + createWidgets(mixer); // for the specified 'mixer' } /** - * Create basic widgets of the Dialog. + * Create basic widgets of the dialogue. */ void DialogAddView::createWidgets(const Mixer *mixer) { QWidget *mainFrame = new QWidget(this); setMainWidget(mainFrame); QVBoxLayout *layout = new QVBoxLayout(mainFrame); const QList &mixers = Mixer::mixers(); // list of all mixers present int mixerIndex = 0; // index of specified 'mixer' if (mixers.count()>1) { // More than one Mixer => show Combo-Box to select Mixer // Mixer widget line QHBoxLayout* mixerNameLayout = new QHBoxLayout(); layout->addLayout(mixerNameLayout); mixerNameLayout->setSpacing(DialogBase::horizontalSpacing()); QLabel *qlbl = new QLabel( i18n("Select mixer:"), mainFrame ); mixerNameLayout->addWidget(qlbl); qlbl->setFixedHeight(qlbl->sizeHint().height()); m_cMixer = new QComboBox(mainFrame); m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); m_cMixer->setToolTip(i18n("Current mixer")); for (int i = 0; ireadableName(); m_cMixer->addItem(QIcon::fromTheme(m->iconName()), name); if (name==mixer->readableName()) mixerIndex = i; } // Select the specified 'mixer' as the current item in the combo box. // If it was not found by the loop above, then select the first item. m_cMixer->setCurrentIndex(mixerIndex); connect(m_cMixer, QOverload::of(&QComboBox::activated), this, &DialogAddView::createPageByID); mixerNameLayout->addWidget(m_cMixer); } if (mixers.count()>0) { QLabel *qlbl = new QLabel( i18n("Select the design for the new view:"), mainFrame ); layout->addWidget(qlbl); createPage(mixer); // equivalent to mixers[mixerIndex] connect(this, &QDialog::accepted, this, &DialogAddView::apply); } else { - // TODO: see DialogChooseBackends::createWidgets() - QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); - layout->addWidget(qlbl); + QWidget *noMixersWarning = KMixToolBox::noDevicesWarningWidget(mainFrame); + layout->addWidget(noMixersWarning); + layout->addStretch(1); } } /** * Create the view profile list for the specified mixer * * @p mixerId the ID (index) of the mixer for which the list should be created. */ void DialogAddView::createPageByID(int mixerId) { Mixer *mixer = Mixer::mixers().at(mixerId); if (mixer!=nullptr) createPage(mixer); } /** * Create the view profile list for the specified mixer * * @p mixer the mixer for which the list should be created */ void DialogAddView::createPage(const Mixer *mixer) { /** --- Reset page ----------------------------------------------- * In case the user selected a new Mixer via m_cMixer, we need * to remove the stuff created on the last call. */ delete m_listForChannelSelector; setButtonEnabled(QDialogButtonBox::Ok, false); /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_listForChannelSelector = new QListWidget(mainFrame); m_listForChannelSelector->setUniformItemSizes(true); m_listForChannelSelector->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_listForChannelSelector, &QListWidget::itemSelectionChanged, this, &DialogAddView::profileSelectionChanged); layout->addWidget(m_listForChannelSelector); for (int i = 0; iisDynamic()) { // TODO: The mixer's backend MUST be inspected to find out the supported profiles. // Hardcoding it here is only a quick workaround. continue; } // Create an item for each view type QString name = viewNames.at(i); QListWidgetItem *item = new QListWidgetItem(name, m_listForChannelSelector); item->setData(Qt::UserRole, viewIds.at(i)); // mixer ID as data } // If there is only one option available to select, then preselect it. if (m_listForChannelSelector->count()==1) m_listForChannelSelector->setCurrentRow(0); } void DialogAddView::profileSelectionChanged() { const QList items = m_listForChannelSelector->selectedItems(); setButtonEnabled(QDialogButtonBox::Ok, !items.isEmpty()); } void DialogAddView::apply() { const QList &mixers = Mixer::mixers(); // list of all mixers present Mixer *mixer = nullptr; // selected mixer found if (mixers.count()==1) { // only one mixer => no combo box => take first entry mixer = mixers.first(); } else if (mixers.count()>1) { // use the mixer that is currently selected in the combo box const int idx = m_cMixer->currentIndex(); if (idx!=-1) mixer = mixers.at(idx); } Q_ASSERT(mixer!=nullptr); QList items = m_listForChannelSelector->selectedItems(); if (items.isEmpty()) return; // nothing selected QListWidgetItem *item = items.first(); const QString viewName = item->data(Qt::UserRole).toString(); qCDebug(KMIX_LOG) << "Result view name" << viewName << "for mixer" << mixer->id(); resultMixerId = mixer->id(); resultViewName = viewName; } diff --git a/gui/dialogchoosebackends.cpp b/gui/dialogchoosebackends.cpp index 54f58dde..2061835b 100644 --- a/gui/dialogchoosebackends.cpp +++ b/gui/dialogchoosebackends.cpp @@ -1,151 +1,145 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gui/dialogchoosebackends.h" #include #include #include #include #include -#include #include "core/mixer.h" +#include "gui/kmixtoolbox.h" /** * Creates a dialog to choose mixers from. All currently known mixers will be shown, and the given mixerID's * will be preselected. * * @param mixerIds A set of preselected mixer ID's */ DialogChooseBackends::DialogChooseBackends(QWidget *parent, const QSet &mixerIds) : QWidget(parent), modified(false) { createWidgets(mixerIds); } /** * Create basic widgets of the dialogue. */ void DialogChooseBackends::createWidgets(const QSet &mixerIds) { QVBoxLayout *vLayout = new QVBoxLayout(this); vLayout->setContentsMargins(0, 0, 0, 0); QLabel *topLabel = new QLabel(i18n("Mixers to show in the popup volume control:"), this); vLayout->addWidget(topLabel); createPage(mixerIds); vLayout->addWidget(m_mixerList, 1); topLabel->setBuddy(m_mixerList); if (Mixer::mixers().isEmpty()) { - // TODO: to KMixToolbox - used here, DialogSelectMaster, DialogAddView - KMessageWidget *noMixersWarning = new KMessageWidget(i18n("No sound cards are installed or are currently available."), this); - noMixersWarning->setIcon(QIcon::fromTheme("dialog-warning")); - noMixersWarning->setMessageType(KMessageWidget::Warning); - noMixersWarning->setCloseButtonVisible(false); - noMixersWarning->setWordWrap(true); - + QWidget *noMixersWarning = KMixToolBox::noDevicesWarningWidget(this); vLayout->addWidget(noMixersWarning); m_mixerList->setEnabled(false); } } /** * Create selection list for the specified mixers. */ void DialogChooseBackends::createPage(const QSet &mixerIds) { m_mixerList = new QListWidget(this); m_mixerList->setUniformItemSizes(true); m_mixerList->setAlternatingRowColors(true); m_mixerList->setSelectionMode(QAbstractItemView::NoSelection); m_mixerList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); #ifndef QT_NO_ACCESSIBILITY m_mixerList->setAccessibleName(i18n("Select Mixers")); #endif bool hasMixerFilter = !mixerIds.isEmpty(); qCDebug(KMIX_LOG) << "MixerIds=" << mixerIds; for (const Mixer *mixer : Mixer::mixers()) { QListWidgetItem *item = new QListWidgetItem(m_mixerList); item->setText(mixer->readableName(true)); item->setSizeHint(QSize(1, 16)); item->setIcon(QIcon::fromTheme(mixer->iconName())); item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsUserCheckable|Qt::ItemNeverHasChildren); item->setData(Qt::UserRole, mixer->id()); const bool mixerShouldBeShown = !hasMixerFilter || mixerIds.contains(mixer->id()); item->setCheckState(mixerShouldBeShown ? Qt::Checked : Qt::Unchecked); } connect(m_mixerList, &QListWidget::itemChanged, this, &DialogChooseBackends::backendsModifiedSlot); connect(m_mixerList, &QListWidget::itemActivated, this, &DialogChooseBackends::itemActivatedSlot); } QSet DialogChooseBackends::getChosenBackends() { QSet newMixerList; for (int row = 0; rowcount(); ++row) { QListWidgetItem *item = m_mixerList->item(row); if (item->checkState()==Qt::Checked) { const QString mixer = item->data(Qt::UserRole).toString(); newMixerList.insert(mixer); qCDebug(KMIX_LOG) << "apply found " << mixer; } } qCDebug(KMIX_LOG) << "New list is " << newMixerList; return newMixerList; } /** * Returns whether there were any modifications (activation/deactivation) and resets the flag. * @return */ bool DialogChooseBackends::getAndResetModifyFlag() { bool modifiedOld = modified; modified = false; return modifiedOld; } bool DialogChooseBackends::getModifyFlag() const { return modified; } void DialogChooseBackends::backendsModifiedSlot() { modified = true; emit backendsModified(); } void DialogChooseBackends::itemActivatedSlot(QListWidgetItem *item) { item->setCheckState(item->checkState()==Qt::Checked ? Qt::Unchecked : Qt::Checked); } diff --git a/gui/dialogselectmaster.cpp b/gui/dialogselectmaster.cpp index 9a4bf49c..921e4590 100644 --- a/gui/dialogselectmaster.cpp +++ b/gui/dialogselectmaster.cpp @@ -1,230 +1,231 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gui/dialogselectmaster.h" #include #include #include #include #include #include "core/ControlManager.h" -#include "core/mixdevice.h" #include "core/mixer.h" +#include "gui/kmixtoolbox.h" -DialogSelectMaster::DialogSelectMaster( Mixer *mixer, QWidget *parent ) - : DialogBase( parent ) + +// TOD: Should this be incorporated into the "Configure KMix" dialogue? + +DialogSelectMaster::DialogSelectMaster(const Mixer *mixer, QWidget *parent) + : DialogBase(parent) { setWindowTitle(i18n("Select Master Channel")); - if ( Mixer::mixers().count() > 0 ) - setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); - else { - setButtons(QDialogButtonBox::Cancel); - } - - m_channelSelector = 0; - createWidgets(mixer); // Open with Mixer Hardware #0 + if (Mixer::mixers().count()>0) setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); + else setButtons(QDialogButtonBox::Cancel); + m_channelSelector = nullptr; + createWidgets(mixer); // for the specified 'mixer' } + /** - * Create basic widgets of the Dialog. + * Create basic widgets of the dialogue. */ -void DialogSelectMaster::createWidgets(Mixer *ptr_mixer) +void DialogSelectMaster::createWidgets(const Mixer *mixer) { QWidget *mainFrame = new QWidget(this); setMainWidget(mainFrame); QVBoxLayout *layout = new QVBoxLayout(mainFrame); - if ( Mixer::mixers().count() > 1 ) { + const QList &mixers = Mixer::mixers(); // list of all mixers present + int mixerIndex = 0; // index of specified 'mixer' + + if (mixers.count()>1) + { // More than one Mixer => show Combo-Box to select Mixer // Mixer widget line QHBoxLayout* mixerNameLayout = new QHBoxLayout(); layout->addLayout( mixerNameLayout ); mixerNameLayout->setContentsMargins(0, 0, 0, 0); mixerNameLayout->setSpacing(DialogBase::horizontalSpacing()); QLabel *qlbl = new QLabel( i18n("Current mixer:"), mainFrame ); mixerNameLayout->addWidget(qlbl); qlbl->setFixedHeight(qlbl->sizeHint().height()); m_cMixer = new QComboBox(mainFrame); m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); connect( m_cMixer, SIGNAL(activated(int)), this, SLOT(createPageByID(int)) ); - for( int i =0; i md = mixer->getLocalMasterMD(); - const QString iconName = (md!=nullptr) ? md->iconName() : "media-playback-start"; - m_cMixer->addItem(QIcon::fromTheme(iconName), mixer->readableName(), mixer->id() ); - } // end for all_Mixers - // Make the current Mixer the current item in the ComboBox - int findIndex = m_cMixer->findData( ptr_mixer->id() ); - if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); + const Mixer *m = mixers[i]; + const QString name = m->readableName(); + m_cMixer->addItem(QIcon::fromTheme(m->iconName()), name); + if (name==mixer->readableName()) mixerIndex = i; + } + + // Select the specified 'mixer' as the current item in the combo box. + // If it was not found by the loop above, then select the first item. + m_cMixer->setCurrentIndex(mixerIndex); - m_cMixer->setToolTip( i18n("Current mixer" ) ); mixerNameLayout->addWidget(m_cMixer, 1); layout->addSpacing(DialogBase::verticalSpacing()); + } - } // end if (more_than_1_Mixer) - - if ( Mixer::mixers().count() > 0 ) { - QLabel *qlbl = new QLabel( i18n("Select the channel representing the master volume:"), mainFrame ); + if (mixers.count()>0) + { + QLabel *qlbl = new QLabel(i18n("Select the channel representing the master volume:"), mainFrame); layout->addWidget(qlbl); - - createPage(ptr_mixer); - connect(this, SIGNAL(accepted()), this, SLOT(apply())); + createPage(mixer); + + connect(this, &QDialog::accepted, this, &DialogSelectMaster::apply); } - else { - // TODO: see DialogChooseBackends::createWidgets() - QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); - layout->addWidget(qlbl); + else + { + QWidget *noMixersWarning = KMixToolBox::noDevicesWarningWidget(mainFrame); + layout->addWidget(noMixersWarning); + layout->addStretch(1); } } + /** - * Create RadioButton's for the Mixer with number 'mixerId'. - * @par mixerId The Mixer, for which the RadioButton's should be created. + * Create the channel list for the specified mixer + * + * @p mixerId the ID (index) of the mixer for which the list should be created. */ void DialogSelectMaster::createPageByID(int mixerId) { - QString mixer_id = m_cMixer->itemData(mixerId).toString(); - Mixer * mixer = Mixer::findMixer(mixer_id); - - if ( mixer != NULL ) - createPage(mixer); + const QString mixer_id = m_cMixer->itemData(mixerId).toString(); + const Mixer *mixer = Mixer::findMixer(mixer_id); + if (mixer!=nullptr) createPage(mixer); } + /** - * Create RadioButton's for the Mixer with number 'mixerId'. - * @par mixerId The Mixer, for which the RadioButton's should be created. + * Create the channel list for the specified mixer + * + * @p mixer the mixer for which the list should be created */ -void DialogSelectMaster::createPage(Mixer* mixer) +void DialogSelectMaster::createPage(const Mixer *mixer) { - /** --- Reset page ----------------------------------------------- * In case the user selected a new Mixer via m_cMixer, we need * to remove the stuff created on the last call. */ // delete the list widget. // This should automatically remove all contained items. delete m_channelSelector; - /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_channelSelector = new QListWidget(mainFrame); #ifndef QT_NO_ACCESSIBILITY m_channelSelector->setAccessibleName( i18n("Select Master Channel") ); #endif m_channelSelector->setSelectionMode(QAbstractItemView::SingleSelection); m_channelSelector->setDragEnabled(false); m_channelSelector->setAlternatingRowColors(true); layout->addWidget(m_channelSelector); -// shared_ptr master = mixer->getLocalMasterMD(); -// QString masterKey = ( master.get() != 0 ) ? master->id() : "----noMaster---"; // Use non-matching name as default - - const MixSet& mixset = mixer->getMixSet(); - MixSet& mset = const_cast(mixset); + const MixSet &mixset = mixer->getMixSet(); + const MixSet &mset = const_cast(mixset); - MasterControl mc = mixer->getGlobalMasterPreferred(false); + const MasterControl mc = mixer->getGlobalMasterPreferred(false); QString masterKey = mc.getControl(); if (!masterKey.isEmpty() && !mset.get(masterKey)) { - shared_ptr master = mixer->getLocalMasterMD(); - if (master.get() != 0) - masterKey = master->id(); + const shared_ptr master = mixer->getLocalMasterMD(); + if (master.get()!=nullptr) masterKey = master->id(); } int msetCount = 0; for (int i = 0; i < mset.count(); ++i) - { - shared_ptr md = mset[i]; - if ( md->playbackVolume().hasVolume() ) - ++msetCount; - } + { + const shared_ptr md = mset[i]; + if (md->playbackVolume().hasVolume()) ++msetCount; + } if (msetCount > 0 && !mixer->isDynamic()) { QString mdName = i18n("Automatic (%1 recommendation)", mixer->getDriverName()); - auto *item = new QListWidgetItem(QIcon::fromTheme("audio-volume-high"), mdName, m_channelSelector); - item->setData(Qt::UserRole, QString()); // ID here: see apply(), empty String => Automatic - if (masterKey.isEmpty()) - m_channelSelector->setCurrentItem(item); + auto *item = new QListWidgetItem(QIcon::fromTheme("audio-volume-high"), mdName, m_channelSelector); + item->setData(Qt::UserRole, QString()); // ID here: see apply(), empty String => Automatic + if (masterKey.isEmpty()) m_channelSelector->setCurrentItem(item); } - // Populate ListView with the MixDevice's having a playbakc volume (excludes pure capture controls and pure enum's) + // Populate the list view with the MixDevice's having a playback volume + // (excludes pure capture controls and pure enum's) for (int i = 0; i < mset.count(); ++i) - { - shared_ptr md = mset[i]; - if ( md->playbackVolume().hasVolume() ) { - QString mdName = md->readableName(); - auto *item = new QListWidgetItem(QIcon::fromTheme(md->iconName()), mdName, m_channelSelector); - item->setData(Qt::UserRole, md->id()); // ID here: see apply() - if ( md->id() == masterKey ) - { // select the current master - m_channelSelector->setCurrentItem(item); + const shared_ptr md = mset[i]; + if (md->playbackVolume().hasVolume()) + { + const QString mdName = md->readableName(); + auto *item = new QListWidgetItem(QIcon::fromTheme(md->iconName()), mdName, m_channelSelector); + item->setData(Qt::UserRole, md->id()); // ID here: see apply() + if (md->id() == masterKey) + { // select the current master + m_channelSelector->setCurrentItem(item); + } } } - } - setButtonEnabled(QDialogButtonBox::Ok, m_channelSelector->count()>0); + setButtonEnabled(QDialogButtonBox::Ok, m_channelSelector->count()>0); } void DialogSelectMaster::apply() { - Mixer *mixer = 0; - if ( Mixer::mixers().count() == 1 ) { - // only one mxier => no combo box => take first entry - mixer = (Mixer::mixers())[0]; + const QList &mixers = Mixer::mixers(); // list of all mixers present + Mixer *mixer = nullptr; // selected mixer found + + if (mixers.count()==1) + { + // only one mixer => no combo box => take first entry + mixer = mixers.first(); } - else if ( Mixer::mixers().count() > 1 ) { - // find mixer that is currently active in the ComboBox - int idx = m_cMixer->currentIndex(); - QString mixer_id = m_cMixer->itemData(idx).toString(); - mixer = Mixer::findMixer(mixer_id); + else if (mixers.count()>1) + { + // use the mixer that is currently selected in the combo box + const int idx = m_cMixer->currentIndex(); + if (idx!=-1) mixer = mixers.at(idx); } - if ( mixer == 0 ) - return; // User must have unplugged everything + if (mixer==nullptr) return; // user must have unplugged everything + QList items = m_channelSelector->selectedItems(); if (items.count()==1) { QListWidgetItem *item = items.first(); QString control_id = item->data(Qt::UserRole).toString(); - mixer->setLocalMasterMD( control_id ); - Mixer::setGlobalMaster(mixer->id(), control_id, true); - ControlManager::instance().announce(mixer->id(), ControlManager::MasterChanged, QString("Select Master Dialog")); - } + mixer->setLocalMasterMD( control_id ); + Mixer::setGlobalMaster(mixer->id(), control_id, true); + ControlManager::instance().announce(mixer->id(), ControlManager::MasterChanged, QString("Select Master Dialog")); + } } - - diff --git a/gui/dialogselectmaster.h b/gui/dialogselectmaster.h index f237f8da..1e3f126c 100644 --- a/gui/dialogselectmaster.h +++ b/gui/dialogselectmaster.h @@ -1,53 +1,54 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DIALOGSELECTMASTER_H #define DIALOGSELECTMASTER_H class QComboBox; class QVBoxLayout; class QListWidget; #include "dialogbase.h" class Mixer; class DialogSelectMaster : public DialogBase { Q_OBJECT public: - explicit DialogSelectMaster(Mixer *mixer = nullptr, QWidget *parent = nullptr); + explicit DialogSelectMaster(const Mixer *mixer = nullptr, QWidget *parent = nullptr); virtual ~DialogSelectMaster() = default; public slots: void apply(); private: - void createWidgets(Mixer*); - void createPage(Mixer*); + void createWidgets(const Mixer *mixer); + void createPage(const Mixer *mixer); + QComboBox* m_cMixer; QListWidget *m_channelSelector; private slots: void createPageByID(int mixerId); }; #endif