diff --git a/core/mixdevice.cpp b/core/mixdevice.cpp index 2bc4c385..2153ac89 100644 --- a/core/mixdevice.cpp +++ b/core/mixdevice.cpp @@ -1,480 +1,480 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "core/mixdevice.h" #include #include #include "core/ControlPool.h" #include "core/mixer.h" #include "dbus/dbuscontrolwrapper.h" #include "gui/guiprofile.h" #include "core/volume.h" static const QString channelTypeToIconName( MixDevice::ChannelType type ) { switch (type) { case MixDevice::AUDIO: return "mixer-pcm"; case MixDevice::BASS: case MixDevice::SURROUND_LFE: // "LFE" SHOULD have an own icon return "mixer-lfe"; case MixDevice::CD: return "mixer-cd"; case MixDevice::EXTERNAL: return "mixer-line"; case MixDevice::MICROPHONE: return "mixer-microphone"; case MixDevice::MIDI: return "mixer-midi"; case MixDevice::RECMONITOR: return "mixer-capture"; case MixDevice::TREBLE: return "mixer-pcm-default"; case MixDevice::UNKNOWN: return "mixer-front"; case MixDevice::VOLUME: return "mixer-master"; case MixDevice::VIDEO: return "mixer-video"; case MixDevice::SURROUND: case MixDevice::SURROUND_BACK: return "mixer-surround"; case MixDevice::SURROUND_CENTERFRONT: case MixDevice::SURROUND_CENTERBACK: return "mixer-surround-center"; case MixDevice::HEADPHONE: return "mixer-headset"; case MixDevice::DIGITAL: return "mixer-digital"; case MixDevice::AC97: return "mixer-ac97"; case MixDevice::SPEAKER: return "mixer-pc-speaker"; case MixDevice::MICROPHONE_BOOST: return "mixer-microphone-boost"; case MixDevice::MICROPHONE_FRONT_BOOST: return "mixer-microphone-front-boost"; case MixDevice::MICROPHONE_FRONT: return "mixer-microphone-front"; case MixDevice::KMIX_COMPOSITE: return "mixer-line"; case MixDevice::APPLICATION_AMAROK: return "amarok"; case MixDevice::APPLICATION_BANSHEE: return "media-player-banshee"; case MixDevice::APPLICATION_XMM2: return "xmms"; case MixDevice::APPLICATION_TOMAHAWK: return "tomahawk"; case MixDevice::APPLICATION_CLEMENTINE: return "application-x-clementine"; case MixDevice::APPLICATION_VLC: return "vlc"; case MixDevice::APPLICATION_STREAM: return "mixer-pcm"; } return "mixer-front"; } /** * Constructs a MixDevice. A MixDevice represents one channel or control of * the mixer hardware. A MixDevice has a type (e.g. PCM), a descriptive name * (for example "Master" or "Headphone" or "IEC 958 Output"), * can have a volume level (2 when stereo), can be recordable and muted. * The ChannelType tells which kind of control the MixDevice is. */ MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ) : _profControl(0) { init(mixer, id, name, channelTypeToIconName(type), (MixSet*)0); } MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) : _profControl(0) { init(mixer, id, name, iconName, moveDestinationMixSet); } void MixDevice::init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) { _artificial = false; _applicationStream = false; _dbusControlWrapper = 0; // will be set in addToPool() _mixer = mixer; _id = id; _enumCurrentId = 0; mediaController = new MediaController(_id); if( name.isEmpty() ) _name = i18n("unknown"); else _name = name; if ( iconName.isEmpty() ) _iconName = "mixer-front"; else _iconName = iconName; _moveDestinationMixSet = moveDestinationMixSet; if ( _id.contains(' ') ) { // The key is used in the config file. IdbusControlWrappert MUST NOT contain spaces qCCritical(KMIX_LOG) << "MixDevice::setId(\"" << id << "\") . Invalid key - it must not contain spaces"; _id.replace(' ', '_'); } // qCDebug(KMIX_LOG) << "MixDevice::init() _id=" << _id; } /* * When a MixDevice shall be finally discarded, you must use this method to free its resources. * You must not use this MixDevice after calling close(). *
* The necessity stems from a memory leak due to object cycle (MixDevice<->DBusControlWrapper), so the reference * counting shared_ptr has no chance to clean up. See Bug 309464 for background information. */ void MixDevice::close() { delete _dbusControlWrapper; _dbusControlWrapper = 0; } MediaController* MixDevice::getMediaController() { return mediaController; } shared_ptr MixDevice::addToPool() { // qCDebug(KMIX_LOG) << "id=" << _mixer->id() << ":" << _id; shared_ptr thisSharedPtr(this); //shared_ptr thisSharedPtr = ControlPool::instance()->add(fullyQualifiedId, this); _dbusControlWrapper = new DBusControlWrapper( thisSharedPtr, dbusPath() ); return thisSharedPtr; } /** * Changes the internal state of this MixDevice. * It does not commit the change to the hardware. * * You might want to call something like m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); after calling this method. */ void MixDevice::increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType) { bool debugme = false; // bool debugme = id() == "PCM:0" ; if (volumeType & Volume::Playback) { Volume& volP = playbackVolume(); long inc = volP.volumeStep(decrease); if (debugme) qCDebug(KMIX_LOG) << ( decrease ? "decrease by " : "increase by " ) << inc ; if (isMuted()) { setMuted(false); } else { volP.changeAllVolumes(inc); if (debugme) qCDebug(KMIX_LOG) << (decrease ? "decrease by " : "increase by ") << inc; } } if (volumeType & Volume::Capture) { if (debugme) qCDebug(KMIX_LOG) << "VolumeType=" << volumeType << " c"; Volume& volC = captureVolume(); long inc = volC.volumeStep(decrease); volC.changeAllVolumes(inc); } } /** * Returns the name of the config group * @param Prefix of the group, e.g. "View_ALSA_USB_01" * @returns The config group name in the format "prefix.mixerId.controlId" */ QString MixDevice::configGroupName(QString prefix) { - QString devgrp = QString("%1.%2.%3").arg(prefix).arg(mixer()->id()).arg(id()); + QString devgrp = QString("%1.%2.%3").arg(prefix, mixer()->id(), id()); return devgrp; } /** * Returns a fully qualified id of this control, as a String in the form "controlId@mixerId" * @return */ QString MixDevice::getFullyQualifiedId() { - QString fqId = QString("%1@%2").arg(_id).arg(_mixer->id()); + QString fqId = QString("%1@%2").arg(_id, _mixer->id()); return fqId; } /** * Creates a deep copy of the given Volume, and adds it to this MixDevice. * * @param playbackVol */ void MixDevice::addPlaybackVolume(Volume &playbackVol) { // Hint: "_playbackVolume" gets COPIED from "playbackVol", because the copy-constructor actually copies the volume levels. _playbackVolume = playbackVol; _playbackVolume.setSwitchType(Volume::PlaybackSwitch); } /** * Creates a deep copy of the given Volume, and adds it to this MixDevice. * * @param captureVol */ void MixDevice::addCaptureVolume (Volume &captureVol) { _captureVolume = captureVol; _captureVolume.setSwitchType(Volume::CaptureSwitch); } void MixDevice::addEnums(QList& ref_enumList) { if ( ref_enumList.count() > 0 ) { int maxEnumId = ref_enumList.count(); for (int i=0; i& MixDevice::enumValues() { return _enumValues; } const QString& MixDevice::id() const { return _id; } const QString MixDevice::dbusPath() { QString controlPath = _id; controlPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); controlPath.replace(QLatin1String("//"), QLatin1String("/")); if ( controlPath.endsWith( '/' ) ) { controlPath.chop(1); } return _mixer->dbusPath() + '/' + controlPath; } bool MixDevice::isMuted() { return ! _playbackVolume.isSwitchActivated(); } /** * Returns whether this MixDevice is virtually muted. Only MixDevice objects w/o a physical switch can be muted virtually. */ bool MixDevice::isVirtuallyMuted() { return !hasPhysicalMuteSwitch() && isMuted(); } void MixDevice::setMuted(bool mute) { _playbackVolume.setSwitch(!mute); } void MixDevice::toggleMute() { setMuted( _playbackVolume.isSwitchActivated()); } bool MixDevice::hasMuteSwitch() { return playbackVolume().hasVolume() || playbackVolume().hasSwitch(); } bool MixDevice::hasPhysicalMuteSwitch() { return playbackVolume().hasSwitch(); } bool MixDevice::isRecSource() { return ( _captureVolume.hasSwitch() && _captureVolume.isSwitchActivated() ); } bool MixDevice::isNotRecSource() { return ( _captureVolume.hasSwitch() && !_captureVolume.isSwitchActivated() ); } void MixDevice::setRecSource(bool value) { _captureVolume.setSwitch( value ); } bool MixDevice::isEnum() { return ( ! _enumValues.empty() ); } int MixDevice::mediaPlay() { return mixer()->mediaPlay(_id); } int MixDevice::mediaPrev() { return mixer()->mediaPrev(_id); } int MixDevice::mediaNext() { return mixer()->mediaNext(_id); } bool MixDevice::operator==(const MixDevice& other) const { return ( _id == other._id ); } void MixDevice::setControlProfile(ProfControl* control) { _profControl = control; } ProfControl* MixDevice::controlProfile() { return _profControl; } /** * This method is currently only called on "kmixctrl --restore" * * Normally we have a working _volume object already, which is very important, * because we need to read the minimum and maximum volume levels. * (Another solution would be to "equip" volFromConfig with maxInt and minInt values). */ bool MixDevice::read( KConfig *config, const QString& grp ) { if ( _mixer->isDynamic() || isArtificial() ) { qCDebug(KMIX_LOG) << "MixDevice::read(): This MixDevice does not permit volume restoration (i.e. because it is handled lower down in the audio stack). Ignoring."; return false; } - QString devgrp = QString("%1.Dev%2").arg(grp).arg(_id); + QString devgrp = QString("%1.Dev%2").arg(grp, _id); KConfigGroup cg = config->group( devgrp ); //qCDebug(KMIX_LOG) << "MixDevice::read() of group devgrp=" << devgrp; readPlaybackOrCapture(cg, false); readPlaybackOrCapture(cg, true ); bool mute = cg.readEntry("is_muted", false); setMuted( mute ); bool recsrc = cg.readEntry("is_recsrc", false); setRecSource( recsrc ); int enumId = cg.readEntry("enum_id", -1); if ( enumId != -1 ) { setEnumId( enumId ); } return true; } void MixDevice::readPlaybackOrCapture(const KConfigGroup& config, bool capture) { Volume& volume = capture ? captureVolume() : playbackVolume(); for ( Volume::ChannelID chid=Volume::CHIDMIN; chid<= Volume::CHIDMAX; ) { QString volstr = getVolString(chid,capture); if ( config.hasKey(volstr) ) { volume.setVolume(chid, config.readEntry(volstr, 0)); } // if saved channel exists chid = (Volume::ChannelID)( 1 + (int)chid); // ugly } // for all channels } /** * called on "kmixctrl --save" and from the GUI's (currently only on exit) */ bool MixDevice::write( KConfig *config, const QString& grp ) { if (_mixer->isDynamic() || isArtificial()) { // qCDebug(KMIX_LOG) << "MixDevice::write(): This MixDevice does not permit volume saving (i.e. because it is handled lower down in the audio stack). Ignoring."; return false; } - QString devgrp = QString("%1.Dev%2").arg(grp).arg(_id); + QString devgrp = QString("%1.Dev%2").arg(grp, _id); KConfigGroup cg = config->group(devgrp); // qCDebug(KMIX_LOG) << "MixDevice::write() of group devgrp=" << devgrp; writePlaybackOrCapture(cg, false); writePlaybackOrCapture(cg, true ); cg.writeEntry("is_muted" , isMuted() ); cg.writeEntry("is_recsrc", isRecSource() ); cg.writeEntry("name", _name); if ( isEnum() ) { cg.writeEntry("enum_id", enumId() ); } return true; } void MixDevice::writePlaybackOrCapture(KConfigGroup& config, bool capture) { Volume& volume = capture ? captureVolume() : playbackVolume(); foreach (VolumeChannel vc, volume.getVolumes() ) { config.writeEntry(getVolString(vc.chid,capture) , (int)vc.volume); } // for all channels } QString MixDevice::getVolString(Volume::ChannelID chid, bool capture) { QString volstr = Volume::channelNameForPersistence(chid); if ( capture ) volstr += "Capture"; return volstr; } /** * Returns the playback volume level in percent. If the volume is muted, 0 is returned. * If the given MixDevice contains no playback volume, the capture volume isd used * instead, and 0 is returned if capturing is disabled for the given MixDevice. * * @returns The volume level in percent */ int MixDevice::getUserfriendlyVolumeLevel() { MixDevice* md = this; bool usePlayback = md->playbackVolume().hasVolume(); Volume& vol = usePlayback ? md->playbackVolume() : md->captureVolume(); bool isActive = usePlayback ? !md->isMuted() : md->isRecSource(); int val = isActive ? vol.getAvgVolumePercent(Volume::MALL) : 0; return val; } diff --git a/core/mixer.cpp b/core/mixer.cpp index 034263c6..b6bdb402 100644 --- a/core/mixer.cpp +++ b/core/mixer.cpp @@ -1,736 +1,735 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken - esken@kde.org * 2002 Helio Chissini de Castro - helio@conectiva.com.br * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "core/mixer.h" #include #include #include "backends/mixer_backend.h" #include "backends/kmix-backends.cpp" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/volume.h" /** * Some general design hints. Hierarchy is Mixer->MixDevice->Volume */ QList Mixer::s_mixers; MasterControl Mixer::_globalMasterCurrent; MasterControl Mixer::_globalMasterPreferred; bool Mixer::m_beepOnVolumeChange = false; int Mixer::numDrivers() { MixerFactory *factory = g_mixerFactories; int num = 0; while( factory->getMixer!=0 ) { num++; factory++; } return num; } /* * Returns a reference of the current mixer list. */ QList& Mixer::mixers() { return s_mixers; } /** * Returns whether there is at least one dynamic mixer active. * @returns true, if at least one dynamic mixer is active */ bool Mixer::dynamicBackendsPresent() { foreach ( Mixer* mixer, Mixer::mixers() ) { if ( mixer->isDynamic() ) return true; } return false; } bool Mixer::pulseaudioPresent() { foreach ( Mixer* mixer, Mixer::mixers() ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } Mixer::Mixer( QString& ref_driverName, int device ) : m_balance(0), _mixerBackend(0L), m_dynamic(false) { _mixerBackend = 0; int driverCount = numDrivers(); for (int driver=0; driver retrieve Mixer factory for that driver getMixerFunc *f = g_mixerFactories[driver].getMixer; if( f!=0 ) { _mixerBackend = f( this, device ); readSetFromHWforceUpdate(); // enforce an initial update on first readSetFromHW() } break; } } } Mixer::~Mixer() { // Close the mixer. This might also free memory, depending on the called backend method close(); delete _mixerBackend; } /* * Find a Mixer. If there is no mixer with the given id, 0 is returned */ Mixer* Mixer::findMixer( const QString& mixer_id) { Mixer *mixer = 0; int mixerCount = Mixer::mixers().count(); for ( int i=0; iid() == mixer_id ) { mixer = (Mixer::mixers())[i]; break; } } return mixer; } /** * Set the final ID of this Mixer. *
Warning: This method is VERY fragile, because it is requires information that we have very late, * especially the _cardInstance. We only know the _cardInstance, when we know the ID of the _mixerBackend->getId(). * OTOH, the Mixer backend needs the _cardInstance during construction of its MixDevice instances. * * This means, we need the _cardInstance during construction of the Mixer, but we only know it after its constructed. * Actually its a design error. The _cardInstance MUST be set and managed by the backend. * * The current solution works but is very hacky - cardInstance is a parameter of openIfValid(). * */ void Mixer::recreateId() { /* As we use "::" and ":" as separators, the parts %1,%2 and %3 may not * contain it. * %1, the driver name is from the KMix backends, it does not contain colons. * %2, the mixer name, is typically coming from an OS driver. It could contain colons. * %3, the mixer number, is a number: it does not contain colons. */ QString mixerName = _mixerBackend->getId(); mixerName.replace(':','_'); QString primaryKeyOfMixer = QString("%1::%2:%3") - .arg(getDriverName()) - .arg(mixerName) + .arg(getDriverName(), mixerName) .arg(getCardInstance()); // The following 3 replaces are for not messing up the config file primaryKeyOfMixer.replace(']','_'); primaryKeyOfMixer.replace('[','_'); // not strictly necessary, but lets play safe primaryKeyOfMixer.replace(' ','_'); primaryKeyOfMixer.replace('=','_'); _id = primaryKeyOfMixer; // qCDebug(KMIX_LOG) << "Early _id=" << _id; } const QString Mixer::dbusPath() { // _id needs to be fixed from the very beginning, as the MixDevice construction uses MixDevice::dbusPath(). // So once the first MixDevice is created, this must return the correct value if (_id.isEmpty()) { if (! _mixerBackend->_cardRegistered) { // Bug 308014: By checking _cardRegistered, we can be sure that everything is fine, including the fact that // the cardId (aka "card instance") is set. If _cardRegistered would be false, we will create potentially // wrong/duplicated DBUS Paths here. qCWarning(KMIX_LOG) << "Mixer id was empty when creating DBUS path. Emergency code created the id=" <<_id; } // Bug 308014: Actually this a shortcut (you could also call it a hack). It would likely better if registerCard() // would create the Id, but it requires cooperation from ALL backends. Also Mixer->getId() would need to // proxy that to the backend. // So for now we lazily create the MixerId here, while creating the first MixDevice for that card. recreateId(); } // mixerName may contain arbitrary characters, so replace all that are not allowed to be be part of a DBUS path QString cardPath = _id; cardPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); cardPath.replace(QLatin1String("//"), QLatin1String("/")); return QString("/Mixers/" + cardPath); } void Mixer::volumeSave( KConfig *config ) { // qCDebug(KMIX_LOG) << "Mixer::volumeSave()"; _mixerBackend->readSetFromHW(); QString grp("Mixer"); grp.append(id()); _mixerBackend->m_mixDevices.write( config, grp ); // This might not be the standard application config object // => Better be safe and call sync(). config->sync(); } void Mixer::volumeLoad( KConfig *config ) { QString grp("Mixer"); grp.append(id()); if ( ! config->hasGroup(grp) ) { // no such group. Volumes (of this mixer) were never saved beforehand. // Thus don't restore anything (also see Bug #69320 for understanding the real reason) return; // make sure to bail out immediately } // else restore the volumes if ( ! _mixerBackend->m_mixDevices.read( config, grp ) ) { // Some mixer backends don't support reading the volume into config // files, so bail out early if that's the case. return; } // set new settings for(int i=0; i<_mixerBackend->m_mixDevices.count() ; i++ ) { shared_ptr md = _mixerBackend->m_mixDevices[i]; if ( md.get() == 0 ) continue; _mixerBackend->writeVolumeToHW( md->id(), md ); if ( md->isEnum() ) _mixerBackend->setEnumIdHW( md->id(), md->enumId() ); } } /** * Opens the mixer. * Also, starts the polling timer, for polling the Volumes from the Mixer. * * @param cardId The cardId Usually this will be 1, but if there is * more than one card with the same name install, then you need * to use 2, 3, ... * * @return true, if Mixer could be opened. */ bool Mixer::openIfValid() { if (_mixerBackend == 0 ) { // if we did not instantiate a suitable Backend, then Mixer is invalid return false; } bool ok = _mixerBackend->openIfValid(); if ( ok ) { recreateId(); shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); if ( recommendedMaster.get() != 0 ) { QString recommendedMasterStr = recommendedMaster->id(); setLocalMasterMD( recommendedMasterStr ); qCDebug(KMIX_LOG) << "Mixer::open() detected master: " << recommendedMaster->id(); } else { if ( !m_dynamic ) qCCritical(KMIX_LOG) << "Mixer::open() no master detected."; else qCDebug(KMIX_LOG) << "Mixer::open() no master detected."; QString noMaster = "---no-master-detected---"; setLocalMasterMD(noMaster); // no master } // cesken: The following connect() looks mighty strange. I removed it on 2013-12-18 //connect( _mixerBackend, SIGNAL(controlChanged()), SIGNAL(controlChanged()) ); new DBusMixerWrapper(this, dbusPath()); } return ok; } /** * Closes the mixer. */ void Mixer::close() { if ( _mixerBackend != 0) _mixerBackend->closeCommon(); } /* ------- WRAPPER METHODS. START ------------------------------ */ unsigned int Mixer::size() const { return _mixerBackend->m_mixDevices.count(); } shared_ptr Mixer::operator[](int num) { shared_ptr md = _mixerBackend->m_mixDevices.at( num ); return md; } MixSet& Mixer::getMixSet() { return _mixerBackend->m_mixDevices; } /** * Returns the driver name, that handles this Mixer. */ QString Mixer::getDriverName() { QString driverName = _mixerBackend->getDriverName(); // qCDebug(KMIX_LOG) << "Mixer::getDriverName() = " << driverName << "\n"; return driverName; } bool Mixer::isOpen() const { if ( _mixerBackend == 0 ) return false; else return _mixerBackend->isOpen(); } void Mixer::readSetFromHWforceUpdate() const { _mixerBackend->readSetFromHWforceUpdate(); } /// Returns translated WhatsThis messages for a control.Translates from QString Mixer::translateKernelToWhatsthis(const QString &kernelName) { return _mixerBackend->translateKernelToWhatsthis(kernelName); } /* ------- WRAPPER METHODS. END -------------------------------- */ int Mixer::balance() const { return m_balance; } void Mixer::setBalance(int balance) { if( balance == m_balance ) { // balance unchanged => return return; } m_balance = balance; shared_ptr master = getLocalMasterMD(); if ( master.get() == 0 ) { // no master device available => return return; } Volume& volP = master->playbackVolume(); setBalanceInternal(volP); Volume& volC = master->captureVolume(); setBalanceInternal(volC); _mixerBackend->writeVolumeToHW( master->id(), master ); emit newBalance( volP ); } void Mixer::setBalanceInternal(Volume& vol) { //_mixerBackend->readVolumeFromHW( master->id(), master ); int left = vol.getVolume(Volume::LEFT); int right = vol.getVolume( Volume::RIGHT ); int refvol = left > right ? left : right; if( m_balance < 0 ) // balance left { vol.setVolume( Volume::LEFT, refvol); vol.setVolume( Volume::RIGHT, (m_balance * refvol) / 100 + refvol ); } else { vol.setVolume( Volume::LEFT, -(m_balance * refvol) / 100 + refvol ); vol.setVolume( Volume::RIGHT, refvol); } } /** * Returns a name suitable for a human user to read (on a label, ...) */ QString Mixer::readableName() { return readableName(false); } /** * Returns a name suitable for a human user to read, possibly with quoted ampersand. The latter is required by * some GUI elements like QRadioButton or when used as a Tab label, as '&' introduces an accelerator there. * * @param ampersandQuoted * @return */ QString Mixer::readableName(bool ampersandQuoted) { QString finalName = _mixerBackend->getName(); if (ampersandQuoted) finalName.replace('&', "&&"); if ( getCardInstance() > 1) finalName = finalName.append(" %1").arg(getCardInstance()); // qCDebug(KMIX_LOG) << "name=" << _mixerBackend->getName() << "instance=" << getCardInstance() << ", finalName" << finalName; return finalName; } QString Mixer::getBaseName() { return _mixerBackend->getName(); } /** * Queries the Driver Factory for a driver. * @par driver Index number. 0 <= driver < numDrivers() */ QString Mixer::driverName( int driver ) { getDriverNameFunc *f = g_mixerFactories[driver].getDriverName; if( f!=0 ) return f(); else return "unknown"; } /* obsoleted by setInstance() void Mixer::setID(QString& ref_id) { _id = ref_id; } */ QString& Mixer::id() { return _id; } QString& Mixer::udi(){ return _mixerBackend->udi(); } /** * Set the global master, which is shown in the dock area and which is accessible via the * DBUS masterVolume() method. * * The parameters are taken over as-is, this means without checking for validity. * This allows the User to define a master card that is not always available * (e.g. it is an USB hotplugging device). Also you can set the master at any time you * like, e.g. after reading the KMix configuration file and before actually constructing * the Mixer instances (hint: this method is static!). * * @param ref_card The card id * @param ref_control The control id. The corresponding control must be present in the card. * @param preferred Whether this is the preferred master (auto-selected on coldplug and hotplug). */ void Mixer::setGlobalMaster(QString ref_card, QString ref_control, bool preferred) { qCDebug(KMIX_LOG) << "ref_card=" << ref_card << ", ref_control=" << ref_control << ", preferred=" << preferred; _globalMasterCurrent.set(ref_card, ref_control); if ( preferred ) _globalMasterPreferred.set(ref_card, ref_control); qCDebug(KMIX_LOG) << "Mixer::setGlobalMaster() card=" <id(); return mixer; } /** * Return the preferred global master. * If there is no preferred global master, returns the current master instead. */ MasterControl& Mixer::getGlobalMasterPreferred(bool fallbackAllowed) { static MasterControl result; if ( !fallbackAllowed || _globalMasterPreferred.isValid() ) { // qCDebug(KMIX_LOG) << "Returning preferred master"; return _globalMasterPreferred; } Mixer* mm = Mixer::getGlobalMasterMixerNoFalback(); if (mm) { result.set(_globalMasterPreferred.getCard(), mm->getRecommendedDeviceId()); if (!result.getControl().isEmpty()) // qCDebug(KMIX_LOG) << "Returning extended preferred master"; return result; } qCDebug(KMIX_LOG) << "Returning current master"; return _globalMasterCurrent; } shared_ptr Mixer::getGlobalMasterMD() { return getGlobalMasterMD(true); } shared_ptr Mixer::getGlobalMasterMD(bool fallbackAllowed) { shared_ptr mdRet; shared_ptr firstDevice; Mixer *mixer = fallbackAllowed ? Mixer::getGlobalMasterMixer() : Mixer::getGlobalMasterMixerNoFalback(); if ( mixer == 0 ) return mdRet; if (_globalMasterCurrent.getControl().isEmpty()) { // Default (recommended) control return mixer->_mixerBackend->recommendedMaster(); } foreach (shared_ptr md, mixer->_mixerBackend->m_mixDevices ) { if ( md.get() == 0 ) continue; // invalid firstDevice=md; if ( md->id() == _globalMasterCurrent.getControl() ) { mdRet = md; break; // found } } if ( mdRet.get() == 0 ) { //For some sound cards when using pulseaudio the mixer id is not proper hence returning the first device as master channel device //This solves the bug id:290177 and problems stated in review #105422 qCDebug(KMIX_LOG) << "Mixer::masterCardDevice() returns 0 (no globalMaster), returning the first device"; mdRet=firstDevice; } return mdRet; } QString Mixer::getRecommendedDeviceId() { if ( _mixerBackend != 0 ) { shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); if ( recommendedMaster.get() != 0 ) return recommendedMaster->id(); } return QString(); } shared_ptr Mixer::getLocalMasterMD() { if (_mixerBackend && _masterDevicePK.isEmpty()) return _mixerBackend->recommendedMaster(); return find( _masterDevicePK ); } void Mixer::setLocalMasterMD(QString &devPK) { _masterDevicePK = devPK; } shared_ptr Mixer::find(const QString& mixdeviceID) { shared_ptr mdRet; foreach (shared_ptr md, _mixerBackend->m_mixDevices ) { if ( md.get() == 0 ) continue; // invalid if ( md->id() == mixdeviceID ) { mdRet = md; break; // found } } return mdRet; } shared_ptr Mixer::getMixdeviceById( const QString& mixdeviceID ) { qCDebug(KMIX_LOG) << "id=" << mixdeviceID << "md=" << _mixerBackend->m_mixDevices.get(mixdeviceID).get()->id(); return _mixerBackend->m_mixDevices.get(mixdeviceID); // shared_ptr md; // int num = _mixerBackend->id2num(mixdeviceID); // if ( num!=-1 && num < (int)size() ) // { // md = (*this)[num]; // } // return md; } /** Call this if you have a *reference* to a Volume object and have modified that locally. Pass the MixDevice associated to that Volume to this method for writing back the changed value to the mixer. Hint: Why do we do it this way? - It is fast (no copying of Volume objects required) - It is easy to understand ( read - modify - commit ) */ void Mixer::commitVolumeChange(shared_ptr md) { _mixerBackend->writeVolumeToHW(md->id(), md); if (md->isEnum()) { _mixerBackend->setEnumIdHW(md->id(), md->enumId()); } if (md->captureVolume().hasSwitch()) { // Make sure to re-read the hardware, because setting capture might have failed. // This is due to exclusive capture groups. // If we wouldn't do this, KMix might show a Capture Switch disabled, but // in reality the capture switch is still on. // // We also cannot rely on a notification from the driver (SocketNotifier), because // nothing has changed, and so there s nothing to notify. _mixerBackend->readSetFromHWforceUpdate(); if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "committing a control with capture volume, that might announce: " << md->id(); _mixerBackend->readSetFromHW(); } if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "committing announces the change of: " << md->id(); // We announce the change we did, so all other parts of KMix can pick up the change ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::Volume, QString("Mixer.commitVolumeChange()")); } // @dbus, used also in kmix app void Mixer::increaseVolume( const QString& mixdeviceID ) { increaseOrDecreaseVolume(mixdeviceID, false); } // @dbus void Mixer::decreaseVolume( const QString& mixdeviceID ) { increaseOrDecreaseVolume(mixdeviceID, true); } /** * Increase or decrease all playback and capture channels of the given control. * This method is very similar to MDWSlider::increaseOrDecreaseVolume(), but it will * NOT auto-unmute. * * @param mixdeviceID The control name * @param decrease true for decrease. false for increase */ void Mixer::increaseOrDecreaseVolume( const QString& mixdeviceID, bool decrease ) { shared_ptr md= getMixdeviceById( mixdeviceID ); if (md.get() != 0) { Volume& volP=md->playbackVolume(); if ( volP.hasVolume() ) { volP.changeAllVolumes(volP.volumeStep(decrease)); } Volume& volC=md->captureVolume(); if ( volC.hasVolume() ) { volC.changeAllVolumes(volC.volumeStep(decrease)); } _mixerBackend->writeVolumeToHW(mixdeviceID, md); } ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::Volume, QString("Mixer.increaseOrDecreaseVolume()")); /************************************************************ It is important, not to implement this method like this: int vol=volume(mixdeviceID); setVolume(mixdeviceID, vol-5); It creates too big rounding errors. If you don't believe me, then do a decreaseVolume() and increaseVolume() with "vol.maxVolume() == 31". ***********************************************************/ } void Mixer::setDynamic ( bool dynamic ) { m_dynamic = dynamic; } bool Mixer::isDynamic() { return m_dynamic; } bool Mixer::moveStream( const QString id, const QString& destId ) { // We should really check that id is within our md's.... bool ret = _mixerBackend->moveStream( id, destId ); ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Mixer.moveStream()")); return ret; } diff --git a/gui/mdwslider.cpp b/gui/mdwslider.cpp index ce05fabf..44a89864 100644 --- a/gui/mdwslider.cpp +++ b/gui/mdwslider.cpp @@ -1,1307 +1,1307 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2007 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gui/mdwslider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/ControlManager.h" #include "core/mixer.h" #include "gui/guiprofile.h" #include "gui/volumeslider.h" #include "gui/viewbase.h" #include "gui/ksmallslider.h" #include "gui/verticaltext.h" #include "gui/mdwmoveaction.h" bool MDWSlider::debugMe = false; /** * MixDeviceWidget that represents a single mix device, including PopUp, muteLED, ... * * Used in KMix main window and DockWidget and PanelApplet. * It can be configured to include or exclude the captureLED and the muteLED. * The direction (horizontal, vertical) can be configured and whether it should * be "small" (uses KSmallSlider instead of a normal slider widget). * * Due to the many options, this is the most complicated MixDeviceWidget subclass. */ MDWSlider::MDWSlider(shared_ptr md, bool showMuteLED, bool showCaptureLED , bool includeMixerName, bool small, Qt::Orientation orientation, QWidget* parent , ViewBase* view , ProfControl* par_ctl ) : 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 ), 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) { createActions(); createWidgets( showMuteLED, showCaptureLED, includeMixerName ); createShortcutActions(); installEventFilter( this ); // filter for popup update(); } MDWSlider::~MDWSlider() { foreach( QAbstractSlider* slider, m_slidersPlayback) { delete slider; } foreach( QAbstractSlider* slider, m_slidersCapture) { delete slider; } } void MDWSlider::createActions() { // create actions (on _mdwActions, see MixDeviceWidget) KToggleAction *taction = _mdwActions->add( "stereo" ); taction->setText( i18n("&Split Channels") ); connect( taction, SIGNAL(triggered(bool)), SLOT(toggleStereoLinked()) ); // QAction *action; // if ( ! m_mixdevice->mixer()->isDynamic() ) { // action = _mdwActions->add( "hide" ); // action->setText( i18n("&Hide") ); // connect( action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool)) ); // } if( m_mixdevice->hasMuteSwitch() ) { taction = _mdwActions->add( "mute" ); taction->setText( i18n("&Muted") ); connect( taction, SIGNAL(toggled(bool)), SLOT(toggleMuted()) ); } if( m_mixdevice->captureVolume().hasSwitch() ) { taction = _mdwActions->add( "recsrc" ); taction->setText( i18n("Set &Record Source") ); connect( taction, SIGNAL(toggled(bool)), SLOT(toggleRecsrc()) ); } if( m_mixdevice->isMovable() ) { m_moveMenu = new QMenu( i18n("Mo&ve"), this); connect( m_moveMenu, SIGNAL(aboutToShow()), SLOT(showMoveMenu()) ); } QAction* qaction = _mdwActions->addAction( "keys" ); qaction->setText( i18n("C&onfigure Shortcuts...") ); connect( qaction, SIGNAL(triggered(bool)), SLOT(defineKeys()) ); } void MDWSlider::addGlobalShortcut(QAction* qaction, const QString& label, bool dynamicControl) { QString finalLabel(label); finalLabel += " - " + mixDevice()->readableName() + ", " + mixDevice()->mixer()->readableName(); qaction->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 KGlobalAccel::setGlobalShortcut(qaction, QKeySequence()); } } 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. I work around this by using a text with setText() that is unique, but still readable to the user. */ - QString actionSuffix = QString(" - %1, %2").arg( mixDevice()->readableName() ).arg( mixDevice()->mixer()->readableName() ); + QString actionSuffix = QString(" - %1, %2").arg( mixDevice()->readableName(), mixDevice()->mixer()->readableName() ); QAction *bi, *bd, *bm; // -1- INCREASE VOLUME SHORTCUT ----------------------------------------- bi = _mdwPopupActions->addAction( QString("Increase volume %1").arg( actionSuffix ) ); QString increaseVolumeName = i18n( "Increase Volume" ); addGlobalShortcut(bi, increaseVolumeName, dynamicControl); if ( ! dynamicControl ) connect( bi, SIGNAL(triggered(bool)), SLOT(increaseVolume()) ); // -2- DECREASE VOLUME SHORTCUT ----------------------------------------- bd = _mdwPopupActions->addAction( QString("Decrease volume %1").arg( actionSuffix ) ); QString decreaseVolumeName = i18n( "Decrease Volume" ); addGlobalShortcut(bd, decreaseVolumeName, dynamicControl); if ( ! dynamicControl ) connect(bd, SIGNAL(triggered(bool)), SLOT(decreaseVolume())); // -3- MUTE VOLUME SHORTCUT ----------------------------------------- bm = _mdwPopupActions->addAction( QString("Toggle mute %1").arg( actionSuffix ) ); QString muteVolumeName = i18n( "Toggle Mute" ); addGlobalShortcut(bm, muteVolumeName, dynamicControl); if ( ! dynamicControl ) connect( bm, SIGNAL(triggered(bool)), SLOT(toggleMuted()) ); } QSizePolicy MDWSlider::sizePolicy() const { if ( _orientation == Qt::Vertical ) { return QSizePolicy( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding ); } else { return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); // return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); } } QSize MDWSlider::sizeHint() const { return QSize( 90, QWidget::sizeHint().height()); } /** * This method is a helper for users of this class who would like * to show multiple MDWSlider, and align the sliders. * It returns the "height" (if vertical) of this widgets label. * Warning: Line wraps are computed for a fixed size (100), this may be inaccurate in case, * the widgets have different sizes. */ int MDWSlider::labelExtentHint() const { if ( _orientation == Qt::Vertical && m_label ) { return m_label->heightForWidth(m_label->minimumWidth()); } return 0; } /** * If a label from another widget has more lines than this widget, then a spacer is added under the label */ void MDWSlider::setLabelExtent(int extent) { if ( _orientation == Qt::Vertical ) { int extentHint = labelExtentHint(); int spacerHeight = (extent > extentHint) ? extent - extentHint : 0; labelSpacer->setFixedHeight(spacerHeight); } } /** * Alignment helper */ bool MDWSlider::hasMuteButton() const { return m_qcb!=0; } /** * If this widget does not have a mute button, but another widget has, we add a spacer here with the * size of a QToolButton (don't know how to make a better estimate) */ void MDWSlider::setMuteButtonSpace(bool value) { if (hasMuteButton() || !value) { muteButtonSpacer->setFixedSize(0,0); muteButtonSpacer->setVisible(false); } else { QToolButton b; muteButtonSpacer->setFixedSize( b.sizeHint() ); } } /** * See "hasMuteButton" */ bool MDWSlider::hasCaptureLED() const { return m_captureCheckbox!=0; } /** * See "setMuteButtonSpace" */ void MDWSlider::setCaptureLEDSpace(bool showCaptureLED) { if ( !showCaptureLED || hasCaptureLED() ) { captureSpacer->setFixedSize(0,0); captureSpacer->setVisible(false); } else captureSpacer->setFixedSize(QCheckBox().sizeHint()); } void MDWSlider::guiAddSlidersAndMediacontrols(bool playSliders, bool capSliders, bool mediaControls, QBoxLayout* layout, const QString& tooltipText, const QString& captureTooltipText) { if (playSliders) addSliders(layout, 'p', m_mixdevice->playbackVolume(), m_slidersPlayback, tooltipText); if (capSliders) addSliders(layout, 'c', m_mixdevice->captureVolume(), m_slidersCapture, captureTooltipText); if (mediaControls) addMediaControls(layout); } void MDWSlider::guiAddCaptureCheckbox(bool wantsCaptureLED, const Qt::Alignment& alignmentForCapture, QBoxLayout* layoutForCapture, const QString& captureTooltipText) { 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))); m_captureCheckbox->setToolTip(captureTooltipText); } } void MDWSlider::guiAddMuteButton(bool wantsMuteButton, Qt::Alignment alignment, QBoxLayout* layoutForMuteButton, const QString& muteTooltipText) { 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"))); setIcon("audio-volume-muted", m_qcb); //setIcon("audio-volume-mutd", m_qcb); layoutForMuteButton->addWidget(m_qcb, 0, alignment); m_qcb->installEventFilter(this); connect(m_qcb, SIGNAL(clicked(bool)), this, SLOT(toggleMuted())); m_qcb->setToolTip(muteTooltipText); } // 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, const QString& tooltipText) { m_iconLabelSimple = new QLabel(this); installEventFilter(m_iconLabelSimple); setIcon(m_mixdevice->iconName(), m_iconLabelSimple); m_iconLabelSimple->setToolTip(tooltipText); layout->addWidget(m_iconLabelSimple, 0, alignment); } /** * Creates all widgets : Icon, Label, Mute-Button, Slider(s) and Capture-Button. */ void MDWSlider::createWidgets( bool showMuteButton, bool showCaptureLED, bool includeMixerName ) { bool includePlayback = _pctl->useSubcontrolPlayback(); bool includeCapture = _pctl->useSubcontrolCapture(); bool wantsPlaybackSliders = includePlayback && ( m_mixdevice->playbackVolume().count() > 0 ); bool wantsCaptureSliders = includeCapture && ( m_mixdevice->captureVolume().count() > 0 ); bool wantsCaptureLED = showCaptureLED && includeCapture; bool wantsMuteButton = showMuteButton && includePlayback; bool hasVolumeSliders = wantsPlaybackSliders || wantsCaptureSliders; // bool bothCaptureANDPlaybackExist = wantsPlaybackSliders && wantsCaptureSliders; MediaController* mediaController = m_mixdevice->getMediaController(); bool wantsMediaControls = mediaController->hasControls(); QString tooltipText = m_mixdevice->readableName(); QString captureTooltipText( i18n( "Capture/Uncapture %1", m_mixdevice->readableName() ) ); QString muteTooltipText( i18n( "Mute/Unmute %1", m_mixdevice->readableName() ) ); if (includeMixerName) { - tooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName() ).arg( tooltipText ); - captureTooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName() ).arg( captureTooltipText ); - muteTooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName() ).arg( muteTooltipText ); + tooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName(), tooltipText ); + captureTooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName(), captureTooltipText ); + muteTooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName(), muteTooltipText ); } // case of vertical sliders: if ( _orientation == Qt::Vertical ) { QVBoxLayout *controlLayout = new QVBoxLayout(this); controlLayout->setAlignment(Qt::AlignHCenter|Qt::AlignTop); setLayout(controlLayout); controlLayout->setContentsMargins(0,0,0,0); guiAddControlIcon(Qt::AlignHCenter|Qt::AlignTop, controlLayout, tooltipText); Qt::Alignment centerAlign = Qt::AlignHCenter | Qt::AlignBottom; //the device label m_label = new QLabel( m_mixdevice->readableName(), this); m_label->setWordWrap(true); int max = 80; QStringList words = m_mixdevice->readableName().split(QChar(' ')); foreach (QString name, words) max = qMax(max,QLabel(name).sizeHint().width()); // if (words.size()>1 && m_label) // m_label->setMinimumWidth(80); // if (m_label->sizeHint().width()>max && m_label->sizeHint().width()>80) // m_label->setMinimumWidth(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, centerAlign ); //spacer with height to match height difference to other slider widgets labelSpacer = new QWidget(this); controlLayout->addWidget( labelSpacer ); labelSpacer->installEventFilter(this); // sliders QBoxLayout *volLayout = new QHBoxLayout( ); volLayout->setAlignment(centerAlign); controlLayout->addItem( volLayout ); guiAddSlidersAndMediacontrols(wantsPlaybackSliders, wantsCaptureSliders, wantsMediaControls, volLayout, tooltipText, captureTooltipText); if ( !hasVolumeSliders ) controlLayout->addStretch(1); // Not sure why we have this for "vertical sliders" case guiAddCaptureCheckbox(wantsCaptureLED, centerAlign, controlLayout, captureTooltipText); // spacer which is shown when no capture button present captureSpacer = new QWidget(this); controlLayout->addWidget( captureSpacer ); captureSpacer->installEventFilter(this); //mute button guiAddMuteButton(wantsMuteButton, centerAlign, controlLayout, muteTooltipText); } else { /* * 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(); rows->addItem( row1 ); m_label = new QLabel(m_mixdevice->readableName(), this); m_label->installEventFilter( this ); row1->addWidget( m_label ); row1->setAlignment(m_label, Qt::AlignVCenter); row1->addStretch(); row1->addWidget(captureSpacer); guiAddCaptureCheckbox(wantsCaptureLED, Qt::AlignRight, row1, captureTooltipText); 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); rows->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); rows->addItem( row2 ); guiAddControlIcon(Qt::AlignVCenter, row2, tooltipText); // --- SLIDERS --------------------------- QBoxLayout *volLayout = new QVBoxLayout( ); volLayout->setAlignment(Qt::AlignVCenter|Qt::AlignRight); row2->addItem( volLayout ); guiAddSlidersAndMediacontrols(wantsPlaybackSliders, wantsCaptureSliders, wantsMediaControls, volLayout, tooltipText, captureTooltipText); guiAddMuteButton(wantsMuteButton, Qt::AlignRight, row2, muteTooltipText); } bool stereoLinked = !_pctl->isSplit(); setStereoLinked( stereoLinked ); layout()->activate(); // Activate it explicitly in KDE3 because of PanelApplet/kicker issues } 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, QWidget *parent) { QToolButton *lbl = new QToolButton(parent); lbl->setIconSize(QSize(IconSize(KIconLoader::Toolbar),IconSize(KIconLoader::Toolbar))); lbl->setAutoRaise(true); lbl->setCheckable(false); setIcon(iconName, lbl); layout->addWidget(lbl); 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(); } void MDWSlider::mediaNext(bool) { mixDevice()->mediaNext(); } void MDWSlider::mediaPlay(bool) { mixDevice()->mediaPlay(); } void MDWSlider::addSliders( QBoxLayout *volLayout, char type, Volume& vol, QList& ref_sliders, QString tooltipText) { const int minSliderSize = fontMetrics().height() * 10; long minvol = vol.minVolume(); long maxvol = vol.maxVolume(); QMap vols = vol.getVolumes(); foreach (VolumeChannel vc, vols ) { //qCDebug(KMIX_LOG) << "Add label to " << vc.chid << ": " << Volume::ChannelNameReadable[vc.chid]; QWidget *subcontrolLabel; QString subcontrolTranslation; if ( type == 'c' ) subcontrolTranslation += i18n("Capture") + ' '; subcontrolTranslation += Volume::channelNameReadable(vc.chid); subcontrolLabel = createLabel(this, subcontrolTranslation, volLayout, true); QAbstractSlider* slider; if ( m_small ) { slider = new KSmallSlider( minvol, maxvol, (maxvol-minvol+1) / Volume::VOLUME_PAGESTEP_DIVISOR, vol.getVolume( vc.chid ), _orientation, this ); } // small else { slider = new VolumeSlider( _orientation, this ); slider->setMinimum(minvol); slider->setMaximum(maxvol); slider->setPageStep(maxvol / Volume::VOLUME_PAGESTEP_DIVISOR); slider->setValue( vol.getVolume( vc.chid ) ); volumeValues.push_back( vol.getVolume( vc.chid ) ); extraData(slider).setSubcontrolLabel(subcontrolLabel); if ( _orientation == Qt::Vertical ) { slider->setMinimumHeight( minSliderSize ); } else { slider->setMinimumWidth( minSliderSize ); } if ( ! _pctl->getBackgroundColor().isEmpty() ) { slider->setStyleSheet("QSlider { background-color: " + _pctl->getBackgroundColor() + " }"); } } // not small extraData(slider).setChid(vc.chid); slider->installEventFilter( this ); if ( type == 'p' ) { slider->setToolTip( tooltipText ); } else { QString captureTip( i18n( "%1 (capture)", tooltipText ) ); slider->setToolTip( captureTip ); } volLayout->addWidget( slider ); // add to layout ref_sliders.append ( slider ); // add to list //ref_slidersChids.append(vc.chid); connect( slider, SIGNAL(valueChanged(int)), SLOT(volumeChange(int)) ); connect( slider, SIGNAL(sliderPressed()), SLOT(sliderPressed()) ); connect( slider, SIGNAL(sliderReleased()), SLOT(sliderReleased()) ); } // 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; KSmallSlider* sl2 = qobject_cast(slider); return sl2->extraData; } void MDWSlider::sliderPressed() { m_sliderInWork = true; } void MDWSlider::sliderReleased() { m_sliderInWork = false; } QWidget* MDWSlider::createLabel(QWidget* parent, QString& label, QBoxLayout *layout, bool small) { QFont qf; qf.setPointSize(8); QWidget* labelWidget; if (_orientation == Qt::Horizontal) { labelWidget = new QLabel(label, parent); if ( small ) ((QLabel*)labelWidget)->setFont(qf); } else { labelWidget = new VerticalText(parent, label); if ( small ) ((VerticalText*)labelWidget)->setFont(qf); } labelWidget->installEventFilter( parent ); layout->addWidget(labelWidget); return labelWidget; } QPixmap MDWSlider::loadIcon( QString filename, KIconLoader::Group group ) { return KIconLoader::global()->loadIcon( filename, group, IconSize(KIconLoader::Toolbar) ); } //void MDWSlider::setIcon( QString filename, QLabel** label ) //{ // if( (*label) == 0 ) // { // *label = new QLabel(this); // installEventFilter( *label ); // } // setIcon(filename, *label); //} /** * Loads the icon with the given iconName in size KIconLoader::Toolbar, and applies it to the label widget. * The label must be either a QLabel or a QToolButton. * * @param label A QToolButton or a QToolButton that will hold the icon. */ void MDWSlider::setIcon( QString filename, QWidget* label ) { QPixmap miniDevPM = loadIcon( filename, KIconLoader::Small ); // QPixmap miniDevPM = loadIcon( filename, KIconLoader::Toolbar); if ( !miniDevPM.isNull() ) { if ( m_small ) { // scale icon QMatrix t; t = t.scale( 10.0/miniDevPM.width(), 10.0/miniDevPM.height() ); miniDevPM = miniDevPM.transformed( t ); label->resize( 10, 10 ); } // small size else { label->setMinimumSize(IconSize(KIconLoader::Toolbar),IconSize(KIconLoader::Toolbar)); } label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QLabel* lbl = qobject_cast(label); if ( lbl != 0 ) { lbl->setPixmap( miniDevPM ); lbl->setAlignment(Qt::AlignHCenter | Qt::AlignCenter); } // QLabel else { QToolButton* tbt = qobject_cast(label); if ( tbt != 0 ) { tbt->setIcon( miniDevPM ); } // QToolButton } } else { qCCritical(KMIX_LOG) << "Pixmap missing. filename=" << filename; } } QString MDWSlider::iconName() { return m_mixdevice->iconName(); } void MDWSlider::toggleStereoLinked() { setStereoLinked( !isStereoLinked() ); } void MDWSlider::setStereoLinked(bool value) { m_linked = value; int overallSlidersToShow = 0; if ( ! m_slidersPlayback.isEmpty() ) overallSlidersToShow += ( m_linked ? 1 : m_slidersPlayback.count() ); if ( ! m_slidersCapture.isEmpty() ) overallSlidersToShow += ( m_linked ? 1 : m_slidersCapture.count() ); bool showSubcontrolLabels = (overallSlidersToShow >= 2); setStereoLinkedInternal(m_slidersPlayback, showSubcontrolLabels); setStereoLinkedInternal(m_slidersCapture , showSubcontrolLabels); update(); // Call update(), so that the sliders can adjust EITHER to the individual values OR the average value. } void MDWSlider::setStereoLinkedInternal(QList& ref_sliders, bool showSubcontrolLabels) { if ( ref_sliders.isEmpty()) return; bool first = true; foreach ( QAbstractSlider* slider1, ref_sliders ) { slider1->setVisible(!m_linked || first); // One slider (the 1st) is always shown extraData(slider1).getSubcontrolLabel()->setVisible(!m_linked && showSubcontrolLabels); // (*) first = false; /* (*) cesken: I have excluded the "|| first" check because the text would not be nice: * It would be "Left" or "Capture Left", while it should be "Playback" and "Capture" in the "linked" case. * * But the only affected situation is when we have playback AND capture on the same control, where we show no label. * It would be nice to put at least a "Capture" label on the capture subcontrol instead. * To achieve this we would need to exchange the Text on the first capture subcontrol dynamically. This can * be done, but I'll leave this open for now. */ } // Redo the tickmarks to last slider in the slider list. // The implementation is not obvious, so lets explain: // We ALWAYS have tickmarks on the LAST slider. Sometimes the slider is not shown, and then we just don't bother. // a) So, if the last slider has tickmarks, we can always call setTicks( true ). // b) if the last slider has NO tickmarks, there ae no tickmarks at all, and we don't need to redo the tickmarks. QSlider* slider = qobject_cast( ref_sliders.last() ); if( slider && slider->tickPosition() != QSlider::NoTicks) setTicks( true ); } void MDWSlider::setLabeled(bool value) { if ( m_label != 0) m_label->setVisible(value); if ( m_muteText != 0) m_muteText->setVisible(value); if ( m_captureText != 0) m_captureText->setVisible(value); layout()->activate(); } void MDWSlider::setTicks( bool value ) { if (m_slidersPlayback.count() != 0) setTicksInternal(m_slidersPlayback, value); if (m_slidersCapture.count() != 0) setTicksInternal(m_slidersCapture, value); } /** * Enables or disables tickmarks * Please note that always only the first and last slider has tickmarks. * */ void MDWSlider::setTicksInternal(QList& ref_sliders, bool ticks) { VolumeSlider* slider = qobject_cast( ref_sliders[0]); if (slider == 0 ) return; // Ticks are only in VolumeSlider, but not in KSmallslider if( ticks ) { if( isStereoLinked() ) slider->setTickPosition( QSlider::TicksRight ); else { slider->setTickPosition( QSlider::NoTicks ); slider = qobject_cast(ref_sliders.last()); slider->setTickPosition( QSlider::TicksLeft ); } } else { slider->setTickPosition( QSlider::NoTicks ); slider = qobject_cast(ref_sliders.last()); slider->setTickPosition( QSlider::NoTicks ); } } void MDWSlider::setIcons(bool value) { if ( m_iconLabelSimple != 0 ) { if ( ( !m_iconLabelSimple->isHidden() ) !=value ) { if (value) m_iconLabelSimple->show(); else m_iconLabelSimple->hide(); layout()->activate(); } } // if it has an icon } void MDWSlider::setColors( QColor high, QColor low, QColor back ) { for( int i=0; i(slider); if ( smallSlider ) smallSlider->setColors( high, low, back ); } for( int i=0; i(slider); if ( smallSlider ) smallSlider->setColors( high, low, back ); } } void MDWSlider::setMutedColors( QColor high, QColor low, QColor back ) { for( int i=0; i(slider); if ( smallSlider ) smallSlider->setGrayColors( high, low, back ); } for( int i=0; i(slider); if ( smallSlider ) smallSlider->setGrayColors( high, low, back ); } } /** This slot is called, when a user has changed the volume via the KMix Slider. */ void MDWSlider::volumeChange( int ) { // if ( mixDevice()->id() == "Headphone:0" ) // { // qCDebug(KMIX_LOG) << "headphone bug"; // } if (!m_slidersPlayback.isEmpty()) { m_waitForSoundSetComplete ++; volumeValues.push_back(m_slidersPlayback.first()->value()); volumeChangeInternal(m_mixdevice->playbackVolume(), m_slidersPlayback); } if (!m_slidersCapture.isEmpty()) { volumeChangeInternal(m_mixdevice->captureVolume(), m_slidersCapture); } bool oldViewBlockSignalState = m_view->blockSignals(true); m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); m_view->blockSignals(oldViewBlockSignalState); } void MDWSlider::volumeChangeInternal(Volume& vol, QList& ref_sliders) { if (isStereoLinked()) { QAbstractSlider* firstSlider = ref_sliders.first(); m_mixdevice->setMuted(false); vol.setAllVolumes(firstSlider->value()); } else { for (int i = 0; i < ref_sliders.count(); i++) { if (m_mixdevice->isMuted()) { // changing from muted state: unmute (the "if" above is actually superfluous) m_mixdevice->setMuted(false); } QAbstractSlider *sliderWidget = ref_sliders[i]; vol.setVolume(extraData(sliderWidget).getChid(), sliderWidget->value()); } // iterate over all sliders } } /** This slot is called, when a user has clicked the recsrc button. Also it is called by any other associated QAction like the context menu. */ void MDWSlider::toggleRecsrc() { setRecsrc( m_mixdevice->isRecSource() ); } void MDWSlider::setRecsrc(bool value ) { if ( m_mixdevice->captureVolume().hasSwitch() ) { m_mixdevice->setRecSource( value ); m_mixdevice->mixer()->commitVolumeChange( m_mixdevice ); } } /** This slot is called, when a user has clicked the mute button. Also it is called by any other associated QAction like the context menu. */ void MDWSlider::toggleMuted() { setMuted( !m_mixdevice->isMuted() ); } void MDWSlider::setMuted(bool value) { if ( m_mixdevice->hasMuteSwitch() ) { m_mixdevice->setMuted( value ); m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); } } void MDWSlider::setDisabled( bool hide ) { emit guiVisibilityChange(this, !hide); } /** * This slot is called on a Keyboard Shortcut event, except for the XF86Audio* shortcuts which are handled by the * KMixWindow class. So for 99.9% of all users, this method is never called. */ void MDWSlider::increaseVolume() { increaseOrDecreaseVolume(false, Volume::Both); } /** * This slot is called on a Keyboard Shortcut event, except for the XF86Audio* shortcuts which hare handled by the * KMixWindow class. So for 99.9% of all users, this method is never called. */ void MDWSlider::decreaseVolume() { increaseOrDecreaseVolume(true, Volume::Both); } /** * Increase or decrease all playback and capture channels of the given control. * This method is very similar to Mixer::increaseOrDecreaseVolume(), but it will * auto-unmute on increase. * * @param mixdeviceID The control name * @param decrease true for decrease. false for increase */ void MDWSlider::increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType) { m_mixdevice->increaseOrDecreaseVolume(decrease, volumeType); // I should possibly not block, as the changes that come back from the Soundcard // will be ignored (e.g. because of capture groups) // qCDebug(KMIX_LOG) << "MDWSlider is blocking signals for " << m_view->id(); // bool oldViewBlockSignalState = m_view->blockSignals(true); m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); // qCDebug(KMIX_LOG) << "MDWSlider is unblocking signals for " << m_view->id(); // m_view->blockSignals(oldViewBlockSignalState); } void MDWSlider::moveStreamAutomatic() { m_mixdevice->mixer()->moveStream(m_mixdevice->id(), ""); } void MDWSlider::moveStream(QString destId) { m_mixdevice->mixer()->moveStream(m_mixdevice->id(), destId); } /** * This is called whenever there are volume updates pending from the hardware for this MDW. */ void MDWSlider::update() { // bool debugMe = (mixDevice()->id() == "PCM:0" ); // if (debugMe) qCDebug(KMIX_LOG) << "The update() PCM:0 playback state" << mixDevice()->isMuted() // << ", vol=" << mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); if ( m_slidersPlayback.count() != 0 || m_mixdevice->hasMuteSwitch() ) 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) { QLabel *l; VerticalText *v; if ((l = dynamic_cast(m_label))) l->setText(m_mixdevice->readableName()); else if ((v = dynamic_cast(m_label))) v->setText(m_mixdevice->readableName()); } updateAccesability(); } /** * * @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" ); // if (debugMe) // { // qCDebug(KMIX_LOG) << "The updateInternal() PCM:0 playback state" << mixDevice()->isMuted() // << ", vol=" << mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); // } for( int i=0; iblockSignals( true ); // slider->setValue( useVolume ); // --- Avoid feedback loops START ----------------- if((volume_index = volumeValues.indexOf(useVolume)) > -1 && --m_waitForSoundSetComplete < 1) { m_waitForSoundSetComplete = 0; volumeValues.removeAt(volume_index); if(!m_sliderInWork) slider->setValue(useVolume); } else if(!m_sliderInWork && m_waitForSoundSetComplete < 1) { slider->setValue(useVolume); } // --- Avoid feedback loops END ----------------- if ( slider->inherits( "KSmallSlider" ) ) { ((KSmallSlider*)slider)->setGray( m_mixdevice->isMuted() ); } slider->blockSignals( oldBlockState ); } // for all sliders // update mute if( m_qcb != 0 ) { bool oldBlockState = m_qcb->blockSignals( true ); QString muteIcon = m_mixdevice->isMuted() ? "audio-volume-muted" : "audio-volume-high"; setIcon(muteIcon, m_qcb); m_qcb->blockSignals( oldBlockState ); } if( m_captureCheckbox ) { bool oldBlockState = m_captureCheckbox->blockSignals( true ); m_captureCheckbox->setChecked( m_mixdevice->isRecSource() ); m_captureCheckbox->blockSignals( oldBlockState ); } } #ifndef QT_NO_ACCESSIBILITY void MDWSlider::updateAccesability() { if (m_linked) { if (!m_slidersPlayback.isEmpty()) m_slidersPlayback[0]->setAccessibleName(m_slidersPlayback[0]->toolTip()); if (!m_slidersCapture.isEmpty()) m_slidersCapture[0]->setAccessibleName(m_slidersCapture[0]->toolTip()); } else { QList vols = m_mixdevice->playbackVolume().getVolumes().values(); foreach (QAbstractSlider *slider, m_slidersPlayback) { slider->setAccessibleName(slider->toolTip()+ " (" +Volume::channelNameReadable(vols.first().chid)+')'); vols.pop_front(); } vols = m_mixdevice->captureVolume().getVolumes().values(); foreach (QAbstractSlider *slider, m_slidersCapture) { slider->setAccessibleName(slider->toolTip()+ " (" +Volume::channelNameReadable(vols.first().chid)+')'); vols.pop_front(); } } } #endif void MDWSlider::showContextMenu( const QPoint& pos ) { if( m_view == 0 ) return; QMenu *menu = m_view->getPopup(); menu->addSection( SmallIcon( "kmix" ), m_mixdevice->readableName() ); if (m_moveMenu) { MixSet *ms = m_mixdevice->getMoveDestinationMixSet(); Q_ASSERT(ms); m_moveMenu->setEnabled((ms->count() > 1)); menu->addMenu( m_moveMenu ); } if ( m_slidersPlayback.count()>1 || m_slidersCapture.count()>1) { KToggleAction *stereo = (KToggleAction *)_mdwActions->action( "stereo" ); if ( stereo ) { stereo->setChecked( !isStereoLinked() ); menu->addAction( stereo ); } } if ( m_mixdevice->captureVolume().hasSwitch() ) { KToggleAction *ta = (KToggleAction *)_mdwActions->action( "recsrc" ); if ( ta ) { ta->setChecked( m_mixdevice->isRecSource() ); menu->addAction( ta ); } } if ( m_mixdevice->hasMuteSwitch() ) { KToggleAction *ta = ( KToggleAction* )_mdwActions->action( "mute" ); if ( ta ) { ta->setChecked( m_mixdevice->isMuted() ); menu->addAction( ta ); } } // QAction *a = _mdwActions->action( "hide" ); // if ( a ) // menu->addAction( a ); QAction *b = _mdwActions->action( "keys" ); if ( b ) { // QAction sep( _mdwPopupActions ); // sep.setSeparator( true ); // menu->addAction( &sep ); menu->addAction( b ); } menu->popup( pos ); } void MDWSlider::showMoveMenu() { MixSet *ms = m_mixdevice->getMoveDestinationMixSet(); Q_ASSERT(ms); _mdwMoveActions->clear(); m_moveMenu->clear(); // Default QAction *a = new QAction(_mdwMoveActions); a->setText( i18n("Automatic According to Category") ); _mdwMoveActions->addAction( QString("moveautomatic"), a); connect(a, SIGNAL(triggered(bool)), SLOT(moveStreamAutomatic()), Qt::QueuedConnection); m_moveMenu->addAction( a ); a = new QAction(_mdwMoveActions); a->setSeparator(true); _mdwMoveActions->addAction( QString("-"), a); m_moveMenu->addAction( a ); foreach (shared_ptr md, *ms) { a = new MDWMoveAction(md, _mdwMoveActions); _mdwMoveActions->addAction( QString("moveto") + md->id(), a); connect(a, SIGNAL(moveRequest(QString)), SLOT(moveStream(QString)), Qt::QueuedConnection); m_moveMenu->addAction( a ); } } /** * An event filter for the various QWidgets. We watch for Mouse press Events, so * that we can popup the context menu. */ bool MDWSlider::eventFilter( QObject* obj, QEvent* e ) { QEvent::Type eventType = e->type(); if (eventType == QEvent::MouseButtonPress) { QMouseEvent *qme = static_cast(e); if (qme->button() == Qt::RightButton) { showContextMenu(); return true; } } else if (eventType == QEvent::ContextMenu) { QPoint pos = reinterpret_cast(obj)->mapToGlobal(QPoint(0, 0)); showContextMenu(pos); return true; } // Attention: We don't filter WheelEvents for KSmallSlider, because it handles WheelEvents itself else if ( eventType == QEvent::Wheel ) // && strcmp(obj->metaObject()->className(),"KSmallSlider") != 0 ) { // Remove the KSmallSlider check. If KSmallSlider comes back, use a cheaper type check - e.g. a boolean value. { QWheelEvent *qwe = static_cast(e); bool increase = (qwe->delta() > 0); if (qwe->orientation() == Qt::Horizontal) // Reverse horizontal scroll: bko228780 increase = !increase; Volume::VolumeTypeFlag volumeType = Volume::Playback; QAbstractSlider *slider = qobject_cast(obj); if (slider != 0) { // qCDebug(KMIX_LOG); // qCDebug(KMIX_LOG); // qCDebug(KMIX_LOG) << "----------------------------- Slider is " << slider; // Mouse is over a slider. So lets apply the wheel event to playback or capture only if(m_slidersCapture.contains(slider)) { // qCDebug(KMIX_LOG) << "Slider is capture " << slider; volumeType = Volume::Capture; } } else { // Mouse not over a slider => do a little guessing if (!m_slidersPlayback.isEmpty()) slider = qobject_cast(m_slidersPlayback.first()); else if (!m_slidersCapture.isEmpty()) slider = qobject_cast(m_slidersCapture.first()); else slider = 0; } increaseOrDecreaseVolume(!increase, volumeType); if (slider != 0) { Volume& volP = m_mixdevice->playbackVolume(); // qCDebug(KMIX_LOG) << "slider=" << slider->objectName(); VolumeSliderExtraData& sliderExtraData = extraData(slider); // qCDebug(KMIX_LOG) << "slider=" << slider->objectName() << "sliderExtraData=" << sliderExtraData.getSubcontrolLabel() << " , chid=" << sliderExtraData.getChid(); volumeValues.push_back(volP.getVolume(sliderExtraData.getChid())); } return true; } return QWidget::eventFilter(obj,e); } diff --git a/gui/viewbase.cpp b/gui/viewbase.cpp index 1d3461ba..ee90012a 100644 --- a/gui/viewbase.cpp +++ b/gui/viewbase.cpp @@ -1,499 +1,499 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "viewbase.h" // Qt #include #include #include // KDE #include #include #include #include #include #include // KMix #include "dialogviewconfiguration.h" #include "gui/guiprofile.h" #include "gui/kmixtoolbox.h" #include "gui/mixdevicewidget.h" #include "gui/mdwslider.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/mixer.h" #include "core/mixertoolbox.h" /** * Creates an empty View. To populate it with MixDevice instances, you must implement * _setMixSet() in your derived class. */ ViewBase::ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actionColletion) : QWidget(parent, f), _popMenu(NULL), _actions(actionColletion), _vflags(vflags), _guiProfileId(guiProfileId) , guiLevel(GuiVisibility::GuiSIMPLE) { setObjectName(id); // When loading the View from the XML profile, guiLevel can get overridden m_viewId = id; configureIcon = QIcon::fromTheme( QLatin1String( "configure" )); if ( _actions == 0 ) { // We create our own action collection, if the actionColletion was 0. // This is currently done for the ViewDockAreaPopup, but only because it has not been converted to use the app-wide // actionCollection(). This is a @todo. _actions = new KActionCollection( this ); } _localActionColletion = new KActionCollection( this ); // Plug in the "showMenubar" action, if the caller wants it. Typically this is only necessary for views in the KMix main window. if ( vflags & ViewBase::HasMenuBar ) { KToggleAction *m = static_cast( _actions->action( name(KStandardAction::ShowMenubar) ) ) ; if ( m != 0 ) { bool visible = ( vflags & ViewBase::MenuBarVisible ); m->setChecked(visible); } } } ViewBase::~ViewBase() { // Hint: The GUI profile will not be removed, as it is pooled and might be applied to a new View. } void ViewBase::addMixer(Mixer *mixer) { _mixers.append(mixer); } //void ViewBase::configurationUpdate() { //} QPushButton* ViewBase::createConfigureViewButton() { QPushButton* configureViewButton = new QPushButton(configureIcon, "", this); configureViewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); configureViewButton->setToolTip(i18n( "Configure this view" )); connect(configureViewButton, SIGNAL(clicked(bool)), SLOT(configureView())); return configureViewButton; } void ViewBase::updateGuiOptions() { setTicks(GlobalConfig::instance().data.showTicks); setLabels(GlobalConfig::instance().data.showLabels); updateMediaPlaybackIcons(); } QString ViewBase::id() const { return m_viewId; } bool ViewBase::isValid() const { return ( !_mixSet.isEmpty() || isDynamic() ); } void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } void ViewBase::setLabels(bool on) { KMixToolBox::setLabels(_mdws, on ); } void ViewBase::setTicks (bool on) { KMixToolBox::setTicks (_mdws, on ); } /** * Updates all playback icons to their (new) state, e.g. Paused, or Playing */ void ViewBase::updateMediaPlaybackIcons() { for (int i = 0; i < _mdws.count(); ++i) { // Currently media controls are always attached to sliders => use MDWSlider MDWSlider* mdw = qobject_cast(_mdws[i]); if (mdw != 0) { mdw->updateMediaButton(); } } } /** * Create all widgets. * This is a loop over all supported devices of the corresponding view. * On each device add() is called - the derived class must implement add() for creating and placing * the real MixDeviceWidget. * The added MixDeviceWidget is appended to the _mdws list. */ void ViewBase::createDeviceWidgets() { _setMixSet(); foreach ( shared_ptr md, _mixSet ) { QWidget* mdw = add(md); // a) Let the View implementation do its work _mdws.append(mdw); // b) Add it to the local list connect(mdw, SIGNAL(guiVisibilityChange(MixDeviceWidget*,bool)), SLOT(guiVisibilitySlot(MixDeviceWidget*,bool))); } if ( !isDynamic() ) { QAction *action = _localActionColletion->addAction("toggle_channels"); action->setText(i18n("&Channels")); connect(action, SIGNAL(triggered(bool)), SLOT(configureView())); } // allow view to "polish" itself constructionFinished(); } /** * Called when a specific control is to be shown or hidden. At the moment it is only called via * the "hide" action in the MDW context menu. * * @param mdw * @param enable */ void ViewBase::guiVisibilitySlot(MixDeviceWidget* mdw, bool enable) { MixDevice* md = mdw->mixDevice().get(); qCDebug(KMIX_LOG) << "Change " << md->id() << " to visible=" << enable; ProfControl* pctl = findMdw(md->id()); if (pctl == 0) { qCWarning(KMIX_LOG) << "MixDevice not found, and cannot be hidden, id=" << md->id(); return; // Ignore } pctl->setVisible(enable); ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::ControlList, QString("ViewBase::guiVisibilitySlot")); } // ---------- Popup stuff START --------------------- void ViewBase::mousePressEvent( QMouseEvent *e ) { if ( e->button() == Qt::RightButton ) showContextMenu(); } /** * Return a popup menu. This contains basic entries. * More can be added by the caller. */ QMenu* ViewBase::getPopup() { popupReset(); return _popMenu; } void ViewBase::popupReset() { QAction *act; delete _popMenu; _popMenu = new QMenu( this ); _popMenu->addSection( QIcon::fromTheme( QLatin1String( "kmix" ) ), i18n("Device Settings" )); act = _localActionColletion->action( "toggle_channels" ); if ( act ) _popMenu->addAction(act); act = _actions->action( "options_show_menubar" ); if ( act ) _popMenu->addAction(act); } /** This will only get executed, when the user has removed all items from the view. Don't remove this method, because then the user cannot get a menu for getting his channels back */ void ViewBase::showContextMenu() { //qCDebug(KMIX_LOG) << "ViewBase::showContextMenu()"; popupReset(); QPoint pos = QCursor::pos(); _popMenu->popup( pos ); } void ViewBase::refreshVolumeLevels() { // is virtual } /** * Check all Mixer instances of this view. * If at least on is dynamic, return true. * Please note that usually there is only one Mixer instance per View. * The only exception as of today (June 2011) is the Tray Popup, which * can contain controls from e.g. ALSA and MPRIS2 backends. */ bool ViewBase::isDynamic() const { foreach (Mixer* mixer , _mixers ) { if ( mixer->isDynamic() ) return true; } return false; } bool ViewBase::pulseaudioPresent() const { // We do not use Mixer::pulseaudioPresent(), as we are only interested in Mixer instances contained in this View. foreach (Mixer* mixer , _mixers ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } void ViewBase::resetMdws() { // We need to delete the current MixDeviceWidgets so we can redraw them while (!_mdws.isEmpty()) delete _mdws.takeFirst(); // _mixSet contains shared_ptr instances, so clear() should be enough to prevent mem leak _mixSet.clear(); // Clean up our _mixSet so we can reapply our GUIProfile } int ViewBase::visibleControls() { int visibleCount = 0; foreach (QWidget* qw, _mdws) { if (qw->isVisible()) ++ visibleCount; } return visibleCount; } /** * Open the View configuration dialog. The user can select which channels he wants * to see and which not. */ void ViewBase::configureView() { Q_ASSERT( !isDynamic() ); Q_ASSERT( !pulseaudioPresent() ); DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); dvc->show(); } void ViewBase::toggleMenuBarSlot() { //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() start\n"; emit toggleMenuBar(); //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() done\n"; } /** * Loads the configuration of this view. *

* Future directions: The view should probably know its config in advance, so we can use it in #load() and #save() * * * @param config The view for this config */ void ViewBase::load(KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); //KConfigGroup cg = config->group( grp ); qCDebug(KMIX_LOG) << "KMixToolBox::loadView() grp=" << grp.toLatin1(); static GuiVisibility guiVisibilities[3] = { GuiVisibility::GuiSIMPLE, GuiVisibility::GuiEXTENDED, GuiVisibility::GuiFULL }; bool guiLevelSet = false; for (int i=0; i<3; ++i) { GuiVisibility& guiCompl = guiVisibilities[i]; bool atLeastOneControlIsShown = false; foreach(QWidget *qmdw, view->_mdws) { if (qmdw->inherits("MixDeviceWidget")) { MixDeviceWidget* mdw = (MixDeviceWidget*) qmdw; shared_ptr md = mdw->mixDevice(); QString devgrp = md->configGroupName(grp); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels bool splitChannels = devcg.readEntry("Split", !mdw->isStereoLinked()); mdw->setStereoLinked(!splitChannels); } // Future directions: "Visibility" is very dirty: It is read from either config file or // GUIProfile. Thus we have a lot of doubled mdw visibility code all throughout KMix. bool mdwEnabled = false; // Consult GuiProfile for visibility mdwEnabled = findMdw(mdw->mixDevice()->id(), guiCompl) != 0; // Match GUI complexity // qCWarning(KMIX_LOG) << "---------- FIRST RUN: md=" << md->id() << ", guiVisibility=" << guiCompl.getId() << ", enabled=" << mdwEnabled; if (mdwEnabled) { atLeastOneControlIsShown = true; } mdw->setVisible(mdwEnabled); } // inherits MixDeviceWidget } // for all MDW's if (atLeastOneControlIsShown) { guiLevelSet = true; setGuiLevel(guiCompl); break; // If there were controls in this complexity level, don't try more } } // for try = 0 ... 1 if (!guiLevelSet) setGuiLevel(guiVisibilities[2]); } void ViewBase::setGuiLevel(GuiVisibility& guiLevel) { this->guiLevel = guiLevel; } /** * Checks whether the given mixDevice shall be shown according to the requested * GuiVisibility. All ProfControl objects are inspected. The first found is returned. * * @param mdwId The control ID * @param requestedGuiComplexityName The GUI name * @return The corresponding ProfControl* * */ ProfControl* ViewBase::findMdw(const QString& mdwId, GuiVisibility visibility) { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { QRegExp idRegExp(pControl->id); if ( mdwId.contains(idRegExp) ) { if (pControl->getVisibility().satisfiesVisibility(visibility)) { // qCDebug(KMIX_LOG) << " MATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); return pControl; } else { // qCDebug(KMIX_LOG) << "NOMATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); } } } // iterate over all ProfControl entries return 0; // not found } /** * Returns the ProfControl* to the given id. The first found is returned. * GuiVisibilityis not taken into account. . All ProfControl objects are inspected. * * @param id The control ID * @return The corresponding ProfControl*, or 0 if no match was found */ ProfControl* ViewBase::findMdw(const QString& id) { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { QRegExp idRegExp(pControl->id); //qCDebug(KMIX_LOG) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); if ( id.contains(idRegExp) ) { return pControl; } } // iterate over all ProfControl entries return 0;// not found } /* * Saves the View configuration */ void ViewBase::save(KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) bool dynamic = isDynamic(); // TODO 11 Dynamic view configuration for (int i = 0; i < view->_mdws.count(); ++i) { QWidget *qmdw = view->_mdws[i]; if (qmdw->inherits("MixDeviceWidget")) { MixDeviceWidget* mdw = (MixDeviceWidget*) qmdw; shared_ptr md = mdw->mixDevice(); //qCDebug(KMIX_LOG) << " grp=" << grp.toLatin1(); //qCDebug(KMIX_LOG) << " mixer=" << view->id().toLatin1(); //qCDebug(KMIX_LOG) << " mdwPK=" << mdw->mixDevice()->id().toLatin1(); - QString devgrp = QString("%1.%2.%3").arg(grp).arg(md->mixer()->id()).arg(md->id()); + QString devgrp = QString("%1.%2.%3").arg(grp, md->mixer()->id(), md->id()); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels devcg.writeEntry("Split", !mdw->isStereoLinked()); } /* if (!dynamic) { devcg.writeEntry("Show", mdw->isVisibleTo(view)); // qCDebug(KMIX_LOG) << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); } */ } // inherits MixDeviceWidget } // for all MDW's if (!dynamic) { // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) if (guiProfile()->isDirty()) { qCDebug(KMIX_LOG) << "Writing dirty profile. grp=" << grp; guiProfile()->writeProfile(); } } } // ---------- Popup stuff END ---------------------