diff --git a/apps/kmix.cpp b/apps/kmix.cpp index b84ac052..8689d823 100644 --- a/apps/kmix.cpp +++ b/apps/kmix.cpp @@ -1,1316 +1,1316 @@ /* * 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) - (ControlChangeType::Type) (ControlChangeType::ControlList | ControlChangeType::MasterChanged), this, + static_cast(ControlChangeType::ControlList|ControlChangeType::MasterChanged), this, "KMixWindow"); // Send an initial volume refresh (otherwise all volumes are 0 until the next change) ControlManager::instance().announce(QString(), ControlChangeType::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) { ControlChangeType::Type type = ControlChangeType::fromInt(changeType); switch (type) { case ControlChangeType::ControlList: case ControlChangeType::MasterChanged: updateDocking(); break; default: ControlManager::warnUnexpectedChangeType(type, 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); - if (w->inherits("KMixerWidget")) + KMixerWidget *mw = qobject_cast(w); + if (mw!=nullptr) { - KMixerWidget* mw = (KMixerWidget*) w; // 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 = (KMixerWidget*) m_wsMixers->widget(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")); } else if (labelsHasChanged || ticksHasChanged) { ControlManager::instance().announce(QString(), ControlChangeType::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 = (KMixerWidget*) m_wsMixers->currentWidget(); + 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 = (KMixerWidget*) m_wsMixers->currentWidget(); - if (kmw) + 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/backends/mixer_alsa9.cpp b/backends/mixer_alsa9.cpp index ec64163e..cd7d4313 100644 --- a/backends/mixer_alsa9.cpp +++ b/backends/mixer_alsa9.cpp @@ -1,1007 +1,1007 @@ /* * KMix -- KDE's full featured mini mixer * Alsa 0.9x and 1.0 - Based on original alsamixer code * from alsa-project ( www/alsa-project.org ) * * * Copyright (C) 2002 Helio Chissini de Castro * 2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // KMix #include "mixer_alsa.h" #include "core/kmixdevicemanager.h" #include "core/mixer.h" #include "core/volume.h" // KDE #include // Qt #include // STD Headers #include #include #include #include #include //#include "core/mixer.h" //class Mixer; // #define if you want MUCH debugging output //#define ALSA_SWITCH_DEBUG //#define KMIX_ALSA_VOLUME_DEBUG Mixer_Backend* ALSA_getMixer(Mixer *mixer, int device ) { Mixer_Backend *l_mixer; l_mixer = new Mixer_ALSA(mixer, device ); return l_mixer; } Mixer_ALSA::Mixer_ALSA( Mixer* mixer, int device ) : Mixer_Backend(mixer, device ) { m_fds = 0; _handle = 0; ctl_handle = 0; _initialUpdate = true; } Mixer_ALSA::~Mixer_ALSA() { close(); } int Mixer_ALSA::identify( snd_mixer_selem_id_t *sid ) { const char * cname = snd_mixer_selem_id_get_name( sid ); QByteArray name = QByteArray::fromRawData( cname, qstrlen(cname) ).toLower(); if (name.contains("master" )) return MixDevice::VOLUME; if (name.contains("master mono")) return MixDevice::VOLUME; if (name.contains("front" ) && !name.contains("mic" )) return MixDevice::VOLUME; if (name.contains("pc speaker" )) return MixDevice::SPEAKER; if (name.contains("capture" )) return MixDevice::RECMONITOR; if (name.contains("music" )) return MixDevice::MIDI; if (name.contains("synth" )) return MixDevice::MIDI; if (name.contains("fm" )) return MixDevice::MIDI; if (name.contains("headphone" )) return MixDevice::HEADPHONE; if (name.contains("bass" )) return MixDevice::BASS; if (name.contains("treble" )) return MixDevice::TREBLE; if (name.contains("cd" )) return MixDevice::CD; if (name.contains("video" )) return MixDevice::VIDEO; if (name.contains("pcm" )) return MixDevice::AUDIO; if (name.contains("wave" )) return MixDevice::AUDIO; if (name.contains("surround" )) return MixDevice::SURROUND_BACK; if (name.contains("center" )) return MixDevice::SURROUND_CENTERFRONT; if (name.contains("ac97" )) return MixDevice::AC97; if (name.contains("coaxial" )) return MixDevice::DIGITAL; if (name.contains("optical" )) return MixDevice::DIGITAL; if (name.contains("iec958" )) return MixDevice::DIGITAL; if (name.contains("digital" )) return MixDevice::DIGITAL; if (name.contains("mic boost" )) return MixDevice::MICROPHONE_BOOST; if (name.contains("mic front" )) return MixDevice::MICROPHONE_FRONT; if (name.contains("front mic" )) return MixDevice::MICROPHONE_FRONT; if (name.contains("mic" )) return MixDevice::MICROPHONE; if (name.contains("lfe" )) return MixDevice::SURROUND_LFE; if (name.contains("monitor" )) return MixDevice::RECMONITOR; if (name.contains("3d" )) return MixDevice::SURROUND; if (name.contains("side" )) return MixDevice::SURROUND_BACK; return MixDevice::EXTERNAL; } int Mixer_ALSA::open() { int masterChosenQuality = 0; int err; snd_mixer_elem_t *elem; snd_mixer_selem_id_t *sid; bool USE_ALSO_ALLOCA = true; // TODO remove alloca() when adding "delete sid" in the destructor or close() if (USE_ALSO_ALLOCA) { snd_mixer_selem_id_alloca( &sid ); } // Determine a card name if( m_devnum < -1 || m_devnum > 31 ) devName = "default"; else devName = QString( "hw:%1" ).arg( m_devnum ); // Open the card err = openAlsaDevice(devName); if ( err != 0 ) { return err; } _udi = KMixDeviceManager::instance()->getUDI_ALSA(m_devnum); if ( _udi.isEmpty() ) { QString msg("No UDI found for '"); msg += devName; msg += "'. Hotplugging not possible"; qCWarning(KMIX_LOG) << msg; } // Run a loop over all controls of the card unsigned int idx = 0; for ( elem = snd_mixer_first_elem( _handle ); elem; elem = snd_mixer_elem_next( elem ) ) { // If element is not active, just skip if ( ! snd_mixer_selem_is_active ( elem ) ) { continue; } /* --- Create basic control structures: snd_mixer_selem_id_t*, ID, ... --------- */ // snd_mixer_selem_id_t* // I believe we must malloc it ourself (just guessing due to missing ALSA documentation) snd_mixer_selem_id_malloc ( &sid ); // !! Return code should be checked. Resource must be freed when unplugging card snd_mixer_selem_get_id( elem, sid ); // Generate ID QString mdID("%1:%2"); mdID = mdID.arg(snd_mixer_selem_id_get_name ( sid ) ) .arg(snd_mixer_selem_id_get_index( sid ) ); mdID.replace(' ','_'); // Any key/ID we use, must not uses spaces (rule) - MixDevice::ChannelType ct = (MixDevice::ChannelType)identify( sid ); + const MixDevice::ChannelType ct = static_cast(identify(sid)); /* ------------------------------------------------------------------------------- */ Volume* volPlay = 0; Volume* volCapture = 0; QList enumList; if ( snd_mixer_selem_is_enumerated(elem) ) { // --- Enumerated --- addEnumerated(elem, enumList); } else { volPlay = addVolume(elem, false); volCapture = addVolume(elem, true ); } QString readableName; readableName = snd_mixer_selem_id_get_name( sid ); int controlInstanceIndex = snd_mixer_selem_id_get_index( sid ); if ( controlInstanceIndex > 0 ) { // Add a number to the control name, like "PCM 2", when the index is > 0 QString idxString; idxString.setNum(1+controlInstanceIndex); readableName += ' '; readableName += idxString; } // There can be an Enum-Control with the same name as a regular control. So we append a ".[cp]enum" prefix to always create a unique ID QString finalMixdeviceID = mdID; if ( ! enumList.isEmpty() ) { if (snd_mixer_selem_is_enum_capture ( elem ) ) finalMixdeviceID = mdID + ".cenum"; // capture enum else finalMixdeviceID = mdID + ".penum"; // playback enum } m_id2numHash[finalMixdeviceID] = idx; //qCDebug(KMIX_LOG) << "m_id2numHash[mdID] mdID=" << mdID << " idx=" << idx; mixer_elem_list.append( elem ); mixer_sid_list.append( sid ); idx++; MixDevice* mdNew = new MixDevice(_mixer, finalMixdeviceID, readableName, ct ); if ( volPlay != 0 ) { mdNew->addPlaybackVolume(*volPlay); delete volPlay; } if ( volCapture != 0 ) { mdNew->addCaptureVolume (*volCapture); delete volCapture; } if ( !enumList.isEmpty() ) { mdNew->addEnums(enumList); qDeleteAll(enumList); // clear temporary list } shared_ptr md = mdNew->addToPool(); m_mixDevices.append( md ); // --- Recommended master ---------------------------------------- if ( md->playbackVolume().hasVolume() ) { if ( mdID == "Master:0" && masterChosenQuality < 100 ) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 100; } else if ( mdID == "PCM:0" && masterChosenQuality < 80) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 80; } else if ( mdID == "Front:0" && masterChosenQuality < 60) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 60; } else if ( mdID == "DAC:0" && masterChosenQuality < 50) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 50; } else if ( mdID == "Headphone:0" && masterChosenQuality < 40) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 40; } else if ( mdID == "Master Mono:0" && masterChosenQuality < 30) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 30; } } } // for all elems m_isOpen = true; // return with success setupAlsaPolling(); // For updates return 0; } // warnOnce will make sure we only print the first ALSA device not found bool Mixer_ALSA::warnOnce = true; /** * This opens a ALSA device for further interaction. * As this is "slightly" more complicated than calling ::open(), it is put in a separate method. */ int Mixer_ALSA::openAlsaDevice(const QString& devName) { int err; QString probeMessage; probeMessage += "Trying ALSA Device '" + devName + "': "; if ( ( err = snd_ctl_open ( &ctl_handle, devName.toLatin1().data(), 0 ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_ctl_open err=" << snd_strerror(err); } return Mixer::ERR_OPEN; } // Mixer name snd_ctl_card_info_t *hw_info; snd_ctl_card_info_alloca(&hw_info); if ( ( err = snd_ctl_card_info ( ctl_handle, hw_info ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_ctl_card_info err=" << snd_strerror(err); } //_stateMessage = errorText( Mixer::ERR_READ ); snd_ctl_close( ctl_handle ); return Mixer::ERR_READ; } const char* mixer_card_name = snd_ctl_card_info_get_name( hw_info ); //QString mixer_card_name_QString = mixer_card_name; registerCard(mixer_card_name); snd_ctl_close( ctl_handle ); /* open mixer device */ if ( ( err = snd_mixer_open ( &_handle, 0 ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_mixer_open err=" << snd_strerror(err); } _handle = 0; return Mixer::ERR_OPEN; // if we cannot open the mixer, we have no devices } if ( ( err = snd_mixer_attach ( _handle, devName.toLatin1().data() ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_mixer_attach err=" << snd_strerror(err); } return Mixer::ERR_OPEN; } if ( ( err = snd_mixer_selem_register ( _handle, NULL, NULL ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_mixer_selem_register err=" << snd_strerror(err); } return Mixer::ERR_READ; } if ( ( err = snd_mixer_load ( _handle ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_mixer_load err=" << snd_strerror(err); } close(); return Mixer::ERR_READ; } Mixer_ALSA::warnOnce = true; qCDebug(KMIX_LOG) << probeMessage << "found"; return 0; } /** * Setup for select on stdin and the mixer fd. Every call * * @return A return value from Mixer::MixerError */ int Mixer_ALSA::setupAlsaPolling() { // --- Step 1: Retrieve FD's from ALSALIB int err; int countNew = 0; if ((countNew = snd_mixer_poll_descriptors_count(_handle)) < 0) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << countNew << "\n"; return Mixer::ERR_OPEN; } /* * The following "if (true)" read in earlier versions: * if ( countNew != m_sns.size() ) * * This mimics alsamixer behaviour. But reality has proven that * it is not enough to check for size change. Situations came up where * the size was identical, but the descriptors changed. This especially * seems to happen shortly after the kernel loads a soundcard driver and alsalib * initializes it. Very hard to reproduce, so I do expect some kind of race condition * there. * So the final solution is to ALWAYS use the freshest fd's * delivered by the snd_mixer_poll_descriptors_count() call from above. */ if (true) { // As documentation purpose, please keep the "if (true)" and the comment above explaining it. while (!m_sns.isEmpty()) delete m_sns.takeFirst(); free(m_fds); - m_fds = (struct pollfd*)calloc(countNew, sizeof(struct pollfd)); + m_fds = static_cast(calloc(countNew, sizeof(struct pollfd))); if (m_fds == NULL) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , calloc() = null" << "\n"; return Mixer::ERR_OPEN; } if ((err = snd_mixer_poll_descriptors(_handle, m_fds, countNew)) < 0) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << err << "\n"; return Mixer::ERR_OPEN; } if (err != countNew) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << err << " m_count=" << countNew << "\n"; return Mixer::ERR_OPEN; } // --- Step 2: Create QSocketNotifier's for the FD's for ( int i = 0; i < countNew; ++i ) { QSocketNotifier* qsn = new QSocketNotifier(m_fds[i].fd, QSocketNotifier::Read); m_sns.append(qsn); connect(qsn, SIGNAL(activated(int)), SLOT(readSetFromHW()), Qt::QueuedConnection); } } return 0; } void Mixer_ALSA::addEnumerated(snd_mixer_elem_t *elem, QList& enumList) { // --- get Enum names START --- int numEnumitems = snd_mixer_selem_get_enum_items(elem); if ( numEnumitems > 0 ) { // OK. no error for (int iEnum = 0; iEnum ignore this entry } } Volume* Mixer_ALSA::addVolume(snd_mixer_elem_t *elem, bool capture) { Volume* vol = 0; long maxVolume = 0, minVolume = 0; // Add volumes if ( !capture && snd_mixer_selem_has_playback_volume(elem) ) { snd_mixer_selem_get_playback_volume_range( elem, &minVolume, &maxVolume ); } else if ( capture && snd_mixer_selem_has_capture_volume(elem) ) { snd_mixer_selem_get_capture_volume_range( elem, &minVolume, &maxVolume ); } // Check if this control has at least one volume control bool hasVolume = snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem); // Check if a appropriate switch is present (appropriate means, based o nthe "capture" parameter) bool hasCommonSwitch = snd_mixer_selem_has_common_switch ( elem ); bool hasSwitch = hasCommonSwitch | capture ? snd_mixer_selem_has_capture_switch ( elem ) : snd_mixer_selem_has_playback_switch ( elem ); if ( hasVolume || hasSwitch ) { //qCDebug(KMIX_LOG) << "Add somthing with chn=" << chn << ", capture=" << capture; vol = new Volume( maxVolume, minVolume, hasSwitch, capture); // Add volumes if ( !capture && snd_mixer_selem_has_playback_volume(elem) ) { if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::RIGHT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_CENTER)) vol->addVolumeChannel(VolumeChannel(Volume::CENTER)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDLEFT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDRIGHT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_CENTER )) vol->addVolumeChannel(VolumeChannel(Volume::REARCENTER)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_WOOFER )) vol->addVolumeChannel(VolumeChannel(Volume::WOOFER)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_SIDE_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDELEFT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_SIDE_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDERIGHT)); } else if ( capture && snd_mixer_selem_has_capture_volume(elem) ) { if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::RIGHT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_CENTER)) vol->addVolumeChannel(VolumeChannel(Volume::CENTER)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDLEFT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDRIGHT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_CENTER )) vol->addVolumeChannel(VolumeChannel(Volume::REARCENTER)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_WOOFER )) vol->addVolumeChannel(VolumeChannel(Volume::WOOFER)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_SIDE_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDELEFT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_SIDE_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDERIGHT)); } } return vol; } void Mixer_ALSA::deinitAlsaPolling() { if ( m_fds ) free( m_fds ); m_fds = 0; while (!m_sns.isEmpty()) delete m_sns.takeFirst(); } int Mixer_ALSA::close() { // qCDebug(KMIX_LOG) << "close " << this; int ret=0; m_isOpen = false; if ( ctl_handle != 0) { //snd_ctl_close( ctl_handle ); ctl_handle = 0; } if ( _handle != 0 ) { //qCDebug(KMIX_LOG) << "IN Mixer_ALSA::close()"; snd_mixer_free ( _handle ); if ( ( ret = snd_mixer_detach ( _handle, devName.toLatin1().data() ) ) < 0 ) { qCDebug(KMIX_LOG) << "snd_mixer_detach err=" << snd_strerror(ret); } int ret2 = 0; if ( ( ret2 = snd_mixer_close ( _handle ) ) < 0 ) { qCDebug(KMIX_LOG) << "snd_mixer_close err=" << snd_strerror(ret2); if ( ret == 0 ) ret = ret2; // no error before => use current error code } _handle = 0; //qCDebug(KMIX_LOG) << "OUT Mixer_ALSA::close()"; } mixer_elem_list.clear(); mixer_sid_list.clear(); m_id2numHash.clear(); deinitAlsaPolling(); closeCommon(); return ret; } /** * Resolve index to a control (snd_mixer_elem_t*) * @par idx Index to query. For any invalid index (including -1) returns a 0 control. */ snd_mixer_elem_t* Mixer_ALSA::getMixerElem(int idx) { snd_mixer_elem_t* elem = 0; if ( ! m_isOpen ) return elem; // unplugging guard if ( idx == -1 ) { return elem; } if ( int( mixer_sid_list.count() ) > idx ) { snd_mixer_selem_id_t * sid = mixer_sid_list[ idx ]; // The next line (hopefully) only finds selem's, not elem's. elem = snd_mixer_find_selem(_handle, sid); if ( elem == 0 ) { // !! Check, whether the warning should be omitted. Probably // Route controls are non-simple elements. qCDebug(KMIX_LOG) << "Error finding mixer element " << idx; } } return elem; /* I would have liked to use the following trivial implementation instead of the code above. But it will also return elem's. which are not selem's. As there is no way to check an elem's type (e.g. elem->type == SND_MIXER_ELEM_SIMPLE), callers of getMixerElem() cannot check the type. :-( snd_mixer_elem_t* elem = mixer_elem_list[ devnum ]; return elem; */ } int Mixer_ALSA::id2num(const QString& id) { //qCDebug(KMIX_LOG) << "id2num() id=" << id; int num = -1; if ( m_id2numHash.contains(id) ) { num = m_id2numHash[id]; } //qCDebug(KMIX_LOG) << "id2num() num=" << num; return num; } bool Mixer_ALSA::hasChangedControls() { if ( !m_fds || !m_isOpen ) return false; setupAlsaPolling(); // Poll on fds with 10ms timeout // Hint: alsamixer has an infinite timeout, but we cannot do this because we would block // the X11 event handling (Qt event loop) with this. int finished = poll(m_fds, m_sns.size(), 10); // TODO Could we pass 0 as timeout here? It makes no real sense to wait! if (finished > 0) { unsigned short revents; if (snd_mixer_poll_descriptors_revents(_handle, m_fds, m_sns.size(), &revents) >= 0) { if (revents & POLLNVAL) { /* Bug 127294 shows, that we receive POLLNVAL when the user unplugs an USB soundcard. Lets close the card. */ qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , Error: poll() returns POLLNVAL\n"; close(); // Card was unplugged (unplug, driver unloaded) } else if (revents & POLLERR) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , Error: poll() returns POLLERR\n"; } else if (revents & POLLIN) { //qCDebug(KMIX_LOG) << "Mixer_ALSA::prepareUpdate() 7\n"; int eventCount = snd_mixer_handle_events(_handle); if (eventCount >= 0) { /* * Treating everything that is not an error as a change, even if eventCount == 0. * For example, when unplugging the headphones from my ThinkPad Laptop ALSA reports "POLLIN" with eventCount == 0. * On the other hand, this means I can not likely detect changes */ // qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() delivered changes. eventCount=" << eventCount; return true; } else { qCWarning(KMIX_LOG) << "Mixer_ALSA::poll() , Error: poll() returns POLLIN with errno=" << eventCount; } } } } return false; } bool Mixer_ALSA::isRecsrcHW( const QString& id ) { int devnum = id2num(id); bool isCurrentlyRecSrc = false; snd_mixer_elem_t *elem = getMixerElem( devnum ); if ( !elem ) { return false; } if ( snd_mixer_selem_has_capture_switch( elem ) ) { // Has a on-off switch // Yes, this element can be record source. But the user can switch it off, so lets see if it is switched on. int swLeft; int ret = snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &swLeft ); if ( ret != 0 ) qCDebug(KMIX_LOG) << "snd_mixer_selem_get_capture_switch() failed 1\n"; if (snd_mixer_selem_has_capture_switch_joined( elem ) ) { isCurrentlyRecSrc = (swLeft != 0); #ifdef ALSA_SWITCH_DEBUG qCDebug(KMIX_LOG) << "has_switch joined: #" << devnum << " >>> " << swLeft << " : " << isCurrentlyRecSrc; #endif } else { int swRight; snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_RIGHT, &swRight ); isCurrentlyRecSrc = ( (swLeft != 0) || (swRight != 0) ); #ifdef ALSA_SWITCH_DEBUG qCDebug(KMIX_LOG) << "has_switch non-joined, state " << isCurrentlyRecSrc; #endif } } else { // Has no on-off switch if ( snd_mixer_selem_has_capture_volume( elem ) ) { // Has a volume, but has no OnOffSwitch => We assume that this is a fixed record source (always on). (esken) isCurrentlyRecSrc = true; #ifdef ALSA_SWITCH_DEBUG qCDebug(KMIX_LOG) << "has_no_switch, state " << isCurrentlyRecSrc; #endif } } return isCurrentlyRecSrc; } /** * Sets the ID of the currently selected Enum entry. * Warning: ALSA supports to have different enums selected on each channel * of the SAME snd_mixer_elem_t. KMix does NOT support that and * always sets both channels (0 and 1). */ void Mixer_ALSA::setEnumIdHW(const QString& id, unsigned int idx) { //qCDebug(KMIX_LOG) << "Mixer_ALSA::setEnumIdHW() id=" << id << " , idx=" << idx << ") 1\n"; int devnum = id2num(id); snd_mixer_elem_t *elem = getMixerElem( devnum ); for (int i = 0; i <= SND_MIXER_SCHN_LAST; ++i) { - int ret = snd_mixer_selem_set_enum_item(elem, (snd_mixer_selem_channel_id_t)i,idx); + int ret = snd_mixer_selem_set_enum_item(elem, static_cast(i), idx); if (ret < 0 && i == 0) { // Log errors only for one channel. This should be enough, and another reason is that I also do not check which channels are supported at all. qCCritical(KMIX_LOG) << "Mixer_ALSA::setEnumIdHW(" << devnum << "), errno=" << ret << "\n"; } } return; } /** * Return the ID of the currently selected Enum entry. * Warning: ALSA supports to have different enums selected on each channel * of the SAME snd_mixer_elem_t. KMix does NOT support that and * always shows the value of the first channel. */ unsigned int Mixer_ALSA::enumIdHW(const QString& id) { int devnum = id2num(id); snd_mixer_elem_t *elem = getMixerElem( devnum ); unsigned int idx = 0; if ( elem != 0 && snd_mixer_selem_is_enumerated(elem) ) { int ret = snd_mixer_selem_get_enum_item(elem,SND_MIXER_SCHN_FRONT_LEFT,&idx); if (ret < 0) { idx = 0; qCCritical(KMIX_LOG) << "Mixer_ALSA::enumIdHW(" << devnum << "), errno=" << ret << "\n"; } } return idx; } int Mixer_ALSA::readVolumeFromHW( const QString& id, shared_ptr md ) { Volume& volumePlayback = md->playbackVolume(); Volume& volumeCapture = md->captureVolume(); int devnum = id2num(id); int elem_sw; long vol; snd_mixer_elem_t *elem = getMixerElem( devnum ); if ( !elem ) { return Mixer::OK_UNCHANGED; } vol = Volume::MNONE; // --- playback volume if ( snd_mixer_selem_has_playback_volume( elem ) ) { if ( md->isVirtuallyMuted() ) { // Special code path for controls w/o physical mute switch. Doing it in all backends is not perfect, // but it saves a lot of code and removes a lot of complexity in the Volume and MixDevice classes. // Don't feed back the actual 0 volume back from the device to KMix. Just do nothing! } else { foreach (VolumeChannel vc, volumePlayback.getVolumes() ) { int ret = 0; switch(vc.chid) { case Volume::LEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , &vol); break; case Volume::RIGHT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , &vol); break; case Volume::CENTER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, &vol); break; case Volume::SURROUNDLEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_LEFT , &vol); break; case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , &vol); break; case Volume::REARCENTER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_CENTER , &vol); break; case Volume::WOOFER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_WOOFER , &vol); break; case Volume::REARSIDELEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , &vol); break; case Volume::REARSIDERIGHT: ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , &vol); break; default: qCDebug(KMIX_LOG) << "FATAL: Unknown channel type for playback << " << vc.chid << " ... please report this"; break; } if ( ret != 0 ) qCDebug(KMIX_LOG) << "readVolumeFromHW(" << devnum << ") [get_playback_volume] failed, errno=" << ret; else volumePlayback.setVolume( vc.chid, vol); //if (id== "Master:0" || id== "PCM:0" ) { qCDebug(KMIX_LOG) << "volumePlayback control=" << id << ", chid=" << i << ", vol=" << vol; } } } } // has playback volume // --- playback switch // TODO: What about has_common_switch() if ( snd_mixer_selem_has_playback_switch( elem ) ) { snd_mixer_selem_get_playback_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &elem_sw ); md->setMuted( elem_sw == 0 ); } vol = Volume::MNONE; // --- capture volume if ( snd_mixer_selem_has_capture_volume ( elem ) ) { foreach (VolumeChannel vc, volumeCapture.getVolumes() ) { int ret = 0; switch(vc.chid) { case Volume::LEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , &vol); break; case Volume::RIGHT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , &vol); break; case Volume::CENTER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, &vol); break; case Volume::SURROUNDLEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_LEFT , &vol); break; case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , &vol); break; case Volume::REARCENTER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_CENTER , &vol); break; case Volume::WOOFER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_WOOFER , &vol); break; case Volume::REARSIDELEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , &vol); break; case Volume::REARSIDERIGHT: ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , &vol); break; default: qCDebug(KMIX_LOG) << "FATAL: Unknown channel type for capture << " << vc.chid << " ... please report this"; break; } if ( ret != 0 ) qCDebug(KMIX_LOG) << "readVolumeFromHW(" << devnum << ") [get_capture_volume] failed, errno=" << ret; else volumeCapture.setVolume( vc.chid, vol); } } // has capture volume // --- capture switch // TODO: What about has_common_switch() if ( snd_mixer_selem_has_capture_switch( elem ) ) { snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &elem_sw ); md->setRecSource( elem_sw == 1 ); // Refresh the capture switch information of *all* controls of this card. // Doing it for all is necessary, because enabling one record source often // automatically disables another record source (due to the hardware design) foreach ( shared_ptr md, m_mixDevices ) { bool isRecsrc = isRecsrcHW( md->id() ); // qCDebug(KMIX_LOG) << "Mixer::setRecordSource(): isRecsrcHW(" << md->id() << ") =" << isRecsrc; md->setRecSource( isRecsrc ); } } // The state Mixer::OK_UNCHANGED is not implemented. It is not strictly required for // non-pollling backends. return Mixer::OK; } int Mixer_ALSA::writeVolumeToHW( const QString& id, shared_ptr md ) { Volume& volumePlayback = md->playbackVolume(); Volume& volumeCapture = md->captureVolume(); int devnum = id2num(id); snd_mixer_elem_t *elem = getMixerElem( devnum ); if ( !elem ) { return 0; } // --- playback switch bool hasPlaybackSwitch = snd_mixer_selem_has_playback_switch( elem ) || snd_mixer_selem_has_common_switch ( elem ); if (hasPlaybackSwitch) { int sw = 0; if (!md->isMuted()) sw = !sw; // invert all bits snd_mixer_selem_set_playback_switch_all(elem, sw); } // --- playback volume if ( snd_mixer_selem_has_playback_volume( elem ) ) { // qCDebug(KMIX_LOG) << "phys=" << md->hasPhysicalMuteSwitch() << ", muted=" << md->isMuted(); if ( md->isVirtuallyMuted() ) { // Special code path for controls w/o physical mute switch. Doing it in all backends is not perfect, // but it saves a lot of code and removes a lot of complexity in the Volume and MixDevice classes. - int ret = snd_mixer_selem_set_playback_volume_all( elem, (long)0); + int ret = snd_mixer_selem_set_playback_volume_all(elem, 0L); if ( ret != 0 ) qCDebug(KMIX_LOG) << "writeVolumeToHW(" << devnum << ") [set_playback_volume] failed, errno=" << ret; } else { foreach (VolumeChannel vc, volumePlayback.getVolumes() ) { int ret = 0; switch(vc.chid) { case Volume::LEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , vc.volume); break; case Volume::RIGHT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , vc.volume); break; case Volume::CENTER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, vc.volume); break; case Volume::SURROUNDLEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_LEFT , vc.volume); break; case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , vc.volume); break; case Volume::REARCENTER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_CENTER , vc.volume); break; case Volume::WOOFER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_WOOFER , vc.volume); break; case Volume::REARSIDELEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , vc.volume); break; case Volume::REARSIDERIGHT: ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , vc.volume); break; default: qCDebug(KMIX_LOG) << "FATAL: Unknown channel type for playback << " << vc.chid << " ... please report this"; break; } if ( ret != 0 ) qCDebug(KMIX_LOG) << "writeVolumeToHW(" << devnum << ") [set_playback_volume] failed, errno=" << ret; //if (id== "Master:0" || id== "PCM:0" ) { qCDebug(KMIX_LOG) << "volumePlayback control=" << id << ", chid=" << vc.chid << ", vol=" << vc.volume; } } } } // has playback volume // --- capture volume if ( snd_mixer_selem_has_capture_volume ( elem ) ) { foreach (VolumeChannel vc, volumeCapture.getVolumes() ) { int ret = 0; switch(vc.chid) { case Volume::LEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , vc.volume); break; case Volume::RIGHT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , vc.volume); break; case Volume::CENTER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, vc.volume); break; case Volume::SURROUNDLEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_LEFT , vc.volume); break; case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , vc.volume); break; case Volume::REARCENTER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_CENTER , vc.volume); break; case Volume::WOOFER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_WOOFER , vc.volume); break; case Volume::REARSIDELEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , vc.volume); break; case Volume::REARSIDERIGHT: ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , vc.volume); break; default: qCDebug(KMIX_LOG) << "FATAL: Unknown channel type for capture << " << vc.chid << " ... please report this"; break; } if ( ret != 0 ) qCDebug(KMIX_LOG) << "writeVolumeToHW(" << devnum << ") [set_capture_volume] failed, errno=" << ret; //if (id== "Master:0" || id== "PCM:0" ) { qCDebug(KMIX_LOG) << "volumecapture control=" << id << ", chid=" << i << ", vol=" << vc.volume; } } } // has capture volume // --- capture switch if ( snd_mixer_selem_has_capture_switch( elem ) ) { // Hint: snd_mixer_selem_has_common_switch() is already covered in the playback . // switch. This is probably enough. It would be helpful, if the ALSA project would // write documentation. Until then, I need to continue guessing semantics. int sw = 0; if ( md->isRecSource()) sw = !sw; // invert all bits snd_mixer_selem_set_capture_switch_all( elem, sw ); } return 0; } QString Mixer_ALSA::errorText( int mixer_error ) { QString l_s_errmsg; switch ( mixer_error ) { case Mixer::ERR_PERM: l_s_errmsg = i18n("You do not have permission to access the alsa mixer device.\n" \ "Please verify if all alsa devices are properly created."); break; case Mixer::ERR_OPEN: l_s_errmsg = i18n("Alsa mixer cannot be found.\n" \ "Please check that the soundcard is installed and the\n" \ "soundcard driver is loaded.\n" ); break; default: l_s_errmsg = Mixer_Backend::errorText( mixer_error ); } return l_s_errmsg; } QString ALSA_getDriverName() { return QStringLiteral("ALSA"); } QString Mixer_ALSA::getDriverName() { return QStringLiteral("ALSA"); } diff --git a/core/ControlManager.cpp b/core/ControlManager.cpp index 4c250885..a208f874 100644 --- a/core/ControlManager.cpp +++ b/core/ControlManager.cpp @@ -1,196 +1,197 @@ /* 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() { 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) { bool listenersModified = false; QSet processedListeners; do { listenersModified = false; QList::iterator it; for (it = listeners.begin(); it != listeners.end(); ++it) { Listener& listener = *it; bool mixerIsOfInterest = listener.getMixerId().isEmpty() || mixerId.isEmpty() || listener.getMixerId() == mixerId; 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) { bool success = QMetaObject::invokeMethod(listener.getTarget(), "controlsChange", Qt::DirectConnection, Q_ARG(int, changeType)); if (GlobalConfig::instance().data.debugControlManager) { qCDebug(KMIX_LOG) << "Listener " << listener.getSourceId() <<" is interested in " << mixerId << ", " << ControlChangeType::toString(changeType); } if (!success) { qCCritical(KMIX_LOG) << "Listener Failed to send to " << listener.getTarget()->metaObject()->className(); } 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); if (GlobalConfig::instance().data.debugControlManager) { qCDebug(KMIX_LOG) << "Announcing " << ControlChangeType::toString(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 sourceId Only for logging */ void ControlManager::addListener(QString mixerId, ControlChangeType::Type changeType, QObject* target, 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; } - for ( ControlChangeType::Type ct = ControlChangeType::TypeFirst; ct != ControlChangeType::TypeLast; ct = (ControlChangeType::Type)(ct << 1)) + for ( ControlChangeType::Type ct = ControlChangeType::TypeFirst; ct != ControlChangeType::TypeLast; + ct = static_cast(ct << 1)) { if ( changeType & ct ) { // Add all listeners. Listener listener = Listener(mixerId, ct, target, sourceId); listeners.append(listener); listenersChanged = true; } } if (GlobalConfig::instance().data.debugControlManager) { 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) { QMutableListIterator it(listeners); while ( it.hasNext()) { 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; it.remove(); // Hint: As we have actual objects no explicit delete is needed listenersChanged = true; } } } void ControlManager::warnUnexpectedChangeType(ControlChangeType::Type 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) { Listener& listener = *it; if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "Listener still connected. Closing it. source=" << listener.getSourceId() << "listener=" << listener.getTarget()->metaObject()->className(); } } diff --git a/core/ControlManager.h b/core/ControlManager.h index 7a297acf..5f1c2691 100644 --- a/core/ControlManager.h +++ b/core/ControlManager.h @@ -1,150 +1,150 @@ /* 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 "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 { 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 = - (ControlChangeType::Type) (ct << 1)) + 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) { switch ( type ) { case 1: return Volume; case 2: return ControlList; case 4: return GUI; case 8: return MasterChanged; default: return None; } }; }; class Listener { public: Listener(const QString mixerId, ControlChangeType::Type changeType, QObject* target, 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; }; private: QString mixerId; ControlChangeType::Type 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/mixdevice.cpp b/core/mixdevice.cpp index 2153ac89..7502f247 100644 --- a/core/mixdevice.cpp +++ b/core/mixdevice.cpp @@ -1,480 +1,481 @@ /* * 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 "core/mixdevice.h" #include #include #include "core/ControlPool.h" #include "core/mixer.h" #include "dbus/dbuscontrolwrapper.h" #include "gui/guiprofile.h" #include "core/volume.h" static const QString channelTypeToIconName( MixDevice::ChannelType type ) { switch (type) { case MixDevice::AUDIO: return "mixer-pcm"; case MixDevice::BASS: case MixDevice::SURROUND_LFE: // "LFE" SHOULD have an own icon return "mixer-lfe"; case MixDevice::CD: return "mixer-cd"; case MixDevice::EXTERNAL: return "mixer-line"; case MixDevice::MICROPHONE: return "mixer-microphone"; case MixDevice::MIDI: return "mixer-midi"; case MixDevice::RECMONITOR: return "mixer-capture"; case MixDevice::TREBLE: return "mixer-pcm-default"; case MixDevice::UNKNOWN: return "mixer-front"; case MixDevice::VOLUME: return "mixer-master"; case MixDevice::VIDEO: return "mixer-video"; case MixDevice::SURROUND: case MixDevice::SURROUND_BACK: return "mixer-surround"; case MixDevice::SURROUND_CENTERFRONT: case MixDevice::SURROUND_CENTERBACK: return "mixer-surround-center"; case MixDevice::HEADPHONE: return "mixer-headset"; case MixDevice::DIGITAL: return "mixer-digital"; case MixDevice::AC97: return "mixer-ac97"; case MixDevice::SPEAKER: return "mixer-pc-speaker"; case MixDevice::MICROPHONE_BOOST: return "mixer-microphone-boost"; case MixDevice::MICROPHONE_FRONT_BOOST: return "mixer-microphone-front-boost"; case MixDevice::MICROPHONE_FRONT: return "mixer-microphone-front"; case MixDevice::KMIX_COMPOSITE: return "mixer-line"; case MixDevice::APPLICATION_AMAROK: return "amarok"; case MixDevice::APPLICATION_BANSHEE: return "media-player-banshee"; case MixDevice::APPLICATION_XMM2: return "xmms"; case MixDevice::APPLICATION_TOMAHAWK: return "tomahawk"; case MixDevice::APPLICATION_CLEMENTINE: return "application-x-clementine"; case MixDevice::APPLICATION_VLC: return "vlc"; case MixDevice::APPLICATION_STREAM: return "mixer-pcm"; } return "mixer-front"; } /** * Constructs a MixDevice. A MixDevice represents one channel or control of * the mixer hardware. A MixDevice has a type (e.g. PCM), a descriptive name * (for example "Master" or "Headphone" or "IEC 958 Output"), * can have a volume level (2 when stereo), can be recordable and muted. * The ChannelType tells which kind of control the MixDevice is. */ MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ) : _profControl(0) { - init(mixer, id, name, channelTypeToIconName(type), (MixSet*)0); + init(mixer, id, name, channelTypeToIconName(type), nullptr); } MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) : _profControl(0) { init(mixer, id, name, iconName, moveDestinationMixSet); } void MixDevice::init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) { _artificial = false; _applicationStream = false; _dbusControlWrapper = 0; // will be set in addToPool() _mixer = mixer; _id = id; _enumCurrentId = 0; mediaController = new MediaController(_id); if( name.isEmpty() ) _name = i18n("unknown"); else _name = name; if ( iconName.isEmpty() ) _iconName = "mixer-front"; else _iconName = iconName; _moveDestinationMixSet = moveDestinationMixSet; if ( _id.contains(' ') ) { // The key is used in the config file. IdbusControlWrappert MUST NOT contain spaces qCCritical(KMIX_LOG) << "MixDevice::setId(\"" << id << "\") . Invalid key - it must not contain spaces"; _id.replace(' ', '_'); } // qCDebug(KMIX_LOG) << "MixDevice::init() _id=" << _id; } /* * When a MixDevice shall be finally discarded, you must use this method to free its resources. * You must not use this MixDevice after calling close(). *
* The necessity stems from a memory leak due to object cycle (MixDevice<->DBusControlWrapper), so the reference * counting shared_ptr has no chance to clean up. See Bug 309464 for background information. */ void MixDevice::close() { delete _dbusControlWrapper; _dbusControlWrapper = 0; } MediaController* MixDevice::getMediaController() { return mediaController; } shared_ptr MixDevice::addToPool() { // qCDebug(KMIX_LOG) << "id=" << _mixer->id() << ":" << _id; shared_ptr thisSharedPtr(this); //shared_ptr thisSharedPtr = ControlPool::instance()->add(fullyQualifiedId, this); _dbusControlWrapper = new DBusControlWrapper( thisSharedPtr, dbusPath() ); return thisSharedPtr; } /** * Changes the internal state of this MixDevice. * It does not commit the change to the hardware. * * You might want to call something like m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); after calling this method. */ void MixDevice::increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType) { bool debugme = false; // bool debugme = id() == "PCM:0" ; if (volumeType & Volume::Playback) { Volume& volP = playbackVolume(); long inc = volP.volumeStep(decrease); if (debugme) qCDebug(KMIX_LOG) << ( decrease ? "decrease by " : "increase by " ) << inc ; if (isMuted()) { setMuted(false); } else { volP.changeAllVolumes(inc); if (debugme) qCDebug(KMIX_LOG) << (decrease ? "decrease by " : "increase by ") << inc; } } if (volumeType & Volume::Capture) { if (debugme) qCDebug(KMIX_LOG) << "VolumeType=" << volumeType << " c"; Volume& volC = captureVolume(); long inc = volC.volumeStep(decrease); volC.changeAllVolumes(inc); } } /** * Returns the name of the config group * @param Prefix of the group, e.g. "View_ALSA_USB_01" * @returns The config group name in the format "prefix.mixerId.controlId" */ QString MixDevice::configGroupName(QString prefix) { QString devgrp = QString("%1.%2.%3").arg(prefix, mixer()->id(), id()); return devgrp; } /** * Returns a fully qualified id of this control, as a String in the form "controlId@mixerId" * @return */ QString MixDevice::getFullyQualifiedId() { QString fqId = QString("%1@%2").arg(_id, _mixer->id()); return fqId; } /** * Creates a deep copy of the given Volume, and adds it to this MixDevice. * * @param playbackVol */ void MixDevice::addPlaybackVolume(Volume &playbackVol) { // Hint: "_playbackVolume" gets COPIED from "playbackVol", because the copy-constructor actually copies the volume levels. _playbackVolume = playbackVol; _playbackVolume.setSwitchType(Volume::PlaybackSwitch); } /** * Creates a deep copy of the given Volume, and adds it to this MixDevice. * * @param captureVol */ void MixDevice::addCaptureVolume (Volume &captureVol) { _captureVolume = captureVol; _captureVolume.setSwitchType(Volume::CaptureSwitch); } void MixDevice::addEnums(QList& ref_enumList) { if ( ref_enumList.count() > 0 ) { int maxEnumId = ref_enumList.count(); for (int i=0; i& MixDevice::enumValues() { return _enumValues; } const QString& MixDevice::id() const { return _id; } const QString MixDevice::dbusPath() { QString controlPath = _id; controlPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); controlPath.replace(QLatin1String("//"), QLatin1String("/")); if ( controlPath.endsWith( '/' ) ) { controlPath.chop(1); } return _mixer->dbusPath() + '/' + controlPath; } bool MixDevice::isMuted() { return ! _playbackVolume.isSwitchActivated(); } /** * Returns whether this MixDevice is virtually muted. Only MixDevice objects w/o a physical switch can be muted virtually. */ bool MixDevice::isVirtuallyMuted() { return !hasPhysicalMuteSwitch() && isMuted(); } void MixDevice::setMuted(bool mute) { _playbackVolume.setSwitch(!mute); } void MixDevice::toggleMute() { setMuted( _playbackVolume.isSwitchActivated()); } bool MixDevice::hasMuteSwitch() { return playbackVolume().hasVolume() || playbackVolume().hasSwitch(); } bool MixDevice::hasPhysicalMuteSwitch() { return playbackVolume().hasSwitch(); } bool MixDevice::isRecSource() { return ( _captureVolume.hasSwitch() && _captureVolume.isSwitchActivated() ); } bool MixDevice::isNotRecSource() { return ( _captureVolume.hasSwitch() && !_captureVolume.isSwitchActivated() ); } void MixDevice::setRecSource(bool value) { _captureVolume.setSwitch( value ); } bool MixDevice::isEnum() { return ( ! _enumValues.empty() ); } int MixDevice::mediaPlay() { return mixer()->mediaPlay(_id); } int MixDevice::mediaPrev() { return mixer()->mediaPrev(_id); } int MixDevice::mediaNext() { return mixer()->mediaNext(_id); } bool MixDevice::operator==(const MixDevice& other) const { return ( _id == other._id ); } void MixDevice::setControlProfile(ProfControl* control) { _profControl = control; } ProfControl* MixDevice::controlProfile() { return _profControl; } /** * This method is currently only called on "kmixctrl --restore" * * Normally we have a working _volume object already, which is very important, * because we need to read the minimum and maximum volume levels. * (Another solution would be to "equip" volFromConfig with maxInt and minInt values). */ bool MixDevice::read( KConfig *config, const QString& grp ) { if ( _mixer->isDynamic() || isArtificial() ) { qCDebug(KMIX_LOG) << "MixDevice::read(): This MixDevice does not permit volume restoration (i.e. because it is handled lower down in the audio stack). Ignoring."; return false; } QString devgrp = QString("%1.Dev%2").arg(grp, _id); KConfigGroup cg = config->group( devgrp ); //qCDebug(KMIX_LOG) << "MixDevice::read() of group devgrp=" << devgrp; readPlaybackOrCapture(cg, false); readPlaybackOrCapture(cg, true ); bool mute = cg.readEntry("is_muted", false); setMuted( mute ); bool recsrc = cg.readEntry("is_recsrc", false); setRecSource( recsrc ); int enumId = cg.readEntry("enum_id", -1); if ( enumId != -1 ) { setEnumId( enumId ); } return true; } void MixDevice::readPlaybackOrCapture(const KConfigGroup& config, bool capture) { Volume& volume = capture ? captureVolume() : playbackVolume(); for ( Volume::ChannelID chid=Volume::CHIDMIN; chid<= Volume::CHIDMAX; ) { QString volstr = getVolString(chid,capture); if ( config.hasKey(volstr) ) { volume.setVolume(chid, config.readEntry(volstr, 0)); } // if saved channel exists - chid = (Volume::ChannelID)( 1 + (int)chid); // ugly + // TODO: ugly, so find a better way (implement Volume::ChannelID::operator++) + chid = static_cast(1+static_cast(chid)); } // for all channels } /** * called on "kmixctrl --save" and from the GUI's (currently only on exit) */ bool MixDevice::write( KConfig *config, const QString& grp ) { if (_mixer->isDynamic() || isArtificial()) { // qCDebug(KMIX_LOG) << "MixDevice::write(): This MixDevice does not permit volume saving (i.e. because it is handled lower down in the audio stack). Ignoring."; return false; } QString devgrp = QString("%1.Dev%2").arg(grp, _id); KConfigGroup cg = config->group(devgrp); // qCDebug(KMIX_LOG) << "MixDevice::write() of group devgrp=" << devgrp; writePlaybackOrCapture(cg, false); writePlaybackOrCapture(cg, true ); cg.writeEntry("is_muted" , isMuted() ); cg.writeEntry("is_recsrc", isRecSource() ); cg.writeEntry("name", _name); if ( isEnum() ) { cg.writeEntry("enum_id", enumId() ); } return true; } void MixDevice::writePlaybackOrCapture(KConfigGroup& config, bool capture) { Volume& volume = capture ? captureVolume() : playbackVolume(); foreach (VolumeChannel vc, volume.getVolumes() ) { - config.writeEntry(getVolString(vc.chid,capture) , (int)vc.volume); + config.writeEntry(getVolString(vc.chid,capture), static_cast(vc.volume)); } // for all channels } QString MixDevice::getVolString(Volume::ChannelID chid, bool capture) { QString volstr = Volume::channelNameForPersistence(chid); if ( capture ) volstr += "Capture"; return volstr; } /** * Returns the playback volume level in percent. If the volume is muted, 0 is returned. * If the given MixDevice contains no playback volume, the capture volume isd used * instead, and 0 is returned if capturing is disabled for the given MixDevice. * * @returns The volume level in percent */ int MixDevice::getUserfriendlyVolumeLevel() { MixDevice* md = this; bool usePlayback = md->playbackVolume().hasVolume(); Volume& vol = usePlayback ? md->playbackVolume() : md->captureVolume(); bool isActive = usePlayback ? !md->isMuted() : md->isRecSource(); int val = isActive ? vol.getAvgVolumePercent(Volume::MALL) : 0; return val; } diff --git a/core/volume.cpp b/core/volume.cpp index 252a0d01..4fc8c1b1 100644 --- a/core/volume.cpp +++ b/core/volume.cpp @@ -1,396 +1,396 @@ /* * 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 "core/volume.h" #include "kmix_debug.h" // for operator<<() #include #include float Volume::VOLUME_STEP_DIVISOR = 20; float Volume::VOLUME_PAGESTEP_DIVISOR = 10; static Volume::ChannelMask channelMask[] = { Volume::MLEFT, Volume::MRIGHT, Volume::MCENTER, Volume::MWOOFER, Volume::MSURROUNDLEFT, Volume::MSURROUNDRIGHT, Volume::MREARSIDELEFT, Volume::MREARSIDERIGHT, Volume::MREARCENTER }; QString Volume::channelNameReadable(ChannelID id) { switch (id) { case LEFT: return (i18nc("Channel name", "Left")); case RIGHT: return (i18nc("Channel name", "Right")); case CENTER: return (i18nc("Channel name", "Center")); case WOOFER: return (i18nc("Channel name", "Subwoofer")); case SURROUNDLEFT: return (i18nc("Channel name", "Surround Left")); case SURROUNDRIGHT: return (i18nc("Channel name", "Surround Right")); case REARSIDELEFT: return (i18nc("Channel name", "Side Left")); case REARSIDERIGHT: return (i18nc("Channel name", "Side Right")); case REARCENTER: return (i18nc("Channel name", "Rear Center")); default: qCWarning(KMIX_LOG) << "called for unknown ID" << id; return (i18nc("Channel name", "Unknown")); } } QString Volume::channelNameForPersistence(ChannelID id) { switch (id) { case LEFT: return ("volumeL"); case RIGHT: return ("volumeR"); case CENTER: return ("volumeCenter"); case WOOFER: return ("volumeWoofer"); case SURROUNDLEFT: return ("volumeSurroundL"); case SURROUNDRIGHT: return ("volumeSurroundR"); case REARSIDELEFT: return ("volumeSideL"); case REARSIDERIGHT: return ("volumeSideR"); case REARCENTER: return ("volumeRearCenter"); default: qCWarning(KMIX_LOG) << "called for unknown ID" << id; return ("unknown"); } } // Forbidden/private. Only here because if there is no CaptureVolume we need the values initialized // And also QMap requires it. Volume::Volume() : _chmask(MNONE) , _minVolume(0) , _maxVolume(0) , _hasSwitch(false) , _switchActivated(false) , _switchType(None) , _isCapture(false) { } /** * Do not use. Only implicitely required for QMap. * * @deprecated Do not use */ VolumeChannel::VolumeChannel() : volume(0) , chid(Volume::NOCHANNEL) { } VolumeChannel::VolumeChannel(Volume::ChannelID c) : volume(0) , chid(c) { } Volume::Volume(long maxVolume, long minVolume, bool hasSwitch, bool isCapture ) { - init((ChannelMask)0, maxVolume, minVolume, hasSwitch, isCapture ); + init(static_cast(0), maxVolume, minVolume, hasSwitch, isCapture ); } /** * @deprecated */ void Volume::addVolumeChannels(ChannelMask chmask) { for ( Volume::ChannelID chid=Volume::CHIDMIN; chid<= Volume::CHIDMAX; ) { if ( chmask & channelMask[chid] ) { addVolumeChannel(VolumeChannel(chid)); } - chid = (Volume::ChannelID)( 1 + (int)chid); // ugly + chid = static_cast(1+static_cast(chid)); } // for all channels } void Volume::addVolumeChannel(VolumeChannel vc) { _volumesL.insert(vc.chid, vc); // Add the corresponding "muted version" of the channel. // VolumeChannel* zeroChannel = new VolumeChannel(vc.chid); // zeroChannel->volume = 0; // _volumesMuted.insert(zeroChannel->chid, *zeroChannel); // TODO remove _volumesMuted } void Volume::init( ChannelMask chmask, long maxVolume, long minVolume, bool hasSwitch, bool isCapture) { _chmask = chmask; _maxVolume = maxVolume; _minVolume = minVolume; _hasSwitch = hasSwitch; _isCapture = isCapture; //_muted = false; // Presume that the switch is active. This will always work: // a) Physical switches will be updated after start from the hardware. // b) Emulated virtual/switches will not receive updates from the hardware, so they shouldn't disable the channels. _switchActivated = true; } QMap Volume::getVolumesWhenActive() const { return _volumesL; } QMap Volume::getVolumes() const { return _volumesL; } /** * Returns the absolute change to do one "step" for this volume. This is similar to a page step in a slider, * namely a fixed percentage of the range. * One step is the percentage given by 100/VOLUME_STEP_DIVISOR. The * default VOLUME_STEP_DIVISOR is 20, so default change is 5% of the volumeSpan(). * * This method guarantees a minimum absolute change of 1, zero is never returned. * * It is NOT verified, that such a volume change would actually be possible. You might hit the upper or lower bounds * of the volume range. * * * @param decrease true, if you want a volume step that decreases the volume by one page step * @return The volume step. It will be negative if you have used decrease==true * */ long Volume::volumeStep(bool decrease) { long inc = volumeSpan() / Volume::VOLUME_STEP_DIVISOR; if ( inc == 0 ) inc = 1; if ( decrease ) inc *= -1; return inc; } // @ compatibility void Volume::setAllVolumes(long vol) { long int finalVol = volrange(vol); QMap::iterator it = _volumesL.begin(); while (it != _volumesL.end()) { it.value().volume = finalVol; //it.value().unmutedVolume= finalVol; ++it; } } void Volume::changeAllVolumes( long step ) { QMap::iterator it = _volumesL.begin(); while (it != _volumesL.end()) { long int finalVol = volrange(it.value().volume + step); it.value().volume = finalVol; // it.value().unmutedVolume= finalVol; ++it; } } /** * Sets the volume for the given Channel * @ compatibility */ void Volume::setVolume( ChannelID chid, long vol) { QMap::iterator it = _volumesL.find(chid); if ( it != _volumesL.end()) { it.value().volume = vol; //it.value().unmutedVolume = vol; } } /** * Copy the volume elements contained in v to this Volume object. */ // void Volume::setVolume(const Volume &v) // { // foreach (VolumeChannel vc, _volumesL ) // { // ChannelID chid = vc.chid; // v.getVolumes()[chid].volume = vc.volume; // //v.getVolumes()[chid].unmutedVolume = vc.volume; // } // } void Volume::setSwitch( bool active ) { _switchActivated = active; // if ( isCapture() ) // return; // // for playback volumes we will not only do the switch, but also set the volume to 0 // QMap::iterator it = _volumesL.begin(); // if ( active ) // { // while (it != _volumesL.end()) // { // VolumeChannel& vc = it.value(); // vc.volume = vc.unmutedVolume; // ++it; // } // } // else // { // while (it != _volumesL.end()) // { // VolumeChannel& vc = it.value(); // vc.unmutedVolume = vc.volume; // vc.volume = 0; // ++it; // } // } } long Volume::maxVolume() { return _maxVolume; } long Volume::minVolume() { return _minVolume; } long Volume::volumeSpan() { return _maxVolume - _minVolume + 1; } /** * Returns the volume of the given channel. */ long Volume::getVolume(ChannelID chid) { return _volumesL.value(chid).volume; } /** * Returns the volume of the given channel. If this Volume is inactive (switched off), 0 is returned. */ long Volume::getVolumeForGUI(ChannelID chid) { if (! isSwitchActivated() ) return 0; return _volumesL.value(chid).volume; } qreal Volume::getAvgVolume(ChannelMask chmask) { int avgVolumeCounter = 0; long long sumOfActiveVolumes = 0; foreach (VolumeChannel vc, _volumesL ) { if (channelMask[vc.chid] & chmask ) { sumOfActiveVolumes += vc.volume; ++avgVolumeCounter; } } if (avgVolumeCounter != 0) { qreal sumOfActiveVolumesQreal = sumOfActiveVolumes; sumOfActiveVolumesQreal /= avgVolumeCounter; return sumOfActiveVolumesQreal; } else return 0; } int Volume::getAvgVolumePercent(ChannelMask chmask) { qreal volume = getAvgVolume(chmask); // min=-100, max=200 => volSpan = 301 // volume = -50 => volShiftedToZero = -50+min = 50 qreal volSpan = volumeSpan(); qreal volShiftedToZero = volume - _minVolume; qreal percentReal = ( volSpan == 0 ) ? 0 : ( 100 * volShiftedToZero ) / ( volSpan - 1); int percent = qRound(percentReal); //qCDebug(KMIX_LOG) << "volSpan=" << volSpan << ", volume=" << volume << ", volShiftedToPositive=" << volShiftedToZero << ", percent=" << percent; return percent; } int Volume::count() { return getVolumes().count(); } /** * returns a "sane" volume level. This means, it is a volume level inside the * valid bounds */ long Volume::volrange( long vol ) { if ( vol < _minVolume ) { return _minVolume; } else if ( vol < _maxVolume ) { return vol; } else { return _maxVolume; } } std::ostream& operator<<(std::ostream& os, const Volume& vol) { os << "("; bool first = true; foreach ( const VolumeChannel vc, vol.getVolumes() ) { if ( !first ) os << ","; else first = false; os << vc.volume; } // all channels os << ")"; os << " [" << vol._minVolume << "-" << vol._maxVolume; if ( vol._switchActivated ) { os << " : switch active ]"; } else { os << " : switch inactive ]"; } return os; } QDebug operator<<(QDebug os, const Volume& vol) { os << "("; bool first = true; foreach ( VolumeChannel vc, vol.getVolumes() ) { if ( !first ) os << ","; else first = false; os << vc.volume; } // all channels os << ")"; os << " [" << vol._minVolume << "-" << vol._maxVolume; if ( vol._switchActivated ) { os << " : switch active ]"; } else { os << " : switch inactive ]"; } return os; } diff --git a/dbus/dbusmixerwrapper.cpp b/dbus/dbusmixerwrapper.cpp index cb965bf3..e26d805f 100644 --- a/dbus/dbusmixerwrapper.cpp +++ b/dbus/dbusmixerwrapper.cpp @@ -1,145 +1,146 @@ /* * 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(), - (ControlChangeType::Type)(ControlChangeType::ControlList | ControlChangeType::Volume), + // TODO: convert ControlChangeType to a QFlags + static_cast(ControlChangeType::ControlList|ControlChangeType::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) { ControlChangeType::Type type = ControlChangeType::fromInt(changeType); switch (type ) { case ControlChangeType::ControlList: createDeviceWidgets(); break; case ControlChangeType::Volume: refreshVolumeLevels(); break; default: ControlManager::warnUnexpectedChangeType(type, 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/gui/kmixdockwidget.cpp b/gui/kmixdockwidget.cpp index 16e0eca3..7250da8d 100644 --- a/gui/kmixdockwidget.cpp +++ b/gui/kmixdockwidget.cpp @@ -1,437 +1,437 @@ /* * 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) - (ControlChangeType::Type) (ControlChangeType::Volume | ControlChangeType::MasterChanged), this, + static_cast(ControlChangeType::Volume|ControlChangeType::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) { ControlChangeType::Type type = ControlChangeType::fromInt(changeType); switch (type ) { case ControlChangeType::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: refreshVolumeLevels(); break; default: ControlManager::warnUnexpectedChangeType(type, 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/ksmallslider.cpp b/gui/ksmallslider.cpp index 6ee9bd45..d63abd3c 100644 --- a/gui/ksmallslider.cpp +++ b/gui/ksmallslider.cpp @@ -1,437 +1,437 @@ /* * 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. */ #include "gui/ksmallslider.h" // For INT_MAX #include #include #include #include #include #include #include #include #include "core/mixer.h" KSmallSlider::KSmallSlider( int minValue, int maxValue, int pageStep, int value, Qt::Orientation orientation, QWidget *parent, const char * /*name*/ ) : QAbstractSlider( parent ) { init(); setOrientation(orientation); setRange(minValue, maxValue); setSingleStep(1); setPageStep(pageStep); setValue(value); setTracking(true); } void KSmallSlider::init() { grayed = false; setFocusPolicy( Qt::TabFocus ); colHigh = QColor(0,255,0); colLow = QColor(255,0,0); colBack = QColor(0,0,0); grayHigh = QColor(255,255,255); grayLow = QColor(128,128,128); grayBack = QColor(0,0,0); } int KSmallSlider::positionFromValue( int v ) const { return positionFromValue( v, available() ); } int KSmallSlider::valueFromPosition( int p ) const { if ( orientation() == Qt::Vertical ) { // Coordiante System starts at TopLeft, but the slider values increase from Bottom to Top // Thus "revert" the position int avail = available(); return valueFromPosition( avail - p, avail ); } else { // Horizontal everything is fine. Slider values match with Coordinate System return valueFromPosition( p, available() ); } } /* postionFromValue() discontinued in in Qt4 => taken from Qt3 */ int KSmallSlider::positionFromValue( int logical_val, int span ) const { if ( span <= 0 || logical_val < minimum() || maximum() <= minimum() ) return 0; if ( logical_val > maximum() ) return span; uint range = maximum() - minimum(); uint p = logical_val - minimum(); if ( range > (uint)INT_MAX/4096 ) { const int scale = 4096*2; return ( (p/scale) * span ) / (range/scale); // ### the above line is probably not 100% correct // ### but fixing it isn't worth the extreme pain... - } else if ( range > (uint)span ) { + } else if ( range > static_cast(span)) { return (2*p*span + range) / (2*range); } else { uint div = span / range; uint mod = span % range; return p*div + (2*p*mod + range) / (2*range); } //equiv. to (p*span)/range + 0.5 // no overflow because of this implicit assumption: // span <= 4096 } /* valueFromPositon() discontinued in in Qt4 => taken from Qt3 */ int KSmallSlider::valueFromPosition( int pos, int span ) const { if ( span <= 0 || pos <= 0 ) return minimum(); if ( pos >= span ) return maximum(); uint range = maximum() - minimum(); - if ( (uint)span > range ) + if (static_cast(span) > range ) return minimum() + (2*pos*range + span) / (2*span); else { uint div = range / span; uint mod = range % span; return minimum() + pos*div + (2*pos*mod + span) / (2*span); } // equiv. to minimum() + (pos*range)/span + 0.5 // no overflow because of this implicit assumption: // pos <= span < sqrt(INT_MAX+0.0625)+0.25 ~ sqrt(INT_MAX) } void KSmallSlider::resizeEvent( QResizeEvent * ) { update(); //QWidget::resizeEvent( ev ); } // Returns the really available space for the slider. If there is no space, 0 is returned; int KSmallSlider::available() const { int available = 0; if ( orientation() == Qt::Vertical) { available = height(); } else { available = width(); } if ( available > 1 ) { available -= 2; } else { available = 0; } return available; } namespace { void gradient( QPainter &p, bool hor, const QRect &rect, const QColor &ca, const QColor &cb, int /*ncols*/) { int rDiff, gDiff, bDiff; int rca, gca, bca, rcb, gcb, bcb; int x, y; if ((rect.width()<=0) || (rect.height()<=0)) return; rDiff = (rcb = cb.red()) - (rca = ca.red()); gDiff = (gcb = cb.green()) - (gca = ca.green()); bDiff = (bcb = cb.blue()) - (bca = ca.blue()); int rl = rca << 16; int gl = gca << 16; int bl = bca << 16; int rcdelta = ((1<<16) / ((!hor) ? rect.height() : rect.width())) * rDiff; int gcdelta = ((1<<16) / ((!hor) ? rect.height() : rect.width())) * gDiff; int bcdelta = ((1<<16) / ((!hor) ? rect.height() : rect.width())) * bDiff; // these for-loops could be merged, but the if's in the inner loop // would make it slow if (!hor) { for ( y = rect.top(); y <= rect.bottom(); ++y ) { rl += rcdelta; gl += gcdelta; bl += bcdelta; p.setPen(QColor(rl>>16, gl>>16, bl>>16)); p.drawLine(rect.left(), y, rect.right(), y); } } else { for( x = rect.left(); x <= rect.right(); ++x) { rl += rcdelta; gl += gcdelta; bl += bcdelta; p.setPen(QColor(rl>>16, gl>>16, bl>>16)); p.drawLine(x, rect.top(), x, rect.bottom()); } } } QColor interpolate( const QColor& low, const QColor& high, int percent ) { if ( percent<=0 ) return low; else if ( percent>=100 ) return high; else return QColor( low.red() + (high.red()-low.red()) * percent/100, low.green() + (high.green()-low.green()) * percent/100, low.blue() + (high.blue()-low.blue()) * percent/100 ); } } void KSmallSlider::paintEvent( QPaintEvent * ) { // qCDebug(KMIX_LOG) << "KSmallSlider::paintEvent: width() = " << width() << ", height() = " << height(); QPainter p( this ); int sliderPos = positionFromValue( QAbstractSlider::value() ); // ------------------------ draw 3d border --------------------------------------------- QStyleOptionSlider option; option.init(this); style()->drawPrimitive ( QStyle::PE_Frame, &option, &p ); // ------------------------ draw lower/left part ---------------------------------------- if ( width()>2 && height()>2 ) { if ( orientation() == Qt::Horizontal ) { QRect outer = QRect( 1, 1, sliderPos, height() - 2 ); // qCDebug(KMIX_LOG) << "KSmallSlider::paintEvent: outer = " << outer; if ( grayed ) gradient( p, true, outer, grayLow, interpolate( grayLow, grayHigh, 100*sliderPos/(width()-2) ), 32 ); else gradient( p, true, outer, colLow, interpolate( colLow, colHigh, 100*sliderPos/(width()-2) ), 32 ); } else { QRect outer = QRect( 1, height()-sliderPos-1, width() - 2, sliderPos-1 ); /* qCDebug(KMIX_LOG) << "KSmallSlider::paintEvent: sliderPos=" << sliderPos << "height()=" << height() << "width()=" << width() << "outer = " << outer << endl; */ if ( grayed ) gradient( p, false, outer, interpolate( grayLow, grayHigh, 100*sliderPos/(height()-2) ), grayLow, 32 ); else gradient( p, false, outer, interpolate( colLow, colHigh, 100*sliderPos/(height()-2) ), colLow, 32 ); } // -------- draw upper/right part -------------------------------------------------- QRect inner; if ( orientation() == Qt::Vertical ) { inner = QRect( 1, 1, width() - 2, height() - 2 -sliderPos ); } else { inner = QRect( sliderPos + 1, 1, width() - 2 - sliderPos, height() - 2 ); } if ( grayed ) { p.setBrush( grayBack ); p.setPen( grayBack ); } else { p.setBrush( colBack ); p.setPen( colBack ); } p.drawRect( inner ); } } void KSmallSlider::mousePressEvent( QMouseEvent *e ) { //resetState(); if ( e->button() == Qt::RightButton ) { return; } int pos = goodPart( e->pos() ); moveSlider( pos ); } void KSmallSlider::mouseMoveEvent( QMouseEvent *e ) { int pos = goodPart( e->pos() ); moveSlider( pos ); } void KSmallSlider::wheelEvent( QWheelEvent * qwe) { // qCDebug(KMIX_LOG) << "KSmallslider::wheelEvent()"; // bko313579 Do not use "delta", as that is setting more related to documents (Editor, Browser). KMix should // simply always use its own VOLUME_STEP_DIVISOR as a base for percentage change. bool decrease = qwe->delta() < 0; if (qwe->orientation() == Qt::Horizontal) // Reverse horizontal scroll: bko228780 decrease = !decrease; int inc = ( maximum() - minimum() ) / Volume::VOLUME_STEP_DIVISOR; if ( inc < 1) inc = 1; //qCDebug(KMIX_LOG) << "KSmallslider::wheelEvent() inc=" << inc << "delta=" << e->delta(); int newVal; if ( !decrease ) { newVal = QAbstractSlider::value() + inc; } else { newVal = QAbstractSlider::value() - inc; } setValue( newVal ); emit valueChanged(newVal); qwe->accept(); // Accept the event // Hint: Qt automatically triggers a valueChange() when we do setValue() } /* * Moves slider to a dedicated position. If the value has changed */ void KSmallSlider::moveSlider( int pos ) { int a = available(); int newPos = qMin( a, qMax( 0, pos ) ); // keep it inside the available bounds of the slider int newVal = valueFromPosition( newPos ); if ( newVal != value() ) { setValue( newVal ); emit valueChanged(newVal); // probably done by Qt: emit valueChanged( value() ); // Only for external use // probably we need update() here } update(); } int KSmallSlider::goodPart( const QPoint &p ) const { if ( orientation() == Qt::Vertical ) { return p.y() - 1; } else { return p.x() - 1; } } /***************** SIZE STUFF START ***************/ QSize KSmallSlider::sizeHint() const { //constPolish(); const int length = 25; const int thick = 10; if ( orientation() == Qt::Vertical ) return QSize( thick, length ); else return QSize( length, thick ); } QSize KSmallSlider::minimumSizeHint() const { QSize s(10,10); return s; } QSizePolicy KSmallSlider::sizePolicy() const { if ( orientation() == Qt::Vertical ) { //qCDebug(KMIX_LOG) << "KSmallSlider::sizePolicy() vertical value=(Fixed,MinimumExpanding)\n"; return QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Expanding ); } else { //qCDebug(KMIX_LOG) << "KSmallSlider::sizePolicy() horizontal value=(MinimumExpanding,Fixed)\n"; return QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); } } /***************** SIZE STUFF END ***************/ void KSmallSlider::setGray( bool value ) { if ( grayed!=value ) { grayed = value; update(); //repaint(); } } bool KSmallSlider::gray() const { return grayed; } void KSmallSlider::setColors( QColor high, QColor low, QColor back ) { colHigh = high; colLow = low; colBack = back; update(); //repaint(); } void KSmallSlider::setGrayColors( QColor high, QColor low, QColor back ) { grayHigh = high; grayLow = low; grayBack = back; update(); //repaint(); } diff --git a/gui/viewbase.cpp b/gui/viewbase.cpp index ee90012a..35c4fd88 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")); } // ---------- 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) { - if (qmdw->inherits("MixDeviceWidget")) + MixDeviceWidget *mdw = qobject_cast(qmdw); + if (mdw!=nullptr) { - MixDeviceWidget* mdw = (MixDeviceWidget*) qmdw; 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]; - if (qmdw->inherits("MixDeviceWidget")) + MixDeviceWidget *mdw = qobject_cast(qmdw); + if (mdw!=nullptr) { - MixDeviceWidget* mdw = (MixDeviceWidget*) qmdw; 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 c4ec6472..d3bed2e9 100644 --- a/gui/viewdockareapopup.cpp +++ b/gui/viewdockareapopup.cpp @@ -1,446 +1,447 @@ /* * 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 - (ControlChangeType::Type) (ControlChangeType::GUI | ControlChangeType::ControlList | ControlChangeType::Volume - | ControlChangeType::MasterChanged), this, QString("ViewDockAreaPopup")); + static_cast(ControlChangeType::GUI|ControlChangeType::ControlList| + ControlChangeType::Volume|ControlChangeType::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) { ControlChangeType::Type type = ControlChangeType::fromInt(changeType); switch (type ) { case ControlChangeType::ControlList: case ControlChangeType::MasterChanged: createDeviceWidgets(); break; case ControlChangeType::GUI: updateGuiOptions(); break; case ControlChangeType::Volume: refreshVolumeLevels(); break; default: ControlManager::warnUnexpectedChangeType(type, 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/viewsliders.cpp b/gui/viewsliders.cpp index 02972164..9a225c95 100644 --- a/gui/viewsliders.cpp +++ b/gui/viewsliders.cpp @@ -1,447 +1,446 @@ /* * 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(), - (ControlChangeType::Type) (ControlChangeType::GUI | ControlChangeType::ControlList | ControlChangeType::Volume), - this, QString("ViewSliders.%1").arg(mixer->id())); - + static_cast(ControlChangeType::GUI|ControlChangeType::ControlList|ControlChangeType::Volume), + this, QString("ViewSliders.%1").arg(mixer->id())); } ViewSliders::~ViewSliders() { ControlManager::instance().removeListener(this); delete _layoutMDW; // qDeleteAll(_separators); } void ViewSliders::controlsChange(int changeType) { ControlChangeType::Type type = ControlChangeType::fromInt(changeType); switch (type) { case ControlChangeType::ControlList: createDeviceWidgets(); break; case ControlChangeType::GUI: updateGuiOptions(); break; case ControlChangeType::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); 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/volumeslider.cpp b/gui/volumeslider.cpp index 271dead9..6b443b87 100644 --- a/gui/volumeslider.cpp +++ b/gui/volumeslider.cpp @@ -1,120 +1,120 @@ //-*-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. */ #include "volumeslider.h" #include #include VolumeSlider::VolumeSlider(Qt::Orientation orientation, QWidget* parent) : QSlider(orientation, parent) ,m_orientation(orientation),m_tooltip(new QLabel(parent,Qt::ToolTip)) { QFontMetrics metrics(m_tooltip->font()); //Setting a size big enough for all values less than 100 QRect labelRect = metrics.boundingRect("88"); // m_tooltip->setContentsMargins(3,1,3,1); m_tooltip->setMinimumWidth(labelRect.width()+5); m_tooltip->setMinimumHeight(labelRect.height()+3); m_tooltip->setAlignment(Qt::AlignCenter); // m_tooltip->setAlignment(Qt::AlignVCenter); } void VolumeSlider::mousePressEvent(QMouseEvent* event) { QSlider::mousePressEvent(event); QStyleOptionSlider opt; initStyleOption(&opt); QRect sliderHandle = style()->subControlRect(QStyle::CC_Slider,&opt,QStyle::SC_SliderHandle,this); if(sliderHandle.contains(event->pos())) { if (m_orientation == Qt::Vertical) { m_tooltip->move(mapToGlobal(sliderHandle.topLeft()).x()+width(),mapToGlobal(sliderHandle.topLeft()).y()); } else { m_tooltip->move(mapToGlobal(sliderHandle.topLeft()).x(),mapToGlobal(sliderHandle.topLeft()).y()+height()); } /** * 1) The following operates on the value of one individual channel slider, and thus is representing only the * value of that channel. This is technically not 100% sound for a corner case: The average of all channels * should be displayed in the "joined slider mode". On the other hand, in "joined slider mode", the slider * should already represent the average of all channels. And as soon as you move the slider, all channels * are changed. So it is likely really exact enough. * * 2) Future directions: It would be better to do the percentage calculation via the Volume class, as it * handles corner cases like muting. But due to "auto-unmuting" the value is factually also correct. As the * VolumeSlider class currently holds no pointer/reference to the "underlying" Volume object, a bit code * "duplication" is acceptable here. */ - qreal percentReal = ((qreal)100 * value() ) / ( maximum() - minimum()); + qreal percentReal = (100.0*value())/(maximum()-minimum()); int percent = qRound(percentReal); m_tooltip->setText(QString::number(percent)); m_tooltip->show(); } } void VolumeSlider::mouseReleaseEvent(QMouseEvent* event) { QSlider::mouseReleaseEvent(event); m_tooltip->hide(); } void VolumeSlider::mouseMoveEvent(QMouseEvent* event) { QSlider::mouseMoveEvent(event); QStyleOptionSlider opt; initStyleOption(&opt); QRect sliderHandle = style()->subControlRect(QStyle::CC_Slider,&opt,QStyle::SC_SliderHandle,this); - qreal percentReal = ((qreal)100 * value() ) / ( maximum() - minimum() ); + qreal percentReal = (100.0*value())/(maximum()-minimum()); int percent = qRound(percentReal); //Change width of label if percent becomes 100 if (percent == 100) { QFontMetrics metrics(m_tooltip->font()); QRect labelRect = metrics.boundingRect("100"); m_tooltip->resize(labelRect.width(),m_tooltip->height()); } else if(m_tooltip->width() > m_tooltip->minimumWidth()) { m_tooltip->resize(m_tooltip->minimumWidth(),m_tooltip->height()); } m_tooltip->setText(QString::number(percent)); if (m_orientation == Qt::Vertical) { m_tooltip->move(mapToGlobal(sliderHandle.topLeft()).x()+width(),mapToGlobal(sliderHandle.topLeft()).y()); } else { m_tooltip->move(mapToGlobal(sliderHandle.topLeft()).x(),mapToGlobal(sliderHandle.topLeft()).y()+height()); } //qCDebug(KMIX_LOG) << "Position is"<