diff --git a/gui/guiprofile.cpp b/gui/guiprofile.cpp index 720c3395..b9887849 100644 --- a/gui/guiprofile.cpp +++ b/gui/guiprofile.cpp @@ -1,933 +1,933 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-2007 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gui/guiprofile.h" // Qt #include #include #include #include // System #include #include // KMix #include "core/mixer.h" #include QMap GUIProfile::s_profiles; GuiVisibility const GuiVisibility::GuiSIMPLE (QString("simple" ) , GuiVisibility::SIMPLE); GuiVisibility const GuiVisibility::GuiEXTENDED(QString("extended") , GuiVisibility::EXTENDED); // For backwards compatibility, GuiFULL has the ID "all", and not "full" GuiVisibility const GuiVisibility::GuiFULL (QString("all" ) , GuiVisibility::FULL); GuiVisibility const GuiVisibility::GuiCUSTOM (QString("custom" ) , GuiVisibility::CUSTOM); GuiVisibility const GuiVisibility::GuiNEVER (QString("never" ) , GuiVisibility::NEVER); bool SortedStringComparator::operator()(const std::string& s1, const std::string& s2) const { return ( s1 < s2 ); } /** * Product comparator for sorting: * We want the comparator to sort ascending by Vendor. "Inside" the Vendors, we sort by Product Name. */ bool ProductComparator::operator()(const ProfProduct* p1, const ProfProduct* p2) const { if ( p1->vendor < p2->vendor ) { return ( true ); } else if ( p1->vendor > p2->vendor ) { return ( false ); } else if ( p1->productName < p2->productName ) { return ( true ); } else if ( p1->productName > p2->productName ) { return ( false ); } else { /** * We reach this point, if vendor and product name is identical. * Actually we don't care about the order then, so we decide that "p1" comes first. * * (Hint: As this is a set comparator, the return value HERE doesn't matter that * much. But if we would decide later to change this Comparator to be a Map Comparator, * we must NOT return a "0" for identity - this would lead to non-insertion on insert()) */ return true; } } GUIProfile::GUIProfile() { _dirty = false; _driverVersionMin = 0; _driverVersionMax = 0; _generation = 1; } GUIProfile::~GUIProfile() { qCWarning(KMIX_LOG) << "Thou shalt not delete any GUI profile. This message is only OK, when quitting KMix"; qDeleteAll(_controls); qDeleteAll(_products); } /** * Clears the GUIProfile cache. You must only call this * before termination of the application, as GUIProfile instances are used in other classes, especially the views. * There is no need to call this in non-GUI applications like kmixd and kmixctrl. */ void GUIProfile::clearCache() { qDeleteAll(s_profiles); s_profiles.clear(); } void GUIProfile::setId(const QString& id) { _id = id; } QString GUIProfile::getId() const { return _id; } bool GUIProfile::isDirty() const { return _dirty; } void GUIProfile::setDirty() { _dirty = true; } /** * Build a profile name. Suitable to use as primary key and to build filenames. * @arg mixer The mixer * @arg profileName The profile name (e.g. "capture", "playback", "my-cool-profile", or "any" * @return The profile name */ QString GUIProfile::buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard) { QString fname; fname += mixer->getDriverName(); if (!ignoreCard) { fname += ".%1.%2"; fname = fname.arg(mixer->getBaseName()).arg(mixer->getCardInstance()); } fname += '.' + profileName; fname.replace(' ','_'); return fname; } /** * Generate a readable profile name (for presenting to the user). * Hint: Currently used as Tab label. */ QString GUIProfile::buildReadableProfileName(Mixer* mixer, QString profileName) { QString fname; fname += mixer->getBaseName(); if ( mixer->getCardInstance() > 1 ) { fname += " %1"; fname = fname.arg(mixer->getCardInstance()); } if ( profileName != "default" ) { fname += ' ' + profileName; } qCDebug(KMIX_LOG) << fname; return fname; } /** * Returns the GUIProfile for the given ID (= "fullyQualifiedName"). * If not found 0 is returned. There is no try to load it. * * @returns The loaded GUIProfile for the given ID */ GUIProfile* GUIProfile::find(QString id) { // Not thread safe (due to non-atomic contains()/get() if ( s_profiles.contains(id) ) { return s_profiles[id]; } else { return 0; } } /** * Finds the correct profile for the given mixer. * If already loaded from disk, returns the cached version. * Otherwise load profile from disk: Priority: Card specific profile, Card unspecific profile * * @arg mixer The mixer * @arg profileName The profile name (e.g. "ALSA.X-Fi.default", or "OSS.intel-cha51.playback") * A special case is "", which means that a card specific name should be generated. * @arg profileNameIsFullyQualified If true, an exact match will be searched. Otherwise it is a simple name like "playback" or "capture" * @arg ignoreCardName If profileName not fully qualified, this is used in building the requestedProfileName * @return GUIProfile* The loaded GUIProfile, or 0 if no profile matched. Hint: if you use allowFallback==true, this should never return 0. */ GUIProfile* GUIProfile::find(Mixer* mixer, QString profileName, bool profileNameIsFullyQualified, bool ignoreCardName) { GUIProfile* guiprof = 0; if ( mixer == 0 || profileName.isEmpty() ) return 0; // if ( mixer->isDynamic() ) { // qCDebug(KMIX_LOG) << "GUIProfile::find() Not loading GUIProfile for Dynamic Mixer (e.g. PulseAudio)"; // return 0; // } QString requestedProfileName; QString fullQualifiedProfileName; if ( profileNameIsFullyQualified ) { requestedProfileName = profileName; fullQualifiedProfileName = profileName; } else { requestedProfileName = buildProfileName(mixer, profileName, ignoreCardName); fullQualifiedProfileName = buildProfileName(mixer, profileName, false); } if ( s_profiles.contains(fullQualifiedProfileName) ) { guiprof = s_profiles.value(fullQualifiedProfileName); // Cached } else { guiprof = loadProfileFromXMLfiles(mixer, requestedProfileName); // Load from XML ###Card specific profile### if ( guiprof != 0 ) { guiprof->_mixerId = mixer->id(); guiprof->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) if ( guiprof->getName().isEmpty() ) { // If the profile didn't contain a name then lets define one guiprof->setName(buildReadableProfileName(mixer,profileName)); // The caller can rename this if he likes guiprof->setDirty(); } if ( requestedProfileName != fullQualifiedProfileName) { // This is very important! // When the final profileName (fullQualifiedProfileName) is different from // what we have loaded (requestedProfileName, e.g. "default"), we MUST // set the profile dirty, so it gets saved. Otherwise we would write the // fullQualifiedProfileName in the kmixrc, and will not find it on the next // start of KMix. guiprof->setDirty(); } addProfile(guiprof); } } return guiprof; } /* * Add the profile to the internal list of profiles (Profile caching). */ void GUIProfile::addProfile(GUIProfile* guiprof) { // Possible TODO: Delete old mapped GUIProfile, if it exists. Otherwise we might leak one GUIProfile instance // per unplug/plug sequence. Its quite likely possible that currently no Backend leads to a // leak: This is because they either don't hotplug cards (PulseAudio, MPRIS2), or they ship // a XML gui profile (so the Cached version is retrieved, and addProfile() is not called). s_profiles[guiprof->getId()] = guiprof; qCDebug(KMIX_LOG) << "I have added" << guiprof->getId() << "; Number of profiles is now " << s_profiles.size() ; } /** * Loads a GUI Profile from disk (xml profile file). * It tries to load the Soundcard specific file first (a). * If it doesn't exist, it will load the default profile corresponding to the soundcard driver (b). */ GUIProfile* GUIProfile::loadProfileFromXMLfiles(Mixer* mixer, QString profileName) { GUIProfile* guiprof = 0; QString fileName = createNormalizedFilename(profileName); QString fileNameFQ = QStandardPaths::locate(QStandardPaths::DataLocation, fileName ); if ( ! fileNameFQ.isEmpty() ) { guiprof = new GUIProfile(); if ( guiprof->readProfile(fileNameFQ) && ( guiprof->match(mixer) > 0) ) { // loaded } else { delete guiprof; // not good (e.g. Parsing error => drop this profile silently) guiprof = 0; } } else { qCDebug(KMIX_LOG) << "Ignore file " <vendor = mixer->getDriverName(); prd->productName = mixer->readableName(); prd->productRelease = "1.0"; fallback->_products.insert(prd); static QString matchAll(".*"); static QString matchAllSctl(".*"); ProfControl* ctl = new ProfControl(matchAll, matchAllSctl); //ctl->regexp = matchAll; // make sure id matches the regexp ctl->setMandatory(true); fallback->_controls.push_back(ctl); fallback->_soundcardDriver = mixer->getDriverName(); fallback->_soundcardName = mixer->readableName(); fallback->finalizeProfile(); fallback->_mixerId = mixer->id(); fallback->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) fallback->setName(buildReadableProfileName(mixer, QString("default"))); // The caller can rename this if he likes fallback->setDirty(); /* -3- Add the profile to the static list * Hint: This looks like a memory leak, as we never remove profiles from memory while KMix runs. * Especially with application streams it looks suspicious. But please be aware that this method is only * called for soundcard hotplugs, and not on stream hotplugs. At least it is supposed to be like that. * * Please also see the docs at addProfile(), they also address the possible memory leakage. */ addProfile(fallback); return fallback; } /** * Fill the profile with the data from the given XML profile file. * @par ref_fileName: Full qualified filename (with path). * @return bool True, if the profile was successfully created. False if not (e.g. parsing error). */ bool GUIProfile::readProfile(const QString& ref_fileName) { QXmlSimpleReader *xmlReader = new QXmlSimpleReader(); qCDebug(KMIX_LOG) << "Read profile:" << ref_fileName ; QFile xmlFile( ref_fileName ); QXmlInputSource source( &xmlFile ); GUIProfileParser* gpp = new GUIProfileParser(this); xmlReader->setContentHandler(gpp); bool ok = xmlReader->parse( source ); //std::cout << "Raw Profile: " << *this; if ( ok ) { ok = finalizeProfile(); } // Read OK else { // !! this error message about faulty profiles should probably be surrounded with i18n() qCCritical(KMIX_LOG) << "ERROR: The profile '" << ref_fileName<< "' contains errors, and is not used."; } delete gpp; delete xmlReader; 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 profileId = getId(); QString fileName = createNormalizedFilename(profileId); - QString fileNameFQ = QStandardPaths::writableLocation(QStandardPaths::DataLocation)+"/"+fileName; + QString fileNameFQ = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/' + fileName; qCDebug(KMIX_LOG) << "Write profile:" << fileNameFQ ; QFile f(fileNameFQ); if ( f.open(QIODevice::WriteOnly | QFile::Truncate) ) { QTextStream out(&f); out << *this; f.close(); ret = true; } if ( ret ) { _dirty = false; } return ret; } /** This is now empty. It can be removed */ bool GUIProfile::finalizeProfile() const { bool ok = true; 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. * A value between 0 (not matching at all) and MAXLONG (perfect match) is returned. * * Here is the current algorithm: * * If the driver doesn't match, 0 is returned. (OK) * If the card-name ... (OK) * is "*", this is worth 1 point * doesn't match, 0 is returned. * matches, this is worth 500 points. * * If the "card type" ... * is empty, this is worth 0 points. !!! not implemented yet * doesn't match, 0 is returned. !!! not implemented yet * matches , this is worth 500 points. !!! not implemented yet * * If the "driver version" doesn't match, 0 is returned. !!! not implemented yet * If the "driver version" matches, this is worth ... * 4000 unlimited <=> "*:*" * 6000 toLower-bound-limited <=> "toLower-bound:*" * 6000 upper-bound-limited <=> "*:upper-bound" * 8000 upper- and toLower-bound limited <=> "toLower-bound:upper-bound" * or 10000 points (upper-bound=toLower-bound=bound <=> "bound:bound" * * The Profile-Generation is added to the already achieved points. (done) * The maximum gain is 900 points. * Thus you can create up to 900 generations (0-899) without "overriding" * the points gained from the "driver version" or "card-type". * * For example: card-name="*" (1), card-type matches (1000), * driver version "*:*" (4000), Profile-Generation 4 (4). * Sum: 1 + 1000 + 4000 + 4 = 5004 * * @todo Implement "card type" match value * @todo Implement "version" match value (must be in backends as well) */ unsigned long GUIProfile::match(Mixer* mixer) { unsigned long matchValue = 0; if ( _soundcardDriver != mixer->getDriverName() ) { return 0; } if ( _soundcardName == "*" ) { matchValue += 1; } else if ( _soundcardName != mixer->getBaseName() ) { return 0; // card name does not match } else { matchValue += 500; // card name matches } // !!! we don't check current for the driver version. // So we assign simply 4000 points for now. matchValue += 4000; if ( _generation < 900 ) { matchValue += _generation; } else { matchValue += 900; } return matchValue; } QString xmlify(QString raw); QString xmlify(QString raw) { // qCDebug(KMIX_LOG) << "Before: " << raw; raw = raw.replace('&', "&"); raw = raw.replace('<', "<"); raw = raw.replace('>', ">"); raw = raw.replace("'", "'"); raw = raw.replace("\"", """); // qCDebug(KMIX_LOG) << "After : " << raw; return raw; } QTextStream& operator<<(QTextStream &os, const GUIProfile& guiprof) { // qCDebug(KMIX_LOG) << "ENTER QTextStream& operator<<"; os << ""; os << endl << endl; os << "" << endl << endl ; os << "" << endl; for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) { ProfProduct* prd = *it; os << "vendor).toUtf8().constData() << "\" name=\"" << xmlify(prd->productName).toUtf8().constData() << "\""; if ( ! prd->productRelease.isNull() ) { os << " release=\"" << xmlify(prd->productRelease).toUtf8().constData() << "\""; } if ( ! prd->comment.isNull() ) { os << " comment=\"" << xmlify(prd->comment).toUtf8().constData() << "\""; } os << " />" << endl; } // for all products os << endl; foreach ( ProfControl* profControl, guiprof.getControls() ) { os << "id).toUtf8().constData() << "\"" ; if ( !profControl->name.isNull() && profControl->name != profControl->id ) { os << " name=\"" << xmlify(profControl->name).toUtf8().constData() << "\"" ; } os << " subcontrols=\"" << xmlify( profControl->renderSubcontrols().toUtf8().constData()) << "\"" ; os << " show=\"" << xmlify(profControl->getVisibility().getId().toUtf8().constData()) << "\"" ; if ( profControl->isMandatory() ) { os << " mandatory=\"true\""; } if ( profControl->isSplit() ) { os << " split=\"true\""; } os << " />" << endl; } // for all controls os << endl; os << "" << endl; // qCDebug(KMIX_LOG) << "EXIT QTextStream& operator<<"; return os; } std::ostream& operator<<(std::ostream& os, const GUIProfile& guiprof) { os << "Soundcard:" << std::endl << " Driver=" << guiprof._soundcardDriver.toUtf8().constData() << std::endl << " Driver-Version min=" << guiprof._driverVersionMin << " max=" << guiprof._driverVersionMax << std::endl << " Card-Name=" << guiprof._soundcardName.toUtf8().constData() << std::endl << " Card-Type=" << guiprof._soundcardType.toUtf8().constData() << std::endl << " Profile-Generation=" << guiprof._generation << std::endl; os << "Profile:" << std::endl << " Id=" << guiprof._id.toUtf8().constData() << std::endl << " Name=" << guiprof._name.toUtf8().constData() << std::endl; for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) { ProfProduct* prd = *it; os << "Product:\n Vendor=" << prd->vendor.toUtf8().constData() << std::endl << " Name=" << prd->productName.toUtf8().constData() << std::endl; if ( ! prd->productRelease.isNull() ) { os << " Release=" << prd->productRelease.toUtf8().constData() << std::endl; } if ( ! prd->comment.isNull() ) { os << " Comment = " << prd->comment.toUtf8().constData() << std::endl; } } // for all products foreach ( ProfControl* profControl, guiprof.getControls() ) { // ProfControl* profControl = *it; os << "Control:\n ID=" << profControl->id.toUtf8().constData() << std::endl; if ( !profControl->name.isNull() && profControl->name != profControl->id ) { os << " Name = " << profControl->name.toUtf8().constData() << std::endl; } os << " Subcontrols=" << profControl->renderSubcontrols().toUtf8().constData() << std::endl; if ( profControl->isMandatory() ) { os << " mandatory=\"true\"" << std::endl; } if ( profControl->isSplit() ) { os << " split=\"true\"" << std::endl; } } // for all controls return os; } ProfControl::ProfControl(QString& id, QString& subcontrols ) : visibility(GuiVisibility::GuiSIMPLE), _mandatory(false), _split(false) { d = new ProfControlPrivate(); this->id = id; setSubcontrols(subcontrols); } ProfControl::ProfControl(const ProfControl &profControl) : visibility(profControl.visibility), _mandatory(false), _split(false) { d = new ProfControlPrivate(); id = profControl.id; name = profControl.name; _useSubcontrolPlayback = profControl._useSubcontrolPlayback; _useSubcontrolCapture = profControl._useSubcontrolCapture; _useSubcontrolPlaybackSwitch = profControl._useSubcontrolPlaybackSwitch; _useSubcontrolCaptureSwitch = profControl._useSubcontrolCaptureSwitch; _useSubcontrolEnum = profControl._useSubcontrolEnum; d->subcontrols = profControl.d->subcontrols; name = profControl.name; backgroundColor = profControl.backgroundColor; switchtype = profControl.switchtype; _mandatory = profControl._mandatory; _split = profControl._split; } ProfControl::~ProfControl() { delete d; } /** * An overridden method for #setVisible(const GuiVisibility&), that either sets GuiVisibility::GuiSIMPLE * or GuiVisibility::GuiNEVER; * * @param visible */ void ProfControl::setVisible(bool visible) { this->visibility = visible ? GuiVisibility::GuiSIMPLE : GuiVisibility::GuiNEVER; } void ProfControl::setVisible(const GuiVisibility& visibility) { this->visibility = visibility; } void ProfControl::setSubcontrols(QString sctls) { d->subcontrols = sctls; _useSubcontrolPlayback = false; _useSubcontrolCapture = false; _useSubcontrolPlaybackSwitch = false; _useSubcontrolCaptureSwitch = false; _useSubcontrolEnum = false; QStringList qsl = sctls.split( ',', QString::SkipEmptyParts, Qt::CaseInsensitive); QStringListIterator qslIt(qsl); while (qslIt.hasNext()) { QString sctl = qslIt.next(); //qCDebug(KMIX_LOG) << "setSubcontrols found: " << sctl.toLocal8Bit().constData(); if ( sctl == "pvolume" ) _useSubcontrolPlayback = true; else if ( sctl == "cvolume" ) _useSubcontrolCapture = true; else if ( sctl == "pswitch" ) _useSubcontrolPlaybackSwitch = true; else if ( sctl == "cswitch" ) _useSubcontrolCaptureSwitch = true; else if ( sctl == "enum" ) _useSubcontrolEnum = true; else if ( sctl == "*" || sctl == ".*") { _useSubcontrolCapture = true; _useSubcontrolCaptureSwitch = true; _useSubcontrolPlayback = true; _useSubcontrolPlaybackSwitch = true; _useSubcontrolEnum = true; } else qCWarning(KMIX_LOG) << "Ignoring unknown subcontrol type '" << sctl << "' in profile"; } } QString ProfControl::renderSubcontrols() { QString sctlString; if ( _useSubcontrolPlayback && _useSubcontrolPlaybackSwitch && _useSubcontrolCapture && _useSubcontrolCaptureSwitch && _useSubcontrolEnum ) { return QString("*"); } else { if ( _useSubcontrolPlayback ) { sctlString += "pvolume,"; } if ( _useSubcontrolCapture ) { sctlString += "cvolume,"; } if ( _useSubcontrolPlaybackSwitch ) { sctlString += "pswitch,"; } if ( _useSubcontrolCaptureSwitch ) { sctlString += "cswitch,"; } if ( _useSubcontrolEnum ) { sctlString += "enum,"; } if ( sctlString.length() > 0 ) { sctlString.chop(1); } return sctlString; } } // ### PARSER START ################################################ GUIProfileParser::GUIProfileParser(GUIProfile* ref_gp) : _guiProfile(ref_gp) { _scope = GUIProfileParser::NONE; // no scope yet } bool GUIProfileParser::startDocument() { _scope = GUIProfileParser::NONE; // no scope yet return true; } bool GUIProfileParser::startElement( const QString& , const QString& , const QString& qName, const QXmlAttributes& attributes ) { switch ( _scope ) { case GUIProfileParser::NONE: /** we are reading the "top level" ***************************/ if ( qName.toLower() == "soundcard" ) { _scope = GUIProfileParser::SOUNDCARD; addSoundcard(attributes); } else { // skip unknown top-level nodes std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; } // we are accepting only break; case GUIProfileParser::SOUNDCARD: if ( qName.toLower() == "product" ) { // Defines product names under which the chipset/hardware is sold addProduct(attributes); } else if ( qName.toLower() == "control" ) { addControl(attributes); } else if ( qName.toLower() == "profile" ) { addProfileInfo(attributes); } else { std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; } // we are accepting , and break; } // switch() return true; } bool GUIProfileParser::endElement( const QString&, const QString&, const QString& qName ) { if ( qName == "soundcard" ) { _scope = GUIProfileParser::NONE; // should work out OK, as we don't nest soundcard entries } return true; } void GUIProfileParser::addSoundcard(const QXmlAttributes& attributes) { /* std::cout << "Soundcard: "; printAttributes(attributes); */ QString driver = attributes.value("driver"); QString version = attributes.value("version"); QString name = attributes.value("name"); QString type = attributes.value("type"); QString generation = attributes.value("generation"); if ( !driver.isNull() && !name.isNull() ) { _guiProfile->_soundcardDriver = driver; _guiProfile->_soundcardName = name; if ( type.isNull() ) { _guiProfile->_soundcardType = ""; } else { _guiProfile->_soundcardType = type; } if ( version.isNull() ) { _guiProfile->_driverVersionMin = 0; _guiProfile->_driverVersionMax = 0; } else { std::pair versionMinMax; splitPair(version, versionMinMax, ':'); _guiProfile->_driverVersionMin = versionMinMax.first.toULong(); _guiProfile->_driverVersionMax = versionMinMax.second.toULong(); } if ( type.isNull() ) { type = ""; }; if ( generation.isNull() ) { _guiProfile->_generation = 0; } else { // Hint: If the conversion fails, _generation will be assigned 0 (which is fine) _guiProfile->_generation = generation.toUInt(); } } } void GUIProfileParser::addProfileInfo(const QXmlAttributes& attributes) { QString name = attributes.value("name"); QString id = attributes.value("id"); _guiProfile->setId(id); _guiProfile->setName(name); } void GUIProfileParser::addProduct(const QXmlAttributes& attributes) { /* std::cout << "Product: "; printAttributes(attributes); */ QString vendor = attributes.value("vendor"); QString name = attributes.value("name"); QString release = attributes.value("release"); QString comment = attributes.value("comment"); if ( !vendor.isNull() && !name.isNull() ) { // Adding a product makes only sense if we have at least vendor and product name ProfProduct *prd = new ProfProduct(); prd->vendor = vendor; prd->productName = name; prd->productRelease = release; prd->comment = comment; _guiProfile->addProduct(prd); } } void GUIProfileParser::addControl(const QXmlAttributes& attributes) { /* std::cout << "Control: "; printAttributes(attributes); */ QString id = attributes.value("id"); QString subcontrols = attributes.value("subcontrols"); QString name = attributes.value("name"); QString show = attributes.value("show"); QString background = attributes.value("background"); QString switchtype = attributes.value("switchtype"); QString mandatory = attributes.value("mandatory"); QString split = attributes.value("split"); bool isMandatory = false; if ( !id.isNull() ) { // We need at least an "id". We can set defaults for the rest, if undefined. if ( subcontrols.isNull() || subcontrols.isEmpty() ) { subcontrols = '*'; // for compatibility reasons, we interpret an empty string as match-all (aka "*") } if ( name.isNull() ) { // ignore. isNull() will be checked by all users. } if ( ! mandatory.isNull() && mandatory == "true" ) { isMandatory = true; } if ( !background.isNull() ) { // ignore. isNull() will be checked by all users. } if ( !switchtype.isNull() ) { // ignore. isNull() will be checked by all users. } ProfControl *profControl = new ProfControl(id, subcontrols); if ( show.isNull() ) { show = '*'; } profControl->name = name; profControl->setVisible(GuiVisibility::getByString(show)); profControl->setBackgroundColor( background ); profControl->setSwitchtype(switchtype); profControl->setMandatory(isMandatory); if ( !split.isNull() && split=="true") { profControl->setSplit(true); } _guiProfile->getControls().push_back(profControl); } // id != null } void GUIProfileParser::printAttributes(const QXmlAttributes& attributes) { if ( attributes.length() > 0 ) { for ( int i = 0 ; i < attributes.length(); i++ ) { std::cout << attributes.qName(i).toUtf8().constData() << ":"<< attributes.value(i).toUtf8().constData() << " , "; } std::cout << std::endl; } } void GUIProfileParser::splitPair(const QString& pairString, std::pair& result, char delim) { int delimPos = pairString.indexOf(delim); if ( delimPos == -1 ) { // delimiter not found => use an empty String for "second" result.first = pairString; result.second = ""; } else { // delimiter found result.first = pairString.mid(0,delimPos); result.second = pairString.left(delimPos+1); } } diff --git a/gui/mdwslider.cpp b/gui/mdwslider.cpp index 30c942f5..903a6a2f 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() ); 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 unaccurate 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 ); } // 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]; //Volume::getSubcontrolTranslation(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 handeled by the * KMixWindow class. So for 99.9% of all users, this methos 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]+")"); + 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]+")"); + 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); }