diff -Nru kmix-4.12.3/apps/KMixApp.cpp kmix-4.12.90/apps/KMixApp.cpp --- kmix-4.12.3/apps/KMixApp.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/apps/KMixApp.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -21,7 +21,8 @@ #include "KMixApp.h" #include "apps/kmix.h" -#include +#include "core/ControlManager.h" +#include "core/GlobalConfig.h" #include @@ -30,6 +31,8 @@ KMixApp::KMixApp() : KUniqueApplication(), m_kmix( 0 ) { + GlobalConfig::init(); + // We must disable QuitOnLastWindowClosed. Rationale: // 1) The normal state of KMix is to only have the dock icon shown. // 2a) The dock icon gets reconstructed, whenever a soundcard is hotplugged or unplugged. @@ -105,6 +108,7 @@ // based on m_kmix to handle this race condition. // Specific protection for the activation-prior-to-full-construction // case exists above in the 'already running case' + GlobalConfig::init(); m_kmix = new KMixWindow(_keepVisibility); //connect(this, SIGNAL(stopUpdatesOnVisibility()), m_kmix, SLOT(stopVisibilityUpdates())); if ( isSessionRestored() && KMainWindow::canBeRestored(0) ) diff -Nru kmix-4.12.3/apps/kmix.cpp kmix-4.12.90/apps/kmix.cpp --- kmix-4.12.3/apps/kmix.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/apps/kmix.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -1,9 +1,7 @@ /* * KMix -- KDE's full featured mini mixer * - * Copyright 1996-2011 Christian Esken - * Copyright 2000-2003 Christian Esken , Stefan Schimanski <1Stein@gmx.de> - * Copyright 2002-2007 Christian Esken , Helio Chissini de Castro + * 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 @@ -32,6 +30,7 @@ #include // include files for KDE +#include #include #include #include @@ -56,6 +55,7 @@ #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" @@ -69,6 +69,7 @@ #include "dbus/dbusmixsetwrapper.h" #include "gui/osdwidget.h" + /* KMixWindow * Constructs a mixer window (KMix main window) */ @@ -83,7 +84,6 @@ // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in setAttribute(Qt::WA_DeleteOnClose, false); - forceNotifierRebuild = false; initActions(); // init actions first, so we can use them in the loadConfig() already loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword initActionsLate(); // init actions that require a loaded config @@ -124,7 +124,6 @@ QString("KMixWindow") ); - // Send an initial volume refresh (otherwise all volumes are 0 until the next change) ControlManager::instance().announce(QString(), ControlChangeType::Volume, QString("Startup")); } @@ -141,13 +140,18 @@ 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(); @@ -291,8 +295,8 @@ void KMixWindow::initPrefDlg() { - m_prefDlg = new KMixPrefDlg(this); - connect(m_prefDlg, SIGNAL(signalApplied(KMixPrefDlg*)), SLOT(applyPrefs(KMixPrefDlg*))); + KMixPrefDlg* prefDlg = KMixPrefDlg::createInstance(this, GlobalConfig::instance()); + connect(prefDlg, SIGNAL(kmixConfigHasChanged()), SLOT(applyPrefs())); } void @@ -353,27 +357,22 @@ */ bool KMixWindow::updateDocking() { - if (m_showDockWidget == false || Mixer::mixers().isEmpty()) + GlobalConfigData& gcd = GlobalConfig::instance().data; + + if ( !gcd.showDockWidget || Mixer::mixers().isEmpty()) { removeDock(); return false; } - if (forceNotifierRebuild) - { - forceNotifierRebuild = false; - removeDock(); - } if (!m_dockWidget) { - m_dockWidget = new KMixDockWidget(this, trayVolumePopupEnabled); + m_dockWidget = new KMixDockWidget(this); } return true; } void KMixWindow::saveConfig() { - kDebug() - << "About to save config"; saveBaseConfig(); saveViewConfig(); saveVolumes(); @@ -382,115 +381,91 @@ #endif // TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!! - kDebug() - << "Saved config ... now syncing explicitly"; KGlobal::config()->sync(); kDebug() << "Saved config ... sync finished"; } -void -KMixWindow::saveBaseConfig() +void KMixWindow::saveBaseConfig() { - kDebug() - << "About to save config (Base)"; - KConfigGroup config(KGlobal::config(), "Global"); + GlobalConfig::instance().writeConfig(); - 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("AllowDocking", m_showDockWidget); - config.writeEntry("TrayVolumeControl", trayVolumePopupEnabled); - config.writeEntry("Tickmarks", GlobalConfig::instance().showTicks); - config.writeEntry("Labels", GlobalConfig::instance().showLabels); - config.writeEntry("showOSD", GlobalConfig::instance().showOSD); - config.writeEntry("Soundmenu.Mixers", GlobalConfig::instance().getMixersForSoundmenu().toList()); - config.writeEntry("startkdeRestore", m_onLogin); - config.writeEntry("AutoStart", allowAutostart); - config.writeEntry("VolumeFeedback", m_beepOnVolumeChange); - config.writeEntry("DefaultCardOnStart", m_defaultCardOnStart); - config.writeEntry("ConfigVersion", KMIX_CONFIG_VERSION); - config.writeEntry("AutoUseMultimediaKeys", m_autouseMultimediaKeys); - - MasterControl& master = Mixer::getGlobalMasterPreferred(); - if (master.isValid()) - { - config.writeEntry("MasterMixer", master.getCard()); - config.writeEntry("MasterMixerDevice", master.getControl()); - } - QString mixerIgnoreExpression = - MixerToolBox::instance()->mixerIgnoreExpression(); - config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression); + KConfigGroup config(KGlobal::config(), "Global"); - if (GlobalConfig::instance().toplevelOrientation == Qt::Horizontal) - config.writeEntry("Orientation", "Horizontal"); - else - config.writeEntry("Orientation", "Vertical"); + 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); - if (GlobalConfig::instance().traypopupOrientation == Qt::Horizontal) - config.writeEntry("Orientation.TrayPopup", "Horizontal"); - else - config.writeEntry("Orientation.TrayPopup", "Vertical"); + MasterControl& master = Mixer::getGlobalMasterPreferred(); + if (master.isValid()) + { + config.writeEntry("MasterMixer", master.getCard()); + config.writeEntry("MasterMixerDevice", master.getControl()); + } + QString mixerIgnoreExpression = MixerToolBox::instance()->mixerIgnoreExpression(); + config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression); - kDebug() - << "Config (Base) saving done"; + kDebug() + << "Base configuration saved"; } -void -KMixWindow::saveViewConfig() +void KMixWindow::saveViewConfig() { - kDebug() - << "About to save config (View)"; - // Save Views + QMap mixerViews; - 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() ){ - if ( !mixer->isDynamic() ) - mixerViews[mixer->id()]; // just insert a map entry -} + // 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() ) + { + if ( !mixer->isDynamic() ) + { + 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 = (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(KGlobal::config().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()); - } - } - } + // -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 = (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(KGlobal::config().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(KGlobal::config(), "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); - kDebug() - << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count(); - } + // -2- Save Meta-Information (which views, and in which order). views-per-mixer list + KConfigGroup pconfig(KGlobal::config(), "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); + kDebug() + << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count(); + } - kDebug() - << "Config (View) saving done"; + kDebug() + << "View configuration saved"; } /** @@ -506,8 +481,6 @@ void KMixWindow::saveVolumes(QString postfix) { - kDebug() - << "About to save config (Volume)"; const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix); KConfig *cfg = new KConfig(kmixctrlRcFilename); for (int i = 0; i < Mixer::mixers().count(); ++i) @@ -521,7 +494,7 @@ cfg->sync(); delete cfg; kDebug() - << "Config (Volume) saving done"; + << "Volume configuration saved"; } QString @@ -539,8 +512,12 @@ KMixWindow::loadConfig() { 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 @@ -548,24 +525,16 @@ { KConfigGroup config(KGlobal::config(), "Global"); - m_showDockWidget = config.readEntry("AllowDocking", true); - trayVolumePopupEnabled = config.readEntry("TrayVolumeControl", true); - GlobalConfig::instance().showTicks = config.readEntry("Tickmarks", true); - GlobalConfig::instance().showLabels = config.readEntry("Labels", true); - GlobalConfig::instance().showOSD = config.readEntry("showOSD", true); +// GlobalConfig& gcfg = GlobalConfig::instance(); + GlobalConfigData& gcd = GlobalConfig::instance().data; QList preferredMixersInSoundMenu; preferredMixersInSoundMenu = config.readEntry("Soundmenu.Mixers", preferredMixersInSoundMenu); GlobalConfig::instance().setMixersForSoundmenu(preferredMixersInSoundMenu.toSet()); - m_onLogin = config.readEntry("startkdeRestore", true); - allowAutostart = config.readEntry("AutoStart", true); - - setBeepOnVolumeChange(config.readEntry("VolumeFeedback", false)); + setBeepOnVolumeChange(gcd.volumeFeedback); m_startVisible = config.readEntry("Visible", false); m_multiDriverMode = config.readEntry("MultiDriver", false); - const QString& orientationString = config.readEntry("Orientation", "Vertical"); - const QString& traypopupOrientationString = config.readEntry("Orientation.TrayPopup", "Vertical"); m_defaultCardOnStart = config.readEntry("DefaultCardOnStart", ""); m_configVersion = config.readEntry("ConfigVersion", 0); // WARNING Don't overwrite m_configVersion with the "correct" value, before having it @@ -587,28 +556,11 @@ Volume::VOLUME_STEP_DIVISOR = (100 / volumePercentageStep); } - - GlobalConfig::instance().volumeOverdrive = config.readEntry("VolumeOverdrive", false); - - GlobalConfig::instance().debugControlManager = config.readEntry("Debug.ControlManager", false); - GlobalConfig::instance().debugGUI = config.readEntry("Debug.GUI", false); - GlobalConfig::instance().debugVolume = config.readEntry("Debug.Volume", false); - // --- Advanced options, without GUI: END ------------------------------------- m_backendFilter = config.readEntry<>("Backends", QList()); kDebug() << "Backends: " << m_backendFilter; - if (orientationString == "Horizontal") - GlobalConfig::instance().toplevelOrientation = Qt::Horizontal; - else - GlobalConfig::instance().toplevelOrientation = Qt::Vertical; - - if (traypopupOrientationString == "Horizontal") - GlobalConfig::instance().traypopupOrientation = Qt::Horizontal; - else - GlobalConfig::instance().traypopupOrientation = Qt::Vertical; - // show/hide menu bar bool showMenubar = config.readEntry("Menubar", true); @@ -1058,7 +1010,7 @@ ViewBase::ViewFlags vflags = ViewBase::HasMenuBar; if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked()) vflags |= ViewBase::MenuBarVisible; - if (GlobalConfig::instance().toplevelOrientation == Qt::Vertical) + if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Vertical) vflags |= ViewBase::Horizontal; else vflags |= ViewBase::Vertical; @@ -1068,9 +1020,16 @@ /* A newly added mixer will automatically added at the top * and thus the window title is also set appropriately */ - QString tabLabel = guiprof->getName(); - if (tabLabel.isEmpty()) - tabLabel = kmw->mixer()->readableName(); + /* + * 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 @@ -1101,40 +1060,38 @@ m_wsMixers->setTabsClosable(!Mixer::pulseaudioPresent() && m_wsMixers->count() > 1); } -bool -KMixWindow::queryClose() +bool KMixWindow::queryClose() { - // kDebug(67100) << "queryClose "; - if (m_showDockWidget && !kapp->sessionSaving() ) - { - // kDebug(67100) << "don't close"; - // 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 - // kDebug(67100) << "close"; - return true; - } + GlobalConfigData& gcd = GlobalConfig::instance().data; + if (gcd.showDockWidget && !kapp->sessionSaving() ) + { + // 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 + // kDebug(67100) << "close"; + return true; + } } -void -KMixWindow::hideOrClose() +void KMixWindow::hideOrClose() { - if (m_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(); - } + 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 @@ -1182,7 +1139,7 @@ // Volume& vol = md->playbackVolume(); // osdWidget->setCurrentVolume(vol.getAvgVolumePercent(Volume::MALL), // md->isMuted()); - if (GlobalConfig::instance().showOSD) + if (GlobalConfig::instance().data.showOSD) { osdWidget->show(); osdWidget->activateOSD(); //Enable the hide timer @@ -1219,34 +1176,13 @@ kapp->quit(); } -void -KMixWindow::showSettings() +/** + * Shows the configuration dialog, with the "general" tab opened. + */ +void KMixWindow::showSettings() { - if (!m_prefDlg->isVisible()) - { - // copy actual values to dialog - m_prefDlg->m_dockingChk->setChecked(m_showDockWidget); - m_prefDlg->m_volumeChk->setChecked(trayVolumePopupEnabled); - m_prefDlg->m_volumeChk->setEnabled(m_showDockWidget); - m_prefDlg->m_onLogin->setChecked(m_onLogin); - m_prefDlg->allowAutostart->setChecked(allowAutostart); - m_prefDlg->m_beepOnVolumeChange->setChecked(m_beepOnVolumeChange); - - m_prefDlg->m_showTicks->setChecked(GlobalConfig::instance().showTicks); - m_prefDlg->m_showLabels->setChecked(GlobalConfig::instance().showLabels); - m_prefDlg->m_showOSD->setChecked(GlobalConfig::instance().showOSD); - - bool toplevelIsVertical = GlobalConfig::instance().toplevelOrientation == Qt::Vertical; - m_prefDlg->_rbVertical->setChecked(toplevelIsVertical); - m_prefDlg->_rbHorizontal->setChecked(!toplevelIsVertical); - - bool traypopupIsVertical = GlobalConfig::instance().traypopupOrientation == Qt::Vertical; - m_prefDlg->_rbTraypopupVertical->setChecked(traypopupIsVertical); - m_prefDlg->_rbTraypopupHorizontal->setChecked(!traypopupIsVertical); - - // show dialog - m_prefDlg->show(); - } + KMixPrefDlg::getInstance()->switchToPage(KMixPrefDlg::PrefGeneral); + KMixPrefDlg::getInstance()->show(); } void @@ -1265,49 +1201,49 @@ * Apply the Preferences from the preferences dialog. Depending on what has been changed, * the corresponding announcemnts are made. */ -void KMixWindow::applyPrefs(KMixPrefDlg *prefDlg) +void KMixWindow::applyPrefs() { - bool labelsHasChanged = GlobalConfig::instance().showLabels ^ prefDlg->m_showLabels->isChecked(); - bool ticksHasChanged = GlobalConfig::instance().showTicks ^ prefDlg->m_showTicks->isChecked(); - bool dockwidgetHasChanged = m_showDockWidget ^ prefDlg->m_dockingChk->isChecked(); - bool systrayPopupHasChanged = trayVolumePopupEnabled ^ prefDlg->m_volumeChk->isChecked(); - Qt::Orientation newToplevelOrientation = prefDlg->_rbVertical->isChecked() ? Qt::Vertical : Qt::Horizontal; - bool toplevelOrientationHasChanged = newToplevelOrientation != GlobalConfig::instance().toplevelOrientation; - Qt::Orientation newTraypopupOrientation = prefDlg->_rbTraypopupVertical->isChecked() ? Qt::Vertical : Qt::Horizontal; - bool traypopupOrientationHasChanged = newTraypopupOrientation != GlobalConfig::instance().traypopupOrientation; - - GlobalConfig::instance().showLabels = prefDlg->m_showLabels->isChecked(); - GlobalConfig::instance().showTicks = prefDlg->m_showTicks->isChecked(); - GlobalConfig::instance().showOSD = prefDlg->m_showOSD->isChecked(); - m_showDockWidget = prefDlg->m_dockingChk->isChecked(); - trayVolumePopupEnabled = prefDlg->m_volumeChk->isChecked(); - m_onLogin = prefDlg->m_onLogin->isChecked(); - allowAutostart = m_prefDlg->allowAutostart->isChecked(); - setBeepOnVolumeChange(prefDlg->m_beepOnVolumeChange->isChecked()); + // -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; - GlobalConfig::instance().toplevelOrientation = newToplevelOrientation; - GlobalConfig::instance().traypopupOrientation = newTraypopupOrientation; + bool toplevelOrientationHasChanged = config.getToplevelOrientation() != configBefore.getToplevelOrientation(); + bool traypopupOrientationHasChanged = config.getTraypopupOrientation() != configBefore.getTraypopupOrientation(); + kDebug() << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged << + ", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation(); + kDebug() << "trayOrientationHasChanged=" << traypopupOrientationHasChanged << + ", config=" << config.getTraypopupOrientation() << ", configBefore=" << configBefore.getTraypopupOrientation(); - if ( systrayPopupHasChanged) + // -2- Determine what effect the changes have ------------------------------------------------------------------ + + if (dockwidgetHasChanged || toplevelOrientationHasChanged + || traypopupOrientationHasChanged) { - // if the user has changed the "volume popup" option, the KStatusNotifier requires a new referenceWidget, - // thus we force a reconstruct. - forceNotifierRebuild = true; + // These might need a complete relayout => announce a ControlList change to rebuild everything + ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Preferences Dialog")); } - if ( systrayPopupHasChanged || 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. + 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) - kapp->processEvents(); - saveConfig(); +// this->repaint(); // make KMix look fast (saveConfig() often uses several seconds) + kapp->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(); } /** @@ -1317,11 +1253,9 @@ * * @param beep true, if a beep should be changed */ -void -KMixWindow::setBeepOnVolumeChange(bool beep) +void KMixWindow::setBeepOnVolumeChange(bool beep) { - m_beepOnVolumeChange = beep; - Mixer::setBeepOnVolumeChange(m_beepOnVolumeChange); + Mixer::setBeepOnVolumeChange(beep); } void diff -Nru kmix-4.12.3/apps/kmixctrl.cpp kmix-4.12.90/apps/kmixctrl.cpp --- kmix-4.12.3/apps/kmixctrl.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/apps/kmixctrl.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -29,6 +29,7 @@ #include #include "gui/kmixtoolbox.h" +#include "core/GlobalConfig.h" #include "core/mixer.h" #include "core/version.h" @@ -55,6 +56,8 @@ KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); KApplication app( false ); + GlobalConfig::init(); + // create mixers QString dummyStringHwinfo; MixerToolBox::instance()->initMixer(false, QList(), dummyStringHwinfo); diff -Nru kmix-4.12.3/apps/kmixd.cpp kmix-4.12.90/apps/kmixd.cpp --- kmix-4.12.3/apps/kmixd.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/apps/kmixd.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -40,6 +40,7 @@ #include // KMix +#include "core/GlobalConfig.h" #include "core/mixertoolbox.h" #include "core/kmixdevicemanager.h" #include "core/version.h" @@ -84,6 +85,8 @@ // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in //setAttribute(Qt::WA_DeleteOnClose, false); + GlobalConfig::init(); + //initActions(); // init actions first, so we can use them in the loadConfig() already loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword //initActionsLate(); // init actions that require a loaded config diff -Nru kmix-4.12.3/apps/kmix.h kmix-4.12.90/apps/kmix.h --- kmix-4.12.3/apps/kmix.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/apps/kmix.h 2014-01-01 04:02:55.000000000 +0000 @@ -40,7 +40,8 @@ #include // KMix -class KMixPrefDlg; +#include "core/GlobalConfig.h" + class KMixDockWidget; class KMixerWidget; class KMixWindow; @@ -89,7 +90,7 @@ void saveVolumes(); void saveVolumes(QString postfix); void saveConfig(); - virtual void applyPrefs( KMixPrefDlg *prefDlg ); + virtual void applyPrefs(); void recreateGUI(bool saveView); void recreateGUI(bool saveConfig, const QString& mixerId, bool forceNewTab); void recreateGUIwithSavingView(); @@ -104,25 +105,19 @@ KAccel *m_keyAccel; KAction* _actionShowMenubar; - // move many of the following to a central static configuration object - // (they come from the KMix config file, so they are really "static". - bool m_showDockWidget; - bool trayVolumePopupEnabled; - private: - bool m_onLogin; - bool allowAutostart; - bool m_beepOnVolumeChange; + /** + * configSnapshot is used to hold the original state before modifications in the preferences dialog + */ + GlobalConfigData configDataSnapshot; + bool m_startVisible; bool m_visibilityUpdateAllowed; bool m_multiDriverMode; // Not officially supported. bool m_autouseMultimediaKeys; // Due to message freeze, not in config dialog in KDE4.4 - bool forceNotifierRebuild; - KTabWidget *m_wsMixers; - KMixPrefDlg *m_prefDlg; KMixDockWidget *m_dockWidget; QString m_hwInfoString; QString m_defaultCardOnStart; diff -Nru kmix-4.12.3/apps/kmixremote kmix-4.12.90/apps/kmixremote --- kmix-4.12.3/apps/kmixremote 1970-01-01 00:00:00.000000000 +0000 +++ kmix-4.12.90/apps/kmixremote 2014-01-01 04:02:55.000000000 +0000 @@ -0,0 +1,127 @@ +################################################################################# +# kmixremote - control kmix from a script. +# +# Set volume +# Get volume +# Mute +################################################################################# + +function usage +{ + echo "Usage:" + echo "List mixers # $0 list" + echo "List controls # $0 list " + echo "Get Volume # $0 get [--master | ]" + echo "Set Volume # $0 set [--master | ] <0..100>" + echo "Mute/Unmute # $0 mute [--master | ] true|false" + echo +} + +function exit_with_error +{ + echo "Error: $1" + echo + usage + exit 1 +} + +# Prints the mixer DBUS ID's on the console. leaving out the "/Mixers/" prefix +function listMixers +{ + qdbus org.kde.kmix /Mixers org.freedesktop.DBus.Properties.Get org.kde.KMix.MixSet mixers | cut -f3 -d/ + errorCode=$? + if test $errorCode != 0; then + echo "Error $errorCode listing mixers. KMix is not running." + fi +} + +# Prints the mixer control DBUS ID's of the given mixer on the console. leaving out the "/Mixers/" prefix +function listControls +{ + qdbus org.kde.kmix $1 org.freedesktop.DBus.Properties.Get org.kde.KMix.Mixer controls | cut -f4 -d/ + errorCode=$? + if test $errorCode != 0; then + echo "Error $errorCode listing controls. KMix is not running." + fi +} + +command="" + +if ! type qdbus >/dev/null 2>&1 ; then + exit_with_error "$0 requires qdbus, but it cannot be found. Please install or check \$PATH" +fi + +# Read args +while true; do + arg=$1 + shift + if test -z "$arg"; then + break + elif test "x--master" = "x$arg"; then + mixer=`qdbus org.kde.kmix /Mixers org.kde.KMix.MixSet.currentMasterMixer` + control=`qdbus org.kde.kmix /Mixers org.kde.KMix.MixSet.currentMasterControl` + elif test "x--help" = "x$arg" -o "x-h" = "x$arg"; then + usage + exit 0 + else + # If not a specific option, then interpret as standad args, in this order: command, mixer, control and genericArg + if test -z "$command"; then + command=$arg + elif test -z "$mixer"; then + mixer="${arg}" + elif test -z "$control"; then + control=$arg + elif test -z "$genericArg"; then + genericArg=$arg + else + exit_with_error "Too many aguments" + fi + fi + #echo $arg +done + + +if test -z "$command"; then + usage + echo " - The mixer to use. Select one from the following list:" + echo "-----------------------------------------------------------------" + listMixers + exit 0 +elif test "xlist" = "x$command"; then + if test -z "$mixer"; then + listMixers + else + # List controls + listControls "/Mixers/${mixer}" + fi + exit 0 +fi + +# All following commands require a mixer +if test -z "$mixer"; then + exit_with_error " argument missing" +fi +if test -z "$control"; then + exit_with_error " argument missing" +fi + +# All following commands require a mixer and a control + +targetControl="/Mixers/${mixer}/${control}" +#echo "ARGS: $command $targetControl $genericArg" + +# --- EXECUTE PHASE -------------------------------------------------------------------------------------------------- +if test "xget" = "x$command"; then + # GET + qdbus org.kde.kmix $targetControl org.freedesktop.DBus.Properties.Get org.kde.KMix.Control volume +elif test "xset" = "x$command"; then + # SET + qdbus org.kde.kmix $targetControl org.freedesktop.DBus.Properties.Set org.kde.KMix.Control volume $genericArg +elif test "xmute" = "x$command"; then + # MUTE + qdbus org.kde.kmix $targetControl org.freedesktop.DBus.Properties.Set org.kde.KMix.Control mute $genericArg +else + exit_with_error "No such command '$command'" +fi + +exit 0 diff -Nru kmix-4.12.3/backends/mixer_backend.cpp kmix-4.12.90/backends/mixer_backend.cpp --- kmix-4.12.3/backends/mixer_backend.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_backend.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -28,8 +28,8 @@ #include -#define POLL_OSS_RATE_SLOW 1500 -#define POLL_OSS_RATE_FAST 50 +#define POLL_RATE_SLOW 1500 +#define POLL_RATE_FAST 50 #include "mixer_backend_i18n.cpp" @@ -79,27 +79,33 @@ m_mixDevices.clear(); } -bool Mixer_Backend::openIfValid() { - bool valid = false; +bool Mixer_Backend::openIfValid() +{ int ret = open(); - if ( ret == 0 && (m_mixDevices.count() > 0 || _mixer->isDynamic())) { - valid = true; - // A better ID is now calculated in mixertoolbox.cpp, and set via setID(), - // but we want a somehow usable fallback just in case. - - if ( needsPolling() ) { - _pollingTimer->start(POLL_OSS_RATE_FAST); + if (ret == 0 && (m_mixDevices.count() > 0 || _mixer->isDynamic())) + { + // Hint: _id is probably not yet perfectly set, as it requires the value from open() and an external + // counter. Thus we start the Timer while _id is not properly set. But it will be done immediately + // by the caller of this method. + // Future directions: Do the counter calculation in the backend. It really belongs there, as it is part of + // the PK calculation. Probably provide a standard implementation in Mixer_Backend itself. Also the + // key should be an own class, like: MixerKey(QString backend, QString baseId, int cardInstance) + if (needsPolling()) + { + _pollingTimer->start(POLL_RATE_FAST); } - else { + else + { // The initial state must be read manually - QTimer::singleShot( POLL_OSS_RATE_FAST, this, SLOT(readSetFromHW()) ); + QTimer::singleShot( POLL_RATE_FAST, this, SLOT(readSetFromHW())); } - } // could be opened + return true; // could be opened + } else { //shutdown(); + return false; // could not open } - return valid; } bool Mixer_Backend::isOpen() { @@ -195,7 +201,7 @@ } else if ( retLoop != Mixer::OK && retLoop != Mixer::OK_UNCHANGED ) { - // If current ret from loop in not OK, then transiton to that: ret (Something) => retLoop (Error) + // If current ret from loop in not OK, then transition to that: ret (Something) => retLoop (Error) ret = retLoop; } } @@ -206,7 +212,7 @@ if ( needsPolling() ) { // Upgrade polling frequency temporarily to be more smoooooth - _pollingTimer->setInterval(POLL_OSS_RATE_FAST); + _pollingTimer->setInterval(POLL_RATE_FAST); QTime fastPollingEndsAt = QTime::currentTime (); fastPollingEndsAt = fastPollingEndsAt.addSecs(5); _fastPollingEndsAt = fastPollingEndsAt; @@ -220,16 +226,12 @@ else { // This code path is entered on Mixer::OK_UNCHANGED and ERROR - if ( !_fastPollingEndsAt.isNull() ) + bool fastPollingEndsNow = (!_fastPollingEndsAt.isNull()) && _fastPollingEndsAt < QTime::currentTime (); + if ( fastPollingEndsNow ) { - // Fast polling is currently active - if( _fastPollingEndsAt < QTime::currentTime () ) - { - kDebug() << "End fast polling"; - _fastPollingEndsAt = QTime(); - if ( needsPolling() ) - _pollingTimer->setInterval(POLL_OSS_RATE_SLOW); - } + kDebug() << "End fast polling"; + _fastPollingEndsAt = QTime(); // NULL time + _pollingTimer->setInterval(POLL_RATE_SLOW); } } } @@ -293,17 +295,6 @@ return false; } -void Mixer_Backend::errormsg(int mixer_error) -{ - QString l_s_errText; - l_s_errText = errorText(mixer_error); - kError() << l_s_errText << "\n"; -} - -int Mixer_Backend::id2num(const QString& id) -{ - return id.toInt(); -} QString Mixer_Backend::errorText(int mixer_error) { diff -Nru kmix-4.12.3/backends/mixer_backend.h kmix-4.12.90/backends/mixer_backend.h --- kmix-4.12.3/backends/mixer_backend.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_backend.h 2014-01-01 04:02:55.000000000 +0000 @@ -54,14 +54,14 @@ * * @return a KMix error code (O=OK). */ - virtual int close(); + virtual int close(); // Not pure virtual. See comment! - /* + /** * Shutdown deinitializes this MixerBackend, freeing resources */ void closeCommon(); - /* + /** * Returns the driver name, e.g. "ALSA" or "OSS". This virtual method is for looking up the * driver name on instanciated objects. * @@ -101,6 +101,7 @@ virtual bool moveStream( const QString& id, const QString& destId ); + // Future directions: Move media*() methods to MediaController class virtual int mediaPlay(QString ) { return 0; }; // implement in the backend if it supports it virtual int mediaPrev(QString ) { return 0; }; // implement in the backend if it supports it virtual int mediaNext(QString ) { return 0;}; // implement in the backend if it supports it @@ -110,21 +111,16 @@ shared_ptr recommendedMaster(); - /** Return a translated error text for the given error number. + /** + * Return a translated error text for the given error number. * Subclasses can override this method to produce platform * specific error descriptions. */ virtual QString errorText(int mixer_error); - /// Prints out a translated error text for the given error number on stderr - void errormsg(int mixer_error); - /// Returns translated WhatsThis messages for a control.Translates from virtual QString translateKernelToWhatsthis(const QString &kernelName); - /// Translate ID to internal device number - virtual int id2num(const QString& id); - // Return an Universal Device Identification (suitable for the OS, especially for Hotplug and Unplug events) virtual QString& udi() { return _udi; }; @@ -160,6 +156,9 @@ void controlChanged( void ); // TODO remove? public slots: +/** + * Re-initialize. Currently only implemented by PulseAudio backend, and this slot might get moved there + */ virtual void reinit() {}; protected: diff -Nru kmix-4.12.3/backends/mixer_mpris2.cpp kmix-4.12.90/backends/mixer_mpris2.cpp --- kmix-4.12.3/backends/mixer_mpris2.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_mpris2.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -52,8 +52,7 @@ m_mixerName = i18n("Playback Streams"); _id = "Playback Streams"; _mixer->setDynamic(); - addAllRunningPlayersAndInitHotplug(); - return 0; + return addAllRunningPlayersAndInitHotplug(); } int Mixer_MPRIS2::close() @@ -97,86 +96,49 @@ QDBusPendingCallWatcher* watchMediaControlReply = new QDBusPendingCallWatcher(repl2, mad); - connect(watchMediaControlReply, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(mediaContolReplyIncoming(QDBusPendingCallWatcher *))); + connect(watchMediaControlReply, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherMediaControl(QDBusPendingCallWatcher *))); return 0; // Presume everything went well. Can't do more for ASYNC calls } -void Mixer_MPRIS2::mediaContolReplyIncoming(QDBusPendingCallWatcher* watcher) +void Mixer_MPRIS2::watcherMediaControl(QDBusPendingCallWatcher* watcher) { - const QDBusMessage& msg = watcher->reply(); - if ( msg.type() == QDBusMessage::ErrorMessage ) - { - kError(67100) << "ERROR in Media control operation, path=" << msg.path() << ", msg=" << msg; - watcher->deleteLater(); - return; - } - - QObject *obj = watcher->parent(); - watcher->deleteLater(); - - MPrisControl* mad = qobject_cast(obj); - if (mad == 0) + MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); + if (mprisCtl == 0) { - kWarning() << "Ignoring unexpected Control Id"; - return; + return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) } - QString id = mad->getId(); - QString busDestination = mad->getBusDestination(); - QString readableName = id; // Start with ID, but replace with reply (if exists) - - - kDebug() << "Media control for id=" << id << ", path=" << msg.path() << ", interface=" << msg.interface() << ", busDestination" << busDestination << ", name= " << readableName; - kDebug() << "msg=" << msg; - + // Actually the code below in this method is more or less just debugging + const QDBusMessage& msg = watcher->reply(); + QString id = mprisCtl->getId(); + QString busDestination = mprisCtl->getBusDestination(); + kDebug() << "Media control for id=" << id << ", path=" << msg.path() << ", interface=" << msg.interface() << ", busDestination" << busDestination; } /** * readVolumeFromHW() should be used only for hotplug (and even that should go away). Everything should operate via * the slot volumeChanged in the future. */ -int Mixer_MPRIS2::readVolumeFromHW( const QString& id, shared_ptr md) +int Mixer_MPRIS2::readVolumeFromHW( const QString& /*id*/, shared_ptr /*md*/) { - int volInt = 0; - - QList arg; - arg.append(QString("org.mpris.MediaPlayer2.Player")); - arg.append(QString("Volume")); - MPrisControl* mad = controls.value(id); -// QDBusMessage msg = mad->propertyIfc->callWithArgumentList(QDBus::Block, "Get", arg); - - QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player")); - QVariant v2 = QVariant(QString("Volume")); - - QDBusPendingReply repl2 = mad->propertyIfc->asyncCall("Get", v1, v2); - repl2.waitForFinished(); - QDBusMessage msg = repl2.reply(); + // Everything is done by notifications => no code neccessary + return Mixer::OK_UNCHANGED; +} - if ( msg.type() == QDBusMessage::ReplyMessage ) - { - QList repl = msg.arguments(); - if ( ! repl.isEmpty() ) - { - QVariant qv = repl.at(0); - // We have to do some very ugly casting from QVariant to QDBusVariant to QVariant. This API totally sucks. - QDBusVariant dbusVariant = qvariant_cast(qv); - QVariant result2 = dbusVariant.variant(); - volInt = result2.toFloat() *100; - - volumeChangedInternal(md, volInt); - if (GlobalConfig::instance().debugVolume) - kDebug() << "changed vol" << volInt; - } - else - { - kError(67100) << "ERROR GET " << id; - return Mixer::ERR_READ; - } - - } - return 0; +/** + * A slot that processes data from the MPrisControl that emit the signal. + * + * @param The emitting MPrisControl + * @param newVolume The new volume + */ +void Mixer_MPRIS2::playbackStateChanged(MPrisControl* mad, MediaController::PlayState playState) +{ + shared_ptr md = m_mixDevices.get(mad->getId()); + md->getMediaController()->setPlayState(playState); + QMetaObject::invokeMethod(this, "announceGUI", Qt::QueuedConnection); +// ControlManager::instance().announce(_mixer->id(), ControlChangeType::GUI, QString("MixerMPRIS2.playbackStateChanged")); } @@ -190,7 +152,7 @@ { shared_ptr md = m_mixDevices.get(mad->getId()); int volInt = newVolume *100; - if (GlobalConfig::instance().debugVolume) + if (GlobalConfig::instance().data.debugVolume) kDebug() << "changed" << volInt; volumeChangedInternal(md, volInt); } @@ -206,7 +168,8 @@ Volume& vol = md->playbackVolume(); vol.setVolume( Volume::LEFT, volumePercentage); md->setMuted(volumePercentage == 0); - ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, QString("MixerMPRIS2.volumeChanged")); + QMetaObject::invokeMethod(this, "announceVolume", Qt::QueuedConnection); +// ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, QString("MixerMPRIS2.volumeChanged")); } // The following is an example message for an incoming volume change: @@ -223,6 +186,13 @@ ] */ +/** + * @overload + * + * @param id + * @param md + * @return + */ int Mixer_MPRIS2::writeVolumeToHW( const QString& id, shared_ptr md ) { Volume& vol = md->playbackVolume(); @@ -239,27 +209,14 @@ arg << QVariant::fromValue(QDBusVariant(volFloat)); MPrisControl* mad = controls.value(id); -// QDBusMessage msg = mad->propertyIfc->callWithArgumentList(QDBus::NoBlock, "Set", arg); QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player")); QVariant v2 = QVariant(QString("Volume")); QVariant v3 = QVariant::fromValue(QDBusVariant(volFloat)); // QVariant v3 = QVariant(volFloat); - //QDBusPendingReply repl2 = + // I don't care too much for the reply, as I won't receive a result. Thus fire-and-forget here. mad->propertyIfc->asyncCall("Set", v1, v2, v3); - /* - repl2.waitForFinished(); - QDBusMessage msg = repl2.reply(); - - - - if ( msg.type() == QDBusMessage::ErrorMessage ) - { - kError(67100) << "ERROR SET " << id << ": " << msg; - return Mixer::ERR_WRITE; - } - */ return 0; } @@ -311,7 +268,7 @@ */ /* - * Bug 311189: Introspecting via "dbusConn.interface()->registeredServiceNames()" does not work too well in + * Bug 311189: Introspecting via "dbusConn.interface()->registeredServiceNames()" does not work too well. * Comment: I am not so sure that registeredServiceNames() is really an issue. It is more likely * in a later step, when talking to the probed apps. Still, I now do a hand crafted 3-line version of * registeredServiceNames() via "ListNames", so I can later more easily change to async. @@ -321,24 +278,22 @@ QDBusPendingReply repl = dbusIfc.asyncCall("ListNames"); repl.waitForFinished(); - if ( repl.isValid() ) + if (! repl.isValid() ) { - QString busDestination; - foreach ( busDestination , repl.value() ) - { - if ( busDestination.startsWith("org.mpris.MediaPlayer2") ) - { - addMprisControlAsync(busDestination); - kDebug() << "MPRIS2: Attached " << busDestination; - } - } + kError() << "Invalid reply while listing Media Players. MPRIS2 players will not be available." << repl.error(); + return 1; } - else + + QString busDestination; + foreach ( busDestination , repl.value() ) { - kError() << "Invalid reply while listing Media Players" << repl.error(); + if ( busDestination.startsWith("org.mpris.MediaPlayer2") ) + { + addMprisControlAsync(busDestination); + kDebug() << "MPRIS2: Attached media player on busDestination=" << busDestination; + } } - return 0; } @@ -389,13 +344,12 @@ * This behavior is total counter-intuitive :-((( */ - // Create ASYNC DBUS queries for the new control + // Create ASYNC DBUS queries for the new control. This effectively starts a chain of async DBUS commands. QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2")); QVariant v2 = QVariant(QString("Identity")); - QDBusPendingReply repl2 = mad->propertyIfc->asyncCall("Get", v1, v2); QDBusPendingCallWatcher* watchIdentity = new QDBusPendingCallWatcher(repl2, mad); - connect(watchIdentity, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(plugControlIdIncoming(QDBusPendingCallWatcher *))); + connect(watchIdentity, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherPlugControlId(QDBusPendingCallWatcher *))); } MixDevice::ChannelType Mixer_MPRIS2::getChannelTypeFromPlayerId(const QString& id) @@ -430,86 +384,188 @@ return ct; } -void Mixer_MPRIS2::plugControlIdIncoming(QDBusPendingCallWatcher* watcher) +void Mixer_MPRIS2::watcherInitialVolume(QDBusPendingCallWatcher* watcher) +{ + MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); + if (mprisCtl == 0) + return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) + + const QDBusMessage& msg = watcher->reply(); + QList repl = msg.arguments(); + if ( ! repl.isEmpty() ) + { + QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); + QVariant result2 = dbusVariant.variant(); + double volume = result2.toDouble(); + volumeChanged(mprisCtl, volume); + } + + watcher->deleteLater(); +} + +void Mixer_MPRIS2::watcherInitialPlayState(QDBusPendingCallWatcher* watcher) +{ + MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); + if (mprisCtl == 0) + return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) + + const QDBusMessage& msg = watcher->reply(); + QList repl = msg.arguments(); + if ( ! repl.isEmpty() ) + { + QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); + QVariant result2 = dbusVariant.variant(); + QString playbackStateString = result2.toString(); + + MediaController::PlayState playState = Mixer_MPRIS2::mprisPlayStateString2PlayState(playbackStateString); + playbackStateChanged(mprisCtl, playState); + } + + watcher->deleteLater(); +} + + +/** + * Convenience method for the watcher*() methods. + * Returns the MPrisControl that is parent of the given watcher, if the reply is valid. In this case you can + * use the result and call watcher->deleteLater() after processing the result. + * + * Otherwise 0 is returned, and watcher->deleteLater() is called. Important You must call watcher->deleteLater() + * yourself for the other (normal/good) case. + * + * @param watcher + * @return + */ +MPrisControl* Mixer_MPRIS2::watcherHelperGetMPrisControl(QDBusPendingCallWatcher* watcher) { const QDBusMessage& msg = watcher->reply(); if ( msg.type() == QDBusMessage::ReplyMessage ) { - QObject *obj = watcher->parent(); + QObject* obj = watcher->parent(); MPrisControl* mad = qobject_cast(obj); - if (mad == 0) + if (mad != 0) { - kWarning() << "Ignoring unexpected Control Id"; - watcher->deleteLater(); - return; + return mad; } - QString id = mad->getId(); - QString busDestination = mad->getBusDestination(); - QString readableName = id; // Start with ID, but replace with reply (if exists) + kWarning() << "Ignoring unexpected Control Id. object=" << obj; + } - kDebug() << "Plugging id=" << id << ", busDestination" << busDestination << ", name= " << readableName; + else if ( msg.type() == QDBusMessage::ErrorMessage ) + { + kError() << "ERROR in Media control operation, path=" << msg.path() << ", msg=" << msg; + } - QList repl = msg.arguments(); - if ( ! repl.isEmpty() ) - { - QVariant qv = repl.at(0); - // We have to do some very ugly casting from QVariant to QDBusVariant to QVariant. This API totally sucks. - QDBusVariant dbusVariant = qvariant_cast(qv); - QVariant result2 = dbusVariant.variant(); - readableName = result2.toString(); -// kDebug() << "REPLY " << result2.type() << ": " << readableName; + watcher->deleteLater(); + return 0; +} - // TODO This hardcoded application list is a quick hack. It should be generalized. - MixDevice::ChannelType ct = getChannelTypeFromPlayerId(id); - MixDevice* mdNew = new MixDevice(_mixer, id, readableName, ct); - // MPRIS2 doesn't support an actual mute switch. Mute is defined as volume = 0.0 - // Thus we won't add the playback switch - Volume* vol = new Volume( 100, 0, false, false); - vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); // MPRIS is only one control ("Mono") - mdNew->addMediaPlayControl(); - mdNew->addMediaNextControl(); - mdNew->addMediaPrevControl(); - mdNew->setApplicationStream(true); - mdNew->addPlaybackVolume(*vol); - - m_mixDevices.append( mdNew->addToPool() ); - - delete vol; // vol is only temporary. mdNew has its own volume object. => delete - - QDBusConnection sessionBus = QDBusConnection::sessionBus(); - sessionBus.connect(busDestination, QString("/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties", "PropertiesChanged", mad, SLOT(volumeChangedIncoming(QString,QVariantMap,QStringList)) ); - connect(mad, SIGNAL(volumeChanged(MPrisControl*,double)), this, SLOT(volumeChanged(MPrisControl*,double)) ); +void Mixer_MPRIS2::watcherPlugControlId(QDBusPendingCallWatcher* watcher) +{ + MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher); + if (mprisCtl == 0) + { + return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged) + } - sessionBus.connect(busDestination, QString("/Player"), "org.freedesktop.MediaPlayer", "TrackChange", mad, SLOT(trackChangedIncoming(QVariantMap)) ); - volumeChanged(mad, mad->playerIfc->property("Volume").toDouble()); + const QDBusMessage& msg = watcher->reply(); + QString id = mprisCtl->getId(); + QString busDestination = mprisCtl->getBusDestination(); + QString readableName = id; // Start with ID, but replace with reply (if exists) - notifyToReconfigureControlsAsync(id); - } - } - else + kDebug() << "Plugging id=" << id << ", busDestination" << busDestination << ", name= " << readableName; + + QList repl = msg.arguments(); + if ( ! repl.isEmpty() ) { - qWarning() << "Error (" << msg.type() << "): " << msg.errorName() << " " << msg.errorMessage(); + // We have to do some very ugly casting from QVariant to QDBusVariant to QVariant. This API totally sucks. + QDBusVariant dbusVariant = qvariant_cast(repl.at(0)); + QVariant result2 = dbusVariant.variant(); + readableName = result2.toString(); + +// kDebug() << "REPLY " << result2.type() << ": " << readableName; + + MixDevice::ChannelType ct = getChannelTypeFromPlayerId(id); + MixDevice* mdNew = new MixDevice(_mixer, id, readableName, ct); + // MPRIS2 doesn't support an actual mute switch. Mute is defined as volume = 0.0 + // Thus we won't add the playback switch + Volume* vol = new Volume( 100, 0, false, false); + vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); // MPRIS is only one control ("Mono") + MediaController* mediaContoller = mdNew->getMediaController(); + mediaContoller->addMediaPlayControl(); + mediaContoller->addMediaNextControl(); + mediaContoller->addMediaPrevControl(); + mdNew->setApplicationStream(true); + mdNew->addPlaybackVolume(*vol); + + m_mixDevices.append( mdNew->addToPool() ); + + delete vol; // vol is only temporary. mdNew has its own volume object. => delete + + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + sessionBus.connect(busDestination, QString("/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties", "PropertiesChanged", mprisCtl, SLOT(onPropertyChange(QString,QVariantMap,QStringList)) ); + connect(mprisCtl, SIGNAL(volumeChanged(MPrisControl*,double)), this, SLOT(volumeChanged(MPrisControl*,double)) ); + connect(mprisCtl, SIGNAL(playbackStateChanged(MPrisControl*,MediaController::PlayState)), SLOT (playbackStateChanged(MPrisControl*,MediaController::PlayState)) ); + + sessionBus.connect(busDestination, QString("/Player"), "org.freedesktop.MediaPlayer", "TrackChange", mprisCtl, SLOT(trackChangedIncoming(QVariantMap)) ); + + // The following line is evil: mad->playerIfc->property("Volume") is in fact a synchronous call, and + // sync calls are strictly forbidden, see bug 317926 + //volumeChanged(mad, mad->playerIfc->property("Volume").toDouble()); + + + // --- Query initial state -------------------------------------------------------------------------------- + QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player")); + + QVariant v2 = QVariant(QString("Volume")); + QDBusPendingReply repl2 = mprisCtl->propertyIfc->asyncCall("Get", v1, v2); + QDBusPendingCallWatcher* watcherOutgoing = new QDBusPendingCallWatcher(repl2, mprisCtl); + connect(watcherOutgoing, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherInitialVolume(QDBusPendingCallWatcher *))); + + v2 = QVariant(QString("PlaybackStatus")); + repl2 = mprisCtl->propertyIfc->asyncCall("Get", v1, v2); + watcherOutgoing = new QDBusPendingCallWatcher(repl2, mprisCtl); + connect(watcherOutgoing, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherInitialPlayState(QDBusPendingCallWatcher *))); + + // Push notifyToReconfigureControls to stack, so it will not be executed synchronously + announceControlListAsync(id); } - // Push notifyToReconfigureControls to stack, so it will not be executed synchronously watcher->deleteLater(); } -void Mixer_MPRIS2::notifyToReconfigureControlsAsync(QString /*streamId*/) +// ----------------------------------------------------------------------------------------------------------- +// ASYNC announce slots, including convenience wrappers +// ----------------------------------------------------------------------------------------------------------- +/** + * Convenience wrapper to do the ASYNC call to #announceControlList() + * @param + */ +void Mixer_MPRIS2::announceControlListAsync(QString /*streamId*/) { // currently we do not use the streamId - QMetaObject::invokeMethod(this, - "notifyToReconfigureControls", - Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "announceControlList", Qt::QueuedConnection); } -void Mixer_MPRIS2::notifyToReconfigureControls() +void Mixer_MPRIS2::announceControlList() { ControlManager::instance().announce(_mixer->id(), ControlChangeType::ControlList, getDriverName()); } +void Mixer_MPRIS2::announceGUI() +{ + ControlManager::instance().announce(_mixer->id(), ControlChangeType::GUI, getDriverName()); +} + +void Mixer_MPRIS2::announceVolume() +{ + ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, getDriverName()); +} + +// ----------------------------------------------------------------------------------------------------------- + + /** * Handles the hotplug of new MPRIS2 enabled Media Players */ @@ -527,6 +583,7 @@ QString id = busDestinationToControlId(name); kDebug() << "Mediaplayer unregisters: " << name << " , id=" << id; + // -1- Remove Mediaplayer connection if (controls.contains(id)) { const MPrisControl *control = controls.value(id); @@ -534,13 +591,14 @@ controls.remove(id); } + // -2- Remove MixDevice from internal list shared_ptr md = m_mixDevices.get(id); if (md) { // We know about the player that is unregistering => remove internally md->close(); m_mixDevices.removeById(id); - notifyToReconfigureControlsAsync(id); + announceControlListAsync(id); kDebug() << "MixDevice 4 useCount=" << md.use_count(); } } @@ -560,10 +618,29 @@ kDebug() << "Track changed"; } +MediaController::PlayState Mixer_MPRIS2::mprisPlayStateString2PlayState(const QString& playbackStatus) +{ + MediaController::PlayState playState; + if (playbackStatus == "Playing") + { + playState = MediaController::PlayPlaying; + } + else if (playbackStatus == "Stopped") + { + playState = MediaController::PlayStopped; + } + else if (playbackStatus == "Paused") + { + playState = MediaController::PlayPaused; + } + + return playState; +} + /** * This slot is a simple proxy that enriches the DBUS signal with our data, which especially contains the id of the MixDevice. */ -void MPrisControl::volumeChangedIncoming(QString /*ifc*/,QVariantMap msg ,QStringList /*sl*/) +void MPrisControl::onPropertyChange(QString /*ifc*/,QVariantMap msg ,QStringList /*sl*/) { QMap::iterator v = msg.find("Volume"); if (v != msg.end() ) @@ -577,8 +654,10 @@ if (v != msg.end() ) { QString playbackStatus = v.value().toString(); - // "Stopped", "Playing", "Paused" + MediaController::PlayState playState = Mixer_MPRIS2::mprisPlayStateString2PlayState(playbackStatus); kDebug() << "PlaybackStatus is now " << playbackStatus; + + emit playbackStateChanged(this, playState); } } diff -Nru kmix-4.12.3/backends/mixer_mpris2.h kmix-4.12.90/backends/mixer_mpris2.h --- kmix-4.12.3/backends/mixer_mpris2.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_mpris2.h 2014-01-01 04:02:55.000000000 +0000 @@ -100,10 +100,11 @@ public slots: void trackChangedIncoming(QVariantMap msg); - void volumeChangedIncoming(QString,QVariantMap,QStringList); + void onPropertyChange(QString,QVariantMap,QStringList); signals: void volumeChanged(MPrisControl* mad, double); + void playbackStateChanged(MPrisControl* mad, MediaController::PlayState); }; class Mixer_MPRIS2 : public Mixer_Backend @@ -132,20 +133,36 @@ virtual int mediaNext(QString id); virtual int mediaControl(QString id, QString command); + static MediaController::PlayState mprisPlayStateString2PlayState(const QString& playbackStatus); + public slots: void volumeChanged(MPrisControl *mad, double); + void playbackStateChanged(MPrisControl* mad, MediaController::PlayState); + void newMediaPlayer(QString name, QString oldOwner, QString newOwner); void addMprisControlAsync(QString arg1); - void notifyToReconfigureControlsAsync(QString streamId); - void notifyToReconfigureControls(); + void announceControlListAsync(QString streamId); + +private slots: + // asynchronous announce call slots + void announceControlList(); + void announceGUI(); + void announceVolume(); // Async QDBusPendingCallWatcher's - void plugControlIdIncoming(QDBusPendingCallWatcher* watcher); - void mediaContolReplyIncoming(QDBusPendingCallWatcher* watcher); + void watcherMediaControl(QDBusPendingCallWatcher* watcher); + void watcherPlugControlId(QDBusPendingCallWatcher* watcher); + void watcherInitialVolume(QDBusPendingCallWatcher* watcher); + void watcherInitialPlayState(QDBusPendingCallWatcher* watcher); + +private: + // Helpers for the watchers + MPrisControl* watcherHelperGetMPrisControl(QDBusPendingCallWatcher* watcher); + private: // void asyncAddMprisControl(QString busDestination); - void messageQueueThreadLoop(); +// void messageQueueThreadLoop(); int addAllRunningPlayersAndInitHotplug(); void volumeChangedInternal(shared_ptr md, int volumePercentage); QString busDestinationToControlId(const QString& busDestination); diff -Nru kmix-4.12.3/backends/mixer_oss4.cpp kmix-4.12.90/backends/mixer_oss4.cpp --- kmix-4.12.3/backends/mixer_oss4.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_oss4.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -509,6 +509,11 @@ return l_s_errmsg; } +int Mixer_OSS4::id2num(const QString& id) +{ + return id.toInt(); +} + bool Mixer_OSS4::prepareUpdateFromHW() { oss_mixerinfo minfo; diff -Nru kmix-4.12.3/backends/mixer_oss4.h kmix-4.12.90/backends/mixer_oss4.h --- kmix-4.12.3/backends/mixer_oss4.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_oss4.h 2014-01-01 04:02:55.000000000 +0000 @@ -37,5 +37,8 @@ int m_numExtensions; int m_modifyCounter; QString m_deviceName; + +private: + int id2num(const QString& id); }; #endif diff -Nru kmix-4.12.3/backends/mixer_oss.cpp kmix-4.12.90/backends/mixer_oss.cpp --- kmix-4.12.3/backends/mixer_oss.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_oss.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -87,9 +87,14 @@ return l_mixer; } -Mixer_OSS::Mixer_OSS( Mixer* mixer, int device) : Mixer_Backend(mixer, device) +Mixer_OSS::Mixer_OSS(Mixer* mixer, int device) : + Mixer_Backend(mixer, device) { - if( device == -1 ) m_devnum = 0; + if (device == -1) + { + m_devnum = 0; + } + m_fd = -1; // point to an invalid FD } Mixer_OSS::~Mixer_OSS() @@ -237,6 +242,7 @@ break; default: l_s_errmsg = Mixer_Backend::errorText(mixer_error); + break; } return l_s_errmsg; } @@ -324,6 +330,20 @@ } +int Mixer_OSS::id2num(const QString& id) +{ + return id.toInt(); +} + +/** + * Prints out a translated error text for the given error number on stderr + */ +void Mixer_OSS::errormsg(int mixer_error) +{ + QString l_s_errText; + l_s_errText = errorText(mixer_error); + kError() << l_s_errText << "\n"; +} int Mixer_OSS::readVolumeFromHW( const QString& id, shared_ptr md ) diff -Nru kmix-4.12.3/backends/mixer_oss.h kmix-4.12.90/backends/mixer_oss.h --- kmix-4.12.3/backends/mixer_oss.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_oss.h 2014-01-01 04:02:55.000000000 +0000 @@ -51,7 +51,8 @@ QString m_deviceName; int setRecsrcToOSS( const QString& id, bool on ); - + void errormsg(int mixer_error); + int id2num(const QString& id); }; #endif diff -Nru kmix-4.12.3/backends/mixer_pulse.cpp kmix-4.12.90/backends/mixer_pulse.cpp --- kmix-4.12.3/backends/mixer_pulse.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_pulse.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -889,7 +889,7 @@ else if (m_devnum == KMIXPA_APP_CAPTURE && s_mixers.contains(KMIXPA_CAPTURE)) ms = s_mixers[KMIXPA_CAPTURE]->getMixSet(); - int maxVol = GlobalConfig::instance().volumeOverdrive ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM; + int maxVol = GlobalConfig::instance().data.volumeOverdrive ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM; Volume v(maxVol, PA_VOLUME_MUTED, true, false); v.addVolumeChannels(dev.chanMask); setVolumeFromPulse(v, dev); @@ -1106,7 +1106,6 @@ } int Mixer_PULSE::id2num(const QString& id) { - //kDebug(67100) << "id2num() id=" << id; int num = -1; // todo: Store this in a hash or similar int i; diff -Nru kmix-4.12.3/backends/mixer_sun.cpp kmix-4.12.90/backends/mixer_sun.cpp --- kmix-4.12.3/backends/mixer_sun.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_sun.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -479,6 +479,11 @@ } } +int Mixer_SUN::id2num(const QString& id) +{ + return id.toInt(); +} + QString SUN_getDriverName() { return "SUNAudio"; } diff -Nru kmix-4.12.3/backends/mixer_sun.h kmix-4.12.90/backends/mixer_sun.h --- kmix-4.12.3/backends/mixer_sun.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/backends/mixer_sun.h 2014-01-01 04:02:55.000000000 +0000 @@ -47,6 +47,9 @@ void GainBalanceToVolume( uint_t& gain, uchar_t& balance, Volume& volume ); int fd; + +private: + int id2num(const QString& id); }; #endif diff -Nru kmix-4.12.3/ChangeLog kmix-4.12.90/ChangeLog --- kmix-4.12.3/ChangeLog 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/ChangeLog 2014-01-01 04:02:55.000000000 +0000 @@ -1,24 +1,25 @@ -V1.90 -Version shipped with KDE3.1 +KDE v4.13 +- Configuration menu is now using Tabs to provide a more standard, convenient and pleasing layout +- Sound Menu: Play button reflects playback status and shows either "Play" or "Pause" + +- Bugfixes and Features: +Bug ID Severity Summary +317926 critical kmix start delayed on 20 seconds +315383 normal mpris volume/play status not in sync with app in kmix applet +299477 normal KMix Does Not Unmute Master Audio Channel +256854 normal Tray popup obscures vertical panel +304144 normal KMix does not distinguish separate channels on one volume control +296951 normal Tray plasmoid scroll/hiding issues [read description] +303608 crash Kmix crashed after selecting Amarok as master and instantly crashed when changing song. Volumes in exeption of the integrated subwoofer go fine; the built-in woofer must be changed trough Alsa, and Amarok must be volumed up/down by it's bar. +214854 normal kmix size is not restored by session management, only position. +319600 major KMix volume icon is unstale during playback -V1.91 -- Multiple soundcards on ALSA are now supported -- Mixer device names are now shown with a vertical label (saves space and looks nice) -- MixerDevice categories. Allows to distribute devices on "New Mixer Tab...". It is used - in the panel applet to show only important devices initally. -- Much nicer "New Mixer Tab..." dialog -v3.0 -- KDE 4 version +KDE v4.11 +- Full Sound Menu: Media Player control support -v3.5 -- This is the KDE 4.2 head version -- Wish 132330: On-screen display of volume level -- Wish 157701: Smaller neater dockarea popup -- Bug 161393: kmix does not associate hotplugged USB HID volume control -- Bug 168658: kmix - master channel missing -- Bug 172958: OSD doesn't update when volume is changed using keyboard -- Feature : Automatically grab XF86VolumeUp, XF86VolumeDown and XF86VolumeMute (done) +v3.8 +- Feature : KMix has now a profile for TerraTec DMX6Fire cards and is able to handle it's weird volume controls v3.6 - This is the KDE 4.4 version @@ -30,5 +31,25 @@ i001: When the user redefines the automatially grabbed XF86Volume* keys, he doesn't have an easy possibilty to get them back, because they are now marked as "no shortcut" in the global shortcut registry. The user must go the KDE system settings module "global shortcuts" and reset them to their default. No user will wfind that!!! So this should be possible inside KMix (ShortcutsDialog or a button "reset volume keys" in the configuration dialog). -v3.8 -- Feature : KMix has now a profile for TerraTec DMX6Fire cards and is able to handle it's weird volume controls +v3.5 +- This is the KDE 4.2 head version +- Wish 132330: On-screen display of volume level +- Wish 157701: Smaller neater dockarea popup +- Bug 161393: kmix does not associate hotplugged USB HID volume control +- Bug 168658: kmix - master channel missing +- Bug 172958: OSD doesn't update when volume is changed using keyboard +- Feature : Automatically grab XF86VolumeUp, XF86VolumeDown and XF86VolumeMute (done) + +v3.0 +- KDE 4 version + +V1.91 +- Multiple soundcards on ALSA are now supported +- Mixer device names are now shown with a vertical label (saves space and looks nice) +- MixerDevice categories. Allows to distribute devices on "New Mixer Tab...". It is used + in the panel applet to show only important devices initally. +- Much nicer "New Mixer Tab..." dialog + +V1.90 +Version shipped with KDE3.1 + diff -Nru kmix-4.12.3/CMakeLists.txt kmix-4.12.90/CMakeLists.txt --- kmix-4.12.3/CMakeLists.txt 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/CMakeLists.txt 2014-01-01 04:02:55.000000000 +0000 @@ -6,6 +6,9 @@ include(KDE4Defaults) include(MacroLibrary) +# Do not yet REQUIRE Phonon. Hint: As long as we do not find_package(), ${KDE4_PHONON_LIBS} will be empty below, but that should not hurt. +#find_package(Phonon REQUIRED) + find_package(Alsa) macro_optional_find_package(PulseAudio "0.9.12") @@ -110,6 +113,7 @@ gui/dialogchoosebackends.cpp gui/guiprofile.cpp gui/osdwidget.cpp + core/MediaController.cpp core/mixertoolbox.cpp core/kmixdevicemanager.cpp core/ControlManager.cpp @@ -125,7 +129,7 @@ kde4_add_kdeinit_executable( kmix ${kmix_KDEINIT_SRCS}) -target_link_libraries(kdeinit_kmix ${KDE4_SOLID_LIBS} ${KDE4_KDEUI_LIBS} ${KDE4_PLASMA_LIBS} ${QT_QTXML_LIBRARY}) +target_link_libraries(kdeinit_kmix ${KDE4_SOLID_LIBS} ${KDE4_KDEUI_LIBS} ${KDE4_PLASMA_LIBS} ${QT_QTXML_LIBRARY} ${KDE4_PHONON_LIBS}) #target_link_libraries(kdeinit_kmix ${KDE4_KUTILS_LIBS} /home/kde/workspace/kdelibs/build/lib/libsolid.so.4.7.0 ${KDE4_KDEUI_LIBS} ${KDE4_PLASMA_LIBS} ${QT_QTXML_LIBRARY}) if (HAVE_LIBASOUND2) @@ -153,14 +157,13 @@ # core/ControlPool.cpp core/GlobalConfig.cpp core/MasterControl.cpp + core/MediaController.cpp core/mixer.cpp core/mixset.cpp core/mixdevice.cpp core/volume.cpp core/mixertoolbox.cpp core/kmixdevicemanager.cpp - backends/mixer_mpris2.cpp - backends/mixer_backend.cpp ) #qt4_add_dbus_adaptor(kded_kmixd_SRCS org.kde.KMixD.xml kmixd.h Mixer) @@ -168,7 +171,7 @@ kde4_add_plugin(kded_kmixd ${kded_kmixd_SRCS}) -target_link_libraries(kded_kmixd ${KDE4_KDEUI_LIBS} ${KDE4_SOLID_LIBS} ${QT_QTXML_LIBRARY}) +target_link_libraries(kded_kmixd ${KDE4_KDEUI_LIBS} ${KDE4_SOLID_LIBS} ${QT_QTXML_LIBRARY} ${KDE4_PHONON_LIBS}) if (HAVE_LIBASOUND2) target_link_libraries(kded_kmixd ${ASOUND_LIBRARY}) @@ -197,6 +200,7 @@ # core/ControlPool.cpp core/GlobalConfig.cpp core/MasterControl.cpp + core/MediaController.cpp core/mixer.cpp core/mixset.cpp core/mixdevice.cpp @@ -210,7 +214,7 @@ kde4_add_kdeinit_executable( kmixctrl ${kmixctrl_KDEINIT_SRCS}) -target_link_libraries(kdeinit_kmixctrl ${KDE4_KDEUI_LIBS} ${KDE4_SOLID_LIBS} ${QT_QTXML_LIBRARY}) +target_link_libraries(kdeinit_kmixctrl ${KDE4_KDEUI_LIBS} ${KDE4_SOLID_LIBS} ${QT_QTXML_LIBRARY} ${KDE4_PHONON_LIBS}) if (HAVE_LIBASOUND2) target_link_libraries(kdeinit_kmixctrl ${ASOUND_LIBRARY}) @@ -233,6 +237,7 @@ install( TARGETS kmixctrl ${INSTALL_TARGETS_DEFAULT_ARGS} ) install( PROGRAMS kmix.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) +install( PROGRAMS apps/kmixremote DESTINATION ${BIN_INSTALL_DIR} ) install( FILES restore_kmix_volumes.desktop DESTINATION ${AUTOSTART_INSTALL_DIR}) install( FILES kmix_autostart.desktop DESTINATION ${AUTOSTART_INSTALL_DIR}) install( FILES kmixui.rc DESTINATION ${DATA_INSTALL_DIR}/kmix ) diff -Nru kmix-4.12.3/core/ControlManager.cpp kmix-4.12.90/core/ControlManager.cpp --- kmix-4.12.3/core/ControlManager.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/ControlManager.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -69,7 +69,7 @@ bool listenerAlreadyProcesed = processedListeners.contains(&listener); if ( listenerAlreadyProcesed ) { - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) kDebug() << "Skipping already processed listener"; continue; } @@ -77,7 +77,7 @@ { bool success = QMetaObject::invokeMethod(listener.getTarget(), "controlsChange", Qt::DirectConnection, Q_ARG(int, changeType)); - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) { kDebug() << "Listener " << listener.getSourceId() <<" is interested in " << mixerId << ", " << ControlChangeType::toString(changeType); @@ -91,7 +91,7 @@ if (listenersChanged) { // The invokeMethod() above has changed the listeners => my Iterator is invalid => restart loop - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) kDebug() << "Listeners modified => restart loop"; listenersChanged = false; listenersModified = true; @@ -103,7 +103,7 @@ } while ( listenersModified); - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) { kDebug() << "Announcing " << ControlChangeType::toString(changeType) << " for " @@ -123,7 +123,7 @@ */ void ControlManager::addListener(QString mixerId, ControlChangeType::Type changeType, QObject* target, QString sourceId) { - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) { kDebug() << "Listening to " << ControlChangeType::toString(changeType) << " for " @@ -141,7 +141,7 @@ listenersChanged = true; } } - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) { kDebug() << "We now have" << listeners.size() << "listeners"; @@ -170,7 +170,7 @@ Listener& listener = it.next(); if (listener.getTarget() == target) { - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) kDebug() << "Stop Listening of " << listener.getSourceId() << " requested by " << sourceId << " from " << target; it.remove(); @@ -187,13 +187,13 @@ void ControlManager::shutdownNow() { - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) kDebug() << "Shutting down ControlManager"; QList::iterator it; for (it = listeners.begin(); it != listeners.end(); ++it) { Listener& listener = *it; - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) kDebug() << "Listener still connected. Closing it. source=" << listener.getSourceId() << "listener=" << listener.getTarget()->metaObject()->className(); diff -Nru kmix-4.12.3/core/GlobalConfig.cpp kmix-4.12.90/core/GlobalConfig.cpp --- kmix-4.12.3/core/GlobalConfig.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/GlobalConfig.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -1,23 +1,110 @@ /* - KMix -- KDE's full featured mini mixer - Copyright (C) 2012 Christian Esken + 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. -*/ + 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 "GlobalConfig.h" -GlobalConfig GlobalConfig::instanceObj; +// instanceObj must be created "late", so we can refer to the correct application config file kmixrc instead of kderc. +GlobalConfig* GlobalConfig::instanceObj; +GlobalConfig::GlobalConfig() : + KConfigSkeleton() +{ + setCurrentGroup("Global"); + // General + addItemBool("Tickmarks", data.showTicks, true); + addItemBool("Labels", data.showLabels, true); + addItemBool("VolumeOverdrive", data.volumeOverdrive, false); + addItemBool("VolumeFeedback", data.beepOnVolumeChange, true); + ItemString* is = addItemString("Orientation", data.orientationMainGUIString, "Vertical"); + kDebug() << is->name() << is->value(); + addItemString("Orientation.TrayPopup", data.orientationTrayPopupString, QLatin1String("Vertical")); + + // Sound Menu + addItemBool("showOSD", data.showOSD, true); + addItemBool("AllowDocking", data.showDockWidget, true); + +// addItemBool("TrayVolumeControl", data.trayVolumePopupEnabled, true); // removed support in KDE4.13. Always active! + + // Startup + addItemBool("AutoStart", data.allowAutostart, true); + addItemBool("VolumeFeedback", data.volumeFeedback, true); + addItemBool("startkdeRestore", data.startkdeRestore, true); + + // Debug options: Not in dialog + addItemBool("Debug.ControlManager", data.debugControlManager, false); + addItemBool("Debug.GUI", data.debugGUI, false); + addItemBool("Debug.Volume", data.debugVolume, false); + + readConfig(); +} + +// --- Special READ/WRITE ---------------------------------------------------------------------------------------- +void GlobalConfig::usrReadConfig() +{ +// kDebug() << "or=" << data.orientationMainGUIString; + // Convert orientation strings to Qt::Orientation + data.convertOrientation(); +} + +//void GlobalConfig::usrWriteConfig() +//{ +// // TODO: Is this any good? When is usrWriteConfig() called? Hopefully BEFORE actually writing. Otherwise +// // I must move this code to #setToplevelOrientation() and #setTraypopupOrientation(). +//} + +Qt::Orientation GlobalConfigData::getToplevelOrientation() +{ + return toplevelOrientation; +} + +Qt::Orientation GlobalConfigData::getTraypopupOrientation() +{ + return traypopupOrientation; +} + +/** + * Converts the orientation strings to Qt::Orientation + */ +void GlobalConfigData::convertOrientation() +{ + toplevelOrientation = stringToOrientation(orientationMainGUIString); + traypopupOrientation = stringToOrientation(orientationTrayPopupString); +} + +void GlobalConfigData::setToplevelOrientation(Qt::Orientation orientation) +{ + toplevelOrientation = orientation; + orientationMainGUIString = orientationToString(toplevelOrientation); +} + +void GlobalConfigData::setTraypopupOrientation(Qt::Orientation orientation) +{ + traypopupOrientation = orientation; + orientationTrayPopupString = orientationToString(traypopupOrientation); +} + +Qt::Orientation GlobalConfigData::stringToOrientation(QString& orientationString) +{ + return orientationString == "Horizontal" ? Qt::Horizontal : Qt::Vertical; +} + +QString GlobalConfigData::orientationToString(Qt::Orientation orientation) +{ + return orientation == Qt::Horizontal ? "Horizontal" : "Vertical"; +} diff -Nru kmix-4.12.3/core/GlobalConfig.h kmix-4.12.90/core/GlobalConfig.h --- kmix-4.12.3/core/GlobalConfig.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/GlobalConfig.h 2014-01-01 04:02:55.000000000 +0000 @@ -1,22 +1,21 @@ /* - 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. -*/ + 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. + */ #ifndef GLOBALCONFIG_H #define GLOBALCONFIG_H @@ -24,45 +23,104 @@ #include #include -class GlobalConfig +#include +#include + +class GlobalConfigData { + friend class GlobalConfig; + +public: + // Hint: We are using the standard 1-arg constructor as copy constructor + + bool showTicks; + bool showLabels; + bool showOSD; + + bool volumeFeedback; + + bool volumeOverdrive; // whether more than recommended volume (typically 0dB) is allowed + bool beepOnVolumeChange; + + // Startup + bool allowAutostart; + bool showDockWidget; + bool startkdeRestore; + + // Debug options + bool debugControlManager; + bool debugGUI; + bool debugVolume; + + Qt::Orientation getToplevelOrientation(); + Qt::Orientation getTraypopupOrientation(); + + void setToplevelOrientation(Qt::Orientation orientation); + void setTraypopupOrientation(Qt::Orientation orientation); + +private: + QString orientationMainGUIString; + QString orientationTrayPopupString; + // The following two values are only converted/cached date from the former fields. + Qt::Orientation toplevelOrientation; + Qt::Orientation traypopupOrientation; + + void convertOrientation(); + Qt::Orientation stringToOrientation(QString& orientationString); + QString orientationToString(Qt::Orientation orientation); + +}; + +class GlobalConfig: public KConfigSkeleton +{ +private: + static GlobalConfig* instanceObj; + public: - bool showTicks; - bool showLabels; - bool showOSD; - Qt::Orientation toplevelOrientation; - Qt::Orientation traypopupOrientation; - void setMixersForSoundmenu(QSet mixersForSoundmenu) { this->mixersForSoundmenu = mixersForSoundmenu; }; - QSet getMixersForSoundmenu() { return mixersForSoundmenu; }; - - static GlobalConfig& instance() { return instanceObj; }; - - bool volumeOverdrive; // whether more than recommended volume (typically 0dB) is allowed - - bool debugControlManager; - bool debugGUI; - bool debugVolume; + static GlobalConfig& instance() + { + return *instanceObj; + } + ; + + /** + * Call this init method when your app core is properly initialized. + * It is very important that KGlobal is initialized then. Otherwise KGlobal::config() could return a reference to + * the "kderc" config instead of the actual application config "kmixrc" or "kmixctrlrc". + * + */ + static void init() + { + instanceObj = new GlobalConfig(); + } + ; + + GlobalConfigData data; + void setMixersForSoundmenu(QSet mixersForSoundmenu) + { + this->mixersForSoundmenu = mixersForSoundmenu; + } + ; + QSet getMixersForSoundmenu() + { + return mixersForSoundmenu; + } + ; protected: - QSet mixersForSoundmenu; + QSet mixersForSoundmenu; private: - GlobalConfig() - { - showTicks = true; - showLabels = true; - showOSD = true; - toplevelOrientation = Qt::Vertical; - traypopupOrientation = Qt::Vertical; - volumeOverdrive = false; - - debugControlManager = false; - debugGUI = false; - debugVolume = false; - }; - - static GlobalConfig instanceObj; + GlobalConfig(); + /** + * @Override + */ + virtual void usrReadConfig(); + /** + * @Override + */ +// virtual void usrWriteConfig(); }; #endif // GLOBALCONFIG_H diff -Nru kmix-4.12.3/core/kmixdevicemanager.cpp kmix-4.12.90/core/kmixdevicemanager.cpp --- kmix-4.12.3/core/kmixdevicemanager.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/kmixdevicemanager.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -57,35 +57,69 @@ QString KMixDeviceManager::getUDI_ALSA(int num) { - QList dl = Solid::Device::listFromType(Solid::DeviceInterface::AudioInterface); + QList dl = Solid::Device::listFromType(Solid::DeviceInterface::AudioInterface); - QString numString; - numString.setNum(num); - bool found = false; - QString udi; - QString devHandle; - foreach ( const Solid::Device &device, dl ) - { -// std::cout << "Coldplug udi = '" << device.udi().toUtf8().data() << "'\n"; - const Solid::AudioInterface *audiohw = device.as(); - if (audiohw && (audiohw->deviceType() & ( Solid::AudioInterface::AudioControl))) { - switch (audiohw->driver()) { - case Solid::AudioInterface::Alsa: - devHandle = audiohw->driverHandle().toList().first().toString(); -// std::cout << ">>> Coldplugged ALSA ='" << devHandle.toUtf8().data() << "'\n"; - if ( numString == devHandle ) { - found = true; -// std::cout << ">>> Match!!! Coldplugged ALSA ='" << devHandle.toUtf8().data() << "'\n"; - udi = device.udi(); - } - break; - default: - break; - } // driver type - } // is an audio control - if ( found) break; - } // foreach - return udi; + QString numString; + numString.setNum(num); + bool found = false; + QString udi; + QString devHandle; + foreach ( const Solid::Device &device, dl ) + { + // std::cout << "Coldplug udi = '" << device.udi().toUtf8().data() << "'\n"; + // LEAK audiohw leaks, but Solid does not document whether it is its own or "my" Object. + const Solid::AudioInterface *audiohw = device.as(); + if (audiohw != 0) + { + if (audiohw->deviceType() & ( Solid::AudioInterface::AudioControl)) + { + switch (audiohw->driver()) + { + case Solid::AudioInterface::Alsa: + devHandle = audiohw->driverHandle().toList().first().toString(); + if ( numString == devHandle ) + { + found = true; + udi = device.udi(); + } + break; + default: + break; + } // driver type + } // is an audio control + + // If I delete audiohw, kmix crashes. If I do not, there is a definite leak according to valgrind: +// ==24561== 4,958 (1,200 direct, 3,758 indirect) bytes in 25 blocks are definitely lost in loss record 1,882 of 1,918 +// ==24561== at 0x4C27D49: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +// ==24561== by 0x5665462: ??? (in /usr/lib64/libsolid.so.4.11.3) +// ==24561== by 0x565EC57: ??? (in /usr/lib64/libsolid.so.4.11.3) +// ==24561== by 0x563610B: Solid::Device::asDeviceInterface(Solid::DeviceInterface::Type const&) const (in /usr/lib64/libsolid.so.4.11.3) +// ==24561== by 0x4EB7DA6: Solid::AudioInterface const* Solid::Device::as() const (device.h:254) +// ==24561== by 0x4EB6F6F: KMixDeviceManager::getUDI_ALSA(int) (kmixdevicemanager.cpp:70) +// ==24561== by 0x4E6C809: Mixer_ALSA::open() (mixer_alsa9.cpp:139) +// ==24561== by 0x4E6334E: Mixer_Backend::openIfValid() (mixer_backend.cpp:84) +// ==24561== by 0x4EBD4F9: Mixer::openIfValid(int) (mixer.cpp:268) +// ==24561== by 0x4EB5F10: MixerToolBox::possiblyAddMixer(Mixer*) (mixertoolbox.cpp:310) +// ==24561== by 0x4EB57AE: MixerToolBox::initMixerInternal(MixerToolBox::MultiDriverMode, QList, QString&) (mixertoolbox.cpp:165) +// ==24561== by 0x4EB52B6: MixerToolBox::initMixer(MixerToolBox::MultiDriverMode, QList, QString&) (mixertoolbox.cpp:85) +// ==24561== by 0x4EB5267: MixerToolBox::initMixer(bool, QList, QString&) (mixertoolbox.cpp:80) +// ==24561== by 0x4E7ED75: KMixWindow::KMixWindow(bool) (kmix.cpp:96) +// ==24561== by 0x4E8A7CF: KMixApp::newInstance() (KMixApp.cpp:112) +// ==24561== by 0x5B30A7E: KUniqueApplication::Private::_k_newInstanceNoFork() (in /usr/lib64/libkdeui.so.5.11.3) +// ==24561== by 0x774A11D: QObject::event(QEvent*) (in /usr/lib64/libQtCore.so.4.8.5) +// ==24561== by 0x61338A2: QApplication::event(QEvent*) (in /usr/lib64/libQtGui.so.4.8.5) +// ==24561== by 0x612E8AB: QApplicationPrivate::notify_helper(QObject*, QEvent*) (in /usr/lib64/libQtGui.so.4.8.5) +// ==24561== by 0x6134E6F: QApplication::notify(QObject*, QEvent*) (in /usr/lib64/libQtGui.so.4.8.5) + + // The data seems to be "static" device info from Solid, so I will leave it alone. +// delete audiohw; + } + if (found) + { + break; + } + } // foreach + return udi; } QString KMixDeviceManager::getUDI_OSS(const QString& devname) @@ -156,10 +190,10 @@ void KMixDeviceManager::unpluggedSlot(const QString& udi) { // std::cout << "Unplugged udi='" << udi.toUtf8().data() << "'\n"; - Solid::Device device(udi); +// Solid::Device device(udi); // At this point the device has already been unplugged by the user. Solid doesn't know anything about the // device except the UDI (not even device.as() is possible). Thus I'll forward any - // unplugging action (could e.g. also be HID or mass storage). The receiver of the signal as to deal with it, + // unplugging action (could e.g. also be HID or mass storage). The receiver of the signal has to deal with it, // but a simple UDI matching is enough. emit unplugged(udi); diff -Nru kmix-4.12.3/core/MediaController.cpp kmix-4.12.90/core/MediaController.cpp --- kmix-4.12.3/core/MediaController.cpp 1970-01-01 00:00:00.000000000 +0000 +++ kmix-4.12.90/core/MediaController.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -0,0 +1,88 @@ +//-*-C++-*- +/* + * 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. + */ + + +/* + * MediaController.cpp + * + * Created on: 17.12.2013 + * Author: chris + */ + +#include "core/MediaController.h" + +//#include +//#include + +#include + +MediaController::MediaController(QString controlId) : + id(controlId), playState(PlayUnknown) +{ + mediaPlayControl = false; + mediaNextControl = false; + mediaPrevControl = false; + + /* + { + // Phonon connection test code + QList devs = Phonon::BackendCapabilities::availableAudioOutputDevices(); + + if (devs.isEmpty()) + return; + + Phonon::AudioOutputDevice& dev = devs[0]; + + QList props = dev.propertyNames(); + kDebug() << "desc=" << dev.description() << ", name=" << dev.name() << ", props="; + QByteArray prop; + int i=0; + foreach (prop, props) + { + kDebug() << "#" << i << ": "<< prop; + ++i; + } + } + */ +} + +MediaController::~MediaController() +{ +} + +/** + * Returns whether this device has at least one media player control. + * @return + */ +bool MediaController::hasControls() +{ + return mediaPlayControl | mediaNextControl | mediaPrevControl; +} + +MediaController::PlayState MediaController::getPlayState() +{ + return playState; +} + +void MediaController::setPlayState(PlayState playState) +{ + this->playState = playState; +} diff -Nru kmix-4.12.3/core/MediaController.h kmix-4.12.90/core/MediaController.h --- kmix-4.12.3/core/MediaController.h 1970-01-01 00:00:00.000000000 +0000 +++ kmix-4.12.90/core/MediaController.h 2014-01-01 04:02:55.000000000 +0000 @@ -0,0 +1,69 @@ +//-*-C++-*- +/* + * 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. + */ + +/* + * MediaController.h + * + * Created on: 17.12.2013 + * Author: chris + */ + +#ifndef MEDIACONTROLLER_H_ +#define MEDIACONTROLLER_H_ + +#include + +/** + * A MediaController controls exactly one Media Player. You can think of it as a single control, like PCM. + */ +class MediaController +{ +public: + enum PlayState { PlayPaused, PlayPlaying, PlayStopped, PlayUnknown }; + + MediaController(QString); + virtual ~MediaController(); + + void addMediaPlayControl() { mediaPlayControl = true; }; + void addMediaNextControl() { mediaNextControl = true; }; + void addMediaPrevControl() { mediaPrevControl = true; }; + bool hasMediaPlayControl() { return mediaPlayControl; }; + bool hasMediaNextControl() { return mediaNextControl; }; + bool hasMediaPrevControl() { return mediaPrevControl; }; + bool hasControls(); + + + MediaController::PlayState getPlayState(); + void setPlayState(PlayState playState); + + bool canSkipNext(); + bool canSkipPrevious(); + +private: + QString id; + PlayState playState; + + bool mediaPlayControl; + bool mediaNextControl; + bool mediaPrevControl; +}; + +#endif /* MEDIACONTROLLER_H_ */ diff -Nru kmix-4.12.3/core/mixdevice.cpp kmix-4.12.90/core/mixdevice.cpp --- kmix-4.12.3/core/mixdevice.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/mixdevice.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -90,7 +90,7 @@ case MixDevice::APPLICATION_TOMAHAWK: return "tomahawk"; case MixDevice::APPLICATION_CLEMENTINE: - return "clementine"; + return "application-x-clementine"; case MixDevice::APPLICATION_VLC: return "vlc"; @@ -127,9 +127,8 @@ _mixer = mixer; _id = id; _enumCurrentId = 0; - mediaPlayControl = false; - mediaNextControl = false; - mediaPrevControl = false; + + mediaController = new MediaController(_id); if( name.isEmpty() ) _name = i18n("unknown"); else @@ -162,6 +161,12 @@ } +MediaController* MixDevice::getMediaController() +{ + return mediaController; +} + + shared_ptr MixDevice::addToPool() { // kDebug() << "id=" << _mixer->id() << ":" << _id; @@ -209,7 +214,8 @@ if (volumeType & Volume::Capture) { - kDebug() << "VolumeType=" << volumeType << " c"; + if (debugme) + kDebug() << "VolumeType=" << volumeType << " c"; Volume& volC = captureVolume(); long inc = volC.volumeStep(decrease); diff -Nru kmix-4.12.3/core/mixdevice.h kmix-4.12.90/core/mixdevice.h --- kmix-4.12.3/core/mixdevice.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/mixdevice.h 2014-01-01 04:02:55.000000000 +0000 @@ -32,6 +32,7 @@ #endif //KMix +#include "core/MediaController.h" class Mixer; class MixSet; class ProfControl; @@ -135,12 +136,8 @@ void addEnums (QList& ref_enumList); // Media controls. New for KMix 4.0 - void addMediaPlayControl() { mediaPlayControl = true; }; - void addMediaNextControl() { mediaNextControl = true; }; - void addMediaPrevControl() { mediaPrevControl = true; }; - bool hasMediaPlayControl() { return mediaPlayControl; }; - bool hasMediaNextControl() { return mediaNextControl; }; - bool hasMediaPrevControl() { return mediaPrevControl; }; + MediaController* getMediaController(); + // TODO move all media player controls to the MediaController class int mediaPlay(); int mediaPrev(); int mediaNext(); @@ -229,7 +226,6 @@ void increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType); - protected: void init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ); @@ -242,6 +238,7 @@ QList _enumValues; // A MixDevice, that is an ENUM, has these _enumValues DBusControlWrapper *_dbusControlWrapper; + MediaController* mediaController; // A virtual control. It will not be saved/restored and/or doesn't get shortcuts // Actually we discriminate those "virtual" controls in artificial controls and dynamic controls: @@ -259,11 +256,6 @@ void readPlaybackOrCapture(const KConfigGroup& config, bool capture); void writePlaybackOrCapture(KConfigGroup& config, bool capture); - - bool mediaPlayControl; - bool mediaNextControl; - bool mediaPrevControl; - }; #endif diff -Nru kmix-4.12.3/core/mixer.cpp kmix-4.12.90/core/mixer.cpp --- kmix-4.12.3/core/mixer.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/mixer.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -181,7 +181,7 @@ primaryKeyOfMixer.replace(' ','_'); primaryKeyOfMixer.replace('=','_'); _id = primaryKeyOfMixer; -// kDebug() << "Early _id=" << _id; + kDebug() << "Early _id=" << _id; } const QString Mixer::dbusPath() @@ -283,7 +283,8 @@ QString noMaster = "---no-master-detected---"; setLocalMasterMD(noMaster); // no master } - connect( _mixerBackend, SIGNAL(controlChanged()), SIGNAL(controlChanged()) ); + // cesken: The following connect() looks mighty strange. I removed it on 2013-12-18 + //connect( _mixerBackend, SIGNAL(controlChanged()), SIGNAL(controlChanged()) ); new DBusMixerWrapper(this, dbusPath()); } @@ -405,11 +406,26 @@ */ QString Mixer::readableName() { + return readableName(false); +} + +/** + * Returns a name suitable for a human user to read, possibly with quoted ampersand. The latter is required by + * some GUI elements like QRadioButton or when used as a Tab label, as '&' introduces an accelerator there. + * + * @param ampersandQuoted + * @return + */ +QString Mixer::readableName(bool ampersandQuoted) +{ QString finalName = _mixerBackend->getName(); -// QString finalName = mixerName.left(mixerName.length() - 2); + if (ampersandQuoted) + finalName.replace('&', "&&"); + if ( getCardInstance() > 1) finalName = finalName.append(" %1").arg(getCardInstance()); +// kDebug() << "name=" << _mixerBackend->getName() << "instance=" << getCardInstance() << ", finalName" << finalName; return finalName; } @@ -584,13 +600,15 @@ shared_ptr Mixer::getMixdeviceById( const QString& mixdeviceID ) { - shared_ptr md; - int num = _mixerBackend->id2num(mixdeviceID); - if ( num!=-1 && num < (int)size() ) - { - md = (*this)[num]; - } - return md; + kDebug() << "id=" << mixdeviceID << "md=" << _mixerBackend->m_mixDevices.get(mixdeviceID).get()->id(); + return _mixerBackend->m_mixDevices.get(mixdeviceID); +// shared_ptr md; +// int num = _mixerBackend->id2num(mixdeviceID); +// if ( num!=-1 && num < (int)size() ) +// { +// md = (*this)[num]; +// } +// return md; } /** @@ -618,12 +636,12 @@ // We also cannot rely on a notification from the driver (SocketNotifier), because // nothing has changed, and so there s nothing to notify. _mixerBackend->readSetFromHWforceUpdate(); - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) kDebug() << "committing a control with capture volume, that might announce: " << md->id(); _mixerBackend->readSetFromHW(); } - if (GlobalConfig::instance().debugControlManager) + if (GlobalConfig::instance().data.debugControlManager) kDebug() << "committing announces the change of: " << md->id(); diff -Nru kmix-4.12.3/core/mixer.h kmix-4.12.90/core/mixer.h --- kmix-4.12.3/core/mixer.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/mixer.h 2014-01-01 04:02:55.000000000 +0000 @@ -75,6 +75,7 @@ unsigned int size() const; /// Returns a pointer to the mix device with the given number + // TODO remove this method. Only used by ViewDockAreaPopup: dockMD = (*mixer)[0]; shared_ptr operator[](int val_i_num); /// Returns a pointer to the mix device whose type matches the value @@ -109,7 +110,8 @@ QString translateKernelToWhatsthis(const QString &kernelName); /// Return the name of the card/chip/hardware, which is suitable for humans - virtual QString readableName(); + QString readableName(); + QString readableName(bool ampersandQuoted); // Returns the name of the driver, e.g. "OSS" or "ALSA0.9" static QString driverName(int num); @@ -122,12 +124,8 @@ */ QString& id(); -// void setCardInstance(int cardInstance); int getCardInstance() const { return _cardInstance; } - //void setID(QString& ref_id); - - /// Returns an Universal Device Identifaction of the Mixer. This is an ID that relates to the underlying operating system. // For OSS and ALSA this is taken from Solid (actually HAL). For Solaris this is just the device name. // Examples: diff -Nru kmix-4.12.3/core/mixertoolbox.cpp kmix-4.12.90/core/mixertoolbox.cpp --- kmix-4.12.3/core/mixertoolbox.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/mixertoolbox.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -132,6 +132,12 @@ bool autodetectionFinished = false; for( int drv=0; drv sane exit from outer loop + break; + } + QString driverName = Mixer::driverName(drv); kDebug(67100) << "Looking for mixers with the : " << driverName << " driver"; if ( useBackendFilter && ! backendList.contains(driverName) ) @@ -141,11 +147,6 @@ } - if ( autodetectionFinished ) { - // inner loop indicates that we are finished => sane exit from outer loop - break; - } - bool regularBackend = driverName != "MPRIS2" && driverName != "PulseAudio"; if (regularBackend && regularBackendFound) { @@ -170,11 +171,12 @@ */ if ( ! useBackendFilter ) { + bool foundSomethingAndLastControlReached = dev == devNumMax && ! Mixer::mixers().isEmpty(); switch ( multiDriverMode ) { case SINGLE: // In Single-Driver-mode we only need to check after we reached devNumMax - if ( dev == devNumMax && ! Mixer::mixers().isEmpty() ) + if ( foundSomethingAndLastControlReached ) autodetectionFinished = true; // highest device number of driver and a Mixer => finished break; @@ -190,13 +192,13 @@ else if ( driverName == "PulseAudio" ) { // PulseAudio is not useful together with MPRIS2. Treat it as "single" - if ( dev == devNumMax && ! Mixer::mixers().isEmpty() ) + if ( foundSomethingAndLastControlReached ) autodetectionFinished = true; } else { // same check as in SINGLE - if ( dev == devNumMax && ! Mixer::mixers().isEmpty() ) + if ( foundSomethingAndLastControlReached ) regularBackendFound = true; } @@ -306,7 +308,11 @@ */ bool MixerToolBox::possiblyAddMixer(Mixer *mixer) { - int newCardInstanceNum = 1 + s_mixerNums[mixer->getBaseName()]; + // TODO bug327471 This is really wrong here: _mixerBackend->getBaseName() is empty, as it will be filled by + // mixer->openIfValid(). See 3 lines below for the call! + QString mixerBasename = mixer->getBaseName(); + int newCardInstanceNum = 1 + s_mixerNums[mixerBasename]; + kDebug() << "mixerBasename=" << mixerBasename << ", cardNumPlanned=" << newCardInstanceNum; if ( mixer->openIfValid(newCardInstanceNum) ) { if ( (!s_ignoreMixerExpression.isEmpty()) && mixer->id().contains(s_ignoreMixerExpression) ) @@ -323,7 +329,7 @@ // This is for creating persistent (reusable) primary keys, which can safely // be referenced (especially for config file access, so it is meant to be persistent!). //s_mixerNums[mixer->getBaseName()]++; - s_mixerNums[mixer->getBaseName()] = newCardInstanceNum; + s_mixerNums[mixerBasename] = newCardInstanceNum; // mixer->setCardInstance(s_mixerNums[mixer->getBaseName()]); // TODO this code must go in mixer->openIfValid() Mixer::mixers().append( mixer ); diff -Nru kmix-4.12.3/core/version.h kmix-4.12.90/core/version.h --- kmix-4.12.3/core/version.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/version.h 2014-01-01 04:02:55.000000000 +0000 @@ -20,6 +20,6 @@ */ #ifndef APP_VERSION -#define APP_VERSION "4.4" +#define APP_VERSION "4.5" #define KMIX_CONFIG_VERSION 3 #endif // APP_VERSION diff -Nru kmix-4.12.3/core/volume.cpp kmix-4.12.90/core/volume.cpp --- kmix-4.12.3/core/volume.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/volume.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -68,8 +68,22 @@ _chmask = MNONE; } -// IIRC we need the default constructor implicitly for a Collection operation -VolumeChannel::VolumeChannel() {} +/** + * Do not use. Only implicitely required for QMap. + * + * @deprecated Do not use + */ +VolumeChannel::VolumeChannel() +{ + volume = 0; + chid = Volume::NOCHANNEL; +} + +VolumeChannel::VolumeChannel(Volume::ChannelID chid) +{ + volume = 0; + this->chid = chid; +} Volume::Volume(long maxVolume, long minVolume, bool hasSwitch, bool isCapture ) { @@ -77,7 +91,7 @@ } /** - * @Deprecated + * @deprecated */ void Volume::addVolumeChannels(ChannelMask chmask) { diff -Nru kmix-4.12.3/core/volume.h kmix-4.12.90/core/volume.h --- kmix-4.12.3/core/volume.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/core/volume.h 2014-01-01 04:02:55.000000000 +0000 @@ -62,7 +62,7 @@ MALL=0xFFFF }; - enum ChannelID { CHIDMIN = 0, + enum ChannelID { NOCHANNEL =-1, CHIDMIN = 0, LEFT = 0, RIGHT = 1, CENTER = 2, WOOFER = 3, @@ -179,20 +179,21 @@ bool _switchActivated; SwitchType _switchType; bool _isCapture; - bool disallowSwitchDisallowRead; }; class VolumeChannel { public: - VolumeChannel(Volume::ChannelID chid) { volume =0; this->chid = chid; } + VolumeChannel(); + /** + * Construct a channel for the given channel id. + * + * @param chid + */ + VolumeChannel(Volume::ChannelID chid); + long volume; Volume::ChannelID chid; - -// protected: -// friend class Volume; -// friend class MixDevice; - VolumeChannel(); // Only required for QMap }; std::ostream& operator<<(std::ostream& os, const Volume& vol); diff -Nru kmix-4.12.3/debian/changelog kmix-4.12.90/debian/changelog --- kmix-4.12.3/debian/changelog 2014-03-04 19:53:45.000000000 +0000 +++ kmix-4.12.90/debian/changelog 2014-03-19 11:03:44.000000000 +0000 @@ -1,3 +1,11 @@ +kmix (4:4.12.90-0ubuntu1) trusty; urgency=medium + + * New upstream beta release + * Add kubuntu_script-interpreter.diff from upstream to add + interpreter to kmixremote + + -- Jonathan Riddell Wed, 19 Mar 2014 11:03:43 +0000 + kmix (4:4.12.3-0ubuntu1) trusty; urgency=medium * New upstream bugfix release diff -Nru kmix-4.12.3/debian/control kmix-4.12.90/debian/control --- kmix-4.12.3/debian/control 2014-03-04 19:53:45.000000000 +0000 +++ kmix-4.12.90/debian/control 2014-03-19 11:03:44.000000000 +0000 @@ -6,7 +6,7 @@ Uploaders: Pino Toscano Build-Depends: kde-sc-dev-latest (>= 4:4.10), cmake, debhelper (>= 7.3.16), pkg-kde-tools (>= 0.12), - kdelibs5-dev (>= 4:4.12.3), + kdelibs5-dev (>= 4:4.12.90), libasound2-dev [linux-any], libpulse-dev (>= 0.9.12), libcanberra-dev, diff -Nru kmix-4.12.3/debian/patches/kubuntu_script-interpreter.diff kmix-4.12.90/debian/patches/kubuntu_script-interpreter.diff --- kmix-4.12.3/debian/patches/kubuntu_script-interpreter.diff 1970-01-01 00:00:00.000000000 +0000 +++ kmix-4.12.90/debian/patches/kubuntu_script-interpreter.diff 2014-03-19 11:03:44.000000000 +0000 @@ -0,0 +1,15 @@ +commit 69703e92424ef29f33070201169bcfd12f8c17cd +Author: Jonathan Riddell +Date: Fri Mar 14 16:04:34 2014 +0000 + + add interpreter to script + +diff --git a/apps/kmixremote b/apps/kmixremote +index 51f4eec..2f21209 100755 +--- a/apps/kmixremote ++++ b/apps/kmixremote +@@ -1,3 +1,4 @@ ++#!/bin/sh + ################################################################################# + # kmixremote - control kmix from a script. + # diff -Nru kmix-4.12.3/debian/patches/series kmix-4.12.90/debian/patches/series --- kmix-4.12.3/debian/patches/series 2014-03-04 19:53:45.000000000 +0000 +++ kmix-4.12.90/debian/patches/series 2014-03-19 11:03:44.000000000 +0000 @@ -1 +1,2 @@ kmix_showeverywhere.diff +kubuntu_script-interpreter.diff diff -Nru kmix-4.12.3/doc/index.docbook kmix-4.12.90/doc/index.docbook --- kmix-4.12.3/doc/index.docbook 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/doc/index.docbook 2014-01-01 04:02:55.000000000 +0000 @@ -50,8 +50,8 @@ &FDLNotice; -2012-11-03 -4.3 +2013-12-21 +4.5 (&kde; 4.13) &kmix; is an application that allows you to change the volume of your sound card. @@ -213,39 +213,27 @@ Configure &kmix; - - Configure &kmix; + + + General configuration + + + General configuration of &kmix; - + - Configure &kmix; + General configuration of &kmix; -Configure various &kmix; parameters. +Configure general &kmix; parameters. Behavior -Dock in system tray - -Check this box to dock &kmix; in system tray. - - - - -Behavior -Enable system tray volume control - -Check this box to enable the volume control from system tray. - - - - -Behavior Volume Feedback Check this box to enable audible feedback on volume change. @@ -253,21 +241,18 @@ -Startup -Restore Volumes on login - -Check this box to enable volume restoration on login. - - - - -Startup -Autostart +Behavior +Volume Overdrive -Check this box to enable &kmix; autostart with desktop environment. +Check this box to allow volume to be more than recommended value (sometimes PulseAudio maximal volume exceeds the normal value). &kmix; restart is needed for this setting to take effect. + + + Uncheck this item if there are audible sound distortions at the maximal volume. + + + - Visual @@ -308,7 +293,89 @@ Check this radio button to orientate the control slider vertically. + + +Slider orientation (System tray volume control) +Horizontal/Vertical + +Same as the previous two radio buttons but for the system tray volume control (the panel that is shown after &LMB; click on &kmix; tray icon). + + + + + + + + Start configuration + + + Start configuration of &kmix; + + + + + + Start configuration of &kmix; + + + +This page allows you to configure various &kmix; start parameters. + + + + +Startup +Restore volumes on login + +Check this box to enable volume restoration on login. + + + Dynamic controls from PulseAudio and MPRIS2 will not be restored. + + + + + + + +Startup +Autostart + +Check this box to enable &kmix; autostart with desktop environment. + + + + + + + + Sound menu configuration + + + Sound menu configuration of &kmix; + + + + + + Sound menu configuration of &kmix; + + + +This page allows you to configure various &kmix; sound menu parameters. + + + +Dock in system tray + +Check this box to dock &kmix; in system tray. + + + + It is possible to select mixers that will be shown in the sound menu using the corresponding list on this page. + + Binary files /tmp/Atgl3MHaub/kmix-4.12.3/doc/kmix-configure-general.png and /tmp/KvsEiceLzk/kmix-4.12.90/doc/kmix-configure-general.png differ Binary files /tmp/Atgl3MHaub/kmix-4.12.3/doc/kmix-configure.png and /tmp/KvsEiceLzk/kmix-4.12.90/doc/kmix-configure.png differ Binary files /tmp/Atgl3MHaub/kmix-4.12.3/doc/kmix-configure-sound-menu.png and /tmp/KvsEiceLzk/kmix-4.12.90/doc/kmix-configure-sound-menu.png differ Binary files /tmp/Atgl3MHaub/kmix-4.12.3/doc/kmix-configure-start.png and /tmp/KvsEiceLzk/kmix-4.12.90/doc/kmix-configure-start.png differ diff -Nru kmix-4.12.3/gui/dialogchoosebackends.cpp kmix-4.12.90/gui/dialogchoosebackends.cpp --- kmix-4.12.3/gui/dialogchoosebackends.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/dialogchoosebackends.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -38,16 +38,19 @@ #include "core/mixdevice.h" #include "core/mixer.h" -DialogChooseBackends::DialogChooseBackends(QSet& mixerIds) - : KDialog( 0 ) +/** + * Creates a dialog to choose mixers from. All currently known mixers will be shown, and the given mixerID's + * will be preselected. + * + * @param mixerIds A set of preselected mixer ID's + * @param noButtons is a migration option. When DialogChooseBackends has been integrated as a Tab, it will be removed. + */ +DialogChooseBackends::DialogChooseBackends(QWidget* parent, const QSet& mixerIds) + : QWidget(parent), modified(false) { - setCaption( i18n( "Select Mixers" ) ); - if ( Mixer::mixers().count() > 0 ) - setButtons( Ok|Cancel ); - else { - setButtons( Cancel ); - } - setDefaultButton( Ok ); +// setCaption( i18n( "Select Mixers" ) ); +// setButtons( None ); + _layout = 0; m_vboxForScrollView = 0; m_scrollableChannelSelector = 0; @@ -65,10 +68,11 @@ /** * Create basic widgets of the Dialog. */ -void DialogChooseBackends::createWidgets(QSet& mixerIds) +void DialogChooseBackends::createWidgets(const QSet& mixerIds) { - m_mainFrame = new QFrame( this ); - setMainWidget( m_mainFrame ); + m_mainFrame = this; +// m_mainFrame = new QFrame( this ); +// setMainWidget( m_mainFrame ); _layout = new QVBoxLayout(m_mainFrame); _layout->setMargin(0); @@ -78,7 +82,6 @@ _layout->addWidget(qlbl); createPage(mixerIds); - connect( this, SIGNAL(okClicked()) , this, SLOT(apply()) ); } else { @@ -92,7 +95,7 @@ * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ -void DialogChooseBackends::createPage(QSet& mixerIds) +void DialogChooseBackends::createPage(const QSet& mixerIds) { m_buttonGroupForScrollView = new QButtonGroup(this); // invisible QButtonGroup m_scrollableChannelSelector = new QScrollArea(m_mainFrame); @@ -105,25 +108,23 @@ m_vboxForScrollView = new KVBox(); + bool hasMixerFilter = !mixerIds.isEmpty(); kDebug() << "MixerIds=" << mixerIds; foreach ( Mixer* mixer, Mixer::mixers()) { - QString mdName = mixer->readableName(); - - mdName.replace('&', "&&"); // Quoting the '&' needed, to prevent QCheckBox creating an accelerator - QCheckBox* qrb = new QCheckBox( mdName, m_vboxForScrollView); - qrb->setObjectName(mixer->id());// The object name is used as ID here: see apply() -// m_buttonGroupForScrollView->addButton(qrb); // TODO remove m_buttonGroupForScrollView + QCheckBox* qrb = new QCheckBox(mixer->readableName(true), m_vboxForScrollView); + qrb->setObjectName(mixer->id());// The object name is used as ID here: see getChosenBackends() + connect(qrb, SIGNAL(stateChanged(int)), SLOT(backendsModifiedSlot())); checkboxes.append(qrb); - qrb->setChecked( mixerIds.contains(mixer->id()) );// preselect the current master + bool mixerShouldBeShown = !hasMixerFilter || mixerIds.contains(mixer->id()); + qrb->setChecked(mixerShouldBeShown); } m_scrollableChannelSelector->setWidget(m_vboxForScrollView); m_vboxForScrollView->show(); // show() is necessary starting with the second call to createPage() } - -void DialogChooseBackends::apply() +QSet DialogChooseBackends::getChosenBackends() { QSet newMixerList; foreach ( QCheckBox* qcb, checkboxes) @@ -134,13 +135,30 @@ kDebug() << "apply found " << qcb->objectName(); } } - - // Announcing MasterChanged, as the sound menu (aka ViewDockAreaPopup) primarily shows master volume(s). - // In any case, ViewDockAreaPopup treats MasterChanged and ControlList the same, so it is better to announce - // the "smaller" change. kDebug() << "New list is " << newMixerList; - GlobalConfig::instance().setMixersForSoundmenu(newMixerList); - ControlManager::instance().announce(QString(), ControlChangeType::MasterChanged, QString("Select Backends Dialog")); + return newMixerList; +} + +/** + * Returns whether there were any modifications (activation/deactivation) and resets the flag. + * @return + */ +bool DialogChooseBackends::getAndResetModifyFlag() +{ + bool modifiedOld = modified; + modified = false; + return modifiedOld; +} + +bool DialogChooseBackends::getModifyFlag() +{ + return modified; +} + +void DialogChooseBackends::backendsModifiedSlot() +{ + modified = true; + emit backendsModified(); } #include "dialogchoosebackends.moc" diff -Nru kmix-4.12.3/gui/dialogchoosebackends.h kmix-4.12.90/gui/dialogchoosebackends.h --- kmix-4.12.3/gui/dialogchoosebackends.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/dialogchoosebackends.h 2014-01-01 04:02:55.000000000 +0000 @@ -34,25 +34,33 @@ class Mixer; -class DialogChooseBackends : public KDialog +class DialogChooseBackends: public QWidget { - Q_OBJECT - public: - DialogChooseBackends(QSet& backends); - ~DialogChooseBackends(); - - public slots: - void apply(); - - private: - void createWidgets(QSet& backends); - void createPage(QSet& backends); - QVBoxLayout* _layout; - QScrollArea* m_scrollableChannelSelector; - KVBox *m_vboxForScrollView; - QButtonGroup *m_buttonGroupForScrollView; - QList checkboxes; - QFrame *m_mainFrame; +Q_OBJECT +public: + DialogChooseBackends(QWidget* parent, const QSet& backends); + ~DialogChooseBackends(); + + QSet getChosenBackends(); + bool getAndResetModifyFlag(); + bool getModifyFlag(); + +signals: + void backendsModified(); + +private: + void createWidgets(const QSet& backends); + void createPage(const QSet& backends); + QVBoxLayout* _layout; + QScrollArea* m_scrollableChannelSelector; + KVBox *m_vboxForScrollView; + QButtonGroup *m_buttonGroupForScrollView; + QList checkboxes; + QWidget *m_mainFrame; + bool modified; + +private slots: + void backendsModifiedSlot(); }; #endif diff -Nru kmix-4.12.3/gui/dialogselectmaster.cpp kmix-4.12.90/gui/dialogselectmaster.cpp --- kmix-4.12.3/gui/dialogselectmaster.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/dialogselectmaster.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -160,10 +160,9 @@ m_vboxForScrollView = new KVBox(); //m_scrollableChannelSelector->viewport() - QString masterKey = "----noMaster---"; // Use a non-matching name as default + shared_ptr master = mixer->getLocalMasterMD(); - if ( master.get() != 0 ) - masterKey = master->id(); + QString masterKey = ( master.get() != 0 ) ? master->id() : "----noMaster---"; // Use non-matching name as default const MixSet& mixset = mixer->getMixSet(); MixSet& mset = const_cast(mixset); @@ -176,17 +175,10 @@ // kDebug(67100) << "DialogSelectMaster::createPage() mset append qrb"; QString mdName = md->readableName(); mdName.replace('&', "&&"); // Quoting the '&' needed, to prevent QRadioButton creating an accelerator - QRadioButton* qrb = new QRadioButton( mdName, m_vboxForScrollView); + QRadioButton* qrb = new QRadioButton(mdName, m_vboxForScrollView); qrb->setObjectName(md->id()); // The object name is used as ID here: see apply() m_buttonGroupForScrollView->addButton(qrb); //(qrb, md->num()); - //_qEnabledCB.append(qrb); - //m_mixerPKs.push_back(md->id()); - if ( md->id() == masterKey ) { - qrb->setChecked(true); // preselect the current master - } - else { - qrb->setChecked(false); - } + qrb->setChecked(md->id() == masterKey); // preselect the current master } } diff -Nru kmix-4.12.3/gui/dialogviewconfiguration.cpp kmix-4.12.90/gui/dialogviewconfiguration.cpp --- kmix-4.12.3/gui/dialogviewconfiguration.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/dialogviewconfiguration.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -65,15 +65,22 @@ setData(Qt::DisplayRole, _name); } +/** + * Serializer. Used for DnD. + */ static QDataStream & operator<< ( QDataStream & s, const DialogViewConfigurationItem & item ) { s << item._id; s << item._shown; s << item._name; s << item._splitted; s << item._iconName; - //kDebug() << "<< unserialize << " << s; + //kDebug() << "<< serialize << " << s; return s; } + +/** + * Deserializer. Used for DnD. + */ static QDataStream & operator>> ( QDataStream & s, DialogViewConfigurationItem & item ) { QString id; s >> id; @@ -90,7 +97,7 @@ QString iconName; s >> iconName; item._iconName = iconName; - //kDebug() << ">> serialize >> " << id << name << iconName; + //kDebug() << ">> deserialize >> " << id << name << iconName; return s; } @@ -439,12 +446,7 @@ newCtl->id = '^' + ctlId + '$'; // Replace the (possible generic) regexp by the actual ID // We have made this an an actual control. As it is derived (from e.g. ".*") it is NOT mandatory. newCtl->setMandatory(false); - if ( isActiveView ) { - newCtl->show = "simple"; - } - else { - newCtl->show = "extended"; - } + newCtl->setVisible(isActiveView); newCtlSet.push_back(newCtl); // kDebug() << "Added to new ControlSet (done): " << newCtl->id; break; diff -Nru kmix-4.12.3/gui/guiprofile.cpp kmix-4.12.90/gui/guiprofile.cpp --- kmix-4.12.3/gui/guiprofile.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/guiprofile.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -38,6 +38,10 @@ #include QMap GUIProfile::s_profiles; +QString const GUIProfile::PNameSimple("simple"); +QString const GUIProfile::PNameExtended("extended"); +QString const GUIProfile::PNameAll("all"); +QString const GUIProfile::PNameCustom("custom"); bool SortedStringComparator::operator()(const std::string& s1, const std::string& s2) const { return ( s1 < s2 ); @@ -149,12 +153,14 @@ QString fname; fname += mixer->getBaseName(); if ( mixer->getCardInstance() > 1 ) { - fname += ' ' + mixer->getCardInstance(); + fname += " %1"; + fname = fname.arg(mixer->getCardInstance()); } if ( profileName != "default" ) { fname += ' ' + profileName; } + kDebug() << fname; return fname; } @@ -269,10 +275,8 @@ GUIProfile* GUIProfile::loadProfileFromXMLfiles(Mixer* mixer, QString profileName) { GUIProfile* guiprof = 0; - QString fileName, fileNameFQ; - - fileName = "profiles/" + profileName + ".xml"; - fileNameFQ = KStandardDirs::locate("appdata", fileName ); + QString fileName = createNormalizedFilename(profileName); + QString fileNameFQ = KStandardDirs::locate("appdata", fileName ); if ( ! fileNameFQ.isEmpty() ) { guiprof = new GUIProfile(); @@ -367,14 +371,22 @@ return ok; } +const QString GUIProfile::createNormalizedFilename(const QString& profileId) +{ + QString profileIdNormalized(profileId); + profileIdNormalized.replace(':', '.'); + + QString fileName("profiles/"); + fileName = fileName + profileIdNormalized + ".xml"; + return fileName; + } bool GUIProfile::writeProfile() { bool ret = false; - QString fileName, fileNameFQ; - fileName = "profiles/" + getId() + ".xml"; - fileName.replace(':', '.'); - fileNameFQ = KStandardDirs::locateLocal("appdata", fileName, true ); + QString profileId = getId(); + QString fileName = createNormalizedFilename(profileId); + QString fileNameFQ = KStandardDirs::locateLocal("appdata", fileName, true ); kDebug() << "Write profile:" << fileNameFQ ; QFile f(fileNameFQ); @@ -399,12 +411,31 @@ return ok; } + +// ------------------------------------------------------------------------------------- void GUIProfile::setControls(ControlSet& newControlSet) { qDeleteAll(_controls); _controls = newControlSet; } +const GUIProfile::ControlSet& GUIProfile::getControls() const +{ + return _controls; +} + +GUIProfile::ControlSet& GUIProfile::getControls() +{ + return _controls; +} + +void GUIProfile::addProduct(ProfProduct* prd) +{ + _products.insert(prd); +} + +// ------------------------------------------------------------------------------------- + /** * Returns how good the given Mixer matches this GUIProfile. @@ -615,6 +646,11 @@ delete d; } +void ProfControl::setVisible(bool visible) +{ + show = visible ? GUIProfile::PNameSimple : GUIProfile::PNameExtended; +} + void ProfControl::setSubcontrols(QString sctls) { d->subcontrols = sctls; @@ -807,7 +843,7 @@ prd->productRelease = release; prd->comment = comment; - _guiProfile->_products.insert(prd); + _guiProfile->addProduct(prd); } } diff -Nru kmix-4.12.3/gui/guiprofile.h kmix-4.12.90/gui/guiprofile.h --- kmix-4.12.3/gui/guiprofile.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/guiprofile.h 2014-01-01 04:02:55.000000000 +0000 @@ -85,7 +85,10 @@ // Visible name for the User ( if name.isNull(), id will be used - And in the future a default lookup table will be consulted ). // Because the name is visible, some kind of i18n() should be used. QString name; + + void setVisible(bool); // show or hide (contains the GUI type: simple, extended, all) + // Future direction: Make "show" private QString show; bool isMandatory() const @@ -131,7 +134,29 @@ class GUIProfile { - public: +public: + typedef std::set ProductSet; + typedef QList ControlSet; + + static const QString PNameSimple; + static const QString PNameExtended; + static const QString PNameAll; + static const QString PNameCustom; + +private: + static QMap& getProfiles() { return s_profiles; } + // Loading + static QString buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard); + static QString buildReadableProfileName(Mixer* mixer, QString profileName); + + static GUIProfile* loadProfileFromXMLfiles(Mixer* mixer, QString profileName); + static void addProfile(GUIProfile* guiprof); + static const QString createNormalizedFilename(const QString& profileId); + + static QMap s_profiles; + + +public: GUIProfile(); virtual ~GUIProfile(); @@ -148,38 +173,30 @@ QString getId() const; QString getMixerId() const { return _mixerId; } - static QMap& getProfiles() { return s_profiles; } unsigned long match(Mixer* mixer); friend std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); friend QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); - typedef std::set ProductSet; - ProductSet _products; - static GUIProfile* find(Mixer* mixer, QString profileName, bool profileNameIsFullyQualified, bool ignoreCardName); static GUIProfile* find(QString id); static GUIProfile* selectProfileFromXMLfiles(Mixer*, QString preferredProfile); static GUIProfile* fallbackProfile(Mixer*); - typedef QList ControlSet; - - const ControlSet& getControls() const - { - return _controls; - } - ControlSet& getControls() - { - return _controls; - } + // --- Getters and setters ---------------------------------------------------------------------- + const ControlSet& getControls() const; + ControlSet& getControls(); void setControls(ControlSet& newControlSet); QString getName() const { return _name; } void setName(QString _name) { this->_name = _name; } - // The values from the tag + void addProduct(ProfProduct*); + + + // --- The values from the tag: No getters and setters for them (yet) ----------------------------- QString _soundcardDriver; // The driver version: 1000*1000*MAJOR + 1000*MINOR + PATCHLEVEL unsigned long _driverVersionMin; @@ -187,16 +204,10 @@ QString _soundcardName; QString _soundcardType; unsigned long _generation; + private: ControlSet _controls; - - // Loading - static QString buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard); - static QString buildReadableProfileName(Mixer* mixer, QString profileName); - - static GUIProfile* loadProfileFromXMLfiles(Mixer* mixer, QString profileName); - static void addProfile(GUIProfile* guiprof); - static QMap s_profiles; + ProductSet _products; QString _id; QString _name; diff -Nru kmix-4.12.3/gui/kmixdockwidget.cpp kmix-4.12.90/gui/kmixdockwidget.cpp --- kmix-4.12.3/gui/kmixdockwidget.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/kmixdockwidget.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -47,12 +47,11 @@ //#define FEATURE_UNITY_POPUP true -KMixDockWidget::KMixDockWidget(KMixWindow* parent, bool volumePopup) +KMixDockWidget::KMixDockWidget(KMixWindow* parent) : KStatusNotifierItem(parent) , _oldToolTipValue(-1) , _oldPixmapType('-') , _kmixMainWindow(parent) - , _contextMenuWasOpen(false) { setToolTipIconByName("kmix"); setTitle(i18n( "Volume Control")); @@ -68,33 +67,18 @@ connect(this, SIGNAL(scrollRequested(int,Qt::Orientation)), this, SLOT(trayWheelEvent(int,Qt::Orientation))); connect(this, SIGNAL(secondaryActivateRequested(QPoint)), this, SLOT(dockMute())); - _volWA = 0; - _dockAreaPopup = 0; - _dockAreaPopupMenuWrapper = 0; - - if (!volumePopup) - { - // No volume popup => Use the KMixWindow as default action of this KStatusNotifierItem - setAssociatedWidget(parent); - kDebug() << "No volume try popup. We are now associated to " << associatedWidget(); - } - else - { - // For bizarre reasons, we wrap the ViewDockAreaPopup in a KMenu. Must relate to how KStatusNotifierItem works. - _dockAreaPopupMenuWrapper = new KMenu(parent); - _volWA = new QWidgetAction(_dockAreaPopupMenuWrapper); - _dockAreaPopup = new ViewDockAreaPopup(_dockAreaPopupMenuWrapper, "dockArea", 0, QString("no-guiprofile-yet-in-dock"), parent); - _volWA->setDefaultWidget(_dockAreaPopup); - _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, - QString("KMixDockWidget") - ); + // For bizarre reasons, we wrap the ViewDockAreaPopup in a KMenu. Must relate to how KStatusNotifierItem works. + _dockAreaPopupMenuWrapper = new KMenu(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, + QString("KMixDockWidget")); // Refresh in all cases. When there is no Golbal Master we still need // to initialize correctly (e.g. for showin 0% or hiding it) @@ -103,11 +87,11 @@ 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; + 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) @@ -132,12 +116,18 @@ } } +/** + * 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(); @@ -217,12 +207,13 @@ char newPixmapType; if ( !md ) { + // no such control => error newPixmapType = 'e'; } else { int percentage = md->getUserfriendlyVolumeLevel(); - if ( percentage <= 0 ) newPixmapType = '0'; // Hint: also negative-values + 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'; @@ -243,92 +234,79 @@ _oldPixmapType = newPixmapType; } +/** + * Called whenever the icon gets "activated". Unusally whn its clicked. + * @overload + * @param pos + */ void KMixDockWidget::activate(const QPoint &pos) { - kDebug() << "Activate at " << pos; - - bool showHideMainWindow = false; - showHideMainWindow |= (_dockAreaPopup == 0); - showHideMainWindow |= (pos.x() == 0 && pos.y() == 0); // HACK. When the action comes from the context menu, the pos is (0,0) + QWidget* dockAreaPopup = _dockAreaPopupMenuWrapper; // TODO Refactor to use _referenceWidget directly + if (dockAreaPopup->isVisible()) + { + dockAreaPopup->hide(); + return; + } - if ( showHideMainWindow ) - { - // Use default KStatusNotifierItem behavior if we are not using the dockAreaPopup - // (or if the action comes from the context menu) - kDebug() << "Use default KStatusNotifierItem behavior"; - setAssociatedWidget(_kmixMainWindow); - // This code path shows the Main Window (or hides it when it was shown) - KStatusNotifierItem::activate(); - 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(); + const QRect& vScreenSize = vdesktop->availableGeometry(dockAreaPopup); - // --- When this code path is executed, we want to show the DockAreaPopup) + if ((x + dockAreaPopup->width()) > (vScreenSize.width() + vScreenSize.x())) + { + // move horizontally, so that it is completely visible + x = vScreenSize.width() + vScreenSize.x() - dockAreaPopup->width() - 1; + kDebug() + << "Multihead: (case 1) moving to" << vScreenSize.x() << "," << vScreenSize.y(); + } + else if (x < vScreenSize.x()) + { + // horizontally out-of bound + x = vScreenSize.x(); + kDebug() << "Multihead: (case 2) moving to" << vScreenSize.x() << "," << vScreenSize.y(); + } - QWidget* dockAreaPopup = _dockAreaPopupMenuWrapper; // TODO Refactor to use _referenceWidget directly - kDebug() << "Skip default KStatusNotifierItem behavior"; - if ( dockAreaPopup->isVisible() ) { - dockAreaPopup->hide(); - kDebug() << "dap is visible => hide and return"; - return; - } + if ((y + dockAreaPopup->height()) > (vScreenSize.height() + vScreenSize.y())) + { + // move horizontally, so that it is completely visible + y = vScreenSize.height() + vScreenSize.y() - dockAreaPopup->height() - 1; + kDebug() << "Multihead: (case 3) moving to" << vScreenSize.x() << "," << vScreenSize.y(); + } + else if (y < vScreenSize.y()) + { + // horizontally out-of bound + y = vScreenSize.y(); + kDebug() << "Multihead: (case 4) moving to" << vScreenSize.x() << "," << vScreenSize.y(); + } -// if (dockAreaPopup->isVisible()) { -// contextMenu()->hide(); -// setAssociatedWidget(_kmixMainWindow); -// KStatusNotifierItem::activate(pos); -// kDebug() << "cm is visible => setAssociatedWidget(_kmixMainWindow)"; -// return; -// } - if ( false ) {} - else { - setAssociatedWidget(_kmixMainWindow); - kDebug() << "cm is NOT visible => setAssociatedWidget(_referenceWidget)"; - - _dockAreaPopupMenuWrapper->removeAction(_volWA); - delete _volWA; - _volWA = new QWidgetAction(_dockAreaPopupMenuWrapper); - _dockAreaPopup = new ViewDockAreaPopup(_dockAreaPopupMenuWrapper, "dockArea", 0, QString("no-guiprofile-yet-in-dock"), _kmixMainWindow); - _volWA->setDefaultWidget(_dockAreaPopup); - _dockAreaPopupMenuWrapper->addAction(_volWA); - - _dockAreaPopup->show(); - dockAreaPopup->show(); - _dockAreaPopup->adjustSize(); - dockAreaPopup->adjustSize(); - int h = dockAreaPopup->height(); - int x = pos.x() - dockAreaPopup->width()/2; - int y = pos.y() - h; - - // kDebug() << "h="<size() << x << 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(); - const QRect& vScreenSize = vdesktop->screenGeometry(dockAreaPopup); - //const QRect screenGeometry(const QWidget *widget) const - if ( (x+dockAreaPopup->width()) > (vScreenSize.width() + vScreenSize.x()) ) { - // move horizontally, so that it is completely visible - dockAreaPopup->move(vScreenSize.width() + vScreenSize.x() - dockAreaPopup->width() -1 , y); - kDebug() << "Multihead: (case 1) moving to" << vScreenSize.x() << "," << vScreenSize.y(); - } - else if ( x < vScreenSize.x() ) { - // horizontally out-of bound - dockAreaPopup->move(vScreenSize.x(), y); - kDebug() << "Multihead: (case 2) moving to" << vScreenSize.x() << "," << vScreenSize.y(); - } - // the above stuff could also be implemented vertically - KWindowSystem::setState( dockAreaPopup->winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::SkipPager ); - } + KWindowSystem::setType(dockAreaPopup->winId(), NET::Dock); + KWindowSystem::setState(dockAreaPopup->winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::SkipPager); + dockAreaPopup->show(); + dockAreaPopup->move(x, y); } @@ -395,11 +373,8 @@ void KMixDockWidget::contextMenuAboutToShow() { // Enable/Disable "Muted" menu item -// kDebug() << "hallo"; KToggleAction *dockMuteAction = static_cast(actionCollection()->action("dock_mute")); updateDockMuteAction(dockMuteAction); - - _contextMenuWasOpen = true; } void KMixDockWidget::updateDockMuteAction ( KToggleAction* dockMuteAction ) diff -Nru kmix-4.12.3/gui/kmixdockwidget.h kmix-4.12.90/gui/kmixdockwidget.h --- kmix-4.12.3/gui/kmixdockwidget.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/kmixdockwidget.h 2014-01-01 04:02:55.000000000 +0000 @@ -42,7 +42,7 @@ friend class KMixWindow; public: - explicit KMixDockWidget(KMixWindow *parent,bool volumePopup); + explicit KMixDockWidget(KMixWindow *parent); virtual ~KMixDockWidget(); void setErrorPixmap(); @@ -60,15 +60,14 @@ void toggleMinimizeRestore(); private: - ViewDockAreaPopup *_dockAreaPopup; + ViewDockAreaPopup *_dockView; KMenu *_dockAreaPopupMenuWrapper; QWidgetAction *_volWA; int _oldToolTipValue; char _oldPixmapType; KMixWindow* _kmixMainWindow; - bool _contextMenuWasOpen; - bool onlyHaveOneMouseButtonAction(); + bool onlyHaveOneMouseButtonAction(); void refreshVolumeLevels(); void updateDockMuteAction ( KToggleAction* dockMuteAction ); diff -Nru kmix-4.12.3/gui/kmixerwidget.cpp kmix-4.12.90/gui/kmixerwidget.cpp --- kmix-4.12.3/gui/kmixerwidget.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/kmixerwidget.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -95,7 +95,7 @@ GUIProfile* guiprof = getGuiprof(); if ( guiprof != 0 ) { - if (GlobalConfig::instance().debugGUI) + if (GlobalConfig::instance().data.debugGUI) kDebug() << "Add a view " << _guiprofId; ViewSliders* view = new ViewSliders( this, guiprof->getId(), _mixer, vflags, _guiprofId, _actionCollection ); possiblyAddView(view); @@ -120,7 +120,7 @@ m_topLayout->addWidget(vbase); _views.push_back(vbase); connect( vbase, SIGNAL(toggleMenuBar()), parentWidget(), SLOT(toggleMenuBar()) ); - if (GlobalConfig::instance().debugGUI) + if (GlobalConfig::instance().data.debugGUI) kDebug() << "CONNECT ViewBase count " << vbase->getMixers().size(); return true; } @@ -153,7 +153,7 @@ const std::vector::const_iterator viewsEnd = _views.end(); for ( std::vector::const_iterator it = _views.begin(); it != viewsEnd; ++it) { ViewBase* view = *it; - if (GlobalConfig::instance().debugVolume) + if (GlobalConfig::instance().data.debugVolume) kDebug(67100) << "KMixerWidget::loadConfig()" << view->id(); view->load(config); view->configurationUpdate(); @@ -162,15 +162,17 @@ -void KMixerWidget::saveConfig( KConfig *config ) +void KMixerWidget::saveConfig(KConfig *config) { - const std::vector::const_iterator viewsEnd = _views.end(); - for ( std::vector::const_iterator it = _views.begin(); it != viewsEnd; ++it) { - ViewBase* view = *it; - if (GlobalConfig::instance().debugVolume) - kDebug(67100) << "KMixerWidget::saveConfig()" << view->id(); - view->save(config); - } // for all tabs + const std::vector::const_iterator viewsEnd = _views.end(); + for (std::vector::const_iterator it = _views.begin(); it != viewsEnd; ++it) + { + ViewBase* view = *it; + if (GlobalConfig::instance().data.debugVolume) + kDebug(67100) + << "KMixerWidget::saveConfig()" << view->id(); + view->save(config); + } // for all tabs } diff -Nru kmix-4.12.3/gui/kmixprefdlg.cpp kmix-4.12.90/gui/kmixprefdlg.cpp --- kmix-4.12.3/gui/kmixprefdlg.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/kmixprefdlg.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -26,6 +26,7 @@ #include #include #include +//#include #include #include @@ -34,137 +35,348 @@ #include #include "gui/kmixerwidget.h" +#include "core/GlobalConfig.h" -KMixPrefDlg::KMixPrefDlg(QWidget *parent) : - KDialog(parent) +KMixPrefDlg* KMixPrefDlg::instance = 0; + +KMixPrefDlg* KMixPrefDlg::getInstance() +{ + return instance; +} + +KMixPrefDlg* KMixPrefDlg::createInstance(QWidget *parent, GlobalConfig& config) { - setCaption(i18n("Configure")); + if (instance == 0) + { + instance = new KMixPrefDlg(parent, config); + } + return instance; + +} + +KMixPrefDlg::KMixPrefDlg(QWidget *parent, GlobalConfig& config) : + KConfigDialog(parent, i18n("Configure"), &config), dialogConfig(config) + +{ + setFaceType(KPageDialog::List); + //setCaption(i18n("Configure")); setButtons(Ok | Cancel | Apply); + setDefaultButton(Ok); + dvc = 0; + // general buttons m_generalTab = new QFrame(this); - setMainWidget(m_generalTab); + m_controlsTab = new QFrame(this); + m_startupTab = new QFrame(this); - QBoxLayout *layout = new QVBoxLayout(m_generalTab); - layout->setMargin(0); - layout->setSpacing(KDialog::spacingHint()); + createStartupTab(); + createGeneralTab(); + createControlsTab(); + updateWidgets(); // I thought KConfigDialog would call this, but I saw during a gdb session that it does not do so. - // --- Behavior --------------------------------------------------------- - QLabel *label = new QLabel(i18n("Behavior"), m_generalTab); - layout->addWidget(label); + showButtonSeparator(true); - m_dockingChk = new QCheckBox(i18n("&Dock in system tray"), m_generalTab); - addWidgetToLayout(m_dockingChk, layout, 10, i18n("Docks the mixer into the KDE system tray")); - connect(m_dockingChk, SIGNAL(stateChanged(int)), SLOT(dockIntoPanelChange(int)) ); + generalPage = addPage(m_generalTab, i18n("General"), "configure"); + startupPage = addPage(m_startupTab, i18n("Start"), "preferences-system-login"); + soundmenuPage = addPage(m_controlsTab, i18n("Sound Menu"), "audio-volume-high"); +} - m_volumeChk = new QCheckBox(i18n("Enable system tray &volume control"), m_generalTab); - addWidgetToLayout(m_volumeChk, layout, 20, i18n("Allows to control the volume from the system tray")); +KMixPrefDlg::~KMixPrefDlg() +{ +} - m_beepOnVolumeChange = new QCheckBox(i18n("Volume Feedback"), m_generalTab); - addWidgetToLayout(m_beepOnVolumeChange, layout, 10, ""); +/** + * Switches to a specific page and shows it. + * @param page + */ +void KMixPrefDlg::switchToPage(KMixPrefPage page) +{ + switch (page) + { + case PrefGeneral: + setCurrentPage(generalPage); + break; + case PrefSoundMenu: + setCurrentPage(soundmenuPage); + break; + case PrefStartup: + setCurrentPage(startupPage); + break; + default: + kWarning() << "Tried to activated unknown preferences page" << page; + break; + } + show(); +} - volumeFeedbackWarning = new QLabel(i18n("Volume feedback is only available for Pulseaudio."), m_generalTab); - volumeFeedbackWarning->setEnabled(false); - addWidgetToLayout(volumeFeedbackWarning, layout, 10, ""); +// --- TABS -------------------------------------------------------------------------------------------------- +void KMixPrefDlg::createStartupTab() +{ + QBoxLayout* layoutStartupTab = new QVBoxLayout(m_startupTab); + layoutStartupTab->setMargin(0); + layoutStartupTab->setSpacing(KDialog::spacingHint()); - // --- Startup --------------------------------------------------------- - label = new QLabel(i18n("Startup"), m_generalTab); - layout->addWidget(label); + QLabel* label = new QLabel(i18n("Startup"), m_startupTab); + layoutStartupTab->addWidget(label); - m_onLogin = new QCheckBox(i18n("Restore volumes on login"), m_generalTab); - addWidgetToLayout(m_onLogin, layout, 10, i18n("Restore all volume levels and switches.")); + m_onLogin = new QCheckBox(i18n("Restore volumes on login"), m_startupTab); + addWidgetToLayout(m_onLogin, layoutStartupTab, 10, i18n("Restore all volume levels and switches."), "startkdeRestore"); dynamicControlsRestoreWarning = new QLabel( - i18n("Dynamic controls from Pulseaudio and MPRIS2 will not be restored."), m_generalTab); + i18n("Dynamic controls from Pulseaudio and MPRIS2 will not be restored."), m_startupTab); dynamicControlsRestoreWarning->setEnabled(false); - addWidgetToLayout(dynamicControlsRestoreWarning, layout, 10, ""); + addWidgetToLayout(dynamicControlsRestoreWarning, layoutStartupTab, 10, "", ""); + + allowAutostart = new QCheckBox(i18n("Autostart"), m_startupTab); + addWidgetToLayout(allowAutostart, layoutStartupTab, 10, + i18n("Enables the KMix autostart service (kmix_autostart.desktop)"), "AutoStart"); - allowAutostart = new QCheckBox(i18n("Autostart"), m_generalTab); - addWidgetToLayout(allowAutostart, layout, 10, i18n("Enables the KMix autostart service (kmix_autostart.desktop)")); allowAutostartWarning = new QLabel( i18n("Autostart can not be enabled, as the autostart file kmix_autostart.desktop is not installed."), - m_generalTab); - addWidgetToLayout(allowAutostartWarning, layout, 10, ""); + m_startupTab); + addWidgetToLayout(allowAutostartWarning, layoutStartupTab, 10, "", ""); + layoutStartupTab->addStretch(); +} + +void KMixPrefDlg::createOrientationGroup(const QString& labelSliderOrientation, QGridLayout* orientationLayout, int row, KMixPrefDlgPrefOrientationType prefType) +{ + QButtonGroup* orientationGroup = new QButtonGroup(m_generalTab); + orientationGroup->setExclusive(true); + QLabel* qlb = new QLabel(labelSliderOrientation, m_generalTab); + QRadioButton* qrbHor = new QRadioButton(i18n("&Horizontal"), m_generalTab); + QRadioButton* qrbVert = new QRadioButton(i18n("&Vertical"), m_generalTab); - // --- Visual --------------------------------------------------------- - label = new QLabel(i18n("Visual"), m_generalTab); + if (prefType == TrayOrientation) + { + _rbTraypopupHorizontal = qrbHor; + _rbTraypopupVertical = qrbVert; + orientationGroup->setObjectName("Orientation.TrayPopup"); + } + else + { + _rbHorizontal = qrbHor; + _rbVertical = qrbVert; + orientationGroup->setObjectName("Orientation"); + } + + // Add both buttons to button group + orientationGroup->addButton(qrbHor); + orientationGroup->addButton(qrbVert); + // Add both buttons and label to layout + orientationLayout->addWidget(qlb, row, 0); + orientationLayout->addWidget(qrbHor, row, 1); + orientationLayout->addWidget(qrbVert, row, 2); + + connect(qrbHor, SIGNAL(toggled(bool)), SLOT(updateButtons())); + connect(qrbVert, SIGNAL(toggled(bool)), SLOT(updateButtons())); + + connect(this, SIGNAL(applyClicked()), SLOT(kmixConfigHasChangedEmitter())); + connect(this, SIGNAL(okClicked()), SLOT(kmixConfigHasChangedEmitter())); + +// connect(qrbHor, SIGNAL(toggled(bool)), SLOT(settingsChangedSlot())); +// connect(qrbVert, SIGNAL(toggled(bool)), SLOT(settingsChangedSlot())); +} + +void KMixPrefDlg::createGeneralTab() +{ + QBoxLayout* layout = new QVBoxLayout(m_generalTab); + layout->setMargin(0); + layout->setSpacing(KDialog::spacingHint()); + + // --- Behavior --------------------------------------------------------- + QLabel* label = new QLabel(i18n("Behavior"), m_generalTab); layout->addWidget(label); + // [CONFIG] + m_beepOnVolumeChange = new QCheckBox(i18n("Volume Feedback"), m_generalTab); + addWidgetToLayout(m_beepOnVolumeChange, layout, 10, "", "VolumeFeedback"); + + volumeFeedbackWarning = new QLabel(i18n("Volume feedback is only available for Pulseaudio."), m_generalTab); + volumeFeedbackWarning->setEnabled(false); + addWidgetToLayout(volumeFeedbackWarning, layout, 20, "", ""); + + // [CONFIG] + m_volumeOverdrive = new QCheckBox(i18n("Volume Overdrive"), m_generalTab); + addWidgetToLayout(m_volumeOverdrive, layout, 10, "Raise volume maximum to 150% (PulseAudio only)", "VolumeOverdrive"); + volumeOverdriveWarning = new QLabel(i18n("You must restart KMix for this setting to take effect."), m_generalTab); + volumeOverdriveWarning->setEnabled(false); + addWidgetToLayout(volumeOverdriveWarning, layout, 20, "", ""); + // --- Visual --------------------------------------------------------- + QLabel* label2 = new QLabel(i18n("Visual"), m_generalTab); + layout->addWidget(label2); + + // [CONFIG] m_showTicks = new QCheckBox(i18n("Show &tickmarks"), m_generalTab); - addWidgetToLayout(m_showTicks, layout, 10, i18n("Enable/disable tickmark scales on the sliders")); + addWidgetToLayout(m_showTicks, layout, 10, i18n("Enable/disable tickmark scales on the sliders"), "Tickmarks"); m_showLabels = new QCheckBox(i18n("Show &labels"), m_generalTab); - addWidgetToLayout(m_showLabels, layout, 10, i18n("Enables/disables description labels above the sliders")); + addWidgetToLayout(m_showLabels, layout, 10, i18n("Enables/disables description labels above the sliders"), + "Labels"); + // [CONFIG] m_showOSD = new QCheckBox(i18n("Show On Screen Display (&OSD)"), m_generalTab); - addWidgetToLayout(m_showOSD, layout, 10, ""); + addWidgetToLayout(m_showOSD, layout, 10, "", "showOSD"); - // Slider orientation (main window) - QBoxLayout *orientationLayout = new QHBoxLayout(); -// orientationLayout->addSpacing(10); - layout->addItem(orientationLayout); - QButtonGroup* orientationGroup = new QButtonGroup(m_generalTab); - orientationGroup->setExclusive(true); - QLabel* qlb = new QLabel(i18n("Slider orientation: "), m_generalTab); - _rbHorizontal = new QRadioButton(i18n("&Horizontal"), m_generalTab); - _rbVertical = new QRadioButton(i18n("&Vertical"), m_generalTab); - orientationGroup->addButton(_rbHorizontal); - orientationGroup->addButton(_rbVertical); - - orientationLayout->addWidget(qlb); - orientationLayout->addWidget(_rbHorizontal); - orientationLayout->addWidget(_rbVertical); - orientationLayout->addStretch(); + // [CONFIG] Slider orientation (main window) + QGridLayout* orientationGrid = new QGridLayout(); + layout->addItem(orientationGrid); + + createOrientationGroup(i18n("Slider orientation: "), orientationGrid, 0, KMixPrefDlg::MainOrientation); // Slider orientation (tray popup). We use an extra setting - QBoxLayout *orientation2Layout = new QHBoxLayout(); -// orientation2Layout->addSpacing(10); + QBoxLayout* orientation2Layout = new QHBoxLayout(); layout->addItem(orientation2Layout); - QButtonGroup* orientation2Group = new QButtonGroup(m_generalTab); - orientation2Group->setExclusive(true); - QLabel* qlb2 = new QLabel(i18n("Slider orientation (System tray volume control):"), m_generalTab); - _rbTraypopupHorizontal = new QRadioButton(i18n("&Horizontal"), m_generalTab); - _rbTraypopupVertical = new QRadioButton(i18n("&Vertical"), m_generalTab); - orientation2Group->addButton(_rbTraypopupVertical); - orientation2Group->addButton(_rbTraypopupHorizontal); - - orientation2Layout->addWidget(qlb2); - orientation2Layout->addWidget(_rbTraypopupHorizontal); - orientation2Layout->addWidget(_rbTraypopupVertical); - orientation2Layout->addStretch(); - + createOrientationGroup(i18n("Slider orientation (System tray volume control):"), orientationGrid, 1, KMixPrefDlg::TrayOrientation); + // Push everything above to the top layout->addStretch(); - - showButtonSeparator(true); - - connect(this, SIGNAL(applyClicked()), SLOT(apply())); - connect(this, SIGNAL(okClicked()), SLOT(apply())); } -KMixPrefDlg::~KMixPrefDlg() +void KMixPrefDlg::createControlsTab() { + layoutControlsTab = new QVBoxLayout(m_controlsTab); + layoutControlsTab->setMargin(0); + layoutControlsTab->setSpacing(KDialog::spacingHint()); + m_dockingChk = new QCheckBox(i18n("&Dock in system tray"), m_controlsTab); + + addWidgetToLayout(m_dockingChk, layoutControlsTab, 10, i18n("Docks the mixer into the KDE system tray"), + "AllowDocking"); + + replaceBackendsInTab(); } -void KMixPrefDlg::addWidgetToLayout(QWidget* widget, QBoxLayout* layout, int spacingBefore, QString toopTipText) + + +// --- Helper -------------------------------------------------------------------------------------------------- + +/** + * Register widget with correct name for KConfigDialog, then add it to the given layout + * + * @param widget + * @param layout + * @param spacingBefore + * @param toopTipText + * @param objectName + */ +void KMixPrefDlg::addWidgetToLayout(QWidget* widget, QBoxLayout* layout, int spacingBefore, QString tooltip, QString kconfigName) { - if ( !toopTipText.isEmpty() ) - widget->setToolTip(toopTipText); + if (!kconfigName.isEmpty()) + { + // Widget to be registered for KConfig + widget->setObjectName("kcfg_" + kconfigName); + } + + if ( !tooltip.isEmpty() ) + { + widget->setToolTip(tooltip); + } + QBoxLayout *l = new QHBoxLayout(); l->addSpacing(spacingBefore); l->addWidget(widget); layout->addItem(l); } +// --- KConfigDialog CUSTOM WIDGET management ------------------------------------------------------------------------ + + +/** + * Update Widgets from config. + *

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

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

