File indexing completed on 2024-05-19 04:49:57

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 "Constraint::TagMatch"
0018 
0019 #include "TagMatch.h"
0020 
0021 #include "playlistgenerator/Constraint.h"
0022 #include "playlistgenerator/ConstraintFactory.h"
0023 
0024 #include "core/collections/QueryMaker.h"
0025 #include "core/meta/Meta.h"
0026 #include "core/meta/Statistics.h"
0027 #include "core/support/Debug.h"
0028 
0029 #include <QRandomGenerator>
0030 
0031 #include <cmath>
0032 #include <cstdlib>
0033 
0034 Constraint*
0035 ConstraintTypes::TagMatch::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
0036 {
0037     if ( p )
0038         return new TagMatch( xmlelem, p );
0039     else
0040         return nullptr;
0041 }
0042 
0043 Constraint*
0044 ConstraintTypes::TagMatch::createNew( ConstraintNode* p )
0045 {
0046     if ( p )
0047         return new TagMatch( p );
0048     else
0049         return nullptr;
0050 }
0051 
0052 ConstraintFactoryEntry*
0053 ConstraintTypes::TagMatch::registerMe()
0054 {
0055     return new ConstraintFactoryEntry( QStringLiteral("TagMatch"),
0056                                        i18n("Match Tags"),
0057                                        i18n("Make all tracks in the playlist match the specified characteristic"),
0058                                        &TagMatch::createFromXml, &TagMatch::createNew );
0059 }
0060 
0061 ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p )
0062         : MatchingConstraint( p )
0063         , m_comparer( new Comparer() )
0064         , m_fieldsModel( new TagMatchFieldsModel() )
0065 {
0066     QDomAttr a;
0067 
0068     a = xmlelem.attributeNode( QStringLiteral("field") );
0069     if ( !a.isNull() ) {
0070         if ( m_fieldsModel->contains( a.value() ) )
0071             m_field = a.value();
0072     }
0073 
0074     a = xmlelem.attributeNode( QStringLiteral("comparison") );
0075     if ( !a.isNull() ) {
0076         m_comparison = a.value().toInt();
0077     }
0078 
0079     a = xmlelem.attributeNode( QStringLiteral("value") );
0080     if ( !a.isNull() ) {
0081         if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) {
0082             m_value = a.value().toInt();
0083         } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
0084             if ( m_comparison == CompareDateWithin ) {
0085                 QStringList parts = a.value().split(' ');
0086                 if ( parts.size() == 2 ) {
0087                     int u = parts.at( 0 ).toInt();
0088                     int v = 0;
0089                     if ( parts.at( 1 ) == QLatin1String("months") )
0090                         v = 1;
0091                     else if ( parts.at( 1 ) == QLatin1String("years") )
0092                         v = 2;
0093                     m_value = QVariant::fromValue( DateRange( u, v ) );
0094                 } else
0095                     m_value = QVariant::fromValue( DateRange( 0, 0 ) );
0096             } else
0097                 m_value = QDate::fromString( a.value(), Qt::ISODate );
0098         } else { // String type
0099             m_value = a.value();
0100         }
0101     }
0102 
0103     a = xmlelem.attributeNode( QStringLiteral("invert") );
0104     if ( !a.isNull() && a.value() == QLatin1String("true") )
0105         m_invert = true;
0106     else
0107         m_invert = false;
0108 
0109     a = xmlelem.attributeNode( QStringLiteral("strictness") );
0110     if ( !a.isNull() )
0111         m_strictness = a.value().toDouble();
0112 }
0113 
0114 ConstraintTypes::TagMatch::TagMatch( ConstraintNode* p )
0115         : MatchingConstraint( p )
0116         , m_comparison( CompareStrEquals )
0117         , m_field( QStringLiteral("title") )
0118         , m_invert( false )
0119         , m_strictness( 1.0 )
0120         , m_value()
0121         , m_comparer( new Comparer() )
0122         , m_fieldsModel( new TagMatchFieldsModel() )
0123 {
0124 }
0125 
0126 ConstraintTypes::TagMatch::~TagMatch()
0127 {
0128     delete m_comparer;
0129     delete m_fieldsModel;
0130 }
0131 
0132 QWidget*
0133 ConstraintTypes::TagMatch::editWidget() const
0134 {
0135     TagMatchEditWidget* e = new TagMatchEditWidget(
0136                                             m_comparison,
0137                                             m_field,
0138                                             m_invert,
0139                                             static_cast<int>( m_strictness * 10 ),
0140                                             m_value );
0141     connect( e, &TagMatchEditWidget::comparisonChanged, this, &TagMatch::setComparison );
0142     connect( e, &TagMatchEditWidget::fieldChanged, this, &TagMatch::setField );
0143     connect( e, &TagMatchEditWidget::invertChanged, this, &TagMatch::setInvert );
0144     connect( e, &TagMatchEditWidget::strictnessChanged, this, &TagMatch::setStrictness );
0145     connect( e, &TagMatchEditWidget::valueChanged, this, &TagMatch::setValue );
0146     return e;
0147 }
0148 
0149 void
0150 ConstraintTypes::TagMatch::toXml( QDomDocument& doc, QDomElement& elem ) const
0151 {
0152     QDomElement c = doc.createElement( QStringLiteral("constraint") );
0153 
0154     c.setAttribute( QStringLiteral("type"), QStringLiteral("TagMatch") );
0155     c.setAttribute( QStringLiteral("field"), m_field );
0156     c.setAttribute( QStringLiteral("comparison"), m_comparison );
0157     c.setAttribute( QStringLiteral("value"), valueToString() );
0158 
0159     if ( m_invert )
0160         c.setAttribute( QStringLiteral("invert"), QStringLiteral("true") );
0161     else
0162         c.setAttribute( QStringLiteral("invert"), QStringLiteral("false") );
0163 
0164     c.setAttribute( QStringLiteral("strictness"), QString::number( m_strictness ) );
0165 
0166     elem.appendChild( c );
0167 }
0168 
0169 QString
0170 ConstraintTypes::TagMatch::getName() const
0171 {
0172     KLocalizedString v( ki18nc( "%1 = empty string or \"not\"; "
0173                                 "%2 = a metadata field, like \"title\" or \"artist name\"; "
0174                                 "%3 = a predicate, can be equals, starts with, ends with or contains; "
0175                                 "%4 = a string to match; "
0176                                 "Example: Match tag: not title contains \"foo\"", "Match tag:%1 %2 %3 %4") );
0177     v = v.subs( m_invert ? i18n(" not") : QLatin1String("") );
0178     v = v.subs( m_fieldsModel->pretty_name_of( m_field ) );
0179     v = v.subs( comparisonToString() );
0180     if ( m_field == QLatin1String("rating") ) {
0181         double r = m_value.toDouble() / 2.0;
0182         v = v.subs( i18ncp("number of stars in the rating of a track", "%1 star", "%1 stars", r) );
0183     } else if ( m_field == QLatin1String("length") ) {
0184         v = v.subs( QTime(0, 0, 0).addMSecs( m_value.toInt() ).toString( QStringLiteral("H:mm:ss") ) );
0185     } else {
0186         if ( m_fieldsModel->type_of( m_field ) == FieldTypeString ) {
0187             // put quotes around any strings (eg, track title or artist name) ...
0188             QString s = i18nc("an arbitrary string surrounded by quotes", "\"%1\"", valueToString() );
0189             v = v.subs( s );
0190         } else {
0191             // ... but don't quote put quotes around anything else
0192             v = v.subs( valueToString() );
0193         }
0194     }
0195     return v.toString();
0196 }
0197 
0198 Collections::QueryMaker*
0199 ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const
0200 {
0201     if ( ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) ) {
0202         int v = m_value.toInt();
0203         int range = static_cast<int>( m_comparer->rangeNum( m_strictness, m_fieldsModel->meta_value_of( m_field ) ) );
0204         if ( m_comparison == CompareNumEquals ) {
0205             if ( !m_invert ) {
0206                 if ( m_strictness < 0.99 ) { // fuzzy approximation of "1.0"
0207                     qm->beginAnd();
0208                     qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
0209                     qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
0210                     qm->endAndOr();
0211                 } else {
0212                     qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals );
0213                 }
0214             } else {
0215                 if ( m_strictness > 0.99 ) {
0216                     qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals );
0217                 }
0218             }
0219         } else if ( m_comparison == CompareNumGreaterThan ) {
0220             if ( m_invert )
0221                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::GreaterThan );
0222             else
0223                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
0224         } else if ( m_comparison == CompareNumLessThan ) {
0225             if ( m_invert )
0226                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::LessThan );
0227             else
0228                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
0229         }
0230     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
0231         uint referenceDate = 0;
0232         int range = m_comparer->rangeDate( m_strictness );
0233         if ( m_comparison == CompareDateBefore ) {
0234             referenceDate = m_value.toDateTime().toSecsSinceEpoch();
0235             if ( m_invert )
0236                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::LessThan );
0237             else
0238                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan );
0239         } else if ( m_comparison == CompareDateOn ) {
0240             referenceDate = m_value.toDateTime().toSecsSinceEpoch();
0241             if ( !m_invert ) {
0242                 qm->beginAnd();
0243                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
0244                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan );
0245                 qm->endAndOr();
0246             }
0247         } else if ( m_comparison == CompareDateAfter ) {
0248             referenceDate = m_value.toDateTime().toSecsSinceEpoch();
0249             if ( m_invert )
0250                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::GreaterThan );
0251             else
0252                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
0253         } else if ( m_comparison == CompareDateWithin ) {
0254             QDateTime now = QDateTime::currentDateTime();
0255             DateRange r = m_value.value<DateRange>();
0256             switch ( r.second ) {
0257                 case 0:
0258                     referenceDate = now.addDays( -1 * r.first ).toSecsSinceEpoch();
0259                     break;
0260                 case 1:
0261                     referenceDate = now.addMonths( -1 * r.first ).toSecsSinceEpoch();
0262                     break;
0263                 case 2:
0264                     referenceDate = now.addYears( -1 * r.first ).toSecsSinceEpoch();
0265                     break;
0266                 default:
0267                     break;
0268             }
0269             if ( m_invert )
0270                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::GreaterThan );
0271             else
0272                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
0273         }
0274     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeString ) {
0275         if ( m_comparison == CompareStrEquals ) {
0276             if ( m_invert )
0277                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true );
0278             else
0279                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true );
0280         } else if ( m_comparison == CompareStrStartsWith ) {
0281             if ( m_invert )
0282                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false );
0283             else
0284                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false );
0285         } else if ( m_comparison == CompareStrEndsWith ) {
0286             if ( m_invert )
0287                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true );
0288             else
0289                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true );
0290         } else if ( m_comparison == CompareStrContains ) {
0291             if ( m_invert )
0292                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, false );
0293             else
0294                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, false );
0295         }
0296         // TODO: regexp
0297     } else {
0298         error() << "TagMatch cannot initialize QM for unknown type";
0299     }
0300 
0301     return qm;
0302 }
0303 
0304 double
0305 ConstraintTypes::TagMatch::satisfaction( const Meta::TrackList& tl ) const
0306 {
0307     double satisfaction = 0.0;
0308     foreach( Meta::TrackPtr t, tl ) {
0309         if ( matches( t ) ) {
0310             satisfaction += 1.0;
0311         }
0312     }
0313     satisfaction /= ( double )tl.size();
0314     return satisfaction;
0315 }
0316 
0317 const QBitArray
0318 ConstraintTypes::TagMatch::whatTracksMatch( const Meta::TrackList& tl )
0319 {
0320     QBitArray match = QBitArray( tl.size() );
0321     for ( int i = 0; i < tl.size(); i++ ) {
0322         if ( matches( tl.at( i ) ) )
0323             match.setBit( i, true );
0324     }
0325     return match;
0326 }
0327 
0328 int
0329 ConstraintTypes::TagMatch::constraintMatchType() const
0330 {
0331     return ( 0 << 28 ) + m_fieldsModel->index_of( m_field );
0332 }
0333 
0334 
0335 QString
0336 ConstraintTypes::TagMatch::comparisonToString() const
0337 {
0338     if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) {
0339         if ( m_comparison == CompareNumEquals ) {
0340             return i18nc("a numerical tag (like year or track number) equals a value","equals");
0341         } else if ( m_comparison == CompareNumGreaterThan ) {
0342             return i18n("greater than");
0343         } else if ( m_comparison == CompareNumLessThan ) {
0344             return i18n("less than");
0345         }
0346     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
0347         if ( m_comparison == CompareDateBefore ) {
0348             return i18n("before");
0349         } else if ( m_comparison == CompareDateOn ) {
0350             return i18n("on");
0351         } else if ( m_comparison == CompareDateAfter ) {
0352             return i18n("after");
0353         } else if ( m_comparison == CompareDateWithin ) {
0354             return i18n("within");
0355         }
0356     } else {
0357         if ( m_comparison == CompareStrEquals ) {
0358             return i18nc("an alphabetical tag (like title or artist name) equals some string","equals");
0359         } else if ( m_comparison == CompareStrStartsWith ) {
0360             return i18nc("an alphabetical tag (like title or artist name) starts with some string","starts with");
0361         } else if ( m_comparison == CompareStrEndsWith ) {
0362             return i18nc("an alphabetical tag (like title or artist name) ends with some string","ends with");
0363         } else if ( m_comparison == CompareStrContains ) {
0364             return i18nc("an alphabetical tag (like title or artist name) contains some string","contains");
0365         } else if ( m_comparison == CompareStrRegExp ) {
0366             return i18n("regexp");
0367         }
0368     }
0369     return i18n("unknown comparison");
0370 }
0371 
0372 QString
0373 ConstraintTypes::TagMatch::valueToString() const
0374 {
0375     if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
0376         if ( m_comparison != CompareDateWithin ) {
0377             return m_value.toDate().toString( Qt::ISODate );
0378         } else {
0379             KLocalizedString unit;
0380             switch ( m_value.value<DateRange>().second ) {
0381                 case 0:
0382                     unit = ki18np("%1 day", "%1 days");
0383                     break;
0384                 case 1:
0385                     unit = ki18np("%1 month", "%1 months");
0386                     break;
0387                 case 2:
0388                     unit = ki18np("%1 year", "%1 years");
0389                     break;
0390                 default:
0391                     break;
0392             }
0393             return unit.subs( m_value.value<DateRange>().first ).toString();
0394         }
0395     } else {
0396         return m_value.toString();
0397     }
0398 }
0399 
0400 bool
0401 ConstraintTypes::TagMatch::matches( Meta::TrackPtr track ) const
0402 {
0403     if ( !m_matchCache.contains( track ) ) {
0404         double v = 0.0;
0405         qint64 fmv = m_fieldsModel->meta_value_of( m_field );
0406         switch ( fmv ) {
0407             case Meta::valUrl:
0408                 v = m_comparer->compareStr( track->prettyUrl(), m_comparison, m_value.toString() );
0409                 break;
0410             case Meta::valTitle:
0411                 v = m_comparer->compareStr( track->prettyName(), m_comparison, m_value.toString() );
0412                 break;
0413             case Meta::valArtist:
0414                 v = m_comparer->compareStr( track->artist()->prettyName(), m_comparison, m_value.toString() );
0415                 break;
0416             case Meta::valAlbum:
0417                 v = m_comparer->compareStr( track->album()->prettyName(), m_comparison, m_value.toString() );
0418                 break;
0419             case Meta::valGenre:
0420                 v = m_comparer->compareStr( track->genre()->prettyName(), m_comparison, m_value.toString() );
0421                 break;
0422             case Meta::valComposer:
0423                 v = m_comparer->compareStr( track->composer()->prettyName(), m_comparison, m_value.toString() );
0424                 break;
0425             case Meta::valYear:
0426                 v = m_comparer->compareNum( track->year()->prettyName().toInt(), m_comparison, m_value.toInt(), m_strictness, fmv );
0427                 break;
0428             case Meta::valComment:
0429                 v = m_comparer->compareStr( track->comment(), m_comparison, m_value.toString() );
0430                 break;
0431             case Meta::valTrackNr:
0432                 v = m_comparer->compareNum( track->trackNumber(), m_comparison, m_value.toInt(), m_strictness, fmv );
0433                 break;
0434             case Meta::valDiscNr:
0435                 v = m_comparer->compareNum( track->discNumber(), m_comparison, m_value.toInt(), m_strictness, fmv );
0436                 break;
0437             case Meta::valLength:
0438                 v = m_comparer->compareNum( track->length(), m_comparison, m_value.toInt(), m_strictness, fmv );
0439                 break;
0440             case Meta::valBitrate:
0441                 v = m_comparer->compareNum( track->bitrate(), m_comparison, m_value.toInt(), m_strictness, fmv );
0442                 break;
0443             case Meta::valFilesize:
0444                 v = m_comparer->compareNum( track->filesize(), m_comparison, m_value.toInt(), m_strictness, fmv );
0445                 break;
0446             case Meta::valCreateDate:
0447                 v = m_comparer->compareDate( track->createDate().toSecsSinceEpoch(), m_comparison, m_value, m_strictness );
0448                 break;
0449             case Meta::valScore:
0450                 v = m_comparer->compareNum( track->statistics()->score(), m_comparison, m_value.toDouble(), m_strictness, fmv );
0451                 break;
0452             case Meta::valRating:
0453                 v = m_comparer->compareNum( track->statistics()->rating(), m_comparison, m_value.toInt(), m_strictness, fmv );
0454                 break;
0455             case Meta::valFirstPlayed:
0456                 v = m_comparer->compareDate( track->statistics()->firstPlayed().toSecsSinceEpoch(), m_comparison, m_value, m_strictness );
0457                 break;
0458             case Meta::valLastPlayed:
0459                 v = m_comparer->compareDate( track->statistics()->lastPlayed().toSecsSinceEpoch(), m_comparison, m_value, m_strictness );
0460                 break;
0461             case Meta::valPlaycount:
0462                 v = m_comparer->compareNum( track->statistics()->playCount(), m_comparison, m_value.toInt(), m_strictness, fmv );
0463                 break;
0464             case Meta::valLabel:
0465                 v = m_comparer->compareLabels( track, m_comparison, m_value.toString() );
0466                 break;
0467             default:
0468                 v = 0.0;
0469                 break;
0470         }
0471         if ( m_invert )
0472             v = 1.0 - v;
0473 
0474         m_matchCache.insert( track, ( v > ( (double)QRandomGenerator::global()->generate() / (double)RAND_MAX ) ) );
0475     }
0476     return m_matchCache.value( track );
0477 }
0478 
0479 void
0480 ConstraintTypes::TagMatch::setComparison( int c )
0481 {
0482     m_comparison = c;
0483     m_matchCache.clear();
0484     Q_EMIT dataChanged();
0485 }
0486 
0487 void
0488 ConstraintTypes::TagMatch::setField( const QString& s )
0489 {
0490     m_field = s;
0491     m_matchCache.clear();
0492     Q_EMIT dataChanged();
0493 }
0494 
0495 void
0496 ConstraintTypes::TagMatch::setInvert( bool v )
0497 {
0498     if ( m_invert != v ) {
0499         foreach( const Meta::TrackPtr t, m_matchCache.keys() ) {
0500             m_matchCache.insert( t, !m_matchCache.value( t ) );
0501         }
0502     }
0503     m_invert = v;
0504     Q_EMIT dataChanged();
0505 }
0506 
0507 void
0508 ConstraintTypes::TagMatch::setStrictness( int v )
0509 {
0510     m_strictness = static_cast<double>( v ) / 10.0;
0511     m_matchCache.clear();
0512 }
0513 
0514 void
0515 ConstraintTypes::TagMatch::setValue( const QVariant& v )
0516 {
0517     m_value = v;
0518     m_matchCache.clear();
0519     Q_EMIT dataChanged();
0520 }
0521 
0522 /******************************
0523  * Edit Widget                *
0524  ******************************/
0525 
0526 ConstraintTypes::TagMatchEditWidget::TagMatchEditWidget(
0527                         const int comparison,
0528                         const QString& field,
0529                         const bool invert,
0530                         const int strictness,
0531                         const QVariant& value )
0532         : QWidget( nullptr )
0533         , m_fieldsModel( new TagMatchFieldsModel() )
0534 {
0535     ui.setupUi( this );
0536 
0537     // plural support in combobox labels
0538     connect( ui.spinBox_ValueDateValue, QOverload<int>::of(&QSpinBox::valueChanged),
0539              this, &TagMatchEditWidget::slotUpdateComboBoxLabels );
0540     ui.comboBox_ValueDateUnit->insertItem(0, i18ncp("within the last %1 days", "day", "days", 0));
0541     ui.comboBox_ValueDateUnit->insertItem(1, i18ncp("within the last %1 months", "month", "months", 0));
0542     ui.comboBox_ValueDateUnit->insertItem(2, i18ncp("within the last %1 years", "year", "years", 0));
0543 
0544     // fill in appropriate defaults for some attributes
0545     ui.qcalendarwidget_DateSpecific->setSelectedDate( QDate::currentDate() );
0546 
0547     // fill in user-specified values before the slots have been connected to we don't have to call back to the constraint a dozen times
0548     ui.comboBox_Field->setModel( m_fieldsModel );
0549     ui.checkBox_Invert->setChecked( invert );
0550 
0551     if ( field == QLatin1String("rating") ) {
0552         ui.comboBox_ComparisonRating->setCurrentIndex( comparison );
0553         ui.slider_StrictnessRating->setValue( strictness );
0554         ui.rating_RatingValue->setRating( value.toInt() );
0555     } else if ( field == QLatin1String("length") ) {
0556         ui.comboBox_ComparisonTime->setCurrentIndex( comparison );
0557         ui.slider_StrictnessTime->setValue( strictness );
0558         ui.timeEdit_TimeValue->setTime( QTime(0, 0, 0).addMSecs( value.toInt() ) );
0559     } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeInt ) {
0560         ui.comboBox_ComparisonInt->setCurrentIndex( comparison );
0561         ui.slider_StrictnessInt->setValue( strictness );
0562         ui.spinBox_ValueInt->setValue( value.toInt() );
0563     } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeDate ) {
0564         ui.comboBox_ComparisonDate->setCurrentIndex( comparison );
0565         ui.slider_StrictnessDate->setValue( strictness );
0566         if ( comparison == TagMatch::CompareDateWithin ) {
0567             ui.stackedWidget_Date->setCurrentIndex( 1 );
0568             ui.spinBox_ValueDateValue->setValue( value.value<DateRange>().first );
0569             ui.comboBox_ValueDateUnit->setCurrentIndex( value.value<DateRange>().second );
0570         } else {
0571             ui.stackedWidget_Date->setCurrentIndex( 0 );
0572             ui.qcalendarwidget_DateSpecific->setSelectedDate( value.toDate() );
0573         }
0574     } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeString ) {
0575         ui.comboBox_ComparisonString->setCurrentIndex( comparison );
0576         ui.lineEdit_StringValue->setText( value.toString() );
0577     }
0578 
0579     // set this after the slot has been connected so that it also sets the field page correctly
0580     ui.comboBox_Field->setCurrentIndex( m_fieldsModel->index_of( field ) );
0581 }
0582 
0583 ConstraintTypes::TagMatchEditWidget::~TagMatchEditWidget()
0584 {
0585     delete m_fieldsModel;
0586 }
0587 
0588 // ComboBox slots for comparisons
0589 void
0590 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonDate_currentIndexChanged( int c )
0591 {
0592     if ( c == TagMatch::CompareDateWithin )
0593         ui.stackedWidget_Date->setCurrentIndex( 1 );
0594     else
0595         ui.stackedWidget_Date->setCurrentIndex( 0 );
0596     Q_EMIT comparisonChanged( c );
0597 }
0598 
0599 void
0600 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonInt_currentIndexChanged( int c )
0601 {
0602     Q_EMIT comparisonChanged( c );
0603 }
0604 
0605 void
0606 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonRating_currentIndexChanged( int c )
0607 {
0608     Q_EMIT comparisonChanged( c );
0609 }
0610 
0611 void
0612 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonString_currentIndexChanged( int c )
0613 {
0614     Q_EMIT comparisonChanged( c );
0615 }
0616 
0617 void
0618 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonTime_currentIndexChanged( int c )
0619 {
0620     Q_EMIT comparisonChanged( c );
0621 }
0622 
0623 // ComboBox slots for field
0624 void
0625 ConstraintTypes::TagMatchEditWidget::on_comboBox_Field_currentIndexChanged( int idx )
0626 {
0627     QString field = m_fieldsModel->field_at( idx );
0628     int c = 0;
0629     int s = 0;
0630     QVariant v;
0631     if ( field == QLatin1String("length") ) {
0632         ui.stackedWidget_Field->setCurrentIndex( 3 );
0633         c = ui.comboBox_ComparisonTime->currentIndex();
0634         s = ui.slider_StrictnessTime->value();
0635         v = QTime(0, 0, 0).msecsTo( ui.timeEdit_TimeValue->time() );
0636     } else if ( field == QLatin1String("rating") ) {
0637         ui.stackedWidget_Field->setCurrentIndex( 4 );
0638         c = ui.comboBox_ComparisonRating->currentIndex();
0639         s = ui.slider_StrictnessRating->value();
0640         v = ui.rating_RatingValue->rating();
0641     } else {
0642         if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeInt ) {
0643             ui.stackedWidget_Field->setCurrentIndex( 0 );
0644             c = ui.comboBox_ComparisonInt->currentIndex();
0645             s = ui.slider_StrictnessInt->value();
0646             v = ui.spinBox_ValueInt->value();
0647         } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeDate ) {
0648             ui.stackedWidget_Field->setCurrentIndex( 1 );
0649             c = ui.comboBox_ComparisonDate->currentIndex();
0650             s = ui.slider_StrictnessDate->value();
0651             if ( c == TagMatch::CompareDateWithin ) {
0652                 ui.stackedWidget_Date->setCurrentIndex( 1 );
0653                 int a = ui.spinBox_ValueDateValue->value();
0654                 int b = ui.comboBox_ValueDateUnit->currentIndex();
0655                 v = QVariant::fromValue( DateRange( a, b ) );
0656             } else {
0657                 ui.stackedWidget_Date->setCurrentIndex( 0 );
0658                 v = ui.qcalendarwidget_DateSpecific->selectedDate();
0659             }
0660         } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeString ) {
0661             ui.stackedWidget_Field->setCurrentIndex( 2 );
0662             c = ui.comboBox_ComparisonString->currentIndex();
0663             s = 1.0;
0664             v = ui.lineEdit_StringValue->text();
0665         }
0666     }
0667 
0668     // TODO: set range limitations and default values depending on field
0669 
0670     Q_EMIT fieldChanged( field );
0671     Q_EMIT valueChanged( v );
0672     Q_EMIT comparisonChanged( c );
0673     Q_EMIT strictnessChanged( s );
0674 }
0675 
0676 // Invert checkbox slot
0677 void
0678 ConstraintTypes::TagMatchEditWidget::on_checkBox_Invert_clicked( bool v )
0679 {
0680     Q_EMIT invertChanged( v );
0681 }
0682 
0683 // Strictness Slider slots
0684 void
0685 ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessDate_valueChanged( int v )
0686 {
0687     Q_EMIT strictnessChanged( v );
0688 }
0689 
0690 void
0691 ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessInt_valueChanged( int v )
0692 {
0693     Q_EMIT strictnessChanged( v );
0694 }
0695 
0696 void
0697 ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessRating_valueChanged( int v )
0698 {
0699     Q_EMIT strictnessChanged( v );
0700 }
0701 
0702 void
0703 ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessTime_valueChanged( int v )
0704 {
0705     Q_EMIT strictnessChanged( v );
0706 }
0707 
0708 // various value slots
0709 void
0710 ConstraintTypes::TagMatchEditWidget::on_kdatewidget_DateSpecific_changed( const QDate& v )
0711 {
0712     Q_EMIT valueChanged( QVariant( v ) );
0713 }
0714 
0715 void
0716 ConstraintTypes::TagMatchEditWidget::on_comboBox_ValueDateUnit_currentIndexChanged( int u )
0717 {
0718     int v = ui.spinBox_ValueDateValue->value();
0719     Q_EMIT valueChanged( QVariant::fromValue( DateRange( v, u ) ) );
0720 }
0721 
0722 void
0723 ConstraintTypes::TagMatchEditWidget::on_spinBox_ValueDateValue_valueChanged( int v )
0724 {
0725     int u = ui.comboBox_ValueDateUnit->currentIndex();
0726     Q_EMIT valueChanged( QVariant::fromValue( DateRange( v, u ) ) );
0727 }
0728 
0729 void
0730 ConstraintTypes::TagMatchEditWidget::on_spinBox_ValueInt_valueChanged( int v )
0731 {
0732     Q_EMIT valueChanged( QVariant( v ) );
0733 }
0734 
0735 void
0736 ConstraintTypes::TagMatchEditWidget::on_lineEdit_StringValue_textChanged( const QString& v )
0737 {
0738     Q_EMIT valueChanged( QVariant( v ) );
0739 }
0740 
0741 void
0742 ConstraintTypes::TagMatchEditWidget::on_rating_RatingValue_ratingChanged( int v )
0743 {
0744     Q_EMIT valueChanged( QVariant( v ) );
0745 }
0746 
0747 void
0748 ConstraintTypes::TagMatchEditWidget::on_timeEdit_TimeValue_timeChanged( const QTime& t )
0749 {
0750     int v = QTime(0, 0, 0).msecsTo( t );
0751     Q_EMIT valueChanged( QVariant( v ) );
0752 }
0753 
0754 void
0755 ConstraintTypes::TagMatchEditWidget::slotUpdateComboBoxLabels( int value )
0756 {
0757     ui.comboBox_ValueDateUnit->setItemText(0, i18ncp("within the last %1 days", "day", "days", value));
0758     ui.comboBox_ValueDateUnit->setItemText(1, i18ncp("within the last %1 months", "month", "months", value));
0759     ui.comboBox_ValueDateUnit->setItemText(2, i18ncp("within the last %1 years", "year", "years", value));
0760 }