diff --git a/apps/kmix.cpp b/apps/kmix.cpp index 8689d823..6c28a037 100644 --- a/apps/kmix.cpp +++ b/apps/kmix.cpp @@ -1,1316 +1,1315 @@ /* * 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 #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::instance()->initMixer(m_multiDriverMode, m_backendFilter, m_hwInfoString, 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(); theKMixDeviceManager->initHotplug(); connect(theKMixDeviceManager, SIGNAL(plugged(const char*,QString,QString&)), SLOT(plugged(const char*,QString,QString&))); connect(theKMixDeviceManager, SIGNAL(unplugged(QString)), SLOT(unplugged(QString))); 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) - static_cast(ControlChangeType::ControlList|ControlChangeType::MasterChanged), this, + ControlManager::ControlList|ControlManager::MasterChanged, this, "KMixWindow"); // Send an initial volume refresh (otherwise all volumes are 0 until the next change) - ControlManager::instance().announce(QString(), ControlChangeType::Volume, "Startup"); + 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::instance()->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(int changeType) +void KMixWindow::controlsChange(ControlManager::ChangeType changeType) { - ControlChangeType::Type type = ControlChangeType::fromInt(changeType); - switch (type) + switch (changeType) { - case ControlChangeType::ControlList: - case ControlChangeType::MasterChanged: + case ControlManager::ControlList: + case ControlManager::MasterChanged: updateDocking(); break; default: - ControlManager::warnUnexpectedChangeType(type, this); + 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("hwinfo")); action->setText(i18n("Hardware &Information")); connect(action, SIGNAL(triggered(bool)), SLOT(slotHWInfo())); 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()) { QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", KIconLoader::Toolbar, IconSize(KIconLoader::Toolbar)); QPushButton* _cornerLabelNew = new QPushButton(); _cornerLabelNew->setIcon(cornerNewPM); _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().toList()); 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::instance()->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(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(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::instance()->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("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, QString& dev) { qCDebug(KMIX_LOG) << "Plugged: dev=" << dev << "(" << driverName << ") udi=" << udi << "\n"; QString driverNameString; driverNameString = driverName; int devNum = dev.toInt(); Mixer *mixer = new Mixer(driverNameString, devNum); if (mixer != 0) { qCDebug(KMIX_LOG) << "Plugged: dev=" << dev << "\n"; if (MixerToolBox::instance()->possiblyAddMixer(mixer)) recreateGUI(true, mixer->id(), true, false); } } void KMixWindow::unplugged(const QString& udi) { qCDebug(KMIX_LOG) << "Unplugged: udi=" << udi << "\n"; for (int i = 0; i < Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; // qCDebug(KMIX_LOG) << "Try Match with:" << mixer->udi() << "\n"; if (mixer->udi() == udi) { qCDebug(KMIX_LOG) << "Unplugged Match: Removing udi=" << udi << "\n"; //KMixToolBox::notification("MasterFallback", "aaa"); bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer()); // Part 1) Remove Tab 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() ) } } MixerToolBox::instance()->removeMixer(mixer); // 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; if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Vertical) vflags |= ViewBase::Horizontal; else vflags |= ViewBase::Vertical; 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(), ControlChangeType::ControlList, QString("Preferences Dialog")); + ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("Preferences Dialog")); } else if (labelsHasChanged || ticksHasChanged) { - ControlManager::instance().announce(QString(), ControlChangeType::GUI, QString("Preferences Dialog")); + 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::slotHWInfo() { KMessageBox::information(0, m_hwInfoString, i18n("Mixer Hardware Information")); } 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) { if (!m_dsm) { m_dsm = new DialogSelectMaster(Mixer::getGlobalMasterMixer(), 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.")); } } 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/apps/kmix.h b/apps/kmix.h index e8089adb..f801bc6c 100644 --- a/apps/kmix.h +++ b/apps/kmix.h @@ -1,166 +1,167 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * * 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 KMIX_H #define KMIX_H #include // Qt #include class QLabel; #include #include class QPushButton; #include class QTabWidget; // KDE class KAccel; class KToggleAction; #include // KMix #include "core/GlobalConfig.h" +#include "core/ControlManager.h" class KMixDockWidget; class KMixerWidget; class KMixWindow; class Mixer; #include "core/mixer.h" class DialogSelectMaster; class KMixWindow : public KXmlGuiWindow { Q_OBJECT public: KMixWindow(bool invisible, bool reset); ~KMixWindow(); private: void saveBaseConfig(); void saveViewConfig(); void loadAndInitConfig(bool reset); void loadBaseConfig(); void initPrefDlg(); void initActions(); void initActionsLate(); void initActionsAfterInitMixer(); void initWidgets(); void fixConfigAfterRead(); bool queryClose() Q_DECL_OVERRIDE; public slots: - void controlsChange(int changeType); + void controlsChange(ControlManager::ChangeType changeType); void quit(); void showSettings(); void showHelp(); void showAbout(); void toggleMenuBar(); void loadVolumes(); void loadVolumes(QString postfix); void saveVolumes(); void saveVolumes(QString postfix); void saveConfig(); virtual void applyPrefs(); void recreateGUI(bool saveView, bool reset); void recreateGUI(bool saveConfig, const QString& mixerId, bool forceNewTab, bool reset); void recreateGUIwithSavingView(); void newMixerShown(int tabIndex); void slotSelectMaster(); private: KMixerWidget* findKMWforTab( const QString& tabId ); void forkExec(const QStringList& args); KAccel *m_keyAccel; KToggleAction* _actionShowMenubar; private: /** * configSnapshot is used to hold the original state before modifications in the preferences dialog */ GlobalConfigData configDataSnapshot; bool m_startVisible; bool m_visibilityUpdateAllowed; bool m_multiDriverMode; // Not officially supported. bool m_autouseMultimediaKeys; // Due to message freeze, not in config dialog in KDE4.4 QTabWidget *m_wsMixers; KMixDockWidget *m_dockWidget; DialogSelectMaster *m_dsm; QString m_hwInfoString; QString m_defaultCardOnStart; bool m_dontSetDefaultCardOnStart; QLabel *m_errorLabel; QList m_backendFilter; unsigned int m_configVersion; void showVolumeDisplay(); void increaseOrDecreaseVolume(bool increase); bool addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition); void setInitialSize(); private: static QString getKmixctrlRcFilename(QString postfix); bool profileExists(QString guiProfileId); bool updateDocking(); void removeDock(); void updateTabsClosable(); private slots: void slotHWInfo(); void slotKdeAudioSetupExec(); void slotConfigureCurrentView(); void plugged( const char* driverName, const QString& udi, QString& dev); void unplugged( const QString& udi); void hideOrClose(); void slotIncreaseVolume(); void slotDecreaseVolume(); void slotMute(); void slotSelectMasterClose(QObject*); void newView(); void saveAndCloseView(int); void loadVolumes1() { loadVolumes(QString("1")); } void loadVolumes2() { loadVolumes(QString("2")); } void loadVolumes3() { loadVolumes(QString("3")); } void loadVolumes4() { loadVolumes(QString("4")); } void saveVolumes1() { saveVolumes(QString("1")); } void saveVolumes2() { saveVolumes(QString("2")); } void saveVolumes3() { saveVolumes(QString("3")); } void saveVolumes4() { saveVolumes(QString("4")); } }; #endif // KMIX_H diff --git a/backends/mixer_backend.cpp b/backends/mixer_backend.cpp index 5da1b760..f07dbaf2 100644 --- a/backends/mixer_backend.cpp +++ b/backends/mixer_backend.cpp @@ -1,327 +1,327 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-2007 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 "mixer_backend.h" #include // for the "ERR_" declarations, #include mixer.h #include "core/mixer.h" #include "core/ControlManager.h" #include #define POLL_RATE_SLOW 1500 #define POLL_RATE_FAST 50 #include "mixer_backend_i18n.cpp" Mixer_Backend::Mixer_Backend(Mixer *mixer, int device) : m_devnum (device) , m_isOpen(false), m_recommendedMaster(), _mixer(mixer), _pollingTimer(0), _cardInstance(1), _cardRegistered(false) { // In all cases create a QTimer. We will use it once as a singleShot(), even if something smart // like ::select() is possible (as in ALSA). And force to do an update. _readSetFromHWforceUpdate = true; _pollingTimer = new QTimer(); // will be started on open() and stopped on close() connect( _pollingTimer, SIGNAL(timeout()), this, SLOT(readSetFromHW()), Qt::QueuedConnection); } void Mixer_Backend::closeCommon() { freeMixDevices(); } int Mixer_Backend::close() { qCDebug(KMIX_LOG) << "Implicit close on " << this << ". Please instead call closeCommon() and close() explicitly (in concrete Backend destructor)"; // ^^^ Background. before the destructor runs, the C++ runtime changes the virtual pointers to point back // to the common base class. So what actually runs is not run Mixer_ALSA::close(), but this method. // // See http://stackoverflow.com/questions/99552/where-do-pure-virtual-function-call-crashes-come-from?lq=1 // // Comment: IMO this is totally stupid and insane behavior of C++, because you cannot simply cannot call // the overwritten (cleanup) methods in the destructor. return 0; } Mixer_Backend::~Mixer_Backend() { unregisterCard(this->getName()); if (!m_mixDevices.isEmpty()) { qCDebug(KMIX_LOG) << "Implicit close on " << this << ". Please instead call closeCommon() and close() explicitly (in concrete Backend destructor)"; } delete _pollingTimer; } void Mixer_Backend::freeMixDevices() { foreach (shared_ptr md, m_mixDevices) md->close(); m_mixDevices.clear(); } bool Mixer_Backend::openIfValid() { int ret = open(); if (ret == 0 && (m_mixDevices.count() > 0 || _mixer->isDynamic())) { if (needsPolling()) { _pollingTimer->start(POLL_RATE_FAST); } else { // The initial state must be read manually QTimer::singleShot( POLL_RATE_FAST, this, SLOT(readSetFromHW())); } return true; // could be opened } else { //shutdown(); return false; // could not open } } bool Mixer_Backend::isOpen() { return m_isOpen; } /** * Queries the backend driver whether there are new changes in any of the controls. * If you cannot find out for a backend, return "true" - this is also the default implementation. * @return true, if there are changes. Otherwise false is returned. */ bool Mixer_Backend::hasChangedControls() { return true; } /** * The name of the Mixer this backend represents. * Often it is just a name/id for the kernel. so name and id are usually identical. Virtual/abstracting backends are * different, as they represent some distinct function like "Application streams" or "Capture Devices". Also backends * that do not have names might can to set ID and name different like i18n("SUN Audio") and "SUNAudio". */ QString Mixer_Backend::getName() const { return m_mixerName; } /** * The id of the Mixer this backend represents. The default implementation simply returns the name. * Often it is just a name/id for the kernel. so name and id are usually identical. See also #Mixer_Backend::getName(). * You must override this method if you want to set ID different from name. */ QString Mixer_Backend::getId() const { return m_mixerName; // Backwards compatibility. PulseAudio overrides it. } /** * After calling this, readSetFromHW() will do a complete update. This will * trigger emitting the appropriate signals like controlChanged(). * * This method is useful, if you need to get a "refresh signal" - used at: * 1) Start of KMix - so that we can be sure an initial signal is emitted * 2) When reconstructing any MixerWidget (e.g. DockIcon after applying preferences) */ void Mixer_Backend::readSetFromHWforceUpdate() const { _readSetFromHWforceUpdate = true; } /** * You can call this to retrieve the freshest information from the mixer HW. * This method is also called regularly by the mixer timer. */ void Mixer_Backend::readSetFromHW() { bool updated = hasChangedControls(); if ( (! updated) && (! _readSetFromHWforceUpdate) ) { // Some drivers (ALSA) are smart. We don't need to run the following // time-consuming update loop if there was no change qCDebug(KMIX_LOG) << "Mixer::readSetFromHW(): smart-update-tick"; return; } _readSetFromHWforceUpdate = false; int ret = Mixer::OK_UNCHANGED; foreach (shared_ptr md, m_mixDevices ) { //bool debugMe = (md->id() == "PCM:0" ); bool debugMe = false; if (debugMe) qCDebug(KMIX_LOG) << "Old PCM:0 playback state" << md->isMuted() << ", vol=" << md->playbackVolume().getAvgVolumePercent(Volume::MALL); int retLoop = readVolumeFromHW( md->id(), md ); if (debugMe) qCDebug(KMIX_LOG) << "New PCM:0 playback state" << md->isMuted() << ", vol=" << md->playbackVolume().getAvgVolumePercent(Volume::MALL); if (md->isEnum() ) { /* * This could be reworked: * Plan: Read everything (including enum's) in readVolumeFromHW(). * readVolumeFromHW() should then be renamed to readHW(). */ md->setEnumId( enumIdHW(md->id()) ); } // Transition the outer return value with the value from this loop iteration if ( retLoop == Mixer::OK && ret == Mixer::OK_UNCHANGED ) { // Unchanged => OK (Changed) ret = Mixer::OK; } else if ( retLoop != Mixer::OK && retLoop != Mixer::OK_UNCHANGED ) { // If current ret from loop in not OK, then transition to that: ret (Something) => retLoop (Error) ret = retLoop; } } if ( ret == Mixer::OK ) { // We explicitly exclude Mixer::OK_UNCHANGED and Mixer::ERROR_READ if ( needsPolling() ) { // Upgrade polling frequency temporarily to be more smoooooth _pollingTimer->setInterval(POLL_RATE_FAST); QTime fastPollingEndsAt = QTime::currentTime (); fastPollingEndsAt = fastPollingEndsAt.addSecs(5); _fastPollingEndsAt = fastPollingEndsAt; //_fastPollingEndsAt = fastPollingEndsAt; qCDebug(KMIX_LOG) << "Start fast polling from " << QTime::currentTime() <<"until " << _fastPollingEndsAt; } - ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, QString("Mixer.fromHW")); + ControlManager::instance().announce(_mixer->id(), ControlManager::Volume, QString("Mixer.fromHW")); } else { // This code path is entered on Mixer::OK_UNCHANGED and ERROR bool fastPollingEndsNow = (!_fastPollingEndsAt.isNull()) && _fastPollingEndsAt < QTime::currentTime (); if ( fastPollingEndsNow ) { qCDebug(KMIX_LOG) << "End fast polling"; _fastPollingEndsAt = QTime(); // NULL time _pollingTimer->setInterval(POLL_RATE_SLOW); } } } /** * Return the MixDevice, that would qualify best as MasterDevice. The default is to return the * first device in the device list. Backends can override this (i.e. the ALSA Backend does so). * The users preference is NOT returned by this method - see the Mixer class for that. */ shared_ptr Mixer_Backend::recommendedMaster() { if ( m_recommendedMaster ) { // Backend has set a recommended master. Thats fine. Using it. return m_recommendedMaster; } else if ( ! m_mixDevices.isEmpty() ) { // Backend has NOT set a recommended master. Evil backend // => lets help out, using the first device (if exists) return m_mixDevices.at(0); } else { if ( !_mixer->isDynamic()) // This should never ever happen, as KMix does NOT accept soundcards without controls qCCritical(KMIX_LOG) << "Mixer_Backend::recommendedMaster(): returning invalid master. This is a bug in KMix. Please file a bug report stating how you produced this."; } // If we reach this code path, then obviously m_recommendedMaster == 0 (see above) return m_recommendedMaster; } /** * Sets the ID of the currently selected Enum entry. * This is a dummy implementation - if the Mixer backend * wants to support it, it must implement the driver specific * code in its subclass (see Mixer_ALSA.cpp for an example). */ void Mixer_Backend::setEnumIdHW(const QString& , unsigned int) { return; } /** * Return the ID of the currently selected Enum entry. * This is a dummy implementation - if the Mixer backend * wants to support it, it must implement the driver specific * code in its subclass (see Mixer_ALSA.cpp for an example). */ unsigned int Mixer_Backend::enumIdHW(const QString& ) { return 0; } /** * Move the stream to a new destination */ bool Mixer_Backend::moveStream( const QString& id, const QString& destId ) { Q_UNUSED(id); Q_UNUSED(destId); return false; } QString Mixer_Backend::errorText(int mixer_error) { QString l_s_errmsg; switch (mixer_error) { case Mixer::ERR_PERM: l_s_errmsg = i18n("kmix:You do not have permission to access the mixer device.\n" \ "Please check your operating systems manual to allow the access."); break; case Mixer::ERR_WRITE: l_s_errmsg = i18n("kmix: Could not write to mixer."); break; case Mixer::ERR_READ: l_s_errmsg = i18n("kmix: Could not read from mixer."); break; case Mixer::ERR_OPEN: l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \ "Please check that the soundcard is installed and that\n" \ "the soundcard driver is loaded.\n"); break; default: l_s_errmsg = i18n("kmix: Unknown error. Please report how you produced this error."); break; } return l_s_errmsg; } diff --git a/backends/mixer_mpris2.cpp b/backends/mixer_mpris2.cpp index 6291876d..8861a202 100644 --- a/backends/mixer_mpris2.cpp +++ b/backends/mixer_mpris2.cpp @@ -1,701 +1,701 @@ /** * KMix -- MPRIS2 backend * * Copyright (C) 2011 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 "mixer_mpris2.h" #include "core/mixer.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "kmix_debug.h" #include #include #include #include #include // Set the QDBUS_DEBUG env variable for debugging Qt DBUS calls. Mixer_Backend* MPRIS2_getMixer(Mixer *mixer, int device ) { return new Mixer_MPRIS2(mixer, device ); } Mixer_MPRIS2::Mixer_MPRIS2(Mixer *mixer, int device) : Mixer_Backend(mixer, device ) { } int Mixer_MPRIS2::open() { if ( m_devnum != 0 ) return Mixer::ERR_OPEN; registerCard(i18n("Playback Streams")); _id = "Playback Streams"; _mixer->setDynamic(); return addAllRunningPlayersAndInitHotplug(); } int Mixer_MPRIS2::close() { m_isOpen = false; closeCommon(); qDeleteAll(controls); controls.clear(); return 0; } int Mixer_MPRIS2::mediaPlay(QString id) { return mediaControl(id, "PlayPause"); } int Mixer_MPRIS2::mediaPrev(QString id) { return mediaControl(id, "Previous"); } int Mixer_MPRIS2::mediaNext(QString id) { return mediaControl(id, "Next"); } /** * Sends a media control command to the given application. * @param applicationId The MPRIS applicationId * @returns Always 0. Hint: Currently nobody uses the return code */ int Mixer_MPRIS2::mediaControl(QString applicationId, QString commandName) { MPrisControl* mad = controls.value(applicationId); if ( mad == 0 ) return 0; // Might have disconnected recently => simply ignore command qCDebug(KMIX_LOG) << "Send " << commandName << " to id=" << applicationId; QDBusPendingReply<> repl2 = mad->playerIfc->asyncCall(commandName); QDBusPendingCallWatcher* watchMediaControlReply = new QDBusPendingCallWatcher(repl2, mad); connect(watchMediaControlReply, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(watcherMediaControl(QDBusPendingCallWatcher*))); return 0; // Presume everything went well. Can't do more for ASYNC calls } void Mixer_MPRIS2::watcherMediaControl(QDBusPendingCallWatcher* watcher) { MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); if (mprisCtl == 0) { return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) } // Actually the code below in this method is more or less just debugging const QDBusMessage& msg = watcher->reply(); QString id = mprisCtl->getId(); QString busDestination = mprisCtl->getBusDestination(); qCDebug(KMIX_LOG) << "Media control for id=" << id << ", path=" << msg.path() << ", interface=" << msg.interface() << ", busDestination" << busDestination; } /** * readVolumeFromHW() should be used only for hotplug (and even that should go away). Everything should operate via * the slot volumeChanged in the future. */ int Mixer_MPRIS2::readVolumeFromHW( const QString& /*id*/, shared_ptr /*md*/) { // Everything is done by notifications => no code necessary return Mixer::OK_UNCHANGED; } /** * A slot that processes data from the MPrisControl that emit the signal. * * @param The emitting MPrisControl * @param newVolume The new volume */ void Mixer_MPRIS2::playbackStateChanged(MPrisControl* mad, MediaController::PlayState playState) { shared_ptr md = m_mixDevices.get(mad->getId()); md->getMediaController()->setPlayState(playState); QMetaObject::invokeMethod(this, "announceGUI", Qt::QueuedConnection); -// ControlManager::instance().announce(_mixer->id(), ControlChangeType::GUI, QString("MixerMPRIS2.playbackStateChanged")); +// ControlManager::instance().announce(_mixer->id(), ControlManager::GUI, QString("MixerMPRIS2.playbackStateChanged")); } /** * A slot that processes data from the MPrisControl that emit the signal. * * @param The emitting MPrisControl * @param newVolume The new volume */ void Mixer_MPRIS2::volumeChanged(MPrisControl* mad, double newVolume) { shared_ptr md = m_mixDevices.get(mad->getId()); int volInt = newVolume *100; if (GlobalConfig::instance().data.debugVolume) qCDebug(KMIX_LOG) << "changed" << volInt; volumeChangedInternal(md, volInt); } void Mixer_MPRIS2::volumeChangedInternal(shared_ptr md, int volumePercentage) { if ( md->isVirtuallyMuted() && volumePercentage == 0) { // Special code path for virtual mute switches. Don't write back the volume if it is muted in the KMix GUI return; } Volume& vol = md->playbackVolume(); vol.setVolume( Volume::LEFT, volumePercentage); md->setMuted(volumePercentage == 0); QMetaObject::invokeMethod(this, "announceVolume", Qt::QueuedConnection); -// ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, QString("MixerMPRIS2.volumeChanged")); +// ControlManager::instance().announce(_mixer->id(), ControlManager::Volume, QString("MixerMPRIS2.volumeChanged")); } // The following is an example message for an incoming volume change: /* signal sender=:1.125 -> dest=(null destination) serial=503 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged string "org.mpris.MediaPlayer2.Player" array [ dict entry( string "Volume" variant double 0.81 ) ] array [ ] */ /** * @overload * * @param id * @param md * @return */ int Mixer_MPRIS2::writeVolumeToHW( const QString& id, shared_ptr md ) { Volume& vol = md->playbackVolume(); double volFloat = 0; if ( ! md->isMuted() ) { int volInt = vol.getVolume(Volume::LEFT); volFloat = volInt/100.0; } QList arg; arg.append(QString("org.mpris.MediaPlayer2.Player")); arg.append(QString("Volume")); arg << QVariant::fromValue(QDBusVariant(volFloat)); MPrisControl* mad = controls.value(id); if ( !mad ) { qCDebug(KMIX_LOG) << "id does not exist:" << id; return 0; } QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player")); QVariant v2 = QVariant(QString("Volume")); QVariant v3 = QVariant::fromValue(QDBusVariant(volFloat)); // QVariant v3 = QVariant(volFloat); // I don't care too much for the reply, as I won't receive a result. Thus fire-and-forget here. mad->propertyIfc->asyncCall("Set", v1, v2, v3); return 0; } void Mixer_MPRIS2::setEnumIdHW(const QString&, unsigned int) { // no enums in MPRIS } unsigned int Mixer_MPRIS2::enumIdHW(const QString&) { // no enums in MPRIS return 0; } bool Mixer_MPRIS2::moveStream( const QString&, const QString& ) { // not supported in MPRIS return false; } /** * Adds all currently running players and then starts listening * for changes (new players, and disappearing players).
* * @return int **/ int Mixer_MPRIS2::addAllRunningPlayersAndInitHotplug() { QDBusConnection dbusConn = QDBusConnection::sessionBus(); if (! dbusConn.isConnected() ) { qCCritical(KMIX_LOG) << "Cannot connect to the D-Bus session bus.\n" << "To start it, run:\n" <<"\teval `dbus-launch --auto-syntax`\n"; return Mixer::ERR_OPEN; } // Start listening for new Mediaplayers bool connected = dbusConn.connect("", QString("/org/freedesktop/DBus"), "org.freedesktop.DBus", "NameOwnerChanged", this, SLOT(newMediaPlayer(QString,QString,QString)) ); if (!connected) { qCWarning(KMIX_LOG) << "MPRIS2 hotplug init failure. New Media Players will not be detected."; } /* Here is a small concurrency issue. * If new players appear between registeredServiceNames() below and the connect() above these players *might* show up doubled in KMix. * There is no simple solution (reversing could have the problem of not-adding), so we live for now with it. */ /* * Bug 311189: Introspecting via "dbusConn.interface()->registeredServiceNames()" does not work too well. * Comment: I am not so sure that registeredServiceNames() is really an issue. It is more likely * in a later step, when talking to the probed apps. Still, I now do a hand crafted 3-line version of * registeredServiceNames() via "ListNames", so I can later more easily change to async. */ QDBusInterface dbusIfc("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", dbusConn); QDBusPendingReply repl = dbusIfc.asyncCall("ListNames"); repl.waitForFinished(); if (! repl.isValid() ) { qCCritical(KMIX_LOG) << "Invalid reply while listing Media Players. MPRIS2 players will not be available." << repl.error(); return 1; } QString busDestination; foreach ( busDestination , repl.value() ) { if ( busDestination.startsWith(QLatin1String("org.mpris.MediaPlayer2")) ) { addMprisControlAsync(busDestination); qCDebug(KMIX_LOG) << "MPRIS2: Attached media player on busDestination=" << busDestination; } } return 0; } QString Mixer_MPRIS2::busDestinationToControlId(const QString& busDestination) { const QString prefix = "org.mpris.MediaPlayer2."; if (! busDestination.startsWith(prefix)) { qCWarning(KMIX_LOG) << "Ignoring unsupported control, busDestination=" << busDestination; return QString(); } return busDestination.mid(prefix.length()); } /** * Asynchronously add the MPRIS control designated by the DBUS busDestination. * to the internal apps list. * * @param conn An open connection to the DBUS Session Bus * @param busDestination The DBUS busDestination, e.g. "org.mpris.MediaPlayer2.amarok" */ void Mixer_MPRIS2::addMprisControlAsync(QString busDestination) { // -1- Create a MPrisControl. Its fields will be filled partially here, partially via ASYNC DUBUS replies QString id = busDestinationToControlId(busDestination); qCDebug(KMIX_LOG) << "Get control of busDestination=" << busDestination << "id=" << id; QDBusConnection conn = QDBusConnection::sessionBus(); QDBusInterface *qdbiProps = new QDBusInterface(QString(busDestination), QString("/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties", conn, this); QDBusInterface *qdbiPlayer = new QDBusInterface(QString(busDestination), QString("/org/mpris/MediaPlayer2"), "org.mpris.MediaPlayer2.Player", conn, this); // -2- Add the control to our official control list MPrisControl* mad = new MPrisControl(id, busDestination); mad->propertyIfc = qdbiProps; mad->playerIfc = qdbiPlayer; controls.insert(id, mad); /* * WTF: - asyncCall("Get", arg) : returns an error message (see below) * - asyncCallWithArgumentList("Get", arg) : returns an error message (see below) * - callWithArgumentList(QDBus::Block, "Get", arg) : works * - syncCall("Get", v1, v2) : works * * kmix(13543) Mixer_MPRIS2::addMPrisControl: (marok), msg2= QDBusMessage(type=Error, service=":1.44", error name="org.freedesktop.DBus.Error.UnknownMethod", error message="No such method 'Get' in interface 'org.freedesktop.DBus.Properties' at object path '/org/mpris/MediaPlayer2' (signature 'av')", signature="s", contents=("No such method 'Get' in interface 'org.freedesktop.DBus.Properties' at object path '/org/mpris/MediaPlayer2' (signature 'av')") ) , isValid= false , isFinished= true , isError= true * * This behavior is total counter-intuitive :-((( */ // Create ASYNC DBUS queries for the new control. This effectively starts a chain of async DBUS commands. QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2")); QVariant v2 = QVariant(QString("Identity")); QDBusPendingReply repl2 = mad->propertyIfc->asyncCall("Get", v1, v2); QDBusPendingCallWatcher* watchIdentity = new QDBusPendingCallWatcher(repl2, mad); connect(watchIdentity, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(watcherPlugControlId(QDBusPendingCallWatcher*))); } MixDevice::ChannelType Mixer_MPRIS2::getChannelTypeFromPlayerId(const QString& id) { // TODO This hardcoded application list is a quick hack. It should be generalized. MixDevice::ChannelType ct = MixDevice::APPLICATION_STREAM; if (id.startsWith(QLatin1String("amarok"))) { ct = MixDevice::APPLICATION_AMAROK; } else if (id.startsWith(QLatin1String("banshee"))) { ct = MixDevice::APPLICATION_BANSHEE; } else if (id.startsWith(QLatin1String("vlc"))) { ct = MixDevice::APPLICATION_VLC; } else if (id.startsWith(QLatin1String("xmms"))) { ct = MixDevice::APPLICATION_XMM2; } else if (id.startsWith(QLatin1String("tomahawk"))) { ct = MixDevice::APPLICATION_TOMAHAWK; } else if (id.startsWith(QLatin1String("clementine"))) { ct = MixDevice::APPLICATION_CLEMENTINE; } return ct; } void Mixer_MPRIS2::watcherInitialVolume(QDBusPendingCallWatcher* watcher) { MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); if (mprisCtl == 0) return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) const QDBusMessage& msg = watcher->reply(); QList repl = msg.arguments(); if ( ! repl.isEmpty() ) { QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); QVariant result2 = dbusVariant.variant(); double volume = result2.toDouble(); volumeChanged(mprisCtl, volume); } watcher->deleteLater(); } void Mixer_MPRIS2::watcherInitialPlayState(QDBusPendingCallWatcher* watcher) { MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); if (mprisCtl == 0) return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) const QDBusMessage& msg = watcher->reply(); QList repl = msg.arguments(); if ( ! repl.isEmpty() ) { QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); QVariant result2 = dbusVariant.variant(); QString playbackStateString = result2.toString(); MediaController::PlayState playState = Mixer_MPRIS2::mprisPlayStateString2PlayState(playbackStateString); playbackStateChanged(mprisCtl, playState); } watcher->deleteLater(); } /** * Convenience method for the watcher*() methods. * Returns the MPrisControl that is parent of the given watcher, if the reply is valid. In this case you can * use the result and call watcher->deleteLater() after processing the result. * * Otherwise 0 is returned, and watcher->deleteLater() is called. Important You must call watcher->deleteLater() * yourself for the other (normal/good) case. * * @param watcher * @return */ MPrisControl* Mixer_MPRIS2::watcherHelperGetMPrisControl(QDBusPendingCallWatcher* watcher) { const QDBusMessage& msg = watcher->reply(); if ( msg.type() == QDBusMessage::ReplyMessage ) { QObject* obj = watcher->parent(); MPrisControl* mad = qobject_cast(obj); if (mad != 0) { return mad; } qCWarning(KMIX_LOG) << "Ignoring unexpected Control Id. object=" << obj; } else if ( msg.type() == QDBusMessage::ErrorMessage ) { qCCritical(KMIX_LOG) << "ERROR in Media control operation, path=" << msg.path() << ", msg=" << msg; } watcher->deleteLater(); return 0; } void Mixer_MPRIS2::watcherPlugControlId(QDBusPendingCallWatcher* watcher) { MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); if (mprisCtl == 0) { return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) } const QDBusMessage& msg = watcher->reply(); QString id = mprisCtl->getId(); QString busDestination = mprisCtl->getBusDestination(); QString readableName = id; // Start with ID, but replace with reply (if exists) qCDebug(KMIX_LOG) << "Plugging id=" << id << ", busDestination" << busDestination << ", name= " << readableName; QList repl = msg.arguments(); if ( ! repl.isEmpty() ) { // We have to do some very ugly casting from QVariant to QDBusVariant to QVariant. This API totally sucks. QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); QVariant result2 = dbusVariant.variant(); readableName = result2.toString(); // qCDebug(KMIX_LOG) << "REPLY " << result2.type() << ": " << readableName; MixDevice::ChannelType ct = getChannelTypeFromPlayerId(id); MixDevice* mdNew = new MixDevice(_mixer, id, readableName, ct); // MPRIS2 doesn't support an actual mute switch. Mute is defined as volume = 0.0 // Thus we won't add the playback switch Volume* vol = new Volume( 100, 0, false, false); vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); // MPRIS is only one control ("Mono") MediaController* mediaContoller = mdNew->getMediaController(); mediaContoller->addMediaPlayControl(); mediaContoller->addMediaNextControl(); mediaContoller->addMediaPrevControl(); mdNew->setApplicationStream(true); mdNew->addPlaybackVolume(*vol); m_mixDevices.append( mdNew->addToPool() ); delete vol; // vol is only temporary. mdNew has its own volume object. => delete QDBusConnection sessionBus = QDBusConnection::sessionBus(); sessionBus.connect(busDestination, QString("/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties", "PropertiesChanged", mprisCtl, SLOT(onPropertyChange(QString,QVariantMap,QStringList)) ); connect(mprisCtl, SIGNAL(volumeChanged(MPrisControl*,double)), this, SLOT(volumeChanged(MPrisControl*,double)) ); connect(mprisCtl, SIGNAL(playbackStateChanged(MPrisControl*,MediaController::PlayState)), SLOT (playbackStateChanged(MPrisControl*,MediaController::PlayState)) ); sessionBus.connect(busDestination, QString("/Player"), "org.freedesktop.MediaPlayer", "TrackChange", mprisCtl, SLOT(trackChangedIncoming(QVariantMap)) ); // The following line is evil: mad->playerIfc->property("Volume") is in fact a synchronous call, and // sync calls are strictly forbidden, see bug 317926 //volumeChanged(mad, mad->playerIfc->property("Volume").toDouble()); // --- Query initial state -------------------------------------------------------------------------------- QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player")); QVariant v2 = QVariant(QString("Volume")); QDBusPendingReply repl2 = mprisCtl->propertyIfc->asyncCall("Get", v1, v2); QDBusPendingCallWatcher* watcherOutgoing = new QDBusPendingCallWatcher(repl2, mprisCtl); connect(watcherOutgoing, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(watcherInitialVolume(QDBusPendingCallWatcher*))); v2 = QVariant(QString("PlaybackStatus")); repl2 = mprisCtl->propertyIfc->asyncCall("Get", v1, v2); watcherOutgoing = new QDBusPendingCallWatcher(repl2, mprisCtl); connect(watcherOutgoing, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(watcherInitialPlayState(QDBusPendingCallWatcher*))); // Push notifyToReconfigureControls to stack, so it will not be executed synchronously announceControlListAsync(id); } watcher->deleteLater(); } // ----------------------------------------------------------------------------------------------------------- // ASYNC announce slots, including convenience wrappers // ----------------------------------------------------------------------------------------------------------- /** * Convenience wrapper to do the ASYNC call to #announceControlList() * @param */ void Mixer_MPRIS2::announceControlListAsync(QString /*streamId*/) { // currently we do not use the streamId QMetaObject::invokeMethod(this, "announceControlList", Qt::QueuedConnection); } void Mixer_MPRIS2::announceControlList() { - ControlManager::instance().announce(_mixer->id(), ControlChangeType::ControlList, getDriverName()); + ControlManager::instance().announce(_mixer->id(), ControlManager::ControlList, getDriverName()); } void Mixer_MPRIS2::announceGUI() { - ControlManager::instance().announce(_mixer->id(), ControlChangeType::GUI, getDriverName()); + ControlManager::instance().announce(_mixer->id(), ControlManager::GUI, getDriverName()); } void Mixer_MPRIS2::announceVolume() { - ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, getDriverName()); + ControlManager::instance().announce(_mixer->id(), ControlManager::Volume, getDriverName()); } // ----------------------------------------------------------------------------------------------------------- /** * Handles the hotplug of new MPRIS2 enabled Media Players */ void Mixer_MPRIS2::newMediaPlayer(QString name, QString oldOwner, QString newOwner) { if ( name.startsWith(QLatin1String("org.mpris.MediaPlayer2")) ) { if ( oldOwner.isEmpty() && !newOwner.isEmpty()) { qCDebug(KMIX_LOG) << "Mediaplayer registers: " << name; addMprisControlAsync(name); } else if ( !oldOwner.isEmpty() && newOwner.isEmpty()) { QString id = busDestinationToControlId(name); qCDebug(KMIX_LOG) << "Mediaplayer unregisters: " << name << " , id=" << id; // -1- Remove Mediaplayer connection if (controls.contains(id)) { const MPrisControl *control = controls.value(id); QObject::disconnect(control,0,0,0); controls.remove(id); } // -2- Remove MixDevice from internal list shared_ptr md = m_mixDevices.get(id); if (md) { // We know about the player that is unregistering => remove internally md->close(); m_mixDevices.removeById(id); announceControlListAsync(id); qCDebug(KMIX_LOG) << "MixDevice 4 useCount=" << md.use_count(); } } else { qCWarning(KMIX_LOG) << "Mediaplayer has registered under a new name. This is currently not supported by KMix"; } } } /** * This slot is a simple proxy that enriches the DBUS signal with our data, which especially contains the id of the MixDevice. */ void MPrisControl::trackChangedIncoming(QVariantMap /*msg*/) { qCDebug(KMIX_LOG) << "Track changed"; } MediaController::PlayState Mixer_MPRIS2::mprisPlayStateString2PlayState(const QString& playbackStatus) { MediaController::PlayState playState = MediaController::PlayStopped; // presume Stopped for unknown state if (playbackStatus == "Playing") { playState = MediaController::PlayPlaying; } else if (playbackStatus == "Stopped") { playState = MediaController::PlayStopped; } else if (playbackStatus == "Paused") { playState = MediaController::PlayPaused; } return playState; } /** * This slot is a simple proxy that enriches the DBUS signal with our data, which especially contains the id of the MixDevice. */ void MPrisControl::onPropertyChange(QString /*ifc*/,QVariantMap msg ,QStringList /*sl*/) { QMap::iterator v = msg.find("Volume"); if (v != msg.end() ) { double volDouble = v.value().toDouble(); qCDebug(KMIX_LOG) << "volumeChanged incoming: vol=" << volDouble; emit volumeChanged( this, volDouble); } v = msg.find("PlaybackStatus"); if (v != msg.end() ) { QString playbackStatus = v.value().toString(); MediaController::PlayState playState = Mixer_MPRIS2::mprisPlayStateString2PlayState(playbackStatus); qCDebug(KMIX_LOG) << "PlaybackStatus is now " << playbackStatus; emit playbackStateChanged(this, playState); } } Mixer_MPRIS2::~Mixer_MPRIS2() { close(); } MPrisControl::MPrisControl(QString id, QString busDestination) : propertyIfc(0) , playerIfc(0) { volume = 0; this->id = id; this->busDestination = busDestination; retrievedElems = MPrisControl::NONE; } MPrisControl::~MPrisControl() { delete propertyIfc; delete playerIfc; } QString Mixer_MPRIS2::getDriverName() { return "MPRIS2"; } QString MPRIS2_getDriverName() { return "MPRIS2"; } diff --git a/core/ControlManager.cpp b/core/ControlManager.cpp index a208f874..ba9d9ffe 100644 --- a/core/ControlManager.cpp +++ b/core/ControlManager.cpp @@ -1,197 +1,190 @@ /* KMix -- KDE's full featured mini mixer Copyright (C) 2012 Christian Esken This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU 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 "ControlManager.h" #include "core/GlobalConfig.h" #include "kmix_debug.h" #include ControlManager ControlManager::instanceSingleton; -ControlManager& ControlManager::instance() +ControlManager &ControlManager::instance() { return instanceSingleton; } ControlManager::ControlManager() { listenersChanged = false; } /** * Announce a change for one or all mixers. * * @param mixerId The mixerId. Use an empty QString() to announce a change for all mixers * @param changeType A bit array of ControlChangeType flags * @param sourceId Only for logging * */ -void ControlManager::announce(QString mixerId, ControlChangeType::Type changeType, QString sourceId) +void ControlManager::announce(const QString &mixerId, ControlManager::ChangeType changeType, const QString &sourceId) { - bool listenersModified = false; - QSet processedListeners; + QSet processedListeners; do { listenersModified = false; - QList::iterator it; - for (it = listeners.begin(); it != listeners.end(); ++it) + for (QList::const_iterator it = listeners.constBegin(); it!=listeners.constEnd(); ++it) { - Listener& listener = *it; + Listener *listener = (*it); - bool mixerIsOfInterest = listener.getMixerId().isEmpty() || mixerId.isEmpty() - || listener.getMixerId() == mixerId; + bool mixerIsOfInterest = listener->getMixerId().isEmpty() || mixerId.isEmpty() + || listener->getMixerId() == mixerId; - bool listenerAlreadyProcesed = processedListeners.contains(&listener); + bool listenerAlreadyProcesed = processedListeners.contains(listener); if ( listenerAlreadyProcesed ) { if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "Skipping already processed listener"; continue; } - if (mixerIsOfInterest && listener.getChangeType() == changeType) + if (mixerIsOfInterest && listener->getChangeType() == changeType) { - bool success = QMetaObject::invokeMethod(listener.getTarget(), "controlsChange", Qt::DirectConnection, - Q_ARG(int, changeType)); + bool success = QMetaObject::invokeMethod(listener->getTarget(), + "controlsChange", + Qt::DirectConnection, + Q_ARG(ControlManager::ChangeType, changeType)); if (GlobalConfig::instance().data.debugControlManager) { - qCDebug(KMIX_LOG) << "Listener " << listener.getSourceId() <<" is interested in " << mixerId - << ", " << ControlChangeType::toString(changeType); + qCDebug(KMIX_LOG) << "Listener" << listener->getSourceId() + << "is interested in" << mixerId + << "type" << changeType; } if (!success) { - qCCritical(KMIX_LOG) << "Listener Failed to send to " << listener.getTarget()->metaObject()->className(); + qCCritical(KMIX_LOG) << "Listener failed to send to " << listener->getTarget()->metaObject()->className(); } - processedListeners.insert(&listener); + processedListeners.insert(listener); if (listenersChanged) { // The invokeMethod() above has changed the listeners => my Iterator is invalid => restart loop if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "Listeners modified => restart loop"; listenersChanged = false; listenersModified = true; break; // break inner loop => restart via outer loop } } } } - while ( listenersModified); + while (listenersModified); if (GlobalConfig::instance().data.debugControlManager) { qCDebug(KMIX_LOG) - << "Announcing " << ControlChangeType::toString(changeType) << " for " - << (mixerId.isEmpty() ? "all cards" : mixerId) << " by " << sourceId; + << "Announcing" << changeType << "for" + << (mixerId.isEmpty() ? "all cards" : mixerId) << "by" << sourceId; } } /** * Adds a listener for the given mixerId and changeType. * Listeners are informed about all corresponding changes via a signal. * Listeners are not informed about changes that originate from oneself (according to sourceId). * * @param mixerId The id of the Mixer you are interested in * @param changetType The changeType of interest - * @param target The QObject, where the notification signal is sent to. It must implement the SLOT controlChanged(QString mixerId,ControlChangeType::Type changeType). + * @param target The QObject, where the notification signal is sent to. It must implement the SLOT controlChanged(QString mixerId,ControlManager::ChangeType changeType). * @param sourceId Only for logging */ -void ControlManager::addListener(QString mixerId, ControlChangeType::Type changeType, QObject* target, QString sourceId) +void ControlManager::addListener(const QString &mixerId, ControlManager::ChangeTypes changeTypes, + QObject *target, const QString &sourceId) { if (GlobalConfig::instance().data.debugControlManager) { qCDebug(KMIX_LOG) - << "Listening to " << ControlChangeType::toString(changeType) << " for " - << (mixerId.isEmpty() ? "all cards" : mixerId) << " by " << sourceId << ". Announcements are sent to " - << target; + << "Listening to" << changeTypes << "for" + << (mixerId.isEmpty() ? "all cards" : mixerId) << "by" << sourceId + << "sent to" << target; } - for ( ControlChangeType::Type ct = ControlChangeType::TypeFirst; ct != ControlChangeType::TypeLast; - ct = static_cast(ct << 1)) + for (ControlManager::ChangeType ct = ChangeType::First; ct!=ChangeType::Last; + ct = static_cast(ct << 1)) { - if ( changeType & ct ) + if (changeTypes & ct) { // Add all listeners. - Listener listener = Listener(mixerId, ct, target, sourceId); + Listener *listener = new Listener(mixerId, ct, target, sourceId); listeners.append(listener); listenersChanged = true; } } if (GlobalConfig::instance().data.debugControlManager) { - qCDebug(KMIX_LOG) - << "We now have" << listeners.size() << "listeners"; + qCDebug(KMIX_LOG) << "We now have" << listeners.size() << "listeners"; } } -/** - * Removes all listeners of the given target. - * @param target The QObject that was used to register via addListener() - */ -void ControlManager::removeListener(QObject* target) -{ - ControlManager::instance().removeListener(target, target->metaObject()->className()); -} - /** * Removes all listeners of the given target. * @param target The QObject that was used to register via addListener() * @param sourceId Optional: Only for logging */ -void ControlManager::removeListener(QObject* target, QString sourceId) +void ControlManager::removeListener(QObject *target, const QString &sourceId) { - QMutableListIterator it(listeners); - while ( it.hasNext()) + QString src = sourceId; + if (src.isEmpty()) src = target->metaObject()->className(); + + QMutableListIterator it(listeners); + while (it.hasNext()) { - Listener& listener = it.next(); - if (listener.getTarget() == target) + Listener *listener = it.next(); + if (listener->getTarget() == target) { if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) - << "Stop Listening of " << listener.getSourceId() << " requested by " << sourceId << " from " << target; + << "Stop Listening of" << listener->getSourceId() << "requested by" << src << "from" << target; it.remove(); // Hint: As we have actual objects no explicit delete is needed listenersChanged = true; } } } -void ControlManager::warnUnexpectedChangeType(ControlChangeType::Type type, QObject *obj) +void ControlManager::warnUnexpectedChangeType(ControlManager::ChangeType type, QObject *obj) { qCWarning(KMIX_LOG) << "Unexpected type " << type << " received by " << obj->metaObject()->className(); } void ControlManager::shutdownNow() { if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "Shutting down ControlManager"; - QList::iterator it; - for (it = listeners.begin(); it != listeners.end(); ++it) + for (QList::const_iterator it = listeners.constBegin(); it!=listeners.constEnd(); ++it) { - Listener& listener = *it; + Listener *listener = (*it); if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) - << "Listener still connected. Closing it. source=" << listener.getSourceId() << "listener=" - << listener.getTarget()->metaObject()->className(); + << "Listener still connected. Closing it. source" << listener->getSourceId() + << "target" << listener->getTarget()->metaObject()->className(); } } - diff --git a/core/ControlManager.h b/core/ControlManager.h index 5f1c2691..25ae8231 100644 --- a/core/ControlManager.h +++ b/core/ControlManager.h @@ -1,150 +1,93 @@ /* Copyright (C) 2012 Christian Esken This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU 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 CONTROLMANAGER_H #define CONTROLMANAGER_H -#include -#include +#include +#include +#include + +class QObject; +class Listener; #include "kmixcore_export.h" -// typedef int ControlChangeType; -// enum ControlChangeType { -// Volume, // Volume or Switch change (Mute or Capture Switch, or Enum) -// ControlList, // Control added or deleted -// GUI // Visual changes, like "split channel" OR "show labels" -// }; -class ControlChangeType : public QObject +class KMIXCORE_EXPORT ControlManager { - Q_OBJECT - public: - enum Type - { - None = 0, // - TypeFirst = 1, - Volume = 1, // Volume or Switch change (Mute or Capture Switch, or Enum) - ControlList = 2, // Control added or deleted - GUI = 4, // Visual changes, like "split channel" OR "show labels" - MasterChanged = 8 // Master (global or local) has changed - , - TypeLast = 16 - }; - - static QString toString(Type changeType) - { - QString ret; - bool needsSeparator = false; - for (ControlChangeType::Type ct = ControlChangeType::TypeFirst; ct != ControlChangeType::TypeLast; ct = - static_cast(ct << 1)) - { - if (changeType & ct) - { - if (needsSeparator) - ret.append('|'); - switch (ct) - { - case Volume: - ret.append("Volume"); - break; - case ControlList: - ret.append("ControlList"); - break; - case GUI: - ret.append("GUI"); - break; - case MasterChanged: - ret.append("MasterChange"); - break; - default: - ret.append("Invalid"); - break; - } - - needsSeparator = true; - } - } - - return ret; - - }; - - static ControlChangeType::Type fromInt(int type) + static ControlManager &instance(); + + enum ChangeType { - switch ( type ) - { - case 1: return Volume; - case 2: return ControlList; - case 4: return GUI; - case 8: return MasterChanged; - default: return None; - } + None = 0, // no change + First = 1, + Volume = 1, // Volume or Switch change (Mute or Capture Switch, or Enum) + ControlList = 2, // Control added or deleted + GUI = 4, // Visual changes, like "split channel" OR "show labels" + MasterChanged = 8, // Master (global or local) has changed + Last = 16 }; + Q_DECLARE_FLAGS(ChangeTypes, ChangeType) + + void announce(const QString &mixerId, ControlManager::ChangeType changeType, const QString &sourceId); + void addListener(const QString &mixerId, ControlManager::ChangeTypes changeTypes, QObject *target, const QString &sourceId); + void removeListener(QObject *target, const QString &sourceId = QString()); + + static void warnUnexpectedChangeType(ControlManager::ChangeType type, QObject *obj); + void shutdownNow(); +private: + ControlManager(); + static ControlManager instanceSingleton; + QList listeners; + bool listenersChanged; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ControlManager::ChangeTypes) + + + class Listener { public: - Listener(const QString mixerId, ControlChangeType::Type changeType, QObject* target, QString& sourceId) + Listener(const QString &mixerId, ControlManager::ChangeType changeType, QObject *target, const QString &sourceId) { this->mixerId = mixerId; this->controlChangeType = changeType; // target is bit dangerous, as it might get deleted. this->target = target; this->sourceId = sourceId; } - const QString& getMixerId() { return mixerId; }; - ControlChangeType::Type& getChangeType() { return controlChangeType; }; - QObject* getTarget() { return target; }; - const QString& getSourceId() { return sourceId; }; + const QString getMixerId() const { return mixerId; }; + ControlManager::ChangeType getChangeType() const { return controlChangeType; }; + QObject *getTarget() const { return target; }; + const QString getSourceId() const { return sourceId; }; private: QString mixerId; - ControlChangeType::Type controlChangeType; - QObject* target; + ControlManager::ChangeType controlChangeType; + QObject *target; QString sourceId; - - }; -class KMIXCORE_EXPORT ControlManager -{ -public: - static ControlManager& instance(); - - void announce(QString mixerId, ControlChangeType::Type changeType, QString sourceId); - void addListener(QString mixerId, ControlChangeType::Type changeType, QObject* target, QString sourceId); - void removeListener(QObject* target); - void removeListener(QObject* target, QString sourceId); - - static void warnUnexpectedChangeType(ControlChangeType::Type type, QObject *obj); - void shutdownNow(); - -private: - ControlManager(); - static ControlManager instanceSingleton; - QList listeners; - bool listenersChanged; -}; #endif // CONTROLMANAGER_H diff --git a/core/mixer.cpp b/core/mixer.cpp index b6bdb402..36116acc 100644 --- a/core/mixer.cpp +++ b/core/mixer.cpp @@ -1,735 +1,735 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken - esken@kde.org * 2002 Helio Chissini de Castro - helio@conectiva.com.br * * 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 "core/mixer.h" #include #include #include "backends/mixer_backend.h" #include "backends/kmix-backends.cpp" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/volume.h" /** * Some general design hints. Hierarchy is Mixer->MixDevice->Volume */ QList Mixer::s_mixers; MasterControl Mixer::_globalMasterCurrent; MasterControl Mixer::_globalMasterPreferred; bool Mixer::m_beepOnVolumeChange = false; int Mixer::numDrivers() { MixerFactory *factory = g_mixerFactories; int num = 0; while( factory->getMixer!=0 ) { num++; factory++; } return num; } /* * Returns a reference of the current mixer list. */ QList& Mixer::mixers() { return s_mixers; } /** * Returns whether there is at least one dynamic mixer active. * @returns true, if at least one dynamic mixer is active */ bool Mixer::dynamicBackendsPresent() { foreach ( Mixer* mixer, Mixer::mixers() ) { if ( mixer->isDynamic() ) return true; } return false; } bool Mixer::pulseaudioPresent() { foreach ( Mixer* mixer, Mixer::mixers() ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } Mixer::Mixer( QString& ref_driverName, int device ) : m_balance(0), _mixerBackend(0L), m_dynamic(false) { _mixerBackend = 0; int driverCount = numDrivers(); for (int driver=0; driver retrieve Mixer factory for that driver getMixerFunc *f = g_mixerFactories[driver].getMixer; if( f!=0 ) { _mixerBackend = f( this, device ); readSetFromHWforceUpdate(); // enforce an initial update on first readSetFromHW() } break; } } } Mixer::~Mixer() { // Close the mixer. This might also free memory, depending on the called backend method close(); delete _mixerBackend; } /* * Find a Mixer. If there is no mixer with the given id, 0 is returned */ Mixer* Mixer::findMixer( const QString& mixer_id) { Mixer *mixer = 0; int mixerCount = Mixer::mixers().count(); for ( int i=0; iid() == mixer_id ) { mixer = (Mixer::mixers())[i]; break; } } return mixer; } /** * Set the final ID of this Mixer. *
Warning: This method is VERY fragile, because it is requires information that we have very late, * especially the _cardInstance. We only know the _cardInstance, when we know the ID of the _mixerBackend->getId(). * OTOH, the Mixer backend needs the _cardInstance during construction of its MixDevice instances. * * This means, we need the _cardInstance during construction of the Mixer, but we only know it after its constructed. * Actually its a design error. The _cardInstance MUST be set and managed by the backend. * * The current solution works but is very hacky - cardInstance is a parameter of openIfValid(). * */ void Mixer::recreateId() { /* As we use "::" and ":" as separators, the parts %1,%2 and %3 may not * contain it. * %1, the driver name is from the KMix backends, it does not contain colons. * %2, the mixer name, is typically coming from an OS driver. It could contain colons. * %3, the mixer number, is a number: it does not contain colons. */ QString mixerName = _mixerBackend->getId(); mixerName.replace(':','_'); QString primaryKeyOfMixer = QString("%1::%2:%3") .arg(getDriverName(), mixerName) .arg(getCardInstance()); // The following 3 replaces are for not messing up the config file primaryKeyOfMixer.replace(']','_'); primaryKeyOfMixer.replace('[','_'); // not strictly necessary, but lets play safe primaryKeyOfMixer.replace(' ','_'); primaryKeyOfMixer.replace('=','_'); _id = primaryKeyOfMixer; // qCDebug(KMIX_LOG) << "Early _id=" << _id; } const QString Mixer::dbusPath() { // _id needs to be fixed from the very beginning, as the MixDevice construction uses MixDevice::dbusPath(). // So once the first MixDevice is created, this must return the correct value if (_id.isEmpty()) { if (! _mixerBackend->_cardRegistered) { // Bug 308014: By checking _cardRegistered, we can be sure that everything is fine, including the fact that // the cardId (aka "card instance") is set. If _cardRegistered would be false, we will create potentially // wrong/duplicated DBUS Paths here. qCWarning(KMIX_LOG) << "Mixer id was empty when creating DBUS path. Emergency code created the id=" <<_id; } // Bug 308014: Actually this a shortcut (you could also call it a hack). It would likely better if registerCard() // would create the Id, but it requires cooperation from ALL backends. Also Mixer->getId() would need to // proxy that to the backend. // So for now we lazily create the MixerId here, while creating the first MixDevice for that card. recreateId(); } // mixerName may contain arbitrary characters, so replace all that are not allowed to be be part of a DBUS path QString cardPath = _id; cardPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); cardPath.replace(QLatin1String("//"), QLatin1String("/")); return QString("/Mixers/" + cardPath); } void Mixer::volumeSave( KConfig *config ) { // qCDebug(KMIX_LOG) << "Mixer::volumeSave()"; _mixerBackend->readSetFromHW(); QString grp("Mixer"); grp.append(id()); _mixerBackend->m_mixDevices.write( config, grp ); // This might not be the standard application config object // => Better be safe and call sync(). config->sync(); } void Mixer::volumeLoad( KConfig *config ) { QString grp("Mixer"); grp.append(id()); if ( ! config->hasGroup(grp) ) { // no such group. Volumes (of this mixer) were never saved beforehand. // Thus don't restore anything (also see Bug #69320 for understanding the real reason) return; // make sure to bail out immediately } // else restore the volumes if ( ! _mixerBackend->m_mixDevices.read( config, grp ) ) { // Some mixer backends don't support reading the volume into config // files, so bail out early if that's the case. return; } // set new settings for(int i=0; i<_mixerBackend->m_mixDevices.count() ; i++ ) { shared_ptr md = _mixerBackend->m_mixDevices[i]; if ( md.get() == 0 ) continue; _mixerBackend->writeVolumeToHW( md->id(), md ); if ( md->isEnum() ) _mixerBackend->setEnumIdHW( md->id(), md->enumId() ); } } /** * Opens the mixer. * Also, starts the polling timer, for polling the Volumes from the Mixer. * * @param cardId The cardId Usually this will be 1, but if there is * more than one card with the same name install, then you need * to use 2, 3, ... * * @return true, if Mixer could be opened. */ bool Mixer::openIfValid() { if (_mixerBackend == 0 ) { // if we did not instantiate a suitable Backend, then Mixer is invalid return false; } bool ok = _mixerBackend->openIfValid(); if ( ok ) { recreateId(); shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); if ( recommendedMaster.get() != 0 ) { QString recommendedMasterStr = recommendedMaster->id(); setLocalMasterMD( recommendedMasterStr ); qCDebug(KMIX_LOG) << "Mixer::open() detected master: " << recommendedMaster->id(); } else { if ( !m_dynamic ) qCCritical(KMIX_LOG) << "Mixer::open() no master detected."; else qCDebug(KMIX_LOG) << "Mixer::open() no master detected."; QString noMaster = "---no-master-detected---"; setLocalMasterMD(noMaster); // no master } // cesken: The following connect() looks mighty strange. I removed it on 2013-12-18 //connect( _mixerBackend, SIGNAL(controlChanged()), SIGNAL(controlChanged()) ); new DBusMixerWrapper(this, dbusPath()); } return ok; } /** * Closes the mixer. */ void Mixer::close() { if ( _mixerBackend != 0) _mixerBackend->closeCommon(); } /* ------- WRAPPER METHODS. START ------------------------------ */ unsigned int Mixer::size() const { return _mixerBackend->m_mixDevices.count(); } shared_ptr Mixer::operator[](int num) { shared_ptr md = _mixerBackend->m_mixDevices.at( num ); return md; } MixSet& Mixer::getMixSet() { return _mixerBackend->m_mixDevices; } /** * Returns the driver name, that handles this Mixer. */ QString Mixer::getDriverName() { QString driverName = _mixerBackend->getDriverName(); // qCDebug(KMIX_LOG) << "Mixer::getDriverName() = " << driverName << "\n"; return driverName; } bool Mixer::isOpen() const { if ( _mixerBackend == 0 ) return false; else return _mixerBackend->isOpen(); } void Mixer::readSetFromHWforceUpdate() const { _mixerBackend->readSetFromHWforceUpdate(); } /// Returns translated WhatsThis messages for a control.Translates from QString Mixer::translateKernelToWhatsthis(const QString &kernelName) { return _mixerBackend->translateKernelToWhatsthis(kernelName); } /* ------- WRAPPER METHODS. END -------------------------------- */ int Mixer::balance() const { return m_balance; } void Mixer::setBalance(int balance) { if( balance == m_balance ) { // balance unchanged => return return; } m_balance = balance; shared_ptr master = getLocalMasterMD(); if ( master.get() == 0 ) { // no master device available => return return; } Volume& volP = master->playbackVolume(); setBalanceInternal(volP); Volume& volC = master->captureVolume(); setBalanceInternal(volC); _mixerBackend->writeVolumeToHW( master->id(), master ); emit newBalance( volP ); } void Mixer::setBalanceInternal(Volume& vol) { //_mixerBackend->readVolumeFromHW( master->id(), master ); int left = vol.getVolume(Volume::LEFT); int right = vol.getVolume( Volume::RIGHT ); int refvol = left > right ? left : right; if( m_balance < 0 ) // balance left { vol.setVolume( Volume::LEFT, refvol); vol.setVolume( Volume::RIGHT, (m_balance * refvol) / 100 + refvol ); } else { vol.setVolume( Volume::LEFT, -(m_balance * refvol) / 100 + refvol ); vol.setVolume( Volume::RIGHT, refvol); } } /** * Returns a name suitable for a human user to read (on a label, ...) */ QString Mixer::readableName() { return readableName(false); } /** * Returns a name suitable for a human user to read, possibly with quoted ampersand. The latter is required by * some GUI elements like QRadioButton or when used as a Tab label, as '&' introduces an accelerator there. * * @param ampersandQuoted * @return */ QString Mixer::readableName(bool ampersandQuoted) { QString finalName = _mixerBackend->getName(); if (ampersandQuoted) finalName.replace('&', "&&"); if ( getCardInstance() > 1) finalName = finalName.append(" %1").arg(getCardInstance()); // qCDebug(KMIX_LOG) << "name=" << _mixerBackend->getName() << "instance=" << getCardInstance() << ", finalName" << finalName; return finalName; } QString Mixer::getBaseName() { return _mixerBackend->getName(); } /** * Queries the Driver Factory for a driver. * @par driver Index number. 0 <= driver < numDrivers() */ QString Mixer::driverName( int driver ) { getDriverNameFunc *f = g_mixerFactories[driver].getDriverName; if( f!=0 ) return f(); else return "unknown"; } /* obsoleted by setInstance() void Mixer::setID(QString& ref_id) { _id = ref_id; } */ QString& Mixer::id() { return _id; } QString& Mixer::udi(){ return _mixerBackend->udi(); } /** * Set the global master, which is shown in the dock area and which is accessible via the * DBUS masterVolume() method. * * The parameters are taken over as-is, this means without checking for validity. * This allows the User to define a master card that is not always available * (e.g. it is an USB hotplugging device). Also you can set the master at any time you * like, e.g. after reading the KMix configuration file and before actually constructing * the Mixer instances (hint: this method is static!). * * @param ref_card The card id * @param ref_control The control id. The corresponding control must be present in the card. * @param preferred Whether this is the preferred master (auto-selected on coldplug and hotplug). */ void Mixer::setGlobalMaster(QString ref_card, QString ref_control, bool preferred) { qCDebug(KMIX_LOG) << "ref_card=" << ref_card << ", ref_control=" << ref_control << ", preferred=" << preferred; _globalMasterCurrent.set(ref_card, ref_control); if ( preferred ) _globalMasterPreferred.set(ref_card, ref_control); qCDebug(KMIX_LOG) << "Mixer::setGlobalMaster() card=" <id(); return mixer; } /** * Return the preferred global master. * If there is no preferred global master, returns the current master instead. */ MasterControl& Mixer::getGlobalMasterPreferred(bool fallbackAllowed) { static MasterControl result; if ( !fallbackAllowed || _globalMasterPreferred.isValid() ) { // qCDebug(KMIX_LOG) << "Returning preferred master"; return _globalMasterPreferred; } Mixer* mm = Mixer::getGlobalMasterMixerNoFalback(); if (mm) { result.set(_globalMasterPreferred.getCard(), mm->getRecommendedDeviceId()); if (!result.getControl().isEmpty()) // qCDebug(KMIX_LOG) << "Returning extended preferred master"; return result; } qCDebug(KMIX_LOG) << "Returning current master"; return _globalMasterCurrent; } shared_ptr Mixer::getGlobalMasterMD() { return getGlobalMasterMD(true); } shared_ptr Mixer::getGlobalMasterMD(bool fallbackAllowed) { shared_ptr mdRet; shared_ptr firstDevice; Mixer *mixer = fallbackAllowed ? Mixer::getGlobalMasterMixer() : Mixer::getGlobalMasterMixerNoFalback(); if ( mixer == 0 ) return mdRet; if (_globalMasterCurrent.getControl().isEmpty()) { // Default (recommended) control return mixer->_mixerBackend->recommendedMaster(); } foreach (shared_ptr md, mixer->_mixerBackend->m_mixDevices ) { if ( md.get() == 0 ) continue; // invalid firstDevice=md; if ( md->id() == _globalMasterCurrent.getControl() ) { mdRet = md; break; // found } } if ( mdRet.get() == 0 ) { //For some sound cards when using pulseaudio the mixer id is not proper hence returning the first device as master channel device //This solves the bug id:290177 and problems stated in review #105422 qCDebug(KMIX_LOG) << "Mixer::masterCardDevice() returns 0 (no globalMaster), returning the first device"; mdRet=firstDevice; } return mdRet; } QString Mixer::getRecommendedDeviceId() { if ( _mixerBackend != 0 ) { shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); if ( recommendedMaster.get() != 0 ) return recommendedMaster->id(); } return QString(); } shared_ptr Mixer::getLocalMasterMD() { if (_mixerBackend && _masterDevicePK.isEmpty()) return _mixerBackend->recommendedMaster(); return find( _masterDevicePK ); } void Mixer::setLocalMasterMD(QString &devPK) { _masterDevicePK = devPK; } shared_ptr Mixer::find(const QString& mixdeviceID) { shared_ptr mdRet; foreach (shared_ptr md, _mixerBackend->m_mixDevices ) { if ( md.get() == 0 ) continue; // invalid if ( md->id() == mixdeviceID ) { mdRet = md; break; // found } } return mdRet; } shared_ptr Mixer::getMixdeviceById( const QString& mixdeviceID ) { qCDebug(KMIX_LOG) << "id=" << mixdeviceID << "md=" << _mixerBackend->m_mixDevices.get(mixdeviceID).get()->id(); return _mixerBackend->m_mixDevices.get(mixdeviceID); // shared_ptr md; // int num = _mixerBackend->id2num(mixdeviceID); // if ( num!=-1 && num < (int)size() ) // { // md = (*this)[num]; // } // return md; } /** Call this if you have a *reference* to a Volume object and have modified that locally. Pass the MixDevice associated to that Volume to this method for writing back the changed value to the mixer. Hint: Why do we do it this way? - It is fast (no copying of Volume objects required) - It is easy to understand ( read - modify - commit ) */ void Mixer::commitVolumeChange(shared_ptr md) { _mixerBackend->writeVolumeToHW(md->id(), md); if (md->isEnum()) { _mixerBackend->setEnumIdHW(md->id(), md->enumId()); } if (md->captureVolume().hasSwitch()) { // Make sure to re-read the hardware, because setting capture might have failed. // This is due to exclusive capture groups. // If we wouldn't do this, KMix might show a Capture Switch disabled, but // in reality the capture switch is still on. // // We also cannot rely on a notification from the driver (SocketNotifier), because // nothing has changed, and so there s nothing to notify. _mixerBackend->readSetFromHWforceUpdate(); if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "committing a control with capture volume, that might announce: " << md->id(); _mixerBackend->readSetFromHW(); } if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "committing announces the change of: " << md->id(); // We announce the change we did, so all other parts of KMix can pick up the change - ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::Volume, + ControlManager::instance().announce(md->mixer()->id(), ControlManager::Volume, QString("Mixer.commitVolumeChange()")); } // @dbus, used also in kmix app void Mixer::increaseVolume( const QString& mixdeviceID ) { increaseOrDecreaseVolume(mixdeviceID, false); } // @dbus void Mixer::decreaseVolume( const QString& mixdeviceID ) { increaseOrDecreaseVolume(mixdeviceID, true); } /** * Increase or decrease all playback and capture channels of the given control. * This method is very similar to MDWSlider::increaseOrDecreaseVolume(), but it will * NOT auto-unmute. * * @param mixdeviceID The control name * @param decrease true for decrease. false for increase */ void Mixer::increaseOrDecreaseVolume( const QString& mixdeviceID, bool decrease ) { shared_ptr md= getMixdeviceById( mixdeviceID ); if (md.get() != 0) { Volume& volP=md->playbackVolume(); if ( volP.hasVolume() ) { volP.changeAllVolumes(volP.volumeStep(decrease)); } Volume& volC=md->captureVolume(); if ( volC.hasVolume() ) { volC.changeAllVolumes(volC.volumeStep(decrease)); } _mixerBackend->writeVolumeToHW(mixdeviceID, md); } - ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::Volume, QString("Mixer.increaseOrDecreaseVolume()")); + ControlManager::instance().announce(md->mixer()->id(), ControlManager::Volume, QString("Mixer.increaseOrDecreaseVolume()")); /************************************************************ It is important, not to implement this method like this: int vol=volume(mixdeviceID); setVolume(mixdeviceID, vol-5); It creates too big rounding errors. If you don't believe me, then do a decreaseVolume() and increaseVolume() with "vol.maxVolume() == 31". ***********************************************************/ } void Mixer::setDynamic ( bool dynamic ) { m_dynamic = dynamic; } bool Mixer::isDynamic() { return m_dynamic; } bool Mixer::moveStream( const QString id, const QString& destId ) { // We should really check that id is within our md's.... bool ret = _mixerBackend->moveStream( id, destId ); - ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Mixer.moveStream()")); + ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("Mixer.moveStream()")); return ret; } diff --git a/dbus/dbusmixerwrapper.cpp b/dbus/dbusmixerwrapper.cpp index e26d805f..4b814309 100644 --- a/dbus/dbusmixerwrapper.cpp +++ b/dbus/dbusmixerwrapper.cpp @@ -1,146 +1,144 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 1996-2004 Christian Esken * Copyright 2011 Igor Poboiko * * 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 "dbusmixerwrapper.h" #include #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/volume.h" #include "kmix_debug.h" #include "dbus/dbusmixsetwrapper.h" #include "mixeradaptor.h" DBusMixerWrapper::DBusMixerWrapper(Mixer* parent, const QString& path) : QObject(parent) , m_dbusPath(path) { m_mixer = parent; new MixerAdaptor( this ); qCDebug(KMIX_LOG) << "Create QDBusConnection for object " << path; QDBusConnection::sessionBus().registerObject( path, this ); ControlManager::instance().addListener( m_mixer->id(), - // TODO: convert ControlChangeType to a QFlags - static_cast(ControlChangeType::ControlList|ControlChangeType::Volume), + ControlManager::ControlList|ControlManager::Volume, this, QString("DBusMixerWrapper.%1").arg(m_mixer->id()) ); if (DBusMixSetWrapper::instance()) DBusMixSetWrapper::instance()->signalMixersChanged(); } DBusMixerWrapper::~DBusMixerWrapper() { ControlManager::instance().removeListener(this); qCDebug(KMIX_LOG) << "Remove QDBusConnection for object " << m_dbusPath; if (DBusMixSetWrapper::instance()) DBusMixSetWrapper::instance()->signalMixersChanged(); } -void DBusMixerWrapper::controlsChange(int changeType) +void DBusMixerWrapper::controlsChange(ControlManager::ChangeType changeType) { - ControlChangeType::Type type = ControlChangeType::fromInt(changeType); - switch (type ) + switch (changeType) { - case ControlChangeType::ControlList: + case ControlManager::ControlList: createDeviceWidgets(); break; - case ControlChangeType::Volume: + case ControlManager::Volume: refreshVolumeLevels(); break; default: - ControlManager::warnUnexpectedChangeType(type, this); + ControlManager::warnUnexpectedChangeType(changeType, this); break; } } QString DBusMixerWrapper::driverName() { return m_mixer->getDriverName(); } QStringList DBusMixerWrapper::controls() { QStringList result; foreach ( shared_ptr md, m_mixer->getMixSet() ) { result.append( md->dbusPath() ); } return result; } QString DBusMixerWrapper::masterControl() { shared_ptr md = m_mixer->getLocalMasterMD(); // XXX: Since empty object path is invalid, using "/" return md ? md->dbusPath() : QString("/"); } bool DBusMixerWrapper::isOpened() { return m_mixer->isOpen(); } int DBusMixerWrapper::balance() { return m_mixer->balance(); } void DBusMixerWrapper::setBalance(int balance) { m_mixer->setBalance(balance); } QString DBusMixerWrapper::readableName() { return m_mixer->readableName(); } QString DBusMixerWrapper::id() { return m_mixer->id(); } QString DBusMixerWrapper::udi() { return m_mixer->udi(); } void DBusMixerWrapper::refreshVolumeLevels() { QDBusMessage signal = QDBusMessage::createSignal( m_dbusPath, "org.kde.KMix.Mixer", "controlChanged" ); QDBusConnection::sessionBus().send( signal ); } void DBusMixerWrapper::createDeviceWidgets() { QDBusMessage signal = QDBusMessage::createSignal( m_dbusPath, "org.kde.KMix.Mixer", "changed" ); QDBusConnection::sessionBus().send( signal ); } diff --git a/dbus/dbusmixerwrapper.h b/dbus/dbusmixerwrapper.h index f6a0384c..69bebd13 100644 --- a/dbus/dbusmixerwrapper.h +++ b/dbus/dbusmixerwrapper.h @@ -1,66 +1,67 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 1996-2004 Christian Esken * Copyright 2011 Igor Poboiko * * 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 DBUSMIXERWRAPPER_H #define DBUSMIXERWRAPPER_H #include #include #include "core/mixer.h" +#include "core/ControlManager.h" class DBusMixerWrapper : public QObject { Q_OBJECT Q_PROPERTY(QString driverName READ driverName) Q_PROPERTY(QString masterControl READ masterControl) Q_PROPERTY(QString readableName READ readableName) Q_PROPERTY(bool opened READ isOpened) Q_PROPERTY(QString id READ id) Q_PROPERTY(QString udi READ udi) Q_PROPERTY(int balance READ balance WRITE setBalance) Q_PROPERTY(QStringList controls READ controls) public: DBusMixerWrapper(Mixer* parent, const QString& path); ~DBusMixerWrapper(); QString driverName(); QStringList controls(); QString masterControl(); bool isOpened(); QString readableName(); QString id(); QString udi(); int balance(); void setBalance(int balance); public slots: - void controlsChange(int changeType); + void controlsChange(ControlManager::ChangeType changeType); private: void createDeviceWidgets(); void refreshVolumeLevels(); Mixer *m_mixer; QString m_dbusPath; }; #endif /* DBUSMIXERWRAPPER_H */ diff --git a/dbus/dbusmixsetwrapper.cpp b/dbus/dbusmixsetwrapper.cpp index 21b7e360..1ef3804b 100644 --- a/dbus/dbusmixsetwrapper.cpp +++ b/dbus/dbusmixsetwrapper.cpp @@ -1,119 +1,118 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2011 Igor Poboiko * * 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 "dbusmixsetwrapper.h" #include "core/mixdevice.h" #include "core/ControlManager.h" #include "mixsetadaptor.h" static DBusMixSetWrapper *instanceSingleton = nullptr; void DBusMixSetWrapper::initialize(QObject* parent, const QString& path) { Q_ASSERT(instanceSingleton==nullptr); instanceSingleton = new DBusMixSetWrapper(parent, path); } DBusMixSetWrapper* DBusMixSetWrapper::instance() { return instanceSingleton; } DBusMixSetWrapper::DBusMixSetWrapper(QObject* parent, const QString& path) : QObject(parent) , m_dbusPath( path ) { new MixSetAdaptor( this ); QDBusConnection::sessionBus().registerObject( m_dbusPath, this ); ControlManager::instance().addListener( QString(), - ControlChangeType::MasterChanged, + ControlManager::MasterChanged, this, QString("DBusMixSetWrapper")); } -void DBusMixSetWrapper::controlsChange(int changeType) +void DBusMixSetWrapper::controlsChange(ControlManager::ChangeType changeType) { - ControlChangeType::Type type = ControlChangeType::fromInt(changeType); - switch (type) + switch (changeType) { - case ControlChangeType::MasterChanged: + case ControlManager::MasterChanged: signalMasterChanged(); break; default: - ControlManager::warnUnexpectedChangeType(type, this); + ControlManager::warnUnexpectedChangeType(changeType, this); } } QStringList DBusMixSetWrapper::mixers() const { QStringList result; Q_FOREACH(Mixer* mixer, Mixer::mixers()) result.append( mixer->dbusPath() ); return result; } QString DBusMixSetWrapper::currentMasterMixer() const { Mixer* masterMixer = Mixer::getGlobalMasterMixer(); return masterMixer ? masterMixer->id() : QString(); } QString DBusMixSetWrapper::currentMasterControl() const { shared_ptr masterControl = Mixer::getGlobalMasterMD(); return masterControl ? masterControl->id() : QString(); } QString DBusMixSetWrapper::preferredMasterMixer() const { return Mixer::getGlobalMasterPreferred().getCard(); } QString DBusMixSetWrapper::preferredMasterControl() const { return Mixer::getGlobalMasterPreferred().getControl(); } void DBusMixSetWrapper::setCurrentMaster(const QString &mixer, const QString &control) { Mixer::setGlobalMaster(mixer, control, false); } void DBusMixSetWrapper::setPreferredMaster(const QString &mixer, const QString &control) { Mixer::setGlobalMaster(mixer, control, true); } void DBusMixSetWrapper::signalMixersChanged() { QDBusMessage signal = QDBusMessage::createSignal( m_dbusPath, "org.kde.KMix.MixSet", "mixersChanged" ); QDBusConnection::sessionBus().send( signal ); } void DBusMixSetWrapper::signalMasterChanged() { QDBusMessage signal = QDBusMessage::createSignal( m_dbusPath, "org.kde.KMix.MixSet", "masterChanged" ); QDBusConnection::sessionBus().send( signal ); } diff --git a/dbus/dbusmixsetwrapper.h b/dbus/dbusmixsetwrapper.h index bf61f37c..f33dca66 100644 --- a/dbus/dbusmixsetwrapper.h +++ b/dbus/dbusmixsetwrapper.h @@ -1,62 +1,63 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2011 Igor Poboiko * * 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 DBUSMIXSETWRAPPER_H #define DBUSMIXSETWRAPPER_H #include #include "core/mixer.h" +#include "core/ControlManager.h" #include "kmixcore_export.h" class KMIXCORE_EXPORT DBusMixSetWrapper : public QObject { Q_OBJECT Q_PROPERTY(QStringList mixers READ mixers) Q_PROPERTY(QString currentMasterMixer READ currentMasterMixer) Q_PROPERTY(QString currentMasterControl READ currentMasterControl) Q_PROPERTY(QString preferredMasterMixer READ preferredMasterMixer) Q_PROPERTY(QString preferredMasterControl READ preferredMasterControl) public: static void initialize(QObject* parent, const QString& path); static DBusMixSetWrapper* instance(); void signalMixersChanged(); void signalMasterChanged(); public slots: QStringList mixers() const; QString currentMasterMixer() const; QString currentMasterControl() const; QString preferredMasterMixer() const; QString preferredMasterControl() const; void setCurrentMaster(const QString &mixer, const QString &control); void setPreferredMaster(const QString &mixer, const QString &control); - void controlsChange(int changeType); + void controlsChange(ControlManager::ChangeType changeType); private: DBusMixSetWrapper(QObject* parent, const QString& path); virtual ~DBusMixSetWrapper() = default; QString m_dbusPath; }; #endif /* DBUSMIXSETWRAPPER_H */ diff --git a/gui/dialogselectmaster.cpp b/gui/dialogselectmaster.cpp index 606efe14..57f7877c 100644 --- a/gui/dialogselectmaster.cpp +++ b/gui/dialogselectmaster.cpp @@ -1,229 +1,229 @@ /* * 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 #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" DialogSelectMaster::DialogSelectMaster( 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 } /** * Create basic widgets of the Dialog. */ void DialogSelectMaster::createWidgets(Mixer *ptr_mixer) { QWidget *mainFrame = new QWidget(this); setMainWidget(mainFrame); QVBoxLayout *layout = new QVBoxLayout(mainFrame); if ( Mixer::mixers().count() > 1 ) { // More than one Mixer => show Combo-Box to select Mixer // Mixer widget line QHBoxLayout* mixerNameLayout = new QHBoxLayout(); layout->addLayout( mixerNameLayout ); mixerNameLayout->setMargin(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; iaddItem( 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 ); 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 ); layout->addWidget(qlbl); createPage(ptr_mixer); connect(this, SIGNAL(accepted()), this, SLOT(apply())); } else { QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); layout->addWidget(qlbl); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's 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); } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPage(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); 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(); } int msetCount = 0; for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) ++msetCount; } if (msetCount > 0 && !mixer->isDynamic()) { QString mdName = i18n("Automatic (%1 recommendation)", mixer->getDriverName()); QPixmap icon = KIconLoader::global()->loadIcon("audio-volume-high", KIconLoader::Small, IconSize(KIconLoader::Small)); QListWidgetItem *item = new QListWidgetItem(icon, 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) for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) { QString mdName = md->readableName(); QPixmap icon = KIconLoader::global()->loadIcon(md->iconName(), KIconLoader::Small, IconSize(KIconLoader::Small)); QListWidgetItem *item = new QListWidgetItem(icon, 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); } } } } void DialogSelectMaster::apply() { Mixer *mixer = 0; if ( Mixer::mixers().count() == 1 ) { // only one mxier => no combo box => take first entry mixer = (Mixer::mixers())[0]; } 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); } if ( mixer == 0 ) 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(), ControlChangeType::MasterChanged, QString("Select Master Dialog")); + ControlManager::instance().announce(mixer->id(), ControlManager::MasterChanged, QString("Select Master Dialog")); } } diff --git a/gui/dialogviewconfiguration.cpp b/gui/dialogviewconfiguration.cpp index 1992cd24..72563d99 100644 --- a/gui/dialogviewconfiguration.cpp +++ b/gui/dialogviewconfiguration.cpp @@ -1,457 +1,457 @@ /* * 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 "dialogviewconfiguration.h" #include #include #include #include #include #include #include #include "gui/guiprofile.h" #include "gui/mixdevicewidget.h" #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent) : QListWidgetItem(parent) { qCDebug(KMIX_LOG) << "DialogViewConfigurationItem() default constructor"; refreshItem(); } DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName) : QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName) { refreshItem(); } void DialogViewConfigurationItem::refreshItem() { setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled); setText(_name); setIcon(KIconLoader::global()->loadIcon( _iconName, KIconLoader::Small, IconSize(KIconLoader::Toolbar) )); setData(Qt::ToolTipRole, _id); // a hack. I am giving up to do it right setData(Qt::DisplayRole, _name); } /** * Serializer. Used for DnD. */ static QDataStream & operator<< ( QDataStream & s, const DialogViewConfigurationItem & item ) { s << item._id; s << item._shown; s << item._name; s << item._splitted; s << item._iconName; //qCDebug(KMIX_LOG) << "<< serialize << " << s; return s; } /** * Deserializer. Used for DnD. */ static QDataStream & operator>> ( QDataStream & s, DialogViewConfigurationItem & item ) { QString id; s >> id; item._id = id; bool shown; s >> shown; item._shown = shown; QString name; s >> name; item._name = name; int splitted; s >> splitted; item._splitted = splitted; QString iconName; s >> iconName; item._iconName = iconName; //qCDebug(KMIX_LOG) << ">> deserialize >> " << id << name << iconName; return s; } DialogViewConfigurationWidget::DialogViewConfigurationWidget(QWidget *parent) : QListWidget(parent), m_activeList(true) { setDragDropMode(QAbstractItemView::DragDrop); setDropIndicatorShown(true); setAcceptDrops(true); setSelectionMode(QAbstractItemView::SingleSelection); setDragEnabled(true); viewport()->setAcceptDrops(true); setAlternatingRowColors(true); } QMimeData* DialogViewConfigurationWidget::mimeData(const QList items) const { if (items.isEmpty()) return 0; QMimeData* mimedata = new QMimeData(); DialogViewConfigurationItem* item = 0; QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); // we only support single selection item = static_cast(items.first()); stream << *item; } bool active = isActiveList(); mimedata->setData("application/x-kde-action-list", data); mimedata->setData("application/x-kde-source-treewidget", active ? "active" : "inactive"); return mimedata; } bool DialogViewConfigurationWidget::dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction /*action*/) { const QByteArray data = mimeData->data("application/x-kde-action-list"); if (data.isEmpty()) return false; QDataStream stream(data); const bool sourceIsActiveList = mimeData->data("application/x-kde-source-treewidget") == "active"; DialogViewConfigurationItem* item = new DialogViewConfigurationItem(0); // needs parent, use this temporarily stream >> *item; item->refreshItem(); emit dropped(this, index, item, sourceIsActiveList); return true; } DialogViewConfiguration::DialogViewConfiguration(QWidget *parent, ViewBase &view) : DialogBase(parent), _view(view) { setWindowTitle( i18n( "Configure Channels" ) ); setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); QWidget *frame = new QWidget( this ); frame->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); setMainWidget( frame ); // The _layout will hold two items: The title and the Drag-n-Drop area QVBoxLayout *layout = new QVBoxLayout(frame); // --- HEADER --- QLabel *qlb = new QLabel( i18n("Configure the visible channels. Drag icons between the lists to update."), frame ); layout->addWidget(qlb); _glayout = new QGridLayout(); _glayout->setMargin(0); layout->addLayout(_glayout); _qlw = 0; _qlwInactive = 0; createPage(); } /** * Drop an item from one list to the other */ void DialogViewConfiguration::slotDropped ( DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ) { //qCDebug(KMIX_LOG) << "dropped item (index" << index << "): " << item->_id << item->_shown << item->_name << item->_splitted << item->_iconName; if ( list == _qlw ) { //DialogViewConfigurationItem* after = index > 0 ? static_cast(list->item(index-1)) : 0; //qCDebug(KMIX_LOG) << "after" << after->text() << after->internalTag(); if ( sourceIsActiveList ) { // has been dragged within the active list (moved). _qlw->insertItem ( index, item ); //moveActive(item, after); } else { // dragged from the inactive list to the active list _qlw->insertItem ( index, item ); //insertActive(item, after, true); } } else if ( list == _qlwInactive ) { // has been dragged to the inactive list -> remove from the active list. //removeActive(item); _qlwInactive->insertItem ( index, item ); } } void DialogViewConfiguration::addSpacer(int row, int col) { QWidget *dummy = new QWidget(); dummy->setFixedWidth(4); _glayout->addWidget(dummy,row,col); } void DialogViewConfiguration::moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to) { foreach ( QListWidgetItem* item, from->selectedItems() ) { QListWidgetItem *clonedItem = item->clone(); to->addItem ( clonedItem ); to->setCurrentItem(clonedItem); delete item; } } void DialogViewConfiguration::moveSelectionToActiveList() { moveSelection(_qlwInactive, _qlw); } void DialogViewConfiguration::moveSelectionToInactiveList() { moveSelection(_qlw, _qlwInactive); } void DialogViewConfiguration::selectionChangedActive() { // bool activeIsNotEmpty = _qlw->selectedItems().isEmpty(); moveRightButton->setEnabled(! _qlw->selectedItems().isEmpty()); moveLeftButton->setEnabled(false); } void DialogViewConfiguration::selectionChangedInactive() { moveLeftButton->setEnabled(! _qlwInactive->selectedItems().isEmpty()); moveRightButton->setEnabled(false); } /** * Create basic widgets of the Dialog. */ void DialogViewConfiguration::createPage() { QList &mdws = _view._mdws; QLabel *l1 = new QLabel( i18n("Visible channels:") ); _glayout->addWidget(l1,0,0); QLabel *l2 = new QLabel( i18n("Available channels:") ); _glayout->addWidget(l2,0,6); QWidget *frame = mainWidget(); _qlwInactive = new DialogViewConfigurationWidget(frame); _qlwInactive->setDragDropMode(QAbstractItemView::DragDrop); _qlwInactive->setActiveList(false); _glayout->addWidget(_qlwInactive,1,6); connect(_qlwInactive, SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); addSpacer(1,1); const QIcon& icon = QIcon::fromTheme( QLatin1String( "arrow-left" )); moveLeftButton = new QPushButton(icon, ""); moveLeftButton->setEnabled(false); moveLeftButton->setToolTip(i18n("Move the selected channel to the visible list")); _glayout->addWidget(moveLeftButton,1,2); connect(moveLeftButton, SIGNAL(clicked(bool)), SLOT(moveSelectionToActiveList())); addSpacer(1,3); const QIcon& icon2 = QIcon::fromTheme( QLatin1String( "arrow-right" )); moveRightButton = new QPushButton(icon2, ""); moveRightButton->setEnabled(false); moveRightButton->setToolTip(i18n("Move the selected channel to the available (hidden) list")); _glayout->addWidget(moveRightButton,1,4); connect(moveRightButton, SIGNAL(clicked(bool)), SLOT(moveSelectionToInactiveList())); addSpacer(1,5); _qlw = new DialogViewConfigurationWidget(frame); _glayout->addWidget(_qlw,1,0); connect(_qlw , SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); // --- CONTROLS IN THE GRID ------------------------------------ //QPalette::ColorRole bgRole; for ( int i=0; iinherits("MixDeviceWidget") ) { MixDeviceWidget *mdw = static_cast(qw); shared_ptr md = mdw->mixDevice(); QString mdName = md->readableName(); int splitted = -1; if ( ! md->isEnum() ) { splitted = ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1 ) ; } //qCDebug(KMIX_LOG) << "add DialogViewConfigurationItem: " << mdName << " visible=" << mdw->isVisible() << "splitted=" << splitted; if ( mdw->isVisible() ) { new DialogViewConfigurationItem(_qlw, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } else { new DialogViewConfigurationItem(_qlwInactive, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } /* if ( ! md->isEnum() && ( ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1) ) ) { cb = new QCheckBox( "", vboxForScrollView ); // split cb->setBackgroundRole(bgRole); cb->setAutoFillBackground(true); _qSplitCB.append(cb); cb->setChecked( ! mdw->isStereoLinked() ); grid->addWidget(cb,1+i,1); } else { _qSplitCB.append(0); } */ /* if ( ! md->isEnum() && ( md->playbackVolume().count() + md->captureVolume().count() >0 ) ) { cb = new QCheckBox( "", vboxForScrollView ); // limit cb->setBackgroundRole(bgRole); cb->setAutoFillBackground(true); _qLimitCB.append(cb); grid->addWidget(cb,1+i,2); } else { */ //_qLimitCB.append(0); /*}*/ } // is not enum } // for all MDW's connect(_qlwInactive, SIGNAL(itemSelectionChanged()), this , SLOT(selectionChangedInactive())); connect(_qlw, SIGNAL(itemSelectionChanged()), this , SLOT(selectionChangedActive())); // scrollArea->updateGeometry(); updateGeometry(); connect(this, SIGNAL(accepted()), this, SLOT(apply())); #ifndef QT_NO_ACCESSIBILITY moveLeftButton->setAccessibleName( i18n("Show the selected channel") ); moveRightButton->setAccessibleName( i18n("Hide the selected channel") ); _qlw->setAccessibleName( i18n("Visible channels") ); _qlwInactive->setAccessibleName( i18n("Available channels") ); #endif } DialogViewConfiguration::~DialogViewConfiguration() { } void DialogViewConfiguration::apply() { // --- We have a 3-Step Apply of the Changes ------------------------------- // -1- Update view and profile ***************************************** GUIProfile* prof = _view.guiProfile(); GUIProfile::ControlSet& oldControlset = prof->getControls(); GUIProfile::ControlSet newControlset; QAbstractItemModel* model; model = _qlw->model(); prepareControls(model, true, oldControlset, newControlset); model = _qlwInactive->model(); prepareControls(model, false, oldControlset, newControlset); // -2- Copy all mandatory "catch-all" controls form the old to the new ControlSet ******* foreach ( ProfControl* pctl, oldControlset) { if ( pctl->isMandatory() ) { ProfControl* newCtl = new ProfControl(*pctl); // The user has selected controls => mandatory controls (RegExp templates) should not been shown any longer newCtl->setVisible(GuiVisibility::GuiNEVER); newControlset.push_back(newCtl); } } prof->setControls(newControlset); prof->finalizeProfile(); prof->setDirty(); // --- Step 3: Tell the view, that it has changed (probably it needs some "polishing" --- if ( _view.getMixers().size() == 1 ) - ControlManager::instance().announce(_view.getMixers().first()->id(), ControlChangeType::ControlList, QString("View Configuration Dialog")); + ControlManager::instance().announce(_view.getMixers().first()->id(), ControlManager::ControlList, QString("View Configuration Dialog")); else - ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("View Configuration Dialog")); + ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("View Configuration Dialog")); } void DialogViewConfiguration::prepareControls(QAbstractItemModel* model, bool isActiveView, GUIProfile::ControlSet& oldCtlSet, GUIProfile::ControlSet& newCtlSet) { int numRows = model->rowCount(); for (int row = 0; row < numRows; ++row) { // -1- Extract the value from the model *************************** QModelIndex index = model->index(row, 0); QVariant vdci; vdci = model->data(index, Qt::ToolTipRole); // TooltipRole stores the ID (well, thats not really clean design, but it works) QString ctlId = vdci.toString(); // -2- Find the mdw, und update it ************************** foreach ( QWidget *qw, _view._mdws ) { MixDeviceWidget *mdw = dynamic_cast(qw); if ( !mdw ) { continue; } if ( mdw->mixDevice()->id() == ctlId ) { mdw->setVisible(isActiveView); break; } // mdw was found } // find mdw // -3- Insert it in the new ControlSet ************************** // qCDebug(KMIX_LOG) << "Should add to new ControlSet: " << ctlId; foreach ( ProfControl* control, oldCtlSet) { //qCDebug(KMIX_LOG) << " checking " << control->id; QRegExp idRegexp(control->id); if ( ctlId.contains(idRegexp) ) { // found. Create a copy ProfControl* newCtl = new ProfControl(*control); newCtl->id = '^' + ctlId + '$'; // Replace the (possible generic) regexp by the actual ID // We have made this an an actual control. As it is derived (from e.g. ".*") it is NOT mandatory. newCtl->setMandatory(false); newCtl->setVisible(isActiveView); newCtlSet.push_back(newCtl); // qCDebug(KMIX_LOG) << "Added to new ControlSet (done): " << newCtl->id; break; } } } } diff --git a/gui/kmixdockwidget.cpp b/gui/kmixdockwidget.cpp index 7250da8d..b9c5b606 100644 --- a/gui/kmixdockwidget.cpp +++ b/gui/kmixdockwidget.cpp @@ -1,437 +1,436 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * Copyright (C) 2001 Preston Brown * Copyright (C) 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/kmixdockwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include "apps/kmix.h" #include "core/ControlManager.h" #include "core/mixer.h" #include "core/mixertoolbox.h" #include "gui/dialogselectmaster.h" #include "gui/mixdevicewidget.h" #include "gui/viewdockareapopup.h" //#define FEATURE_UNITY_POPUP true KMixDockWidget::KMixDockWidget(KMixWindow* parent) : KStatusNotifierItem(parent) , _oldToolTipValue(-1) , _oldPixmapType('-') , _kmixMainWindow(parent) , _delta(0) { setToolTipIconByName("kmix"); setTitle(i18n( "Volume Control")); setCategory(Hardware); setStatus(Active); // TODO Unity / Gnome only support one type of activation (left-click == right-click) // So we should show here the ViewDockAreaPopup instead of the menu: //bool onlyOneMouseButtonAction = onlyHaveOneMouseButtonAction(); createMenuActions(); connect(this, SIGNAL(scrollRequested(int,Qt::Orientation)), this, SLOT(trayWheelEvent(int,Qt::Orientation))); connect(this, SIGNAL(secondaryActivateRequested(QPoint)), this, SLOT(dockMute())); // For bizarre reasons, we wrap the ViewDockAreaPopup in a QMenu. Must relate to how KStatusNotifierItem works. _dockAreaPopupMenuWrapper = new QMenu(parent); _volWA = new QWidgetAction(_dockAreaPopupMenuWrapper); _dockView = new ViewDockAreaPopup(_dockAreaPopupMenuWrapper, "dockArea", 0, QString("no-guiprofile-yet-in-dock"), parent); _volWA->setDefaultWidget(_dockView); _dockAreaPopupMenuWrapper->addAction(_volWA); connect(contextMenu(), SIGNAL(aboutToShow()), this, SLOT(contextMenuAboutToShow())); ControlManager::instance().addListener( QString(), // All mixers (as the Global master Mixer might change) - static_cast(ControlChangeType::Volume|ControlChangeType::MasterChanged), this, + ControlManager::Volume|ControlManager::MasterChanged, this, QString("KMixDockWidget")); // Refresh in all cases. When there is no Global Master we still need // to initialize correctly (e.g. for showing 0% or hiding it) refreshVolumeLevels(); } KMixDockWidget::~KMixDockWidget() { ControlManager::instance().removeListener(this); // Note: deleting _volWA also deletes its associated ViewDockAreaPopup (_referenceWidget) and prevents the // action to be left with a dangling pointer. // cesken: I adapted the patch from https://bugs.kde.org/show_bug.cgi?id=220621#c27 to branch /branches/work/kmix delete _volWA; } -void KMixDockWidget::controlsChange(int changeType) +void KMixDockWidget::controlsChange(ControlManager::ChangeType changeType) { - ControlChangeType::Type type = ControlChangeType::fromInt(changeType); - switch (type ) + switch (changeType) { - case ControlChangeType::MasterChanged: + case ControlManager::MasterChanged: // Notify the main window, as it might need to update the visibiliy of the dock icon. // _kmixMainWindow->updateDocking(); // _kmixMainWindow->saveConfig(); refreshVolumeLevels(); { QAction *selectMasterAction = findAction("select_master"); if(selectMasterAction) { // Review #120432 : Guard findAction("select_master"), as it is sometimes 0 on the KF5 build // This is probably not a final solution, but better than a crash. selectMasterAction->setEnabled(Mixer::getGlobalMasterMixer() != 0); } else { qCWarning(KMIX_LOG) << "select_master action not found. Cannot enable it in the Systray."; } } break; - case ControlChangeType::Volume: + case ControlManager::Volume: refreshVolumeLevels(); break; default: - ControlManager::warnUnexpectedChangeType(type, this); + ControlManager::warnUnexpectedChangeType(changeType, this); } } QAction* KMixDockWidget::findAction(const char* actionName) { QList actions = actionCollection(); int size = actions.size(); for (int i=0; idata().toString() == QString::fromUtf8(actionName)) return action; } qCWarning(KMIX_LOG) << "ACTION" << actionName << "NOT FOUND!"; return Q_NULLPTR; } /** * Updates all visual parts of the volume, namely tooltip and pixmap */ void KMixDockWidget::refreshVolumeLevels() { setVolumeTip(); updatePixmap(); } /** * Creates the right-click menu */ void KMixDockWidget::createMenuActions() { QMenu *menu = contextMenu(); if (!menu) return; // We do not use a menu shared_ptr md = Mixer::getGlobalMasterMD(); if ( md.get() != 0 && md->hasMuteSwitch() ) { // Put "Mute" selector in context menu KToggleAction *action = new KToggleAction(i18n("M&ute"), this); action->setData("dock_mute"); addAction("dock_mute", action); updateDockMuteAction(action); connect(action, SIGNAL(triggered(bool)), SLOT(dockMute())); menu->addAction( action ); } // Put "Select Master Channel" dialog in context menu QAction *action = _kmixMainWindow->actionCollection()->action("select_master"); action->setEnabled(Mixer::getGlobalMasterMixer() != 0); menu->addAction(action); // Context menu entry to access Phonon settings menu->addAction(_kmixMainWindow->actionCollection()->action("launch_kdesoundsetup")); // Context menu entry to access KMix settings // action name from tier3/kconfigwidgets/src/kstandardaction_p.h menu->addAction(_kmixMainWindow->actionCollection()->action("options_configure")); } void KMixDockWidget::setVolumeTip() { shared_ptr md = Mixer::getGlobalMasterMD(); QString tip; QString subTip; int virtualToolTipValue = 0; if ( md.get() == 0 ) { tip = i18n("No mixer devices available"); virtualToolTipValue = -2; } else { // Playback volume will be used for the DockIcon if available. // This heuristic is "good enough" for the DockIcon for now. int val = md->getUserfriendlyVolumeLevel(); if (md->isMuted()) tip = i18n("Volume muted"); else tip = i18n("Volume at %1%", val); subTip = i18n("%1 - %2", md->mixer()->readableName().toHtmlEscaped(), md->readableName().toHtmlEscaped()); // create a new "virtual" value. With that we see "volume changes" as well as "muted changes" virtualToolTipValue = val; if ( md->isMuted() ) virtualToolTipValue += 10000; } // The actual updating is only done when the "toolTipValue" was changed (to avoid flicker) if ( virtualToolTipValue != _oldToolTipValue ) { // changed (or completely new tooltip) setToolTipTitle(tip); setToolTipSubTitle(subTip); } _oldToolTipValue = virtualToolTipValue; } void KMixDockWidget::updatePixmap() { shared_ptr md = Mixer::getGlobalMasterMD(); char newPixmapType; if ( !md ) { // no such control => error newPixmapType = 'e'; } else { int percentage = md->getUserfriendlyVolumeLevel(); if ( percentage <= 0 ) newPixmapType = '0'; // Hint: also muted, and also negative-values else if ( percentage < 25 ) newPixmapType = '1'; else if ( percentage < 75 ) newPixmapType = '2'; else newPixmapType = '3'; } if ( newPixmapType != _oldPixmapType ) { // Pixmap must be changed => do so switch ( newPixmapType ) { case 'e': setIconByName( "kmixdocked_error" ); break; case 'm': case '0': setIconByName( "audio-volume-muted" ); break; case '1': setIconByName( "audio-volume-low" ); break; case '2': setIconByName( "audio-volume-medium" ); break; case '3': setIconByName( "audio-volume-high" ); break; } } _oldPixmapType = newPixmapType; } /** * Called whenever the icon gets "activated". Usually when its clicked. * @overload * @param pos */ void KMixDockWidget::activate(const QPoint &pos) { QWidget* dockAreaPopup = _dockAreaPopupMenuWrapper; // TODO Refactor to use _dockAreaPopupMenuWrapper directly if (dockAreaPopup->isVisible()) { dockAreaPopup->hide(); return; } _dockAreaPopupMenuWrapper->removeAction(_volWA); delete _volWA; _volWA = new QWidgetAction(_dockAreaPopupMenuWrapper); _dockView = new ViewDockAreaPopup(_dockAreaPopupMenuWrapper, "dockArea", 0, QString("no-guiprofile-yet-in-dock"), _kmixMainWindow); _volWA->setDefaultWidget(_dockView); _dockAreaPopupMenuWrapper->addAction(_volWA); //_dockView->show(); // TODO cesken check: this should be automatic // Showing, to hopefully get the geometry manager started. We need width and height below. Also // vdesktop->availableGeometry(dockAreaPopup) needs to know on which screen the widget will be shown. // dockAreaPopup->show(); _dockView->adjustSize(); dockAreaPopup->adjustSize(); int x = pos.x() - dockAreaPopup->width() / 2; if (x < 0) x = pos.x(); int y = pos.y() - dockAreaPopup->height() / 2; if (y < 0) y = pos.y(); // Now handle Multihead displays. And also make sure that the dialog is not // moved out-of-the screen on the right (see Bug 101742). const QDesktopWidget* vdesktop = QApplication::desktop(); int screenNumber = vdesktop->screenNumber(pos); const QRect& vScreenSize = vdesktop->availableGeometry(screenNumber); if ((x + dockAreaPopup->width()) > (vScreenSize.width() + vScreenSize.x())) { // move horizontally, so that it is completely visible x = vScreenSize.width() + vScreenSize.x() - dockAreaPopup->width() - 1; qCDebug(KMIX_LOG) << "Multihead: (case 1) moving to" << x << "," << y; } else if (x < vScreenSize.x()) { // horizontally out-of bound x = vScreenSize.x(); qCDebug(KMIX_LOG) << "Multihead: (case 2) moving to" << x << "," << y; } if ((y + dockAreaPopup->height()) > (vScreenSize.height() + vScreenSize.y())) { // move horizontally, so that it is completely visible y = vScreenSize.height() + vScreenSize.y() - dockAreaPopup->height() - 1; qCDebug(KMIX_LOG) << "Multihead: (case 3) moving to" << x << "," << y; } else if (y < vScreenSize.y()) { // horizontally out-of bound y = vScreenSize.y(); qCDebug(KMIX_LOG) << "Multihead: (case 4) moving to" << x << "," << y; } KWindowSystem::setType(dockAreaPopup->winId(), NET::Dock); KWindowSystem::setState(dockAreaPopup->winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::SkipPager); dockAreaPopup->show(); dockAreaPopup->move(x, y); } void KMixDockWidget::trayWheelEvent(int delta,Qt::Orientation wheelOrientation) { shared_ptr md = Mixer::getGlobalMasterMD(); if ( md.get() == 0 ) return; Volume &vol = ( md->playbackVolume().hasVolume() ) ? md->playbackVolume() : md->captureVolume(); // qCDebug(KMIX_LOG) << "I am seeing a wheel event with delta=" << delta << " and orientation=" << wheelOrientation; if (wheelOrientation == Qt::Horizontal) // Reverse horizontal scroll: bko228780 { delta = -delta; } // bko313579, bko341536, Review #121725 - Use delta and round it by 120. _delta += delta; bool decrease = delta < 0; unsigned long inc = 0; while (_delta >= 120) { _delta -= 120; inc++; } while (_delta <= -120) { _delta += 120; inc++; } if (inc == 0) { return; } long cv = vol.volumeStep(decrease) * inc; bool isInactive = vol.isCapture() ? !md->isRecSource() : md->isMuted(); // qCDebug(KMIX_LOG) << "Operating on capture=" << vol.isCapture() << ", isInactive=" << isInactive; if ( cv > 0 && isInactive) { // increasing from muted state: unmute and start with a low volume level if ( vol.isCapture()) md->setRecSource(true); else md->setMuted(false); vol.setAllVolumes(cv); } else vol.changeAllVolumes(cv); md->mixer()->commitVolumeChange(md); refreshVolumeLevels(); } void KMixDockWidget::dockMute() { shared_ptr md = Mixer::getGlobalMasterMD(); if ( md ) { md->toggleMute(); md->mixer()->commitVolumeChange( md ); refreshVolumeLevels(); } } /** * Returns whether the running Desktop only supports one Mouse Button * Hint: Unity / Gnome only support one type of activation (left-click == right-click). */ bool KMixDockWidget::onlyHaveOneMouseButtonAction() { QDBusConnection connection = QDBusConnection::sessionBus(); bool unityIsRunning = (connection.interface()->isServiceRegistered("com.canonical.Unity.Panel.Service")); // Possibly implement other detectors, like for Gnome 3 or Gnome 2 return unityIsRunning; } void KMixDockWidget::contextMenuAboutToShow() { // Enable/Disable "Muted" menu item KToggleAction *dockMuteAction = static_cast(findAction("dock_mute")); qCDebug(KMIX_LOG) << "DOCK MUTE" << dockMuteAction; if (dockMuteAction) updateDockMuteAction(dockMuteAction); } void KMixDockWidget::updateDockMuteAction ( KToggleAction* dockMuteAction ) { shared_ptr md = Mixer::getGlobalMasterMD(); if ( md && dockMuteAction != 0 ) { Volume& vol = md->playbackVolume().hasVolume() ? md->playbackVolume() : md->captureVolume(); bool isInactive = vol.isCapture() ? !md->isRecSource() : md->isMuted(); bool hasSwitch = vol.isCapture() ? vol.hasSwitch() : md->hasMuteSwitch(); dockMuteAction->setEnabled( hasSwitch ); dockMuteAction->setChecked( isInactive ); } } diff --git a/gui/kmixdockwidget.h b/gui/kmixdockwidget.h index 5f4b27c0..5898de25 100644 --- a/gui/kmixdockwidget.h +++ b/gui/kmixdockwidget.h @@ -1,84 +1,85 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * Copyright (C) 2003 Sven Leiber * * 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 KMIXDOCKWIDGET_H #define KMIXDOCKWIDGET_H class QAction; class QString; class QWidgetAction; class KToggleAction; #include class QMenu; class KMixWindow; class Mixer; #include "core/mixdevice.h" +#include "core/ControlManager.h" class ViewDockAreaPopup; class Volume; class KMixDockWidget : public KStatusNotifierItem { Q_OBJECT friend class KMixWindow; public: explicit KMixDockWidget(KMixWindow *parent); virtual ~KMixDockWidget(); void setErrorPixmap(); void ignoreNextEvent(); void update(); public slots: void setVolumeTip(); void updatePixmap(); void activate(const QPoint &pos) Q_DECL_OVERRIDE; - void controlsChange(int changeType); + void controlsChange(ControlManager::ChangeType changeType); protected: void createMenuActions(); void toggleMinimizeRestore(); private: ViewDockAreaPopup *_dockView; QMenu *_dockAreaPopupMenuWrapper; QWidgetAction *_volWA; int _oldToolTipValue; char _oldPixmapType; KMixWindow* _kmixMainWindow; int _delta; bool onlyHaveOneMouseButtonAction(); void refreshVolumeLevels(); void updateDockMuteAction ( KToggleAction* dockMuteAction ); QAction* findAction(const char* actionName); private slots: void dockMute(); void trayWheelEvent(int delta,Qt::Orientation); void contextMenuAboutToShow(); }; #endif diff --git a/gui/kmixprefdlg.cpp b/gui/kmixprefdlg.cpp index 68fa4d58..0be95b96 100644 --- a/gui/kmixprefdlg.cpp +++ b/gui/kmixprefdlg.cpp @@ -1,463 +1,463 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * Copyright (C) 2001 Preston Brown * * 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/kmixprefdlg.h" #include #include #include #include #include #include #include #include #include "dialogbase.h" #include "dialogstatesaver.h" #include "gui/kmixerwidget.h" #include "core/GlobalConfig.h" KMixPrefDlg* KMixPrefDlg::instance = 0; KMixPrefDlg* KMixPrefDlg::getInstance() { return instance; } KMixPrefDlg* KMixPrefDlg::createInstance(QWidget *parent, GlobalConfig& config) { if (instance == 0) { instance = new KMixPrefDlg(parent, config); } return instance; } KMixPrefDlg::KMixPrefDlg(QWidget *parent, GlobalConfig& config) : KConfigDialog(parent, i18n("Configure"), &config), dialogConfig(config) { setFaceType(KPageDialog::List); //setCaption(i18n("Configure")); dvc = 0; dvcSpacerBelow = 0; // general buttons m_generalTab = new QFrame(this); m_controlsTab = new QFrame(this); m_startupTab = new QFrame(this); createStartupTab(); createGeneralTab(); createControlsTab(); updateWidgets(); // I thought KConfigDialog would call this, but I saw during a gdb session that it does not do so. generalPage = addPage(m_generalTab, i18n("General"), "configure"); startupPage = addPage(m_startupTab, i18n("Startup"), "preferences-system-login"); soundmenuPage = addPage(m_controlsTab, i18n("Volume Control"), "audio-volume-high"); new DialogStateSaver(this); } /** * Switches to a specific page and shows it. * @param page */ void KMixPrefDlg::switchToPage(KMixPrefPage page) { switch (page) { case PrefGeneral: setCurrentPage(generalPage); break; case PrefSoundMenu: setCurrentPage(soundmenuPage); break; case PrefStartup: setCurrentPage(startupPage); break; default: qCWarning(KMIX_LOG) << "Tried to activated unknown preferences page" << page; break; } show(); } // --- TABS -------------------------------------------------------------------------------------------------- void KMixPrefDlg::createStartupTab() { layoutStartupTab = new QVBoxLayout(m_startupTab); layoutStartupTab->setMargin(0); layoutStartupTab->setSpacing(DialogBase::verticalSpacing()); allowAutostart = new QCheckBox(i18n("Start KMix on desktop startup"), m_startupTab); addWidgetToLayout(allowAutostart, layoutStartupTab, 10, i18n("Start KMix automatically when the desktop starts."), "AutoStart"); allowAutostartWarning = new KMessageWidget( i18n("Autostart will not work, because the autostart file kmix_autostart.desktop is missing. Check that KMix is installed correctly."), m_startupTab); allowAutostartWarning->setIcon(QIcon::fromTheme("dialog-error")); allowAutostartWarning->setMessageType(KMessageWidget::Error); allowAutostartWarning->setCloseButtonVisible(false); allowAutostartWarning->setWordWrap(true); allowAutostartWarning->setVisible(false); addWidgetToLayout(allowAutostartWarning, layoutStartupTab, 2, "", ""); layoutStartupTab->addItem(new QSpacerItem(1, 2*DialogBase::verticalSpacing())); m_onLogin = new QCheckBox(i18n("Restore previous volume settings on desktop startup"), m_startupTab); addWidgetToLayout(m_onLogin, layoutStartupTab, 10, i18n("Restore all mixer volume levels and switches to their last used settings when the desktop starts."), "startkdeRestore"); dynamicControlsRestoreWarning = new KMessageWidget( i18n("The volume of PulseAudio or MPRIS2 dynamic controls will not be restored."), m_startupTab); dynamicControlsRestoreWarning->setIcon(QIcon::fromTheme("dialog-warning")); dynamicControlsRestoreWarning->setMessageType(KMessageWidget::Warning); dynamicControlsRestoreWarning->setCloseButtonVisible(false); dynamicControlsRestoreWarning->setWordWrap(true); dynamicControlsRestoreWarning->setVisible(false); addWidgetToLayout(dynamicControlsRestoreWarning, layoutStartupTab, 2, "", ""); layoutStartupTab->addStretch(); } void KMixPrefDlg::createOrientationGroup(const QString& labelSliderOrientation, QGridLayout* orientationLayout, int row, KMixPrefDlgPrefOrientationType prefType) { QButtonGroup* orientationGroup = new QButtonGroup(m_generalTab); orientationGroup->setExclusive(true); QLabel* qlb = new QLabel(labelSliderOrientation, m_generalTab); QRadioButton* qrbHor = new QRadioButton(i18n("&Horizontal"), m_generalTab); QRadioButton* qrbVert = new QRadioButton(i18n("&Vertical"), m_generalTab); if (prefType == TrayOrientation) { _rbTraypopupHorizontal = qrbHor; _rbTraypopupVertical = qrbVert; orientationGroup->setObjectName("Orientation.TrayPopup"); } else { _rbHorizontal = qrbHor; _rbVertical = qrbVert; orientationGroup->setObjectName("Orientation"); } // Add both buttons to button group //qrbHor->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); orientationGroup->addButton(qrbHor); orientationGroup->addButton(qrbVert); // Add both buttons and label to layout orientationLayout->addWidget(qlb, row, 0, Qt::AlignLeft); orientationLayout->addWidget(qrbHor, row, 1, Qt::AlignLeft); orientationLayout->addWidget(qrbVert, row, 2, Qt::AlignLeft); orientationLayout->addItem(new QSpacerItem(1,1,QSizePolicy::Expanding), row, 3); connect(qrbHor, SIGNAL(toggled(bool)), SLOT(updateButtons())); connect(qrbVert, SIGNAL(toggled(bool)), SLOT(updateButtons())); connect(button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(kmixConfigHasChangedEmitter())); connect(button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(kmixConfigHasChangedEmitter())); // connect(qrbHor, SIGNAL(toggled(bool)), SLOT(settingsChangedSlot())); // connect(qrbVert, SIGNAL(toggled(bool)), SLOT(settingsChangedSlot())); } void KMixPrefDlg::createGeneralTab() { QBoxLayout* layout = new QVBoxLayout(m_generalTab); layout->setMargin(0); layout->setSpacing(DialogBase::verticalSpacing()); // --- Behavior --------------------------------------------------------- QGroupBox *grp = new QGroupBox(i18n("Behavior"), m_generalTab); grp->setFlat(true); layout->addWidget(grp); // [CONFIG] m_beepOnVolumeChange = new QCheckBox(i18n("Volume feedback"), m_generalTab); addWidgetToLayout(m_beepOnVolumeChange, layout, 10, "", "VolumeFeedback"); m_volumeOverdrive = new QCheckBox(i18n("Volume overdrive"), m_generalTab); addWidgetToLayout(m_volumeOverdrive, layout, 10, i18nc("@info:tooltip", "Raise the maximum volume to 150%"), "VolumeOverdrive"); volumeFeedbackWarning = new KMessageWidget( i18n("Volume feedback and volume overdrive are only available for PulseAudio."), m_generalTab); volumeFeedbackWarning->setIcon(QIcon::fromTheme("dialog-warning")); volumeFeedbackWarning->setMessageType(KMessageWidget::Warning); volumeFeedbackWarning->setCloseButtonVisible(false); volumeFeedbackWarning->setWordWrap(true); volumeFeedbackWarning->setVisible(false); addWidgetToLayout(volumeFeedbackWarning, layout, 2, "", ""); volumeOverdriveWarning = new KMessageWidget( i18n("KMix must be restarted for the Volume Overdrive setting to take effect."), m_generalTab); volumeOverdriveWarning->setIcon(QIcon::fromTheme("dialog-information")); volumeOverdriveWarning->setMessageType(KMessageWidget::Information); volumeOverdriveWarning->setCloseButtonVisible(false); volumeOverdriveWarning->setWordWrap(true); volumeOverdriveWarning->setVisible(false); addWidgetToLayout(volumeOverdriveWarning, layout, 2, "", ""); // --- Visual --------------------------------------------------------- layout->addItem(new QSpacerItem(1, DialogBase::verticalSpacing())); grp = new QGroupBox(i18n("Visual"), m_generalTab); grp->setFlat(true); layout->addWidget(grp); // [CONFIG] m_showTicks = new QCheckBox(i18n("Show &tickmarks"), m_generalTab); addWidgetToLayout(m_showTicks, layout, 10, i18n("Enable/disable tickmark scales on the sliders"), "Tickmarks"); m_showLabels = new QCheckBox(i18n("Show &labels"), m_generalTab); addWidgetToLayout(m_showLabels, layout, 10, i18n("Enables/disables description labels above the sliders"), "Labels"); // [CONFIG] m_showOSD = new QCheckBox(i18n("Show On Screen Display (&OSD)"), m_generalTab); addWidgetToLayout(m_showOSD, layout, 10, "", "showOSD"); // [CONFIG] Slider orientation (main window) layout->addItem(new QSpacerItem(1, DialogBase::verticalSpacing())); QGridLayout* orientationGrid = new QGridLayout(); orientationGrid->setHorizontalSpacing(DialogBase::horizontalSpacing()); layout->addItem(orientationGrid); // Slider orientation (main window, and tray popup separately). createOrientationGroup(i18n("Slider orientation (main window): "), orientationGrid, 0, KMixPrefDlg::MainOrientation); createOrientationGroup(i18n("Slider orientation (system tray popup):"), orientationGrid, 1, KMixPrefDlg::TrayOrientation); // Push everything above to the top layout->addStretch(); } void KMixPrefDlg::createControlsTab() { layoutControlsTab = new QVBoxLayout(m_controlsTab); layoutControlsTab->setMargin(0); layoutControlsTab->setSpacing(DialogBase::verticalSpacing()); m_dockingChk = new QCheckBox(i18n("Dock in system tray"), m_controlsTab); addWidgetToLayout(m_dockingChk, layoutControlsTab, 10, i18n("Dock the mixer into the system tray. Click on it to open the popup volume control."), "AllowDocking"); layoutControlsTab->addItem(new QSpacerItem(1, 2*DialogBase::verticalSpacing())); replaceBackendsInTab(); } // --- Helper -------------------------------------------------------------------------------------------------- /** * Register widget with correct name for KConfigDialog, then add it to the given layout * * @param widget * @param layout * @param spacingBefore * @param toopTipText * @param objectName */ void KMixPrefDlg::addWidgetToLayout(QWidget* widget, QBoxLayout* layout, int spacingBefore, QString tooltip, QString kconfigName) { if (!kconfigName.isEmpty()) { // Widget to be registered for KConfig widget->setObjectName("kcfg_" + kconfigName); } if ( !tooltip.isEmpty() ) { widget->setToolTip(tooltip); } QBoxLayout *l = new QHBoxLayout(); l->addSpacing(spacingBefore); l->addWidget(widget); layout->addItem(l); } // --- KConfigDialog CUSTOM WIDGET management ------------------------------------------------------------------------ /** * Update Widgets from config. *

* Hint: this get internally called by KConfigdialog on initialization and reset. */ void KMixPrefDlg::updateWidgets() { if (dialogConfig.data.debugConfig) qCDebug(KMIX_LOG) << ""; bool toplevelHorizontal = dialogConfig.data.getToplevelOrientation() == Qt::Horizontal; _rbHorizontal->setChecked(toplevelHorizontal); _rbVertical->setChecked(!toplevelHorizontal); bool trayHorizontal = dialogConfig.data.getTraypopupOrientation() == Qt::Horizontal; _rbTraypopupHorizontal->setChecked(trayHorizontal); _rbTraypopupVertical->setChecked(!trayHorizontal); } /** * Updates config from the widgets. And emits the signal kmixConfigHasChanged(). *

* Hint: this get internally called by KConfigDialog after pressing the OK or Apply button. */ void KMixPrefDlg::updateSettings() { Qt::Orientation toplevelOrientation = _rbHorizontal->isChecked() ? Qt::Horizontal : Qt::Vertical; if (dialogConfig.data.debugConfig) qCDebug(KMIX_LOG) << "toplevelOrientation" << toplevelOrientation << ", _rbHorizontal->isChecked()" << _rbHorizontal->isChecked(); dialogConfig.data.setToplevelOrientation(toplevelOrientation); Qt::Orientation trayOrientation = _rbTraypopupHorizontal->isChecked() ? Qt::Horizontal : Qt::Vertical; if (dialogConfig.data.debugConfig) qCDebug(KMIX_LOG) << "trayOrientation" << trayOrientation << ", _rbTraypopupHorizontal->isChecked()" << _rbTraypopupHorizontal->isChecked(); dialogConfig.data.setTraypopupOrientation(trayOrientation); // Announcing MasterChanged, as the sound menu (aka ViewDockAreaPopup) primarily shows master volume(s). // In any case, ViewDockAreaPopup treats MasterChanged and ControlList the same, so it is better to announce // the "smaller" change. bool modified = dvc->getAndResetModifyFlag(); if (modified) { GlobalConfig::instance().setMixersForSoundmenu(dvc->getChosenBackends()); - ControlManager::instance().announce(QString(), ControlChangeType::MasterChanged, QString("Select Backends Dialog")); + ControlManager::instance().announce(QString(), ControlManager::MasterChanged, QString("Select Backends Dialog")); } } void KMixPrefDlg::kmixConfigHasChangedEmitter() { emit(kmixConfigHasChanged()); } /** * Returns whether the custom widgets (orientation checkboxes) has changed. *

* Hint: this get internally called by KConfigDialog from updateButtons(). * @return */ bool KMixPrefDlg::hasChanged() { bool orientationFromConfigIsHor = dialogConfig.data.getToplevelOrientation() == Qt::Horizontal; bool orientationFromWidgetIsHor = _rbHorizontal->isChecked(); if (dialogConfig.data.debugConfig) qCDebug(KMIX_LOG) << "Orientation MAIN fromConfig=" << (orientationFromConfigIsHor ? "Hor" : "Vert") << ", fromWidget=" << (orientationFromWidgetIsHor ? "Hor" : "Vert"); bool changed = orientationFromConfigIsHor ^ orientationFromWidgetIsHor; if (!changed) { bool orientationFromConfigIsHor = dialogConfig.data.getTraypopupOrientation() == Qt::Horizontal; orientationFromWidgetIsHor = _rbTraypopupHorizontal->isChecked(); if (dialogConfig.data.debugConfig) qCDebug(KMIX_LOG) << "Orientation TRAY fromConfig=" << (orientationFromConfigIsHor ? "Hor" : "Vert") << ", fromWidget=" << (orientationFromWidgetIsHor ? "Hor" : "Vert"); changed = orientationFromConfigIsHor ^ orientationFromWidgetIsHor; } if (!changed) { changed = dvc->getModifyFlag(); } if (dialogConfig.data.debugConfig) qCDebug(KMIX_LOG) << "hasChanged=" << changed; return changed; } void KMixPrefDlg::showEvent(QShowEvent * event) { // -1- Replace widgets ------------------------------------------------------------ // Hotplug can change mixers or backends => recreate tab replaceBackendsInTab(); KConfigDialog::showEvent(event); // -2- Change visibility and enable status (of the new widgets) ---------------------- // As GUI can change, the warning will only been shown on demand dynamicControlsRestoreWarning->setVisible(Mixer::dynamicBackendsPresent()); QString autostartConfigFilename = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "/autostart/kmix_autostart.desktop"); if (dialogConfig.data.debugConfig) qCDebug(KMIX_LOG) << "autostartConfigFilename = " << autostartConfigFilename; bool autostartFileExists = !autostartConfigFilename.isEmpty(); allowAutostartWarning->setVisible(!autostartFileExists); allowAutostart->setEnabled(autostartFileExists); // Only PulseAudio supports volume feedback and volume overdrive. // Disable those configuration options for other backends, and // show a warning message. const bool pulseAudioAvailable = Mixer::pulseaudioPresent(); if (!pulseAudioAvailable) { m_beepOnVolumeChange->setChecked(false); m_beepOnVolumeChange->setEnabled(false); m_volumeOverdrive->setChecked(false); m_volumeOverdrive->setEnabled(false); volumeFeedbackWarning->setVisible(true); volumeOverdriveWarning->setVisible(false); } else { volumeFeedbackWarning->setVisible(false); volumeOverdriveWarning->setVisible(true); } } void KMixPrefDlg::replaceBackendsInTab() { if (dvc != 0) { layoutControlsTab->removeWidget(dvc); delete dvc; layoutControlsTab->removeItem(dvcSpacerBelow); delete dvcSpacerBelow; } QSet backendsFromConfig = GlobalConfig::instance().getMixersForSoundmenu(); dvc = new DialogChooseBackends(0, backendsFromConfig); connect(dvc, SIGNAL(backendsModified()), SLOT(updateButtons())); layoutControlsTab->addWidget(dvc); dvc->show(); // Push everything above to the top // layoutControlsTab->addStretch(); dvcSpacerBelow = new QSpacerItem(1,1); layoutControlsTab->addItem(dvcSpacerBelow); } diff --git a/gui/osdwidget.cpp b/gui/osdwidget.cpp index 0a0089de..5f328c06 100644 --- a/gui/osdwidget.cpp +++ b/gui/osdwidget.cpp @@ -1,218 +1,217 @@ /******************************************************************* * osdwidget.cpp * Copyright 2009 Aurélien Gâteau * Copyright 2009 Dario Andres Rodriguez * Copyright 2009 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "gui/osdwidget.h" // Qt #include #include #include #include #include // KDE #include #include #include #include #include #include #include "core/ControlManager.h" #include OSDWidget::OSDWidget(QWidget * parent) : Plasma::Dialog(parent, Qt::ToolTip), m_scene(new QGraphicsScene(this)), m_container(new QGraphicsWidget), m_iconLabel(new Plasma::Label), m_volumeLabel(new Plasma::Label), m_meter(new Plasma::Meter), m_hideTimer(new QTimer(this)) { //Setup the window properties KWindowSystem::setState(winId(), NET::KeepAbove); KWindowSystem::setType(winId(), NET::Tooltip); setAttribute(Qt::WA_X11NetWmWindowTypeToolTip, true); m_meter->setMeterType(Plasma::Meter::BarMeterHorizontal); m_meter->setMaximum(100); //Set a fixed width for the volume label. To do that we need the text with the maximum width //(this is true if the volume is at 100%). We simply achieve that by calling "setCurrentVolume". setCurrentVolume(100, false); /* We are registering for volume changes of all cards. An alternative * would be to register to volume changes of the global master and additionally * register to MasterChanges. That could be slightly more efficient */ ControlManager::instance().addListener( QString(), // all mixers - ControlChangeType::Volume, + ControlManager::Volume, this, QString("OSDWidget") ); //Setup the auto-hide timer m_hideTimer->setInterval(2000); m_hideTimer->setSingleShot(true); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hide())); //Setup the OSD layout QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(m_container); layout->setContentsMargins(0, 0, 0, 0); layout->addItem(m_iconLabel); layout->addItem(m_meter); layout->addItem(m_volumeLabel); m_scene->addItem(m_container); setGraphicsWidget(m_container); themeUpdated(); connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeUpdated())); // e.g. for updating font } OSDWidget::~OSDWidget() { ControlManager::instance().removeListener(this); } -void OSDWidget::controlsChange(int changeType) +void OSDWidget::controlsChange(ControlManager::ChangeType changeType) { - ControlChangeType::Type type = ControlChangeType::fromInt(changeType); shared_ptr master = Mixer::getGlobalMasterMD(); - switch (type ) + switch (changeType) { - case ControlChangeType::Volume: + case ControlManager::Volume: if ( master ) { setCurrentVolume(master->playbackVolume().getAvgVolumePercent(Volume::MALL), master->isMuted()); } break; default: - ControlManager::warnUnexpectedChangeType(type, this); + ControlManager::warnUnexpectedChangeType(changeType, this); break; } } void OSDWidget::activateOSD() { m_hideTimer->start(); } void OSDWidget::themeUpdated() { //Set a font which makes the text appear as big (height-wise) as the meter. //QFont font = QFont(m_volumeLabel->nativeWidget()->font()); Plasma::Theme* theme = Plasma::Theme::defaultTheme(); QPalette palette = m_volumeLabel->palette(); palette.setColor(QPalette::WindowText, theme->color(Plasma::Theme::TextColor)); m_volumeLabel->setPalette(palette); QFont font = theme->font(Plasma::Theme::DefaultFont); font.setPointSize(15); m_volumeLabel->setFont(font); QFontMetrics qfm(font); QRect textSize = qfm.boundingRect("100 % "); int widthHint = textSize.width(); int heightHint = textSize.height(); //setCurrentVolume(100,false); m_volumeLabel->setMinimumWidth(widthHint); m_volumeLabel->setMaximumHeight(heightHint); m_volumeLabel->nativeWidget()->setFixedWidth(widthHint); // m_volumeLabel->setText(oldText); //Cache the icon pixmaps QSize iconSize; if (!Plasma::Theme::defaultTheme()->imagePath("icons/audio").isEmpty()) { QFontMetrics fm(m_volumeLabel->font()); iconSize = QSize(fm.height(), fm.height()); Plasma::Svg svgIcon; svgIcon.setImagePath("icons/audio"); svgIcon.setContainsMultipleImages(true); svgIcon.resize(iconSize); m_volumeHighPixmap = svgIcon.pixmap("audio-volume-high"); m_volumeMediumPixmap = svgIcon.pixmap("audio-volume-medium"); m_volumeLowPixmap = svgIcon.pixmap("audio-volume-low"); m_volumeMutedPixmap = svgIcon.pixmap("audio-volume-muted"); } else { iconSize = QSize(IconSize(KIconLoader::Toolbar), IconSize(KIconLoader::Toolbar)); m_volumeHighPixmap = QIcon::fromTheme( QLatin1String( "audio-volume-high" )).pixmap(iconSize); m_volumeMediumPixmap = QIcon::fromTheme( QLatin1String( "audio-volume-medium" )).pixmap(iconSize); m_volumeLowPixmap = QIcon::fromTheme( QLatin1String( "audio-volume-low" )).pixmap(iconSize); m_volumeMutedPixmap = QIcon::fromTheme( QLatin1String( "audio-volume-muted" )).pixmap(iconSize); } m_iconLabel->nativeWidget()->setPixmap(m_volumeHighPixmap); m_iconLabel->nativeWidget()->setFixedSize(iconSize); m_iconLabel->setMinimumSize(iconSize); m_iconLabel->setMaximumSize(iconSize); m_meter->setMaximumHeight(iconSize.height()); m_volumeLabel->setMinimumHeight(iconSize.height()); m_volumeLabel->setMaximumHeight(iconSize.height()); m_volumeLabel->nativeWidget()->setFixedHeight(iconSize.height()); m_volumeLabel->setAlignment(Qt::AlignCenter); m_volumeLabel->setWordWrap(false); m_container->setMinimumSize(iconSize.width() * 13 + m_volumeLabel->nativeWidget()->width(), iconSize.height()); m_container->setMaximumSize(iconSize.width() * 13 + m_volumeLabel->nativeWidget()->width(), iconSize.height()); syncToGraphicsWidget(); } /** * Set volume level in percent */ void OSDWidget::setCurrentVolume(int volumeLevel, bool muted) { // qCDebug(KMIX_LOG) << "Meter is visible: " << m_meter->isVisible(); if ( muted ) { volumeLevel = 0; } m_meter->setValue(volumeLevel); if (!muted && (volumeLevel > 0)) { if (volumeLevel < 25) { m_iconLabel->nativeWidget()->setPixmap(m_volumeLowPixmap); } else if (volumeLevel < 75) { m_iconLabel->nativeWidget()->setPixmap(m_volumeMediumPixmap); } else { m_iconLabel->nativeWidget()->setPixmap(m_volumeHighPixmap); } } else { m_iconLabel->nativeWidget()->setPixmap(m_volumeMutedPixmap); } //Show the volume % m_volumeLabel->setText(QString::number(volumeLevel) + " %"); // if you change the text, please adjust textSize in themeUpdated() } diff --git a/gui/osdwidget.h b/gui/osdwidget.h index 989a8876..9d5c66ce 100644 --- a/gui/osdwidget.h +++ b/gui/osdwidget.h @@ -1,68 +1,68 @@ /******************************************************************* * osdwidget.h * Copyright 2009 Aurélien Gâteau * Copyright 2009 Dario Andres Rodriguez * Copyright 2009 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #ifndef OSDWIDGET__H #define OSDWIDGET__H #include #include class QTimer; class QGraphicsWidget; namespace Plasma { class Label; class Meter; } class OSDWidget : public Plasma::Dialog { Q_OBJECT public: explicit OSDWidget(QWidget * parent = 0); virtual ~OSDWidget(); void setCurrentVolume(int volumeLevel, bool muted); void activateOSD(); public slots: - void controlsChange(int changeType); + void controlsChange(ControlManager::ChangeType changeType); private: QGraphicsScene *m_scene; QGraphicsWidget *m_container; Plasma::Label *m_iconLabel; Plasma::Label *m_volumeLabel; Plasma::Meter *m_meter; QTimer *m_hideTimer; QPixmap m_volumeHighPixmap; QPixmap m_volumeMediumPixmap; QPixmap m_volumeLowPixmap; QPixmap m_volumeMutedPixmap; private slots: void themeUpdated(); }; #endif diff --git a/gui/viewbase.cpp b/gui/viewbase.cpp index 35c4fd88..6b819ed8 100644 --- a/gui/viewbase.cpp +++ b/gui/viewbase.cpp @@ -1,499 +1,499 @@ /* * 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 "viewbase.h" // Qt #include #include #include // KDE #include #include #include #include #include #include // KMix #include "dialogviewconfiguration.h" #include "gui/guiprofile.h" #include "gui/kmixtoolbox.h" #include "gui/mixdevicewidget.h" #include "gui/mdwslider.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/mixer.h" #include "core/mixertoolbox.h" /** * Creates an empty View. To populate it with MixDevice instances, you must implement * _setMixSet() in your derived class. */ ViewBase::ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actionColletion) : QWidget(parent, f), _popMenu(NULL), _actions(actionColletion), _vflags(vflags), _guiProfileId(guiProfileId) , guiLevel(GuiVisibility::GuiSIMPLE) { setObjectName(id); // When loading the View from the XML profile, guiLevel can get overridden m_viewId = id; configureIcon = QIcon::fromTheme( QLatin1String( "configure" )); if ( _actions == 0 ) { // We create our own action collection, if the actionColletion was 0. // This is currently done for the ViewDockAreaPopup, but only because it has not been converted to use the app-wide // actionCollection(). This is a @todo. _actions = new KActionCollection( this ); } _localActionColletion = new KActionCollection( this ); // Plug in the "showMenubar" action, if the caller wants it. Typically this is only necessary for views in the KMix main window. if ( vflags & ViewBase::HasMenuBar ) { KToggleAction *m = static_cast( _actions->action( name(KStandardAction::ShowMenubar) ) ) ; if ( m != 0 ) { bool visible = ( vflags & ViewBase::MenuBarVisible ); m->setChecked(visible); } } } ViewBase::~ViewBase() { // Hint: The GUI profile will not be removed, as it is pooled and might be applied to a new View. } void ViewBase::addMixer(Mixer *mixer) { _mixers.append(mixer); } //void ViewBase::configurationUpdate() { //} QPushButton* ViewBase::createConfigureViewButton() { QPushButton* configureViewButton = new QPushButton(configureIcon, "", this); configureViewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); configureViewButton->setToolTip(i18n( "Configure this view" )); connect(configureViewButton, SIGNAL(clicked(bool)), SLOT(configureView())); return configureViewButton; } void ViewBase::updateGuiOptions() { setTicks(GlobalConfig::instance().data.showTicks); setLabels(GlobalConfig::instance().data.showLabels); updateMediaPlaybackIcons(); } QString ViewBase::id() const { return m_viewId; } bool ViewBase::isValid() const { return ( !_mixSet.isEmpty() || isDynamic() ); } void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } void ViewBase::setLabels(bool on) { KMixToolBox::setLabels(_mdws, on ); } void ViewBase::setTicks (bool on) { KMixToolBox::setTicks (_mdws, on ); } /** * Updates all playback icons to their (new) state, e.g. Paused, or Playing */ void ViewBase::updateMediaPlaybackIcons() { for (int i = 0; i < _mdws.count(); ++i) { // Currently media controls are always attached to sliders => use MDWSlider MDWSlider* mdw = qobject_cast(_mdws[i]); if (mdw != 0) { mdw->updateMediaButton(); } } } /** * Create all widgets. * This is a loop over all supported devices of the corresponding view. * On each device add() is called - the derived class must implement add() for creating and placing * the real MixDeviceWidget. * The added MixDeviceWidget is appended to the _mdws list. */ void ViewBase::createDeviceWidgets() { _setMixSet(); foreach ( shared_ptr md, _mixSet ) { QWidget* mdw = add(md); // a) Let the View implementation do its work _mdws.append(mdw); // b) Add it to the local list connect(mdw, SIGNAL(guiVisibilityChange(MixDeviceWidget*,bool)), SLOT(guiVisibilitySlot(MixDeviceWidget*,bool))); } if ( !isDynamic() ) { QAction *action = _localActionColletion->addAction("toggle_channels"); action->setText(i18n("&Channels")); connect(action, SIGNAL(triggered(bool)), SLOT(configureView())); } // allow view to "polish" itself constructionFinished(); } /** * Called when a specific control is to be shown or hidden. At the moment it is only called via * the "hide" action in the MDW context menu. * * @param mdw * @param enable */ void ViewBase::guiVisibilitySlot(MixDeviceWidget* mdw, bool enable) { MixDevice* md = mdw->mixDevice().get(); qCDebug(KMIX_LOG) << "Change " << md->id() << " to visible=" << enable; ProfControl* pctl = findMdw(md->id()); if (pctl == 0) { qCWarning(KMIX_LOG) << "MixDevice not found, and cannot be hidden, id=" << md->id(); return; // Ignore } pctl->setVisible(enable); - ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::ControlList, QString("ViewBase::guiVisibilitySlot")); + ControlManager::instance().announce(md->mixer()->id(), ControlManager::ControlList, QString("ViewBase::guiVisibilitySlot")); } // ---------- Popup stuff START --------------------- void ViewBase::mousePressEvent( QMouseEvent *e ) { if ( e->button() == Qt::RightButton ) showContextMenu(); } /** * Return a popup menu. This contains basic entries. * More can be added by the caller. */ QMenu* ViewBase::getPopup() { popupReset(); return _popMenu; } void ViewBase::popupReset() { QAction *act; delete _popMenu; _popMenu = new QMenu( this ); _popMenu->addSection( QIcon::fromTheme( QLatin1String( "kmix" ) ), i18n("Device Settings" )); act = _localActionColletion->action( "toggle_channels" ); if ( act ) _popMenu->addAction(act); act = _actions->action( "options_show_menubar" ); if ( act ) _popMenu->addAction(act); } /** This will only get executed, when the user has removed all items from the view. Don't remove this method, because then the user cannot get a menu for getting his channels back */ void ViewBase::showContextMenu() { //qCDebug(KMIX_LOG) << "ViewBase::showContextMenu()"; popupReset(); QPoint pos = QCursor::pos(); _popMenu->popup( pos ); } void ViewBase::refreshVolumeLevels() { // is virtual } /** * Check all Mixer instances of this view. * If at least on is dynamic, return true. * Please note that usually there is only one Mixer instance per View. * The only exception as of today (June 2011) is the Tray Popup, which * can contain controls from e.g. ALSA and MPRIS2 backends. */ bool ViewBase::isDynamic() const { foreach (Mixer* mixer , _mixers ) { if ( mixer->isDynamic() ) return true; } return false; } bool ViewBase::pulseaudioPresent() const { // We do not use Mixer::pulseaudioPresent(), as we are only interested in Mixer instances contained in this View. foreach (Mixer* mixer , _mixers ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } void ViewBase::resetMdws() { // We need to delete the current MixDeviceWidgets so we can redraw them while (!_mdws.isEmpty()) delete _mdws.takeFirst(); // _mixSet contains shared_ptr instances, so clear() should be enough to prevent mem leak _mixSet.clear(); // Clean up our _mixSet so we can reapply our GUIProfile } int ViewBase::visibleControls() { int visibleCount = 0; foreach (QWidget* qw, _mdws) { if (qw->isVisible()) ++ visibleCount; } return visibleCount; } /** * Open the View configuration dialog. The user can select which channels he wants * to see and which not. */ void ViewBase::configureView() { Q_ASSERT( !isDynamic() ); Q_ASSERT( !pulseaudioPresent() ); DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); dvc->show(); } void ViewBase::toggleMenuBarSlot() { //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() start\n"; emit toggleMenuBar(); //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() done\n"; } /** * Loads the configuration of this view. *

* Future directions: The view should probably know its config in advance, so we can use it in #load() and #save() * * * @param config The view for this config */ void ViewBase::load(KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); //KConfigGroup cg = config->group( grp ); qCDebug(KMIX_LOG) << "KMixToolBox::loadView() grp=" << grp.toLatin1(); static GuiVisibility guiVisibilities[3] = { GuiVisibility::GuiSIMPLE, GuiVisibility::GuiEXTENDED, GuiVisibility::GuiFULL }; bool guiLevelSet = false; for (int i=0; i<3; ++i) { GuiVisibility& guiCompl = guiVisibilities[i]; bool atLeastOneControlIsShown = false; foreach(QWidget *qmdw, view->_mdws) { MixDeviceWidget *mdw = qobject_cast(qmdw); if (mdw!=nullptr) { shared_ptr md = mdw->mixDevice(); QString devgrp = md->configGroupName(grp); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels bool splitChannels = devcg.readEntry("Split", !mdw->isStereoLinked()); mdw->setStereoLinked(!splitChannels); } // Future directions: "Visibility" is very dirty: It is read from either config file or // GUIProfile. Thus we have a lot of doubled mdw visibility code all throughout KMix. bool mdwEnabled = false; // Consult GuiProfile for visibility mdwEnabled = findMdw(mdw->mixDevice()->id(), guiCompl) != 0; // Match GUI complexity // qCWarning(KMIX_LOG) << "---------- FIRST RUN: md=" << md->id() << ", guiVisibility=" << guiCompl.getId() << ", enabled=" << mdwEnabled; if (mdwEnabled) { atLeastOneControlIsShown = true; } mdw->setVisible(mdwEnabled); } // inherits MixDeviceWidget } // for all MDW's if (atLeastOneControlIsShown) { guiLevelSet = true; setGuiLevel(guiCompl); break; // If there were controls in this complexity level, don't try more } } // for try = 0 ... 1 if (!guiLevelSet) setGuiLevel(guiVisibilities[2]); } void ViewBase::setGuiLevel(GuiVisibility& guiLevel) { this->guiLevel = guiLevel; } /** * Checks whether the given mixDevice shall be shown according to the requested * GuiVisibility. All ProfControl objects are inspected. The first found is returned. * * @param mdwId The control ID * @param requestedGuiComplexityName The GUI name * @return The corresponding ProfControl* * */ ProfControl* ViewBase::findMdw(const QString& mdwId, GuiVisibility visibility) { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { QRegExp idRegExp(pControl->id); if ( mdwId.contains(idRegExp) ) { if (pControl->getVisibility().satisfiesVisibility(visibility)) { // qCDebug(KMIX_LOG) << " MATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); return pControl; } else { // qCDebug(KMIX_LOG) << "NOMATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); } } } // iterate over all ProfControl entries return 0; // not found } /** * Returns the ProfControl* to the given id. The first found is returned. * GuiVisibilityis not taken into account. . All ProfControl objects are inspected. * * @param id The control ID * @return The corresponding ProfControl*, or 0 if no match was found */ ProfControl* ViewBase::findMdw(const QString& id) { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { QRegExp idRegExp(pControl->id); //qCDebug(KMIX_LOG) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); if ( id.contains(idRegExp) ) { return pControl; } } // iterate over all ProfControl entries return 0;// not found } /* * Saves the View configuration */ void ViewBase::save(KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) bool dynamic = isDynamic(); // TODO 11 Dynamic view configuration for (int i = 0; i < view->_mdws.count(); ++i) { QWidget *qmdw = view->_mdws[i]; MixDeviceWidget *mdw = qobject_cast(qmdw); if (mdw!=nullptr) { shared_ptr md = mdw->mixDevice(); //qCDebug(KMIX_LOG) << " grp=" << grp.toLatin1(); //qCDebug(KMIX_LOG) << " mixer=" << view->id().toLatin1(); //qCDebug(KMIX_LOG) << " mdwPK=" << mdw->mixDevice()->id().toLatin1(); QString devgrp = QString("%1.%2.%3").arg(grp, md->mixer()->id(), md->id()); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels devcg.writeEntry("Split", !mdw->isStereoLinked()); } /* if (!dynamic) { devcg.writeEntry("Show", mdw->isVisibleTo(view)); // qCDebug(KMIX_LOG) << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); } */ } // inherits MixDeviceWidget } // for all MDW's if (!dynamic) { // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) if (guiProfile()->isDirty()) { qCDebug(KMIX_LOG) << "Writing dirty profile. grp=" << grp; guiProfile()->writeProfile(); } } } // ---------- Popup stuff END --------------------- diff --git a/gui/viewdockareapopup.cpp b/gui/viewdockareapopup.cpp index d3bed2e9..1d0bb641 100644 --- a/gui/viewdockareapopup.cpp +++ b/gui/viewdockareapopup.cpp @@ -1,447 +1,445 @@ /* * 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/viewdockareapopup.h" // Qt #include #include #include #include #include #include #include // KDE #include #include // KMix #include "apps/kmix.h" #include "core/mixer.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "gui/dialogchoosebackends.h" #include "gui/guiprofile.h" #include "gui/kmixprefdlg.h" #include "gui/mdwslider.h" #include "dialogbase.h" // Restore volume button feature is incomplete => disabling for KDE 4.10 #undef RESTORE_VOLUME_BUTTON QString ViewDockAreaPopup::InternedString_Star = QString("*"); QString ViewDockAreaPopup::InternedString_Subcontrols = QString("pvolume,cvolume,pswitch,cswitch"); ProfControl* ViewDockAreaPopup::MatchAllForSoundMenu = 0; //ProfControl(ViewDockAreaPopup::InternedString_Star, ViewDockAreaPopup::InternedString_Subcontrols); ViewDockAreaPopup::ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW) : ViewBase(parent, id, 0, vflags, guiProfileId), _kmixMainWindow(dockW) { resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); /* * Bug 206724: * Following are excerpts of trying to receive key events while this popup is open. * The best I could do with lots of hacks is to get the keyboard events after a mouse-click in the popup. * But such a solution is neither intuitive nor helpful - if one clicks, then usage of keyboard makes not much sense any longer. * I finally gave up on fixing Bug 206724. */ /* releaseKeyboard(); setFocusPolicy(Qt::StrongFocus); setFocus(Qt::TabFocusReason); releaseKeyboard(); mainWindowButton->setFocusPolicy(Qt::StrongFocus); Also implemented the following "event handlers", but they do not show any signs of key presses: keyPressEvent() x11Event() eventFilter() */ foreach ( Mixer* mixer, Mixer::mixers() ) { // Adding all mixers, as we potentially want to show all master controls addMixer(mixer); // The list will be redone in _setMixSet() with the actual Mixer instances to use } restoreVolumeIcon = QIcon::fromTheme(QLatin1String("quickopen-file")); createDeviceWidgets(); // Register listeners for all mixers ControlManager::instance().addListener( QString(), // all mixers - static_cast(ControlChangeType::GUI|ControlChangeType::ControlList| - ControlChangeType::Volume|ControlChangeType::MasterChanged), + ControlManager::GUI|ControlManager::ControlList|ControlManager::Volume|ControlManager::MasterChanged, this, QString("ViewDockAreaPopup")); } ViewDockAreaPopup::~ViewDockAreaPopup() { ControlManager::instance().removeListener(this); delete _layoutMDW; // Hint: optionsLayout and "everything else" is deleted when "delete _layoutMDW" cascades down } -void ViewDockAreaPopup::controlsChange(int changeType) +void ViewDockAreaPopup::controlsChange(ControlManager::ChangeType changeType) { - ControlChangeType::Type type = ControlChangeType::fromInt(changeType); - switch (type ) + switch (changeType) { - case ControlChangeType::ControlList: - case ControlChangeType::MasterChanged: + case ControlManager::ControlList: + case ControlManager::MasterChanged: createDeviceWidgets(); break; - case ControlChangeType::GUI: + case ControlManager::GUI: updateGuiOptions(); break; - case ControlChangeType::Volume: + case ControlManager::Volume: refreshVolumeLevels(); break; default: - ControlManager::warnUnexpectedChangeType(type, this); + ControlManager::warnUnexpectedChangeType(changeType, this); break; } } void ViewDockAreaPopup::wheelEvent ( QWheelEvent * e ) { if ( _mdws.isEmpty() ) return; } void ViewDockAreaPopup::configurationUpdate() { // TODO Do we still need configurationUpdate(). It was never implemented for ViewDockAreaPopup } // TODO Currently no right-click, as we have problems to get the ViewDockAreaPopup resized void ViewDockAreaPopup::showContextMenu() { // no right-button-menu on "dock area popup" return; } void ViewDockAreaPopup::resetRefs() { seperatorBetweenMastersAndStreams = 0; separatorBetweenMastersAndStreamsInserted = false; separatorBetweenMastersAndStreamsRequired = false; configureViewButton = 0; restoreVolumeButton1 = 0; restoreVolumeButton2 = 0; restoreVolumeButton3 = 0; restoreVolumeButton4 = 0; mainWindowButton = 0; optionsLayout = 0; _layoutMDW = 0; } void ViewDockAreaPopup::_setMixSet() { resetMdws(); if (optionsLayout != 0) { QLayoutItem *li2; while ((li2 = optionsLayout->takeAt(0))) delete li2; } // Hint : optionsLayout itself is deleted when "delete _layoutMDW" cascades down if (_layoutMDW != 0) { QLayoutItem *li; while ((li = _layoutMDW->takeAt(0))) delete li; } /* * Strangely enough, I cannot delete optionsLayout in a loop. I get a strange stacktrace: * Application: KMix (kmix), signal: Segmentation fault [...] #6 0x00007f9c9a282900 in QString::shared_null () from /usr/lib/x86_64-linux-gnu/libQtCore.so.4 #7 0x00007f9c9d4286b0 in ViewDockAreaPopup::_setMixSet (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:164 #8 0x00007f9c9d425700 in ViewBase::createDeviceWidgets (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewbase.cpp:137 #9 0x00007f9c9d42845b in ViewDockAreaPopup::controlsChange (this=0x1272b60, changeType=2) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:91 */ // if ( optionsLayout != 0 ) // { // QLayoutItem *li2; // while ( ( li2 = optionsLayout->takeAt(0) ) ) // strangely enough, it crashes here // delete li2; // } // --- Due to the strange crash, delete everything manually : START --------------- // I am a bit confused why this doesn't crash. I moved the "optionsLayout->takeAt(0) delete" loop at the beginning, // so the objects should already be deleted. ... delete configureViewButton; delete restoreVolumeButton1; delete restoreVolumeButton2; delete restoreVolumeButton3; delete restoreVolumeButton4; delete mainWindowButton; delete seperatorBetweenMastersAndStreams; // --- Due to the strange crash, delete everything manually : END --------------- resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); /* * BKO 299754: Looks like I need to explicitly delete layout(). I have no idea why * "delete _layoutMDW" is not enough, as that is supposed to be the layout * of this ViewDockAreaPopup * (Hint: it might have been 0 already. Nowadays it is definitely, see #resetRefs()) */ delete layout(); // BKO 299754 _layoutMDW = new QGridLayout(this); _layoutMDW->setSpacing(DialogBase::verticalSpacing()); // Review #121166: Add some space over device icons, otherwise they may hit window border _layoutMDW->setContentsMargins(0,5,0,0); //_layoutMDW->setSizeConstraint(QLayout::SetMinimumSize); _layoutMDW->setSizeConstraint(QLayout::SetMaximumSize); _layoutMDW->setObjectName(QLatin1String("KmixPopupLayout")); setLayout(_layoutMDW); // Adding all mixers, as we potentially want to show all master controls. Due to hotplugging // we have to redo the list on each _setMixSet() (instead of setting it once in the Constructor) _mixers.clear(); QSet preferredMixersForSoundmenu = GlobalConfig::instance().getMixersForSoundmenu(); // qCDebug(KMIX_LOG) << "Launch with " << preferredMixersForSoundmenu; foreach ( Mixer* mixer, Mixer::mixers() ) { bool useMixer = preferredMixersForSoundmenu.isEmpty() || preferredMixersForSoundmenu.contains(mixer->id()); if (useMixer) addMixer(mixer); } // The following loop is for the case when everything gets filtered out. We "reset" to show everything then. // Hint: Filtering everything out can only be an "accident", e.g. when restarting KMix with changed hardware or // backends. if ( _mixers.isEmpty() ) { foreach ( Mixer* mixer, Mixer::mixers() ) { addMixer(mixer); } } // A loop that adds the Master control of each card foreach ( Mixer* mixer, _mixers ) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id(); shared_ptrdockMD = mixer->getLocalMasterMD(); if ( !dockMD && mixer->size() > 0 ) { // If we have no dock device yet, we will take the first available mixer device. dockMD = (*mixer)[0]; } if ( dockMD ) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id(); if ( !dockMD->isApplicationStream() && dockMD->playbackVolume().hasVolume()) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id() << ": YES"; // don't add application streams here. They are handled below, so // we make sure to not add them twice _mixSet.append(dockMD); } } } // loop over all cards // Add all application streams foreach ( Mixer* mixer2 , _mixers ) { foreach ( shared_ptr md, mixer2->getMixSet() ) { if (md->isApplicationStream()) { _mixSet.append(md); } } } } QWidget* ViewDockAreaPopup::add(shared_ptr md) { bool vertical = (GlobalConfig::instance().data.getTraypopupOrientation() == Qt::Vertical); // I am wondering whether using vflags for this would still make sense /* QString dummyMatchAll("*"); QString matchAllPlaybackAndTheCswitch("pvolume,cvolume,pswitch,cswitch"); // Leak | relevant | pctl Each time a stream is added, a new ProfControl gets created. // It cannot be deleted in ~MixDeviceWidget, as ProfControl* ownership is not consistent. // here a new pctl is created (could be deleted), but in ViewSliders the ProcControl is taken from the // MixDevice, which in turn uses it from the GUIProfile. // Summarizing: ProfControl* is either owned by the GUIProfile or created new (ownership unclear). // Hint: dummyMatchAll and matchAllPlaybackAndTheCswitch leak together with pctl ProfControl *pctl = new ProfControl( dummyMatchAll, matchAllPlaybackAndTheCswitch); */ if ( !md->isApplicationStream() ) { separatorBetweenMastersAndStreamsRequired = true; } if ( !separatorBetweenMastersAndStreamsInserted && separatorBetweenMastersAndStreamsRequired && md->isApplicationStream() ) { // First application stream => add separator separatorBetweenMastersAndStreamsInserted = true; int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); int row = vertical ? 0 : sliderColumn; int col = vertical ? sliderColumn : 0; seperatorBetweenMastersAndStreams = new QFrame(this); if (vertical) seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::VLine); else seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::HLine); _layoutMDW->addWidget( seperatorBetweenMastersAndStreams, row, col ); //_layoutMDW->addItem( new QSpacerItem( 5, 5 ), row, col ); } if (MatchAllForSoundMenu == 0) { // Lazy init of static member on first use MatchAllForSoundMenu = new ProfControl(ViewDockAreaPopup::InternedString_Star, ViewDockAreaPopup::InternedString_Subcontrols); } ProfControl *pctl = MatchAllForSoundMenu; MixDeviceWidget *mdw = new MDWSlider( md, // only 1 device. true, // Show Mute LE true, // Show Record LED true, // Include Mixer Name false, // Small vertical ? Qt::Vertical : Qt::Horizontal, this, // parent this // NOT ANYMORE!!! -> Is "NULL", so that there is no RMB-popup , pctl ); mdw->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); //if (sliderColumn == 1 ) sliderColumn =0; int row = vertical ? 0 : sliderColumn; int col = vertical ? sliderColumn : 0; _layoutMDW->addWidget( mdw, row, col ); //qCDebug(KMIX_LOG) << "ADDED " << md->id() << " at column " << sliderColumn; return mdw; } void ViewDockAreaPopup::constructionFinished() { // qCDebug(KMIX_LOG) << "ViewDockAreaPopup::constructionFinished()\n"; mainWindowButton = new QPushButton(QIcon::fromTheme("show-mixer"), "" , this); mainWindowButton->setObjectName(QLatin1String("MixerPanel")); mainWindowButton->setToolTip(i18n("Show the full mixer window")); connect(mainWindowButton, SIGNAL(clicked()), SLOT(showPanelSlot())); configureViewButton = createConfigureViewButton(); optionsLayout = new QHBoxLayout(); optionsLayout->addWidget(mainWindowButton); optionsLayout->addWidget(configureViewButton); #ifdef RESTORE_VOLUME_BUTTON restoreVolumeButton1 = createRestoreVolumeButton(1); optionsLayout->addWidget( restoreVolumeButton1 ); // TODO enable only if user has saved a volume profile // optionsLayout->addWidget( createRestoreVolumeButton(2) ); // optionsLayout->addWidget( createRestoreVolumeButton(3) ); // optionsLayout->addWidget( createRestoreVolumeButton(4) ); #endif int sliderRow = _layoutMDW->rowCount(); _layoutMDW->addLayout(optionsLayout, sliderRow, 0, 1, _layoutMDW->columnCount()); updateGuiOptions(); _layoutMDW->update(); _layoutMDW->activate(); //bool fnc = focusNextChild(); //qCWarning(KMIX_LOG) << "fnc=" <setToolTip(i18n("Load volume profile %1", storageSlot)); profileButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return profileButton; } void ViewDockAreaPopup::refreshVolumeLevels() { foreach ( QWidget* qw, _mdws ) { //qCDebug(KMIX_LOG) << "rvl: " << qw; MixDeviceWidget* mdw = qobject_cast(qw); if ( mdw != 0 ) mdw->update(); } } void ViewDockAreaPopup::configureView() { // Q_ASSERT( !pulseaudioPresent() ); // QSet currentlyActiveMixersInDockArea; // foreach ( Mixer* mixer, _mixers ) // { // currentlyActiveMixersInDockArea.insert(mixer->id()); // } KMixPrefDlg* prefDlg = KMixPrefDlg::getInstance(); //prefDlg->setActiveMixersInDock(currentlyActiveMixersInDockArea); prefDlg->switchToPage(KMixPrefDlg::PrefSoundMenu); } /** * This gets activated whne a user clicks the "Mixer" PushButton in this popup. */ void ViewDockAreaPopup::showPanelSlot() { _kmixMainWindow->setVisible(true); KWindowSystem::setOnDesktop(_kmixMainWindow->winId(), KWindowSystem::currentDesktop()); KWindowSystem::activateWindow(_kmixMainWindow->winId()); // This is only needed when the window is already visible. static_cast(parent())->hide(); } diff --git a/gui/viewdockareapopup.h b/gui/viewdockareapopup.h index 30286d2f..55cc50a5 100644 --- a/gui/viewdockareapopup.h +++ b/gui/viewdockareapopup.h @@ -1,87 +1,89 @@ //-*-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 ViewDockAreaPopup_h #define ViewDockAreaPopup_h #include "viewbase.h" #include #include +#include "core/ControlManager.h" + class QBoxLayout; class QFrame; class QGridLayout; class QWidget; class Mixer; class MixDevice; class KMixWindow; class ViewDockAreaPopup : public ViewBase { Q_OBJECT public: ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW); virtual ~ViewDockAreaPopup(); QWidget* add(shared_ptr md) Q_DECL_OVERRIDE; void constructionFinished() Q_DECL_OVERRIDE; void refreshVolumeLevels() Q_DECL_OVERRIDE; void showContextMenu() Q_DECL_OVERRIDE; void configurationUpdate() Q_DECL_OVERRIDE; protected: KMixWindow *_kmixMainWindow; void wheelEvent ( QWheelEvent * e ) Q_DECL_OVERRIDE; void _setMixSet() Q_DECL_OVERRIDE; private: QGridLayout* _layoutMDW; QPushButton* createRestoreVolumeButton ( int storageSlot ); bool separatorBetweenMastersAndStreamsInserted; bool separatorBetweenMastersAndStreamsRequired; QFrame* seperatorBetweenMastersAndStreams; QBoxLayout* optionsLayout; QPushButton* configureViewButton; QPushButton *mainWindowButton; QPushButton *restoreVolumeButton1; QPushButton *restoreVolumeButton2; QPushButton *restoreVolumeButton3; QPushButton *restoreVolumeButton4; QIcon restoreVolumeIcon; static ProfControl* MatchAllForSoundMenu; static QString InternedString_Star; static QString InternedString_Subcontrols; public slots: - void controlsChange(int changeType); + void controlsChange(ControlManager::ChangeType changeType); void configureView() Q_DECL_OVERRIDE; private slots: void showPanelSlot(); void resetRefs(); }; #endif diff --git a/gui/viewsliders.cpp b/gui/viewsliders.cpp index 9a225c95..3eaceee8 100644 --- a/gui/viewsliders.cpp +++ b/gui/viewsliders.cpp @@ -1,446 +1,445 @@ /* * 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. */ //#define TEST_MIXDEVICE_COMPOSITE #undef TEST_MIXDEVICE_COMPOSITE #ifdef TEST_MIXDEVICE_COMPOSITE #ifdef __GNUC__ #warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #warning !!! MIXDEVICE COMPOSITE TESTING IS ACTIVATED !!! #warning !!! THIS IS PRE-ALPHA CODE! !!! #warning !!! DO NOT SHIP KMIX IN THIS STATE !!! #warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #endif #endif #include "gui/viewsliders.h" // KMix #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/mixdevicecomposite.h" #include "core/mixer.h" #include "gui/guiprofile.h" #include "gui/mdwenum.h" #include "gui/mdwslider.h" #include "gui/verticaltext.h" // KDE #include // Qt #include #include #include #include #include #include #include /** * Generic View implementation. This can hold now all kinds of controls (not just Sliders, as * the class name suggests). */ ViewSliders::ViewSliders(QWidget* parent, QString id, Mixer* mixer, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actColl) : ViewBase(parent, id, Qt::FramelessWindowHint, vflags, guiProfileId, actColl), _layoutEnum(0) { addMixer(mixer); _configureViewButton = 0; _layoutMDW = 0; _layoutSliders = 0; _layoutEnum = 0; emptyStreamHint = 0; createDeviceWidgets(); ControlManager::instance().addListener(mixer->id(), - static_cast(ControlChangeType::GUI|ControlChangeType::ControlList|ControlChangeType::Volume), + ControlManager::GUI|ControlManager::ControlList|ControlManager::Volume, this, QString("ViewSliders.%1").arg(mixer->id())); } ViewSliders::~ViewSliders() { ControlManager::instance().removeListener(this); delete _layoutMDW; // qDeleteAll(_separators); } -void ViewSliders::controlsChange(int changeType) +void ViewSliders::controlsChange(ControlManager::ChangeType changeType) { - ControlChangeType::Type type = ControlChangeType::fromInt(changeType); - switch (type) + switch (changeType) { - case ControlChangeType::ControlList: + case ControlManager::ControlList: createDeviceWidgets(); break; - case ControlChangeType::GUI: + case ControlManager::GUI: updateGuiOptions(); break; - case ControlChangeType::Volume: + case ControlManager::Volume: if (GlobalConfig::instance().data.debugVolume) qCDebug(KMIX_LOG) << "NOW I WILL REFRESH VOLUME LEVELS. I AM " << id(); refreshVolumeLevels(); break; default: - ControlManager::warnUnexpectedChangeType(type, this); + ControlManager::warnUnexpectedChangeType(changeType, this); break; } } QWidget* ViewSliders::add(shared_ptr md) { MixDeviceWidget *mdw; Qt::Orientation orientation = GlobalConfig::instance().data.getToplevelOrientation(); if ( md->isEnum() ) { mdw = new MDWEnum( md, // MixDevice (parameter) orientation, // Orientation this, // parent this // View widget , md->controlProfile() ); // if ( _layoutEnum == 0 ) { // // lazily creation of Layout for the first enum // _layoutEnum = new QVBoxLayout(); // _layoutMDW->addLayout( _layoutEnum ); // } _layoutEnum->addWidget(mdw); } // an enum else { mdw = new MDWSlider( md, // MixDevice (parameter) true, // Show Mute LED true, // Show Record LED false, // Include Mixer Name false, // Small orientation, // Orientation this, // parent this , md->controlProfile() ); // View widget _layoutSliders->addWidget(mdw); } return mdw; } void ViewSliders::_setMixSet() { resetMdws(); delete emptyStreamHint; emptyStreamHint = 0; // Our _layoutSliders now should only contain spacer widgets from the addSpacing() calls in add() above. // We need to trash those too otherwise all sliders gradually migrate away from the edge :p if (_layoutSliders != 0) { QLayoutItem *li; while ((li = _layoutSliders->takeAt(0))) delete li; // delete _layoutSliders; _layoutSliders = 0; } delete _configureViewButton; _configureViewButton = 0; if (_layoutEnum != 0) { QLayoutItem *li2; while ((li2 = _layoutEnum->takeAt(0)) != 0) delete li2; // delete _layoutEnum; _layoutEnum = 0; } delete _layoutMDW; _layoutMDW = 0; // We will be recreating our sliders, so make sure we trash all the separators too. //qDeleteAll(_separators); _separators.clear(); if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Horizontal) { // Horizontal slider => put them vertically _layoutMDW = new QVBoxLayout(this); _layoutMDW->setAlignment(Qt::AlignLeft | Qt::AlignTop); _layoutSliders = new QVBoxLayout(); _layoutSliders->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); } else { // Vertical slider => put them horizontally _layoutMDW = new QHBoxLayout(this); _layoutMDW->setAlignment(Qt::AlignHCenter | Qt::AlignTop); _layoutSliders = new QHBoxLayout(); _layoutSliders->setAlignment(Qt::AlignHCenter | Qt::AlignTop); } _layoutSliders->setContentsMargins(0, 0, 0, 0); _layoutSliders->setSpacing(0); _layoutMDW->setContentsMargins(0, 0, 0, 0); _layoutMDW->setSpacing(0); // The stretch added here is so that, if there is width to spare, the // controls are centered in the window but the configure button is // always at the bottom right. _layoutMDW->addStretch(); _layoutMDW->addItem(_layoutSliders); _layoutMDW->addStretch(); _layoutEnum = new QVBoxLayout(); _layoutMDW->addLayout(_layoutEnum); // Hint: This text comparison is not a clean solution, but one that will work for quite a while. QString viewId(id()); if (viewId.contains(".Capture_Streams.")) emptyStreamHint = new QLabel(i18n("Nothing is capturing audio.")); else if (viewId.contains(".Playback_Streams.")) emptyStreamHint = new QLabel(i18n("Nothing is playing audio.")); else if (viewId.contains(".Capture_Devices.")) emptyStreamHint = new QLabel(i18n("No capture devices.")); else if (viewId.contains(".Playback_Devices.")) emptyStreamHint = new QLabel(i18n("No playback devices.")); else emptyStreamHint = new QLabel(i18n("Nothing is playing audio.")); // Fallback. Assume Playback stream emptyStreamHint->setAlignment(Qt::AlignCenter); emptyStreamHint->setWordWrap(true); emptyStreamHint->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); _layoutMDW->addWidget(emptyStreamHint); #ifdef TEST_MIXDEVICE_COMPOSITE QList > mds; // For temporary test #endif // This method iterates the controls from the Profile // Each control is checked, whether it is also contained in the mixset, and // applicable for this kind of View. If yes, the control is accepted and inserted. GUIProfile* guiprof = guiProfile(); if (guiprof != 0) { foreach (Mixer* mixer , _mixers ){ const MixSet& mixset = mixer->getMixSet(); foreach ( ProfControl* control, guiprof->getControls() ) { //ProfControl* control = *it; // The TabName of the control matches this View name (!! attention: Better use some ID, due to i18n() ) QRegExp idRegexp(control->id); //bool isExactRegexp = control->id.startsWith('^') && control->id.endsWith('$'); // for optimizing //isExactRegexp &= ( ! control->id.contains(".*") ); // For now. Might be removed in the future, as it cannot be done properly !!! //qCDebug(KMIX_LOG) << "ViewSliders::setMixSet(): Check GUIProfile id==" << control->id << "\n"; // The following for-loop could be simplified by using a std::find_if for ( int i=0; i md = mixset[i]; if ( md->id().contains(idRegexp) ) { // Match found (by name) if ( _mixSet.contains( md ) ) { continue; // dup check } // Now check whether subcontrols match bool subcontrolPlaybackWanted = (control->useSubcontrolPlayback() && ( md->playbackVolume().hasVolume() || md->hasMuteSwitch()) ); bool subcontrolCaptureWanted = (control->useSubcontrolCapture() && ( md->captureVolume() .hasVolume() || md->captureVolume() .hasSwitch()) ); bool subcontrolEnumWanted = (control->useSubcontrolEnum() && md->isEnum()); bool subcontrolWanted = subcontrolPlaybackWanted | subcontrolCaptureWanted | subcontrolEnumWanted; if ( !subcontrolWanted ) { continue; } md->setControlProfile(control); if ( !control->name.isNull() ) { // Apply the custom name from the profile md->setReadableName(control->name);// @todo: This is the wrong place. It only applies to controls in THIS type of view } if ( !control->getSwitchtype().isNull() ) { if ( control->getSwitchtype() == "On" ) md->playbackVolume().setSwitchType(Volume::OnSwitch); else if ( control->getSwitchtype() == "Off" ) md->playbackVolume().setSwitchType(Volume::OffSwitch); } _mixSet.append(md); #ifdef TEST_MIXDEVICE_COMPOSITE if ( md->id() == "Front:0" || md->id() == "Surround:0") { mds.append(md);} // For temporary test #endif // We use no "break;" ,as multiple devices could match the regexp (e.g. "^.*$") } // name matches } // loop for finding a suitable MixDevice } // iteration over all controls from the Profile } // if there is a profile } // Iteration over all Mixers emptyStreamHint->setVisible(_mixSet.isEmpty() && isDynamic()); // show a hint why a tab is empty (dynamic controls!!!) // visibleControls() == 0 could be used for the !isDynamic() case #ifdef TEST_MIXDEVICE_COMPOSITE // @todo: This is currently hardcoded, and instead must be read as usual from the Profile MixDeviceComposite *mdc = new MixDeviceComposite(_mixer, "Composite_Test", mds, "A Composite Control #1", MixDevice::KMIX_COMPOSITE); Volume::ChannelMask chn = Volume::MMAIN; Volume* vol = new Volume( chn, 0, 100, true, true); mdc->addPlaybackVolume(*vol); QString ctlId("Composite_Test"); QString ctlMatchAll("*"); ProfControl* pctl = new ProfControl(ctlId, ctlMatchAll); mdc->setControlProfile(pctl); _mixSet->append(mdc); #endif } void ViewSliders::constructionFinished() { configurationUpdate(); //if ( !pulseaudioPresent() ) // TODO 11 Dynamic view configuration if ( !isDynamic() ) { _configureViewButton = createConfigureViewButton(); _layoutEnum->addStretch(); _layoutEnum->addWidget(_configureViewButton); } updateGuiOptions(); } void ViewSliders::configurationUpdate() { // Adjust height of top part by setting it to the maximum of all mdw's bool haveCaptureLEDs = false; int labelExtent = 0; bool haveMuteButtons = false; // Find out whether any MDWSlider has Switches. If one has, then we need "extents" for (int i = 0; i < _mdws.count(); i++) { MDWSlider* mdw = ::qobject_cast(_mdws[i]); if (mdw && mdw->isVisibleTo(this)) { labelExtent = qMax(labelExtent, mdw->labelExtentHint()); //qCDebug(KMIX_LOG) << "########## EXTENT for " << id() << " is " << labelExtent; haveCaptureLEDs = haveCaptureLEDs || mdw->hasCaptureLED(); haveMuteButtons = haveMuteButtons || mdw->hasMuteButton(); } // The following "break" cannot be done any longer, as we need to calculate the highest labelExtent from ALL mdw's // if (haveCaptureLEDs && haveMuteButtons) // break; // We know all we want. Lets break. } //qCDebug(KMIX_LOG) << "topPartExtent is " << topPartExtent; bool firstVisibleControlFound = false; for ( int i=0; i<_mdws.count(); i++ ) { MixDeviceWidget* mdw = ::qobject_cast(_mdws[i]); MDWSlider* mdwSlider = ::qobject_cast(_mdws[i]); if ( mdw ) { // guiLevel has been set earlier, by inspecting the controls ProfControl* matchingControl = findMdw(mdw->mixDevice()->id(), guiLevel); mdw->setVisible(matchingControl != 0); if ( mdwSlider ) { // additional options for sliders mdwSlider->setLabelExtent(labelExtent); mdwSlider->setMuteButtonSpace(haveMuteButtons); mdwSlider->setCaptureLEDSpace(haveCaptureLEDs); } bool thisControlIsVisible = mdw->isVisibleTo(this); bool showSeparator = ( firstVisibleControlFound && thisControlIsVisible); if ( _separators.contains( mdw->mixDevice()->id() )) { QFrame* sep = _separators[mdw->mixDevice()->id()]; sep->setVisible(showSeparator); } if ( thisControlIsVisible ) firstVisibleControlFound=true; } } // for all MDW's _layoutMDW->activate(); } void ViewSliders::refreshVolumeLevels() { for (int i = 0; i < _mdws.count(); i++) { QWidget *mdwx = _mdws[i]; MixDeviceWidget* mdw = ::qobject_cast(mdwx); if (mdw != 0) { // sanity check #ifdef TEST_MIXDEVICE_COMPOSITE // --- start --- The following 4 code lines should be moved to a more // generic place, as it only works in this View. But it // should also work in the ViewDockareaPopup and everywhere else. MixDeviceComposite* mdc = ::qobject_cast(mdw->mixDevice()); if (mdc != 0) { mdc->update(); } // --- end --- #endif if (GlobalConfig::instance().data.debugVolume) { bool debugMe = (mdw->mixDevice()->id() == "PCM:0"); if (debugMe) qCDebug(KMIX_LOG) << "Old PCM:0 playback state" << mdw->mixDevice()->isMuted() << ", vol=" << mdw->mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); } mdw->update(); } else { qCCritical(KMIX_LOG) << "ViewSliders::refreshVolumeLevels(): mdw is not a MixDeviceWidget\n"; // no slider. Cannot happen in theory => skip it } } } diff --git a/gui/viewsliders.h b/gui/viewsliders.h index 4dcbccaa..127d5d5e 100644 --- a/gui/viewsliders.h +++ b/gui/viewsliders.h @@ -1,63 +1,64 @@ //-*-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 ViewSliders_h #define ViewSliders_h class QBoxLayout; #include #include class QLabel; #include class QWidget; class Mixer; -#include "viewbase.h" +#include "gui/viewbase.h" +#include "core/ControlManager.h" class ViewSliders : public ViewBase { Q_OBJECT public: ViewSliders(QWidget* parent, QString id, Mixer* mixer, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actColl); virtual ~ViewSliders(); QWidget* add(shared_ptr) Q_DECL_OVERRIDE; void constructionFinished() Q_DECL_OVERRIDE; void configurationUpdate() Q_DECL_OVERRIDE; public slots: - void controlsChange(int changeType); + void controlsChange(ControlManager::ChangeType changeType); protected: void _setMixSet() Q_DECL_OVERRIDE; private: void refreshVolumeLevels() Q_DECL_OVERRIDE; QBoxLayout* _layoutMDW; QLayout* _layoutSliders; QBoxLayout* _layoutEnum; QHash _separators; QPushButton* _configureViewButton; QLabel* emptyStreamHint; }; #endif