+ * Hint: this get internally called by KConfigDialog from updateButtons(). + * @return + */ +bool KMixPrefDlg::hasChanged() +{ + bool orientationFromConfigIsHor = dialogConfig.data.getToplevelOrientation() == Qt::Horizontal; + bool orientationFromWidgetIsHor = _rbHorizontal->isChecked(); + kDebug() << "Orientation MAIN fromConfig=" << (orientationFromConfigIsHor ? "Hor" : "Vert") << ", fromWidget=" << (orientationFromWidgetIsHor ? "Hor" : "Vert"); + + bool changed = orientationFromConfigIsHor ^ orientationFromWidgetIsHor; + if (!changed) + { + bool orientationFromConfigIsHor = dialogConfig.data.getTraypopupOrientation() == Qt::Horizontal; + orientationFromWidgetIsHor = _rbTraypopupHorizontal->isChecked(); + kDebug() << "Orientation TRAY fromConfig=" << (orientationFromConfigIsHor ? "Hor" : "Vert") << ", fromWidget=" << (orientationFromWidgetIsHor ? "Hor" : "Vert"); + + changed = orientationFromConfigIsHor ^ orientationFromWidgetIsHor; + } + if (!changed) + { + changed = dvc->getModifyFlag(); + } + + kDebug() << "hasChanged=" << changed; + + return changed; +} + void KMixPrefDlg::showEvent(QShowEvent * event) { + // -1- Replace widgets ------------------------------------------------------------ + // Hotplug can change mixers or backends => recreate tab + replaceBackendsInTab(); + + // -2- Change visibility and enable status (of the new widgets) ---------------------- + // As GUI can change, the warning will only been shown on demand dynamicControlsRestoreWarning->setVisible(Mixer::dynamicBackendsPresent()); @@ -174,38 +386,43 @@ volumeFeedbackWarning->setVisible(!volumeFeebackAvailable); m_beepOnVolumeChange->setDisabled(!volumeFeebackAvailable); - /* - // KConfig* autostartConfig = new KConfig("kmix_autostart", KConfig::FullConfig, "autostart"); - // kDebug() << "accessMode = " << autostartConfig->accessMode(); - // bool autostartFileExists = (autostartConfig->accessMode() == KConfigBase::NoAccess); - */ + bool overdriveAvailable = volumeFeebackAvailable; // "shortcut" for Mixer::pulseaudioPresent() (see above) + m_volumeOverdrive->setVisible(overdriveAvailable); + volumeOverdriveWarning->setVisible(overdriveAvailable); + QString autostartConfigFilename = KGlobal::dirs()->findResource("autostart", QString("kmix_autostart.desktop")); kDebug() << "autostartConfigFilename = " << autostartConfigFilename; bool autostartFileExists = !autostartConfigFilename.isNull(); - allowAutostartWarning->setEnabled(autostartFileExists); + //allowAutostartWarning->setEnabled(autostartFileExists); allowAutostartWarning->setVisible(!autostartFileExists); allowAutostart->setEnabled(autostartFileExists); KDialog::showEvent(event); } -void KMixPrefDlg::apply() -{ - emit signalApplied(this); -} -void KMixPrefDlg::dockIntoPanelChange(int state) +void KMixPrefDlg::replaceBackendsInTab() { - if (state == Qt::Unchecked) - { - m_volumeChk->setDisabled(true); - } - else + if (dvc != 0) { - m_volumeChk->setEnabled(true); + layoutControlsTab->removeWidget(dvc); + delete dvc; } + + QSet backendsFromConfig = GlobalConfig::instance().getMixersForSoundmenu(); + dvc = new DialogChooseBackends(0, backendsFromConfig); + connect(dvc, SIGNAL(backendsModified()), SLOT(updateButtons())); + + dvc->show(); + layoutControlsTab->addWidget(dvc); + + // Push everything above to the top + layoutControlsTab->addStretch(); } + + + #include "kmixprefdlg.moc" diff -Nru kmix-4.12.3/gui/kmixprefdlg.h kmix-4.12.90/gui/kmixprefdlg.h --- kmix-4.12.3/gui/kmixprefdlg.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/kmixprefdlg.h 2014-01-01 04:02:55.000000000 +0000 @@ -22,6 +22,7 @@ #ifndef KMIXPREFDLG_H #define KMIXPREFDLG_H +#include #include class KMixPrefWidget; @@ -29,53 +30,100 @@ class QBoxLayout; class QCheckBox; class QFrame; +#include class QLabel; class QRadioButton; class QShowEvent; class QWidget; -class -KMixPrefDlg : public KDialog -{ - Q_OBJECT +#include "core/GlobalConfig.h" +#include "gui/dialogchoosebackends.h" + - friend class KMixWindow; +class KMixPrefDlg: public KConfigDialog +{ +Q_OBJECT - public: - KMixPrefDlg( QWidget *parent ); - virtual ~KMixPrefDlg(); - - signals: - void signalApplied( KMixPrefDlg *prefDlg ); - - private slots: - void apply(); - void dockIntoPanelChange(int state); - - protected: - void showEvent ( QShowEvent * event ); - - private: - void addWidgetToLayout(QWidget* widget, QBoxLayout* layout, int spacingBefore, QString toopTipText); - - - QFrame *m_generalTab; - - QCheckBox *m_dockingChk; - QCheckBox *m_volumeChk; - QLabel *dynamicControlsRestoreWarning; - QCheckBox *m_showTicks; - QCheckBox *m_showLabels; - QCheckBox* m_showOSD; - QCheckBox *m_onLogin; - QCheckBox *allowAutostart; - QLabel *allowAutostartWarning; - QCheckBox *m_beepOnVolumeChange; - QLabel *volumeFeedbackWarning; - QRadioButton *_rbVertical; - QRadioButton *_rbHorizontal; - QRadioButton *_rbTraypopupVertical; - QRadioButton *_rbTraypopupHorizontal; +public: + enum KMixPrefPage + { + PrefGeneral, PrefSoundMenu, PrefStartup + }; + + static KMixPrefDlg* createInstance(QWidget *parent, GlobalConfig& config); + static KMixPrefDlg* getInstance(); + void switchToPage(KMixPrefPage page); + +signals: + void kmixConfigHasChanged(); + +private slots: + void kmixConfigHasChangedEmitter(); + +protected: + void showEvent(QShowEvent * event); + /** + * Orientation is not supported by default => implement manually + * @Override + */ + void updateWidgets(); + /** + * Orientation is not supported by default => implement manually + * @Override + */ + void updateSettings(); + + bool hasChanged(); + +private: + static KMixPrefDlg* instance; + + KMixPrefDlg(QWidget *parent, GlobalConfig& config); + virtual ~KMixPrefDlg(); + + enum KMixPrefDlgPrefOrientationType + { + MainOrientation, TrayOrientation + }; + + GlobalConfig& dialogConfig; + + void addWidgetToLayout(QWidget* widget, QBoxLayout* layout, int spacingBefore, QString tooltip, QString kconfigName); + + void createStartupTab(); + void replaceBackendsInTab(); + void createGeneralTab(); + void createControlsTab(); + void createOrientationGroup(const QString& labelSliderOrientation, QGridLayout* orientationLayout, int row, KMixPrefDlgPrefOrientationType type); + + QFrame *m_generalTab; + QFrame *m_startupTab; + QFrame *m_controlsTab; + + QCheckBox *m_dockingChk; + QLabel *dynamicControlsRestoreWarning; + QCheckBox *m_showTicks; + QCheckBox *m_showLabels; + QCheckBox* m_showOSD; + QCheckBox *m_onLogin; + QCheckBox *allowAutostart; + QLabel *allowAutostartWarning; + QCheckBox *m_beepOnVolumeChange; + QCheckBox *m_volumeOverdrive; + QLabel *volumeFeedbackWarning; + QLabel *volumeOverdriveWarning; + + QBoxLayout *layoutControlsTab; + DialogChooseBackends* dvc; + + QRadioButton *_rbVertical; + QRadioButton *_rbHorizontal; + QRadioButton *_rbTraypopupVertical; + QRadioButton *_rbTraypopupHorizontal; + + KPageWidgetItem* generalPage; + KPageWidgetItem* soundmenuPage; + KPageWidgetItem* startupPage; }; #endif // KMIXPREFDLG_H diff -Nru kmix-4.12.3/gui/mdwenum.cpp kmix-4.12.90/gui/mdwenum.cpp --- kmix-4.12.3/gui/mdwenum.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/mdwenum.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -58,7 +58,7 @@ // KStandardAction::showMenubar() is in MixDeviceWidget now KToggleAction *action = _mdwActions->add( "hide" ); action->setText( i18n("&Hide") ); - connect(action, SIGNAL(triggered(bool)), SLOT(setDisabled())); + connect(action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool))); QAction *c = _mdwActions->addAction( "keys" ); c->setText( i18n("C&onfigure Shortcuts...") ); connect(c, SIGNAL(triggered(bool)), SLOT(defineKeys())); @@ -170,17 +170,10 @@ } } -void MDWEnum::setDisabled() -{ - setDisabled( true ); -} -void MDWEnum::setDisabled( bool value ) { - if ( m_disabled!=value) - { - value ? hide() : show(); - m_disabled = value; - } +void MDWEnum::setDisabled( bool hide ) +{ + emit guiVisibilityChange(this, !hide); } /** diff -Nru kmix-4.12.3/gui/mdwenum.h kmix-4.12.90/gui/mdwenum.h --- kmix-4.12.3/gui/mdwenum.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/mdwenum.h 2014-01-01 04:02:55.000000000 +0000 @@ -56,7 +56,6 @@ public slots: // GUI hide and show - void setDisabled(); void setDisabled(bool); // Enum handling: next and selecting @@ -67,6 +66,9 @@ void update(); virtual void showContextMenu(const QPoint& pos = QCursor::pos()); +signals: + virtual void guiVisibilityChange(MixDeviceWidget* source, bool enable); + private: void createWidgets(); diff -Nru kmix-4.12.3/gui/mdwslider.cpp kmix-4.12.90/gui/mdwslider.cpp --- kmix-4.12.3/gui/mdwslider.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/mdwslider.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -52,7 +52,6 @@ #include "gui/mdwmoveaction.h" -VolumeSliderExtraData MDWSlider::DummVolumeSliderExtraData; bool MDWSlider::debugMe = false; /** * MixDeviceWidget that represents a single mix device, including PopUp, muteLED, ... @@ -72,7 +71,8 @@ MixDeviceWidget(md,small,orientation,parent,view, par_ctl), m_linked(true), muteButtonSpacer(0), captureSpacer(0), labelSpacer(0), m_iconLabelSimple(0), m_qcb(0), m_muteText(0), - m_label( 0 ), /*m_captureLED( 0 ),*/ + m_label( 0 ), + mediaButton(0), m_captureCheckbox(0), m_captureText(0), labelSpacing(0), muteButtonSpacing(false), captureLEDSpacing(false), _mdwMoveActions(new KActionCollection(this)), m_moveMenu(0), m_sliderInWork(0), m_waitForSoundSetComplete(0) @@ -107,7 +107,7 @@ if ( ! m_mixdevice->mixer()->isDynamic() ) { action = _mdwActions->add( "hide" ); action->setText( i18n("&Hide") ); - connect( action, SIGNAL(triggered(bool)), SLOT(setDisabled()) ); + connect( action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool)) ); } if( m_mixdevice->hasMuteSwitch() ) @@ -133,8 +133,27 @@ connect( action, SIGNAL(triggered(bool)), SLOT(defineKeys()) ); } +void MDWSlider::addGlobalShortcut(KAction* action, const QString& label, bool dynamicControl) +{ + QString finalLabel(label); + finalLabel += " - " + mixDevice()->readableName() + ", " + mixDevice()->mixer()->readableName(); + + action->setText(label); + if (!dynamicControl) + { + // virtual / dynamic controls won't get shortcuts + // #ifdef __GNUC__ + // #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed + // #endif + // b->enableGlobalShortcut(); + // enableGlobalShortcut() is not there => use workaround + action->setGlobalShortcut(dummyShortcut); + } +} + void MDWSlider::createShortcutActions() { + bool dynamicControl = mixDevice()->mixer()->isDynamic(); // The following actions are for the "Configure Shortcuts" dialog /* PLEASE NOTE THAT global shortcuts are saved with the name as set with setName(), instead of their action name. This is a bug according to the thread "Global shortcuts are saved with their text-name and not their action-name - Bug?" on kcd. @@ -143,58 +162,40 @@ QString actionSuffix = QString(" - %1, %2").arg( mixDevice()->readableName() ).arg( mixDevice()->mixer()->readableName() ); KAction *b; + // -1- INCREASE VOLUME SHORTCUT ----------------------------------------- b = _mdwPopupActions->addAction( QString("Increase volume %1").arg( actionSuffix ) ); QString increaseVolumeName = i18n( "Increase Volume" ); - increaseVolumeName += " - " + mixDevice()->readableName() + ", " + mixDevice()->mixer()->readableName(); - b->setText( increaseVolumeName ); -// #ifdef __GNUC__ -// #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed -// #endif - if ( ! mixDevice()->mixer()->isDynamic() ) { - // virtual / dynamic controls won't get shortcuts - b->setGlobalShortcut(dummyShortcut); // -<- enableGlobalShortcut() is not there => use workaround - // b->enableGlobalShortcut(); + addGlobalShortcut(b, increaseVolumeName, dynamicControl); + if ( ! dynamicControl ) connect( b, SIGNAL(triggered(bool)), SLOT(increaseVolume()) ); - } + // -2- DECREASE VOLUME SHORTCUT ----------------------------------------- b = _mdwPopupActions->addAction( QString("Decrease volume %1").arg( actionSuffix ) ); QString decreaseVolumeName = i18n( "Decrease Volume" ); - decreaseVolumeName += " - " + mixDevice()->readableName() + ", " + mixDevice()->mixer()->readableName(); - b->setText( decreaseVolumeName ); -/* #ifdef __GNUC__ - #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed - #endif*/ - if ( ! mixDevice()->mixer()->isDynamic() ) { - // virtual / dynamic controls won't get shortcuts - b->setGlobalShortcut(dummyShortcut); // -<- enableGlobalShortcut() is not there => use workaround - // b->enableGlobalShortcut(); - connect( b, SIGNAL(triggered(bool)), SLOT(decreaseVolume()) ); - } + addGlobalShortcut(b, decreaseVolumeName, dynamicControl); + if ( ! dynamicControl ) + connect(b, SIGNAL(triggered(bool)), SLOT(decreaseVolume())); + // -3- MUTE VOLUME SHORTCUT ----------------------------------------- b = _mdwPopupActions->addAction( QString("Toggle mute %1").arg( actionSuffix ) ); QString muteVolumeName = i18n( "Toggle Mute" ); - muteVolumeName += " - " + mixDevice()->readableName() + ", " + mixDevice()->mixer()->readableName(); - b->setText( muteVolumeName ); -/* #ifdef __GNUC__ - #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed - #endif*/ - if ( ! mixDevice()->mixer()->isDynamic() ) { - // virtual / dynamic controls won't get shortcuts - b->setGlobalShortcut(dummyShortcut); // -<- enableGlobalShortcut() is not there => use workaround - // b->enableGlobalShortcut(); + addGlobalShortcut(b, muteVolumeName, dynamicControl); + if ( ! dynamicControl ) connect( b, SIGNAL(triggered(bool)), SLOT(toggleMuted()) ); - } } QSizePolicy MDWSlider::sizePolicy() const { - if ( _orientation == Qt::Vertical ) { + if ( _orientation == Qt::Vertical ) + { return QSizePolicy( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding ); } - else { - return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); + else + { + return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); +// return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); } } @@ -259,23 +260,76 @@ */ bool MDWSlider::hasCaptureLED() const { -// return m_captureLED!=0; return m_captureCheckbox!=0; } /** * See "setMuteButtonSpace" */ -void MDWSlider::setCaptureLEDSpace(bool value) +void MDWSlider::setCaptureLEDSpace(bool showCaptureLED) { - if ( !value || hasCaptureLED() ) { + if ( !showCaptureLED || hasCaptureLED() ) { captureSpacer->setFixedSize(0,0); captureSpacer->setVisible(false); } else captureSpacer->setFixedSize(QCheckBox().sizeHint()); -// captureSpacer->setFixedSize(16,16); } +void MDWSlider::guiAddSlidersAndMediacontrols(bool playSliders, bool capSliders, bool mediaControls, QBoxLayout* layout) +{ + if (playSliders) + addSliders(layout, 'p', m_mixdevice->playbackVolume(), m_slidersPlayback); + + if (capSliders) + addSliders(layout, 'c', m_mixdevice->captureVolume(), m_slidersCapture); + + if (mediaControls) + addMediaControls(layout); +} + +void MDWSlider::guiAddCaptureCheckbox(bool wantsCaptureLED, const Qt::Alignment& alignmentForCapture, QBoxLayout* layoutForCapture) +{ + if (wantsCaptureLED && m_mixdevice->captureVolume().hasSwitch()) + { + m_captureCheckbox = new QCheckBox(i18n("capture"), this); + m_captureCheckbox->installEventFilter(this); + layoutForCapture->addWidget(m_captureCheckbox, alignmentForCapture); + connect(m_captureCheckbox, SIGNAL(toggled(bool)), this, SLOT(setRecsrc(bool))); + QString muteTip(i18n("Capture/Uncapture %1", m_mixdevice->readableName())); + m_captureCheckbox->setToolTip(muteTip); + } +} + +void MDWSlider::guiAddMuteButton(bool wantsMuteButton, Qt::Alignment alignment, QBoxLayout* layoutForMuteButton) +{ + if (wantsMuteButton && m_mixdevice->hasMuteSwitch()) + { + m_qcb = new QToolButton(this); + m_qcb->setAutoRaise(true); + m_qcb->setCheckable(false); + m_qcb->setIcon(QIcon(loadIcon("audio-volume-muted"))); + layoutForMuteButton->addWidget(m_qcb, alignment); + m_qcb->installEventFilter(this); + connect(m_qcb, SIGNAL(clicked(bool)), this, SLOT(toggleMuted())); + QString muteTip(i18n("Mute/Unmute %1", m_mixdevice->readableName())); + m_qcb->setToolTip(muteTip); + } + + // Spacer will be shown, when no mute button is displayed + muteButtonSpacer = new QWidget(this); + layoutForMuteButton->addWidget( muteButtonSpacer ); + muteButtonSpacer->installEventFilter(this); + +} + +void MDWSlider::guiAddControlIcon(Qt::Alignment alignment, QBoxLayout* layout) +{ + m_iconLabelSimple = new QLabel(this); + installEventFilter(m_iconLabelSimple); + setIcon(m_mixdevice->iconName(), m_iconLabelSimple); + m_iconLabelSimple->setToolTip(m_mixdevice->readableName()); + layout->addWidget(m_iconLabelSimple, alignment); +} /** * Creates all widgets : Icon, Label, Mute-Button, Slider(s) and Capture-Button. @@ -286,10 +340,13 @@ bool includeCapture = _pctl->useSubcontrolCapture(); bool wantsPlaybackSliders = includePlayback && ( m_mixdevice->playbackVolume().count() > 0 ); bool wantsCaptureSliders = includeCapture && ( m_mixdevice->captureVolume().count() > 0 ); - bool hasVolumeSliders = wantsPlaybackSliders || wantsCaptureSliders; - // bool bothCaptureANDPlaybackExist = wantsPlaybackSliders && wantsCaptureSliders; + bool wantsCaptureLED = showCaptureLED && includeCapture; + bool wantsMuteButton = showMuteButton && includePlayback; + bool hasVolumeSliders = wantsPlaybackSliders || wantsCaptureSliders; + // bool bothCaptureANDPlaybackExist = wantsPlaybackSliders && wantsCaptureSliders; - bool wantsMediaControls = ( m_mixdevice->hasMediaNextControl() || m_mixdevice->hasMediaPlayControl() || m_mixdevice->hasMediaPrevControl() ); + MediaController* mediaController = m_mixdevice->getMediaController(); + bool wantsMediaControls = mediaController->hasControls(); // case of vertical sliders: if ( _orientation == Qt::Vertical ) @@ -299,19 +356,14 @@ setLayout(controlLayout); controlLayout->setContentsMargins(0,0,0,0); - //add device icon - m_iconLabelSimple = 0L; - setIcon( m_mixdevice->iconName() ); - m_iconLabelSimple->setToolTip( m_mixdevice->readableName() ); - controlLayout->addWidget( m_iconLabelSimple, 0, Qt::AlignHCenter ); + guiAddControlIcon(Qt::AlignHCenter|Qt::AlignTop, controlLayout); - //5px space - //controlLayout->addSpacing( 5 ); + Qt::Alignment centerAlign = Qt::AlignHCenter | Qt::AlignBottom; //the device label m_label = new QLabel( m_mixdevice->readableName(), this); m_label->setWordWrap(true); - int max = 0; + int max = 80; QStringList words = m_mixdevice->readableName().split(QChar(' ')); foreach (QString name, words) max = qMax(max,QLabel(name).sizeHint().width()); @@ -319,46 +371,27 @@ // m_label->setMinimumWidth(80); // if (m_label->sizeHint().width()>max && m_label->sizeHint().width()>80) // m_label->setMinimumWidth(max); - m_label->setMinimumWidth(qMax(80,max)); + m_label->setMinimumWidth(max); m_label->setMinimumHeight(m_label->heightForWidth(m_label->minimumWidth())); m_label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_label->setAlignment(Qt::AlignHCenter); - controlLayout->addWidget(m_label, 0, Qt::AlignHCenter ); + controlLayout->addWidget(m_label, 0, centerAlign ); - //spacer with height to match height difference to other sliderwidgets + //spacer with height to match height difference to other slider widgets labelSpacer = new QWidget(this); controlLayout->addWidget( labelSpacer ); labelSpacer->installEventFilter(this); // sliders - QHBoxLayout *volLayout = new QHBoxLayout( ); - volLayout->setAlignment(Qt::AlignHCenter|Qt::AlignBottom); - //volLayout->setSpacing(5); + QBoxLayout *volLayout = new QHBoxLayout( ); + volLayout->setAlignment(centerAlign); controlLayout->addItem( volLayout ); - if ( hasVolumeSliders ) - { - if ( wantsPlaybackSliders ) - addSliders( volLayout, 'p', m_mixdevice->playbackVolume(), m_slidersPlayback); - if ( wantsCaptureSliders ) - addSliders( volLayout, 'c', m_mixdevice->captureVolume() , m_slidersCapture ); - if ( wantsMediaControls ) - addMediaControls( volLayout ); // Please note that the addmediaControls() is in the hasVolumeSliders check onyl because it was easier to integrate - controlLayout->addSpacing( 3 ); - } else { - controlLayout->addStretch(1); - } + guiAddSlidersAndMediacontrols(wantsPlaybackSliders, wantsCaptureSliders, wantsMediaControls, volLayout); + if ( !hasVolumeSliders ) + controlLayout->addStretch(1); // Not sure why we have this for "vertical sliders" case - //capture button - if ( showCaptureLED && includeCapture && m_mixdevice->captureVolume().hasSwitch() ) - { - m_captureCheckbox = new QCheckBox( i18n("capture") , this); - m_captureCheckbox->installEventFilter( this ); - controlLayout->addWidget( m_captureCheckbox, 0, Qt::AlignHCenter ); - connect( m_captureCheckbox, SIGNAL(toggled(bool)), this, SLOT(setRecsrc(bool)) ); - QString muteTip( i18n( "Capture/Uncapture %1", m_mixdevice->readableName() ) ); - m_captureCheckbox->setToolTip( muteTip ); - } + guiAddCaptureCheckbox(wantsCaptureLED, centerAlign, controlLayout); // spacer which is shown when no capture button present captureSpacer = new QWidget(this); @@ -367,104 +400,50 @@ //mute button - if ( showMuteButton && includePlayback && m_mixdevice->hasMuteSwitch() ) - { - m_qcb = new QToolButton(this); - m_qcb->setAutoRaise(true); - m_qcb->setCheckable(false); - - m_qcb->setIcon( QIcon( loadIcon("audio-volume-muted") ) ); - - controlLayout->addWidget( m_qcb , 0, Qt::AlignHCenter); - m_qcb->installEventFilter(this); - connect ( m_qcb, SIGNAL(clicked(bool)), this, SLOT(toggleMuted()) ); - QString muteTip( i18n( "Mute/Unmute %1", m_mixdevice->readableName() ) ); - m_qcb->setToolTip( muteTip ); - } - - //spacer shown, when no mute button is displayed - muteButtonSpacer = new QWidget(this); - controlLayout->addWidget( muteButtonSpacer ); - muteButtonSpacer->installEventFilter(this); - + guiAddMuteButton(wantsMuteButton, centerAlign, controlLayout); } else { - QVBoxLayout *_layout = new QVBoxLayout( this ); + /* + * Horizontal sliders: row1 contains the label (and capture button). + * row2 contains icon, sliders, and mute button + */ + + QVBoxLayout *rows = new QVBoxLayout( this ); + // --- ROW1 ------------------------------------------------------------------------ QHBoxLayout *row1 = new QHBoxLayout(); - _layout->addItem( row1 ); + rows->addItem( row1 ); - m_label = new QLabel(this); - m_label->setText( m_mixdevice->readableName() ); + m_label = new QLabel(m_mixdevice->readableName(), this); m_label->installEventFilter( this ); row1->addWidget( m_label ); row1->setAlignment(m_label, Qt::AlignVCenter); - if ( showCaptureLED && includeCapture && m_mixdevice->captureVolume().hasSwitch() ) - { - m_captureCheckbox = new QCheckBox( i18n("capture") , this); - m_captureCheckbox->installEventFilter( this ); - row1->addWidget( m_captureCheckbox); - row1->setAlignment(m_captureCheckbox, Qt::AlignRight); - connect( m_captureCheckbox, SIGNAL(toggled(bool)), this, SLOT(setRecsrc(bool)) ); - QString muteTip( i18n( "Capture/Uncapture %1", m_mixdevice->readableName() ) ); - m_captureCheckbox->setToolTip( muteTip ); - } + row1->addStretch(); + row1->addWidget(captureSpacer); + + guiAddCaptureCheckbox(wantsCaptureLED, Qt::AlignRight, row1); + captureSpacer = new QWidget(this); // create, but do not add to any layout (not used!) + // --- ROW2 ------------------------------------------------------------------------ QHBoxLayout *row2 = new QHBoxLayout(); row2->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); - _layout->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); - _layout->addItem( row2 ); + rows->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); + rows->addItem( row2 ); + guiAddControlIcon(Qt::AlignVCenter, row2); - m_iconLabelSimple = 0L; - setIcon( m_mixdevice->iconName() ); - QString toolTip( m_mixdevice->readableName() ); - m_iconLabelSimple->setToolTip( toolTip ); - row2->addWidget( m_iconLabelSimple ); - row2->setAlignment(m_iconLabelSimple, Qt::AlignVCenter); - captureSpacer = new QWidget(this); -// controlLayout->addWidget( captureSpacer ); -// captureSpacer->installEventFilter(this); - - //row2->addSpacing( 10 ); - // --- SLIDERS --------------------------- - QBoxLayout *volLayout; - volLayout = new QVBoxLayout( ); + QBoxLayout *volLayout = new QVBoxLayout( ); volLayout->setAlignment(Qt::AlignVCenter|Qt::AlignRight); row2->addItem( volLayout ); - if ( hasVolumeSliders ) - { - if ( wantsPlaybackSliders && m_mixdevice->playbackVolume().count() > 0 ) - addSliders( volLayout, 'p', m_mixdevice->playbackVolume(), m_slidersPlayback ); - if ( wantsCaptureSliders && m_mixdevice->captureVolume().count() > 0 ) - addSliders( volLayout, 'c', m_mixdevice->captureVolume() , m_slidersCapture ); - if ( wantsMediaControls ) - addMediaControls( volLayout ); - } - - if ( showMuteButton && includePlayback && m_mixdevice->hasMuteSwitch() ) - { - m_qcb = new QToolButton(this); - m_qcb->setAutoRaise(true); - m_qcb->setCheckable(false); - m_qcb->setIcon( QIcon( loadIcon("audio-volume-muted") ) ); - row2->addWidget( m_qcb ); - m_qcb->installEventFilter(this); - connect ( m_qcb, SIGNAL(clicked(bool)), this, SLOT(toggleMuted()) ); - QString muteTip( i18n( "Mute/Unmute %1", m_mixdevice->readableName() ) ); - m_qcb->setToolTip( muteTip ); - } - - muteButtonSpacer = new QWidget(this); - row2->addWidget( muteButtonSpacer ); - muteButtonSpacer->installEventFilter(this); + guiAddSlidersAndMediacontrols(wantsPlaybackSliders, wantsCaptureSliders, wantsMediaControls, volLayout); + guiAddMuteButton(wantsMuteButton, Qt::AlignRight, row2); } bool stereoLinked = !_pctl->isSplit(); @@ -473,36 +452,72 @@ layout()->activate(); // Activate it explicitly in KDE3 because of PanelApplet/kicker issues } - void MDWSlider::addMediaControls(QBoxLayout* volLayout) - { - QBoxLayout *mediaLayout; - if ( _orientation == Qt::Vertical ) - mediaLayout = new QVBoxLayout(); - else - mediaLayout = new QHBoxLayout(); - - if ( mixDevice()->hasMediaPrevControl()) - { - QToolButton *lbl = addMediaButton("media-skip-backward", mediaLayout); - connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaPrev(bool)) ); - } - if ( mixDevice()->hasMediaPlayControl()) - { - QToolButton *lbl = addMediaButton("media-playback-start", mediaLayout); - connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaPlay(bool)) ); - } - if ( mixDevice()->hasMediaNextControl()) - { - QToolButton *lbl = addMediaButton("media-skip-forward", mediaLayout); - connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaNext(bool)) ); - } - volLayout->addLayout(mediaLayout); - } +QString MDWSlider::calculatePlaybackIcon(MediaController::PlayState playState) +{ + QString mediaIconName; + switch (playState) + { + case MediaController::PlayPlaying: + // playing => show pause icon + mediaIconName = "media-playback-pause"; + break; + case MediaController::PlayPaused: + // stopped/paused => show play icon + mediaIconName = "media-playback-start"; + break; + case MediaController::PlayStopped: + // stopped/paused => show play icon + mediaIconName = "media-playback-start"; + break; + default: + // unknown => not good, probably result from player has not yet arrived => show a play button + mediaIconName = "media-playback-start"; + break; + } + + return mediaIconName; +} + +void MDWSlider::addMediaControls(QBoxLayout* volLayout) +{ + MediaController* mediaController = mixDevice()->getMediaController(); + + QBoxLayout *mediaLayout; + if (_orientation == Qt::Vertical) + mediaLayout = new QVBoxLayout(); + else + mediaLayout = new QHBoxLayout(); + +// QFrame* frame1 = new QFrame(this); +// frame1->setFrameShape(QFrame::StyledPanel); + QWidget* frame = this; // or frame1 + mediaLayout->addStretch(); + if (mediaController->hasMediaPrevControl()) + { + QToolButton *lbl = addMediaButton("media-skip-backward", mediaLayout, frame); + connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaPrev(bool))); + } + if (mediaController->hasMediaPlayControl()) + { + MediaController::PlayState playState = mediaController->getPlayState(); + QString mediaIcon = calculatePlaybackIcon(playState); + mediaButton = addMediaButton(mediaIcon, mediaLayout, frame); + connect(mediaButton, SIGNAL(clicked(bool)), this, SLOT(mediaPlay(bool))); + } + if (mediaController->hasMediaNextControl()) + { + QToolButton *lbl = addMediaButton("media-skip-forward", mediaLayout, frame); + connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaNext(bool))); + } + mediaLayout->addStretch(); + volLayout->addLayout(mediaLayout); +} -QToolButton* MDWSlider::addMediaButton(QString iconName, QLayout* layout) + +QToolButton* MDWSlider::addMediaButton(QString iconName, QLayout* layout, QWidget *parent) { - QToolButton *lbl = new QToolButton(this); + QToolButton *lbl = new QToolButton(parent); lbl->setIconSize(QSize(22,22)); lbl->setAutoRaise(true); lbl->setCheckable(false); @@ -513,6 +528,19 @@ return lbl; } +/** + * Updates the icon according to the data model. + */ +void MDWSlider::updateMediaButton() +{ + if (mediaButton == 0) + return; // has no media button + + MediaController* mediaController = mixDevice()->getMediaController(); + QString mediaIconName = calculatePlaybackIcon(mediaController->getPlayState()); + setIcon(mediaIconName, mediaButton); +} + void MDWSlider::mediaPrev(bool) { mixDevice()->mediaPrev(); @@ -593,19 +621,21 @@ } // for all channels of this device } - +/** + * Return the VolumeSliderExtraData from either VolumeSlider or KSmallSlider. + * You MUST extend this method, should you decide to add more Slider Widget classes. + * + * @param slider + * @return + */ VolumeSliderExtraData& MDWSlider::extraData(QAbstractSlider *slider) { VolumeSlider* sl = qobject_cast(slider); - if ( sl ) return sl->extraData; + if ( sl ) + return sl->extraData; KSmallSlider* sl2 = qobject_cast(slider); - if ( sl2 ) return sl2->extraData; - - kError(67100) << "Invalid slider"; -// char* foo = 0; -// char a = foo[3]; // Traceback ;) - return MDWSlider::DummVolumeSliderExtraData; + return sl2->extraData; } @@ -649,20 +679,16 @@ return KIconLoader::global()->loadIcon( filename, KIconLoader::Small, KIconLoader::SizeSmallMedium ); } -void MDWSlider::setIcon( QString filename ) -{ - setIcon(filename, &m_iconLabelSimple); -} -void MDWSlider::setIcon( QString filename, QLabel** label ) -{ - if( (*label) == 0 ) - { - *label = new QLabel(this); - installEventFilter( *label ); - } - setIcon(filename, *label); -} +//void MDWSlider::setIcon( QString filename, QLabel** label ) +//{ +// if( (*label) == 0 ) +// { +// *label = new QLabel(this); +// installEventFilter( *label ); +// } +// setIcon(filename, *label); +//} void MDWSlider::setIcon( QString filename, QWidget* label ) { @@ -677,7 +703,11 @@ miniDevPM = miniDevPM.transformed( t ); label->resize( 10, 10 ); } // small size - label->setMinimumSize(22,22); + else + { + label->setMinimumSize(22,22); + } + label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QLabel* lbl = qobject_cast(label); if ( lbl != 0 ) @@ -690,19 +720,13 @@ QToolButton* tbt = qobject_cast(label); if ( tbt != 0 ) { -/* QIcon ic(miniDevPM); - lbl->setIcon(ic);*/ - tbt->setIcon( miniDevPM ); -// tbt->setAlignment(Qt::AlignHCenter | Qt::AlignCenter); + tbt->setIcon( miniDevPM ); } // QToolButton - else { - kError() << "Invalid widget type ... cannot set icon"; - } } } else { - kError(67100) << "Pixmap missing." << endl; + kError(67100) << "Pixmap missing. filename=" << filename << endl; } } @@ -943,29 +967,9 @@ } -void MDWSlider::setDisabled() +void MDWSlider::setDisabled( bool hide ) { - // We can only disable, not enable. Because if the slider is not shown the user can not - // right-click it to show it. Once explained, it is obvious. :-) - setDisabled( true ); -} - -void MDWSlider::setDisabled( bool value ) -{ - // kDebug() << "disable #1: value=" << value; - if ( m_disabled!=value) - { - kDebug() << "disable: value=" << value; - //value ? hide() : show(); - setVisible(!value); // TODO Hiding via context menu does not work. Why??? - m_disabled = value; - m_view->configurationUpdate(); -#ifdef __GNUC__ -#warning Hiding a slider via context menu is broken. hide() or setVisible(xyz) simply seems to do nothing. -#endif - // announce() recreates everyting, but it does not get better. - //ControlManager::instance().announce(m_mixdevice->mixer()->id(), ControlChangeType::ControlList, QString("MDWSlider::setDisabled")); - } + emit guiVisibilityChange(this, !hide); } @@ -1030,7 +1034,8 @@ updateInternal(m_mixdevice->playbackVolume(), m_slidersPlayback, m_mixdevice->isMuted() ); if ( m_slidersCapture.count() != 0 || m_mixdevice->captureVolume().hasSwitch() ) updateInternal(m_mixdevice->captureVolume(), m_slidersCapture, m_mixdevice->isNotRecSource() ); - if (m_label) { + if (m_label) + { QLabel *l; VerticalText *v; if ((l = dynamic_cast(m_label))) @@ -1041,7 +1046,12 @@ updateAccesability(); } -// TODO passing "muted" should not be necessary any longer - due to getVolumeForGUI() +/** + * + * @param vol + * @param ref_sliders + * @param muted Future directions: passing "muted" should not be necessary any longer - due to getVolumeForGUI() + */ void MDWSlider::updateInternal(Volume& vol, QList& ref_sliders, bool muted) { // bool debugMe = (mixDevice()->id() == "PCM:0" ); diff -Nru kmix-4.12.3/gui/mdwslider.h kmix-4.12.90/gui/mdwslider.h --- kmix-4.12.3/gui/mdwslider.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/mdwslider.h 2014-01-01 04:02:55.000000000 +0000 @@ -69,9 +69,10 @@ void setLabeled( bool value ); void setTicks( bool ticks ); void setIcons( bool value ); - void setIcon( QString filename, QLabel** label ); +// void setIcon( QString filename, QLabel** label ); void setIcon( QString filename, QWidget* label ); - QToolButton* addMediaButton(QString iconName, QLayout* layout); + QToolButton* addMediaButton(QString iconName, QLayout* layout, QWidget *parent); + void updateMediaButton(); void setColors( QColor high, QColor low, QColor back ); void setMutedColors( QColor high, QColor low, QColor back ); @@ -96,7 +97,6 @@ void toggleMuted(); void toggleStereoLinked(); - void setDisabled(); void setDisabled( bool value ); void update(); void showMoveMenu(); @@ -108,6 +108,7 @@ signals: void toggleMenuBar(bool value); + void guiVisibilityChange(MixDeviceWidget* source, bool enable); private slots: void setRecsrc( bool value ); @@ -128,7 +129,6 @@ private: KShortcut dummyShortcut; - void setIcon( QString iconname ); QPixmap loadIcon( QString filename ); void createWidgets( bool showMuteLED, bool showCaptureLED ); void addSliders( QBoxLayout *volLayout, char type, Volume& vol, QList& ref_sliders); @@ -144,7 +144,13 @@ #endif QWidget* createLabel(QWidget* parent, QString& label, QBoxLayout *layout, bool); - + QString calculatePlaybackIcon(MediaController::PlayState playState); + void guiAddSlidersAndMediacontrols(bool playSliders, bool capSliders, bool mediaControls, QBoxLayout* layout); + void guiAddCaptureCheckbox(bool wantsCaptureLED, const Qt::Alignment& alignmentForCapture, + QBoxLayout* layoutForCapture); + void guiAddMuteButton(bool wantsMuteButton, Qt::Alignment alignment, QBoxLayout* layoutForMuteButton); + void guiAddControlIcon(Qt::Alignment alignment, QBoxLayout* layout); + void addGlobalShortcut(KAction* action, const QString& label, bool dynamicControl); bool m_linked; @@ -158,7 +164,8 @@ QLabel* m_muteText; QLabel *m_label; // is either QLabel or VerticalText - + QToolButton *mediaButton; + QCheckBox* m_captureCheckbox; QLabel* m_captureText; diff -Nru kmix-4.12.3/gui/mixdevicewidget.cpp kmix-4.12.90/gui/mixdevicewidget.cpp --- kmix-4.12.3/gui/mixdevicewidget.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/mixdevicewidget.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -55,7 +55,7 @@ bool small, Qt::Orientation orientation, QWidget* parent, ViewBase* view, ProfControl* par_pctl) : QWidget( parent ), m_mixdevice( md ), m_view( view ), _pctl(par_pctl), - m_disabled( false ), _orientation( orientation ), m_small( small ) + _orientation( orientation ), m_small( small ) , m_shortcutsDialog(0) { _mdwActions = new KActionCollection( this ); @@ -82,11 +82,6 @@ _mdwActions->addAction( action->objectName(), action ); } -bool MixDeviceWidget::isDisabled() const -{ - return m_disabled; -} - void MixDeviceWidget::defineKeys() { // Dialog for *global* shortcuts of this MDW @@ -98,11 +93,11 @@ } void MixDeviceWidget::volumeChange( int ) { /* is virtual */ } -void MixDeviceWidget::setDisabled( bool ) { /* is virtual */ } +//void MixDeviceWidget::setDisabled( bool ) { /* is virtual */ } //void MixDeviceWidget::setVolume( int /*channel*/, int /*vol*/ ) { /* is virtual */ } //void MixDeviceWidget::setVolume( Volume /*vol*/ ) { /* is virtual */ } -void MixDeviceWidget::update() { /* is virtual */ } -void MixDeviceWidget::showContextMenu( const QPoint &pos ) { /* is virtual */ } +//void MixDeviceWidget::update() { /* is virtual */ } +//void MixDeviceWidget::showContextMenu( const QPoint &pos ) { /* is virtual */ } void MixDeviceWidget::setColors( QColor , QColor , QColor ) { /* is virtual */ } void MixDeviceWidget::setIcons( bool ) { /* is virtual */ } void MixDeviceWidget::setLabeled( bool ) { /* is virtual */ } diff -Nru kmix-4.12.3/gui/mixdevicewidget.h kmix-4.12.90/gui/mixdevicewidget.h --- kmix-4.12.3/gui/mixdevicewidget.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/mixdevicewidget.h 2014-01-01 04:02:55.000000000 +0000 @@ -52,7 +52,6 @@ void addActionToPopup( KAction *action ); - virtual bool isDisabled() const; shared_ptr mixDevice() { return m_mixdevice; } virtual void setColors( QColor high, QColor low, QColor back ); @@ -66,15 +65,19 @@ public slots: - virtual void setDisabled( bool value ); virtual void defineKeys(); - virtual void update(); - virtual void showContextMenu( const QPoint &pos = QCursor::pos() ); + virtual void showContextMenu( const QPoint &pos = QCursor::pos() ) = 0; + /** + * update() is called whenever there are volume updates pending from the hardware for this MDW. + */ + virtual void update() = 0; + +signals: + virtual void guiVisibilityChange(MixDeviceWidget* source, bool enable) = 0; protected slots: + virtual void setDisabled( bool value ) = 0; void volumeChange( int ); -// virtual void setVolume( int channel, int volume ); -// virtual void setVolume( Volume volume ); protected: @@ -83,7 +86,6 @@ KActionCollection* _mdwPopupActions; ViewBase* m_view; ProfControl* _pctl; - bool m_disabled; Qt::Orientation _orientation; bool m_small; KShortcutsDialog* m_shortcutsDialog; diff -Nru kmix-4.12.3/gui/viewbase.cpp kmix-4.12.90/gui/viewbase.cpp --- kmix-4.12.3/gui/viewbase.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/viewbase.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -39,6 +39,7 @@ #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" @@ -75,9 +76,6 @@ m->setChecked(visible); } } - - - } ViewBase::~ViewBase() @@ -92,8 +90,8 @@ _mixers.append(mixer); } -void ViewBase::configurationUpdate() { // TODO still in use?!? -} +//void ViewBase::configurationUpdate() { +//} @@ -108,8 +106,9 @@ void ViewBase::updateGuiOptions() { - setTicks(GlobalConfig::instance().showTicks); - setLabels(GlobalConfig::instance().showLabels); + setTicks(GlobalConfig::instance().data.showTicks); + setLabels(GlobalConfig::instance().data.showLabels); + updateMediaPlaybackIcons(); } QString ViewBase::id() const { @@ -126,6 +125,22 @@ 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 @@ -139,9 +154,9 @@ { 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 ( !pulseaudioPresent() ) // TODO 11 Dynamic view configuration if ( !isDynamic() ) { QAction *action = _localActionColletion->addAction("toggle_channels"); @@ -153,6 +168,28 @@ 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(); + kDebug() << "Change " << md->id() << " to visible=" << enable; + ProfControl* pctl = findMdw(md->id()); + if (pctl == 0) + { + kWarning() << "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 ) { @@ -260,7 +297,7 @@ */ void ViewBase::configureView() { - Q_ASSERT( !isDynamic() ); // TODO 11 Dynamic view configuration + Q_ASSERT( !isDynamic() ); Q_ASSERT( !pulseaudioPresent() ); DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); @@ -276,102 +313,125 @@ /** + * 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 ); - kDebug(67100) << "KMixToolBox::loadView() grp=" << grp.toAscii(); + ViewBase *view = this; + QString grp = "View."; + grp += view->id(); + //KConfigGroup cg = config->group( grp ); + kDebug(67100) + << "KMixToolBox::loadView() grp=" << grp.toAscii(); - static QString guiComplexityNames[3] = { QString("simple"), QString("extended"), QString("all") }; + static QString guiComplexityNames[3] = + { GUIProfile::PNameSimple, GUIProfile::PNameExtended, GUIProfile::PNameAll }; - // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) - bool dynamic = isDynamic(); // TODO 11 Dynamic view configuration + // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) + bool dynamic = isDynamic(); - for ( GUIComplexity chosenGuiComplexity = ViewBase::SIMPLE; chosenGuiComplexity <= ViewBase::ALL; ++chosenGuiComplexity ) - { - bool atLeastOneControlIsShown = false; - for (int i=0; i < view->_mdws.count(); ++i ){ - QWidget *qmdw = view->_mdws[i]; - if ( qmdw->inherits("MixDeviceWidget") ) - { - /* Workaround for a bug. KMix in KDE4.0 wrote group names like "[View.Base.Base.Front:0]", with - a duplicated "Base" which *should* have been Soundcard ID,like in "[View.Base.ALSA::HDA_NVidia:1.Front:0]". - - Workaround: If found, write back correct group name. - */ - MixDeviceWidget* mdw = (MixDeviceWidget*)qmdw; - shared_ptr md = mdw->mixDevice(); - - QString devgrp = md->configGroupName(grp); - KConfigGroup devcg = config->group( devgrp ); - - QString buggyDevgrp = QString("%1.%2.%3").arg(grp).arg(view->id()).arg(md->id()); - KConfigGroup buggyDevgrpCG = config->group( buggyDevgrp ); - if ( buggyDevgrpCG.exists() ) { - buggyDevgrpCG.copyTo(&devcg); - // Group will be deleted in KMixerWidget::fixConfigAfterRead() - } - - if ( mdw->inherits("MDWSlider") ) - { - // only sliders have the ability to split apart in mutliple channels - bool splitChannels = devcg.readEntry("Split", !mdw->isStereoLinked()); - mdw->setStereoLinked( !splitChannels ); - } - - bool mdwEnabled = false; - if ( !dynamic && devcg.hasKey("Show") ) - { - mdwEnabled = ( true == devcg.readEntry("Show", true) ); - //kDebug() << "Load devgrp" << devgrp << "show=" << mdwEnabled; - //kDebug(67100) << "KMixToolBox::loadView() for" << devgrp << "from config-file: mdwEnabled==" << mdwEnabled; - } - else - { - // if not configured in config file, use the default from the profile - //GUIProfile::ControlSet cset = (view->guiProfile()->getControls()); - ProfControl* matchingControl = findMdw(mdw->mixDevice()->id(), guiComplexityNames[chosenGuiComplexity]); - if ( matchingControl != 0 ) - { - mdwEnabled = true; - atLeastOneControlIsShown = true; - } - } - mdw->setVisible(mdwEnabled); - } // inherits MixDeviceWidget - } // for all MDW's - if ( atLeastOneControlIsShown ) - { - this->guiComplexity = chosenGuiComplexity; - break; // If there were controls in this complexity level, don't try more - } - } // for try = 0 ... 1 //kDebug(67100) << "KMixToolBox::loadView() for" << devgrp << "FINAL: mdwEnabled==" << mdwEnabled; + for (GUIComplexity guiCompl = ViewBase::SIMPLE; guiCompl <= ViewBase::ALL; ++guiCompl) + { + bool atLeastOneControlIsShown = false; + foreach(QWidget *qmdw, view->_mdws) +// for (int i = 0; i < view->_mdws.count(); ++i) + { +// QWidget *qmdw = view->_mdws[i]; + if (qmdw->inherits("MixDeviceWidget")) + { + 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 mutliple 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; + if (!dynamic && devcg.hasKey("Show")) + { + mdwEnabled = (true == devcg.readEntry("Show", true)); + } + else + { + // If not configured in config file, use the default from the profile + if (findMdw(mdw->mixDevice()->id(), guiComplexityNames[guiCompl]) != 0) + { + mdwEnabled = true; + } + } + if (mdwEnabled) + { + atLeastOneControlIsShown = true; + } + mdw->setVisible(mdwEnabled); + } // inherits MixDeviceWidget + } // for all MDW's + if (atLeastOneControlIsShown) + { + this->guiComplexity = guiCompl; + break; // If there were controls in this complexity level, don't try more + } + } // for try = 0 ... 1 } /** * Checks whether the given mixDevice shall be shown according to the requested * GUI complexity. All ProfControl objects are inspected. The first found is returned. - * @param If true, the requestedGuiComplexity must be exactly equal. otherwise it can be equal or lower * + * @param mdwId The control ID + * @param requestedGuiComplexityName The GUI name + * @return The corresponding ProfControl* + * */ ProfControl* ViewBase::findMdw(const QString& mdwId, QString requestedGuiComplexityName) { - foreach ( ProfControl* pControl, guiProfile()->getControls() ) - { - //ProfControl* pControl = *it; - QRegExp idRegExp(pControl->id); - //kDebug(67100) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); - if ( mdwId.contains(idRegExp) && - pControl->show == requestedGuiComplexityName ) + foreach ( ProfControl* pControl, guiProfile()->getControls() ) + { + QRegExp idRegExp(pControl->id); + //kDebug(67100) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); + if ( mdwId.contains(idRegExp) && + pControl->show == requestedGuiComplexityName ) { - return pControl; + return pControl; } - } // iterate over all ProfControl entries - - return 0; // not found + } // iterate over all ProfControl entries + + return 0;// not found +} + + +/** + * Returns the ProfControl* to the given id. If none is found, 0 is returned. + * GUI complexity. All ProfControl objects are inspected. The first found is returned. + * + * @param id The control ID + * @return The corresponding ProfControl* + */ +ProfControl* ViewBase::findMdw(const QString& id) +{ + foreach ( ProfControl* pControl, guiProfile()->getControls() ) + { + QRegExp idRegExp(pControl->id); + //kDebug(67100) << "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 } /* @@ -379,48 +439,52 @@ */ void ViewBase::save(KConfig *config) { - ViewBase *view = this; - QString grp = "View."; - grp += view->id(); -// KConfigGroup cg = config->group( grp ); - kDebug(67100) << "KMixToolBox::saveView() grp=" << grp; - - // 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 = (MixDeviceWidget*)qmdw; - shared_ptr md = mdw->mixDevice(); - - //kDebug(67100) << " grp=" << grp.toAscii(); - //kDebug(67100) << " mixer=" << view->id().toAscii(); - //kDebug(67100) << " mdwPK=" << mdw->mixDevice()->id().toAscii(); - - QString devgrp = QString("%1.%2.%3").arg(grp).arg(md->mixer()->id()).arg(md->id()); - KConfigGroup devcg = config->group( devgrp ); - - if ( mdw->inherits("MDWSlider") ) - { - // only sliders have the ability to split apart in mutliple channels - devcg.writeEntry( "Split", ! mdw->isStereoLinked() ); - } - if ( !dynamic ) { - devcg.writeEntry( "Show" , mdw->isVisibleTo(view) ); + 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 = (MixDeviceWidget*) qmdw; + shared_ptr md = mdw->mixDevice(); + + //kDebug(67100) << " grp=" << grp.toAscii(); + //kDebug(67100) << " mixer=" << view->id().toAscii(); + //kDebug(67100) << " mdwPK=" << mdw->mixDevice()->id().toAscii(); + + QString devgrp = QString("%1.%2.%3").arg(grp).arg(md->mixer()->id()).arg(md->id()); + KConfigGroup devcg = config->group(devgrp); + + if (mdw->inherits("MDWSlider")) + { + // only sliders have the ability to split apart in mutliple channels + devcg.writeEntry("Split", !mdw->isStereoLinked()); + } + if (!dynamic) + { + devcg.writeEntry("Show", mdw->isVisibleTo(view)); // kDebug() << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); - } + } - } // inherits MixDeviceWidget - } // for all MDW's + } // inherits MixDeviceWidget + } // for all MDW's - if ( !dynamic ) { - // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) - kDebug(67100) << "GUIProfile is dirty: " << guiProfile()->isDirty(); - if ( guiProfile()->isDirty() ) - guiProfile()->writeProfile(); - } + if (!dynamic) + { + // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) + if (guiProfile()->isDirty()) + { + kDebug(67100) + << "Writing dirty profile. grp=" << grp; + guiProfile()->writeProfile(); + } + } } diff -Nru kmix-4.12.3/gui/viewbase.h kmix-4.12.90/gui/viewbase.h --- kmix-4.12.3/gui/viewbase.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/viewbase.h 2014-01-01 04:02:55.000000000 +0000 @@ -38,6 +38,7 @@ #include "core/mixdevice.h" #include "core/mixset.h" #include "gui/guiprofile.h" +#include "gui/mixdevicewidget.h" /** * The ViewBase is a virtual base class, to be used for subclassing the real Mixer Views. @@ -86,7 +87,7 @@ virtual QWidget* add(shared_ptr) = 0; // This method is called after a configuration update (show/hide controls, split/unsplit). - virtual void configurationUpdate(); // TODO remove + virtual void configurationUpdate() = 0; void load(KConfig *config); void save(KConfig *config); @@ -117,6 +118,7 @@ GUIProfile* guiProfile() { return GUIProfile::find(_guiProfileId); }; GUIComplexity getGuiComplexity() { return guiComplexity; }; + ProfControl* findMdw(const QString& id); ProfControl* findMdw(const QString& mdwId, QString requestedGuiComplexityName); @@ -164,6 +166,11 @@ private: QString m_viewId; + void updateMediaPlaybackIcons(); + +private slots: + void guiVisibilitySlot(MixDeviceWidget* source, bool enable); + }; #endif diff -Nru kmix-4.12.3/gui/viewdockareapopup.cpp kmix-4.12.90/gui/viewdockareapopup.cpp --- kmix-4.12.3/gui/viewdockareapopup.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/viewdockareapopup.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -44,6 +44,7 @@ #include "core/GlobalConfig.h" #include "gui/dialogchoosebackends.h" #include "gui/guiprofile.h" +#include "gui/kmixprefdlg.h" #include "gui/mdwslider.h" // Restore volume button feature is incomplete => disabling for KDE 4.10 @@ -55,7 +56,7 @@ ViewDockAreaPopup::ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW) : - ViewBase(parent, id, 0, vflags, guiProfileId), _dock(dockW) + ViewBase(parent, id, 0, vflags, guiProfileId), _kmixMainWindow(dockW) { resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); @@ -122,6 +123,11 @@ } +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() { @@ -148,20 +154,20 @@ { resetMdws(); - if ( optionsLayout != 0 ) - { - QLayoutItem *li2; - while ( ( li2 = optionsLayout->takeAt(0) ) ) - delete li2; - } - + 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; - } + 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: @@ -193,23 +199,16 @@ delete seperatorBetweenMastersAndStreams; // --- Due to the strange crash, delete everything manually : END --------------- -// kDebug() << "1 layout()=" << layout() << ", _layoutMDW=" << _layoutMDW; - // BKO 299754 . Delete _layoutMDW before resetting ref. Otherwise it would be "delete 0;", aka "NOP" -// delete _layoutMDW; -// delete layout(); resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - //delete optionsLayout; - /* * 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) + * (Hint: it might have been 0 already. Nowadays it is definitely, see #resetRefs()) */ delete layout(); // BKO 299754 -// delete _layoutMDW; _layoutMDW = new QGridLayout(this); _layoutMDW->setSpacing(KDialog::spacingHint()); _layoutMDW->setMargin(0); @@ -223,7 +222,7 @@ _mixers.clear(); QSet preferredMixersForSoundmenu = GlobalConfig::instance().getMixersForSoundmenu(); - kDebug() << "Launch with " << preferredMixersForSoundmenu; +// kDebug() << "Launch with " << preferredMixersForSoundmenu; foreach ( Mixer* mixer, Mixer::mixers() ) { bool useMixer = preferredMixersForSoundmenu.isEmpty() || preferredMixersForSoundmenu.contains(mixer->id()); @@ -283,7 +282,7 @@ QWidget* ViewDockAreaPopup::add(shared_ptr md) { - bool vertical = (GlobalConfig::instance().traypopupOrientation == Qt::Vertical); // I am wondering whether using vflags for this would still make sense + 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"); @@ -323,7 +322,7 @@ true, // Show Mute LE true, // Show Record LED false, // Small - GlobalConfig::instance().traypopupOrientation, + vertical ? Qt::Vertical : Qt::Horizontal, this, // parent this // NOT ANYMORE!!! -> Is "NULL", so that there is no RMB-popup , pctl @@ -396,14 +395,15 @@ { // Q_ASSERT( !pulseaudioPresent() ); - QSet currentlyActiveMixersInDockArea; - foreach ( Mixer* mixer, _mixers ) - { - currentlyActiveMixersInDockArea.insert(mixer->id()); - } - - DialogChooseBackends* dvc = new DialogChooseBackends(currentlyActiveMixersInDockArea); - dvc->show(); +// QSet currentlyActiveMixersInDockArea; +// foreach ( Mixer* mixer, _mixers ) +// { +// currentlyActiveMixersInDockArea.insert(mixer->id()); +// } + + KMixPrefDlg* prefDlg = KMixPrefDlg::getInstance(); + //prefDlg->setActiveMixersInDock(currentlyActiveMixersInDockArea); + prefDlg->switchToPage(KMixPrefDlg::PrefSoundMenu); } /** @@ -411,9 +411,9 @@ */ void ViewDockAreaPopup::showPanelSlot() { - _dock->setVisible(true); - KWindowSystem::setOnDesktop(_dock->winId(), KWindowSystem::currentDesktop()); - KWindowSystem::activateWindow(_dock->winId()); + _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 -Nru kmix-4.12.3/gui/viewdockareapopup.h kmix-4.12.90/gui/viewdockareapopup.h --- kmix-4.12.3/gui/viewdockareapopup.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/viewdockareapopup.h 2014-01-01 04:02:55.000000000 +0000 @@ -46,9 +46,10 @@ virtual void constructionFinished(); virtual void refreshVolumeLevels(); virtual void showContextMenu(); + virtual void configurationUpdate(); protected: - KMixWindow *_dock; + KMixWindow *_kmixMainWindow; void wheelEvent ( QWheelEvent * e ); virtual void _setMixSet(); diff -Nru kmix-4.12.3/gui/viewsliders.cpp kmix-4.12.90/gui/viewsliders.cpp --- kmix-4.12.3/gui/viewsliders.cpp 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/viewsliders.cpp 2014-01-01 04:02:55.000000000 +0000 @@ -102,7 +102,7 @@ break; case ControlChangeType::Volume: - if (GlobalConfig::instance().debugVolume) + if (GlobalConfig::instance().data.debugVolume) kDebug() << "NOW I WILL REFRESH VOLUME LEVELS. I AM " << id(); refreshVolumeLevels(); @@ -119,7 +119,7 @@ QWidget* ViewSliders::add(shared_ptr md) { MixDeviceWidget *mdw; - Qt::Orientation orientation = GlobalConfig::instance().toplevelOrientation; + Qt::Orientation orientation = GlobalConfig::instance().data.getToplevelOrientation(); if ( md->isEnum() ) { @@ -194,7 +194,7 @@ //qDeleteAll(_separators); _separators.clear(); - if (GlobalConfig::instance().toplevelOrientation == Qt::Horizontal) + if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Horizontal) { // Horizontal slider => put them vertically _layoutMDW = new QVBoxLayout(this); @@ -374,13 +374,6 @@ // This is a bit hacky. Using "simple" can be wrong on the very first start of KMix (but usually it is not!) ProfControl* matchingControl = findMdw(mdw->mixDevice()->id(), QString("simple")); mdw->setVisible(matchingControl != 0); - /* - if ( mdwSlider == 0 ) - { - // not a slider => debug output for enums - kDebug() << "Show ENUM " << mdw->mixDevice()->id() << " ? matchingControl=" << matchingControl; - } - */ if ( mdwSlider ) { @@ -426,7 +419,7 @@ // --- end --- #endif - if (GlobalConfig::instance().debugVolume) + if (GlobalConfig::instance().data.debugVolume) { bool debugMe = (mdw->mixDevice()->id() == "PCM:0"); if (debugMe) diff -Nru kmix-4.12.3/gui/volumesliderextradata.h kmix-4.12.90/gui/volumesliderextradata.h --- kmix-4.12.3/gui/volumesliderextradata.h 2014-01-01 06:33:52.000000000 +0000 +++ kmix-4.12.90/gui/volumesliderextradata.h 2014-01-01 04:02:55.000000000 +0000 @@ -29,7 +29,7 @@ class VolumeSliderExtraData { public: - VolumeSliderExtraData() : subcontrolLabel(0) {}; + VolumeSliderExtraData() : chid(Volume::NOCHANNEL), subcontrolLabel(0) {}; ~VolumeSliderExtraData() {}; void setChid(Volume::ChannelID chid) { this->chid = chid; }; Volume::ChannelID getChid() { return chid; };