File indexing completed on 2024-05-05 04:48:47

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 }