Warning, file /multimedia/amarok/src/playlistgenerator/ConstraintGroup.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /**************************************************************************************** 0002 * Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #define DEBUG_PREFIX "APG::ConstraintGroup" 0018 0019 #include "ConstraintGroup.h" 0020 #include "ConstraintFactory.h" 0021 #include "constraints/Matching.h" 0022 0023 #include "core/meta/Meta.h" 0024 #include "core/collections/QueryMaker.h" 0025 #include "core/support/Debug.h" 0026 0027 #include <QList> 0028 #include <QString> 0029 #include <QtGlobal> 0030 0031 ConstraintGroup::ConstraintGroup( QDomElement& xmlelem, ConstraintNode* p ) : ConstraintNode( p ) 0032 { 0033 DEBUG_BLOCK 0034 if ( xmlelem.tagName() == QLatin1String("group") ) { 0035 if ( xmlelem.attribute( QStringLiteral("matchtype") ) == QLatin1String("any") ) { 0036 m_matchtype = MatchAny; 0037 } else { 0038 m_matchtype = MatchAll; 0039 } 0040 } else if ( xmlelem.tagName() == QLatin1String("constrainttree") ) { 0041 // root node of a constraint tree 0042 m_matchtype = MatchAll; 0043 } else { 0044 m_matchtype = MatchAll; 0045 } 0046 debug() << getName(); 0047 } 0048 0049 ConstraintGroup::ConstraintGroup( ConstraintNode* p ) : ConstraintNode( p ), m_matchtype( MatchAll ) 0050 { 0051 DEBUG_BLOCK 0052 debug() << "new default ConstraintGroup"; 0053 } 0054 0055 ConstraintGroup* 0056 ConstraintGroup::createFromXml( QDomElement& xmlelem, ConstraintNode* p ) 0057 { 0058 ConstraintGroup* cg = new ConstraintGroup( xmlelem, p ); 0059 ConstraintFactory* cfact = ConstraintFactory::instance(); 0060 0061 // Load the children, which can be either groups or constraints 0062 for ( int i = 0; i < xmlelem.childNodes().count(); i++ ) { 0063 QDomElement childXmlElem = xmlelem.childNodes().item( i ).toElement(); 0064 if ( !childXmlElem.isNull() ) { 0065 if ( childXmlElem.tagName() == QLatin1String("group") ) { 0066 cfact->createGroup( childXmlElem, cg ); 0067 } else if ( childXmlElem.tagName() == QLatin1String("constraint") ) { 0068 cfact->createConstraint( childXmlElem, cg ); 0069 } else { 0070 debug() << "unknown child: " << childXmlElem.nodeName(); 0071 } 0072 } 0073 } 0074 return cg; 0075 } 0076 0077 ConstraintGroup* 0078 ConstraintGroup::createNew( ConstraintNode* p ) 0079 { 0080 return new ConstraintGroup( p ); 0081 } 0082 0083 QString 0084 ConstraintGroup::getName() const 0085 { 0086 if ( m_matchtype == MatchAny ) { 0087 return i18nc("name of a type of constraint group", "\"Match Any\" group"); 0088 } else if ( m_matchtype == MatchAll ) { 0089 return i18nc("name of a type of constraint group", "\"Match All\" group"); 0090 } else { 0091 return i18nc("name of a type of constraint group", "Unknown match group"); 0092 } 0093 } 0094 0095 QWidget* 0096 ConstraintGroup::editWidget() const 0097 { 0098 ConstraintGroupEditWidget* e = new ConstraintGroupEditWidget( m_matchtype ); 0099 connect( e, &ConstraintGroupEditWidget::clickedMatchAny, this, &ConstraintGroup::setMatchAny ); 0100 connect( e, &ConstraintGroupEditWidget::clickedMatchAll, this, &ConstraintGroup::setMatchAll ); 0101 return e; 0102 } 0103 0104 void 0105 ConstraintGroup::toXml( QDomDocument& doc, QDomElement& elem ) const 0106 { 0107 QDomElement group; 0108 0109 if ( elem.tagName() == QLatin1String("generatorpreset") ) { 0110 group = doc.createElement( QStringLiteral("constrainttree") ); // unmodifiable root element of the constraint tree 0111 } else { 0112 group = doc.createElement( QStringLiteral("group") ); 0113 if ( m_matchtype == MatchAny ) { 0114 group.setAttribute( QStringLiteral("matchtype"), QStringLiteral("any") ); 0115 } else { 0116 group.setAttribute( QStringLiteral("matchtype"), QStringLiteral("all") ); 0117 } 0118 } 0119 0120 foreach( ConstraintNode* child, m_children ) { 0121 child->toXml( doc, group ); 0122 } 0123 0124 elem.appendChild( group ); 0125 } 0126 0127 Collections::QueryMaker* 0128 ConstraintGroup::initQueryMaker( Collections::QueryMaker* qm ) const 0129 { 0130 if ( m_children.size() > 0 ) { 0131 if ( m_matchtype == MatchAny ) { 0132 qm->beginOr(); 0133 } else if ( m_matchtype == MatchAll ) { 0134 qm->beginAnd(); 0135 } else { 0136 return qm; 0137 } 0138 0139 foreach( ConstraintNode* child, m_children ) { 0140 child->initQueryMaker( qm ); 0141 } 0142 0143 return qm->endAndOr(); 0144 } else { 0145 return qm; 0146 } 0147 } 0148 0149 double 0150 ConstraintGroup::satisfaction( const Meta::TrackList& l ) const 0151 { 0152 // shortcut if the playlist is empty 0153 if ( l.size() <= 0 ) { 0154 return 1.0; 0155 } 0156 0157 if ( m_children.isEmpty() ) { 0158 return 1.0; 0159 } 0160 0161 double s; 0162 if ( m_matchtype == MatchAny ) { 0163 s = 0.0; 0164 } else if ( m_matchtype == MatchAll ) { 0165 s = 1.0; 0166 } else { 0167 return 1.0; 0168 } 0169 0170 QHash<int,int> constraintMatchTypes; 0171 0172 // TODO: there's got to be a more efficient way of handling interdependent constraints 0173 for ( int i = 0; i < m_children.size(); i++ ) { 0174 ConstraintNode* child = m_children[i]; 0175 double chS = child->satisfaction( l ); 0176 if ( m_matchtype == MatchAny ) { 0177 s = qMax( s, chS ); 0178 } else if ( m_matchtype == MatchAll ) { 0179 s = qMin( s, chS ); 0180 } 0181 0182 // prepare for proper handling of non-independent constraints 0183 ConstraintTypes::MatchingConstraint* cge = dynamic_cast<ConstraintTypes::MatchingConstraint*>( child ); 0184 if ( cge ) { 0185 constraintMatchTypes.insertMulti( cge->constraintMatchType(), i ); 0186 } 0187 } 0188 0189 // remove the independent constraints from the hash 0190 foreach( int key, constraintMatchTypes.uniqueKeys() ) { 0191 QList<int> vals = constraintMatchTypes.values( key ); 0192 if ( vals.size() <= 1 ) { 0193 constraintMatchTypes.remove( key ); 0194 } 0195 } 0196 0197 return combineInterdependentConstraints( l, s, constraintMatchTypes ); 0198 } 0199 0200 quint32 0201 ConstraintGroup::suggestPlaylistSize() const 0202 { 0203 quint32 s = 0; 0204 quint32 c = 0; 0205 foreach( ConstraintNode* child, m_children ) { 0206 quint32 x = child->suggestPlaylistSize(); 0207 if ( x > 0 ) { 0208 s += x; 0209 c++; 0210 } 0211 } 0212 if ( c > 0 ) { 0213 return s / c; 0214 } else { 0215 return 0; 0216 } 0217 } 0218 0219 double 0220 ConstraintGroup::combineInterdependentConstraints( const Meta::TrackList& l, const double s, const QHash<int,int>& cmt ) const 0221 { 0222 /* Handle interdependent constraints properly. 0223 * See constraints/Matching.h for a description of why this is necessary. */ 0224 foreach( int key, cmt.uniqueKeys() ) { 0225 QList<int> vals = cmt.values( key ); 0226 // set up the blank matching array 0227 QBitArray m; 0228 if ( m_matchtype == MatchAny ) { 0229 m = QBitArray( l.size(), false ); 0230 } else { 0231 m = QBitArray( l.size(), true ); 0232 } 0233 0234 // combine the arrays from the appropriate constraints 0235 foreach( int v, vals ) { 0236 ConstraintTypes::MatchingConstraint* cge = dynamic_cast<ConstraintTypes::MatchingConstraint*>( m_children[v] ); 0237 if ( m_matchtype == MatchAny ) { 0238 m |= cge->whatTracksMatch( l ); 0239 } else if ( m_matchtype == MatchAll ) { 0240 m &= cge->whatTracksMatch( l ); 0241 } 0242 } 0243 0244 // turn the array into a satisfaction value 0245 double chS = 0.0; 0246 for ( int j = 0; j < l.size(); j++ ) { 0247 if ( m.testBit( j ) ) { 0248 chS += 1.0; 0249 } 0250 } 0251 chS /= ( double )l.size(); 0252 0253 // choose the appropriate satisfaction value 0254 if ( m_matchtype == MatchAny ) { 0255 return qMax( s, chS ); 0256 } else if ( m_matchtype == MatchAll ) { 0257 return qMin( s, chS ); 0258 } else { 0259 return s; 0260 } 0261 } 0262 0263 return s; 0264 } 0265 0266 void 0267 ConstraintGroup::setMatchAny() 0268 { 0269 m_matchtype = MatchAny; 0270 Q_EMIT dataChanged(); 0271 } 0272 0273 void 0274 ConstraintGroup::setMatchAll() 0275 { 0276 m_matchtype = MatchAll; 0277 Q_EMIT dataChanged(); 0278 } 0279 0280 /****************************** 0281 * Edit Widget * 0282 ******************************/ 0283 0284 ConstraintGroupEditWidget::ConstraintGroupEditWidget( const ConstraintGroup::MatchType t ) 0285 { 0286 ui.setupUi( this ); 0287 QMetaObject::connectSlotsByName( this ); 0288 0289 switch( t ) { 0290 case ConstraintGroup::MatchAny: 0291 ui.radioButton_MatchAny->setChecked( true ); 0292 break; 0293 case ConstraintGroup::MatchAll: 0294 ui.radioButton_MatchAll->setChecked( true ); 0295 break; 0296 } 0297 } 0298 0299 void 0300 ConstraintGroupEditWidget::on_radioButton_MatchAll_clicked( bool c ) 0301 { 0302 if ( c ) { 0303 Q_EMIT clickedMatchAll(); 0304 Q_EMIT updated(); 0305 } 0306 } 0307 0308 void 0309 ConstraintGroupEditWidget::on_radioButton_MatchAny_clicked( bool c ) 0310 { 0311 if ( c ) { 0312 Q_EMIT clickedMatchAny(); 0313 Q_EMIT updated(); 0314 } 0315 }