1 /****************************************************************************************
2  * Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com>                          *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #define DEBUG_PREFIX "Constraint::TagMatch"
18 
19 #include "TagMatch.h"
20 
21 #include "playlistgenerator/Constraint.h"
22 #include "playlistgenerator/ConstraintFactory.h"
23 
24 #include "core/collections/QueryMaker.h"
25 #include "core/meta/Meta.h"
26 #include "core/meta/Statistics.h"
27 #include "core/support/Debug.h"
28 
29 #include <math.h>
30 #include <stdlib.h>
31 
32 Constraint*
createFromXml(QDomElement & xmlelem,ConstraintNode * p)33 ConstraintTypes::TagMatch::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
34 {
35     if ( p )
36         return new TagMatch( xmlelem, p );
37     else
38         return nullptr;
39 }
40 
41 Constraint*
createNew(ConstraintNode * p)42 ConstraintTypes::TagMatch::createNew( ConstraintNode* p )
43 {
44     if ( p )
45         return new TagMatch( p );
46     else
47         return nullptr;
48 }
49 
50 ConstraintFactoryEntry*
registerMe()51 ConstraintTypes::TagMatch::registerMe()
52 {
53     return new ConstraintFactoryEntry( QStringLiteral("TagMatch"),
54                                        i18n("Match Tags"),
55                                        i18n("Make all tracks in the playlist match the specified characteristic"),
56                                        &TagMatch::createFromXml, &TagMatch::createNew );
57 }
58 
TagMatch(QDomElement & xmlelem,ConstraintNode * p)59 ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p )
60         : MatchingConstraint( p )
61         , m_comparer( new Comparer() )
62         , m_fieldsModel( new TagMatchFieldsModel() )
63 {
64     QDomAttr a;
65 
66     a = xmlelem.attributeNode( QStringLiteral("field") );
67     if ( !a.isNull() ) {
68         if ( m_fieldsModel->contains( a.value() ) )
69             m_field = a.value();
70     }
71 
72     a = xmlelem.attributeNode( QStringLiteral("comparison") );
73     if ( !a.isNull() ) {
74         m_comparison = a.value().toInt();
75     }
76 
77     a = xmlelem.attributeNode( QStringLiteral("value") );
78     if ( !a.isNull() ) {
79         if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) {
80             m_value = a.value().toInt();
81         } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
82             if ( m_comparison == CompareDateWithin ) {
83                 QStringList parts = a.value().split(' ');
84                 if ( parts.size() == 2 ) {
85                     int u = parts.at( 0 ).toInt();
86                     int v = 0;
87                     if ( parts.at( 1 ) == QLatin1String("months") )
88                         v = 1;
89                     else if ( parts.at( 1 ) == QLatin1String("years") )
90                         v = 2;
91                     m_value = QVariant::fromValue( DateRange( u, v ) );
92                 } else
93                     m_value = QVariant::fromValue( DateRange( 0, 0 ) );
94             } else
95                 m_value = QDate::fromString( a.value(), Qt::ISODate );
96         } else { // String type
97             m_value = a.value();
98         }
99     }
100 
101     a = xmlelem.attributeNode( QStringLiteral("invert") );
102     if ( !a.isNull() && a.value() == QLatin1String("true") )
103         m_invert = true;
104     else
105         m_invert = false;
106 
107     a = xmlelem.attributeNode( QStringLiteral("strictness") );
108     if ( !a.isNull() )
109         m_strictness = a.value().toDouble();
110 }
111 
TagMatch(ConstraintNode * p)112 ConstraintTypes::TagMatch::TagMatch( ConstraintNode* p )
113         : MatchingConstraint( p )
114         , m_comparison( CompareStrEquals )
115         , m_field( QStringLiteral("title") )
116         , m_invert( false )
117         , m_strictness( 1.0 )
118         , m_value()
119         , m_comparer( new Comparer() )
120         , m_fieldsModel( new TagMatchFieldsModel() )
121 {
122 }
123 
~TagMatch()124 ConstraintTypes::TagMatch::~TagMatch()
125 {
126     delete m_comparer;
127     delete m_fieldsModel;
128 }
129 
130 QWidget*
editWidget() const131 ConstraintTypes::TagMatch::editWidget() const
132 {
133     TagMatchEditWidget* e = new TagMatchEditWidget(
134                                             m_comparison,
135                                             m_field,
136                                             m_invert,
137                                             static_cast<int>( m_strictness * 10 ),
138                                             m_value );
139     connect( e, &TagMatchEditWidget::comparisonChanged, this, &TagMatch::setComparison );
140     connect( e, &TagMatchEditWidget::fieldChanged, this, &TagMatch::setField );
141     connect( e, &TagMatchEditWidget::invertChanged, this, &TagMatch::setInvert );
142     connect( e, &TagMatchEditWidget::strictnessChanged, this, &TagMatch::setStrictness );
143     connect( e, &TagMatchEditWidget::valueChanged, this, &TagMatch::setValue );
144     return e;
145 }
146 
147 void
toXml(QDomDocument & doc,QDomElement & elem) const148 ConstraintTypes::TagMatch::toXml( QDomDocument& doc, QDomElement& elem ) const
149 {
150     QDomElement c = doc.createElement( QStringLiteral("constraint") );
151 
152     c.setAttribute( QStringLiteral("type"), QStringLiteral("TagMatch") );
153     c.setAttribute( QStringLiteral("field"), m_field );
154     c.setAttribute( QStringLiteral("comparison"), m_comparison );
155     c.setAttribute( QStringLiteral("value"), valueToString() );
156 
157     if ( m_invert )
158         c.setAttribute( QStringLiteral("invert"), QStringLiteral("true") );
159     else
160         c.setAttribute( QStringLiteral("invert"), QStringLiteral("false") );
161 
162     c.setAttribute( QStringLiteral("strictness"), QString::number( m_strictness ) );
163 
164     elem.appendChild( c );
165 }
166 
167 QString
getName() const168 ConstraintTypes::TagMatch::getName() const
169 {
170     QString v( i18nc( "%1 = empty string or \"not\"; "
171                       "%2 = a metadata field, like \"title\" or \"artist name\"; "
172                       "%3 = a predicate, can be equals, starts with, ends with or contains; "
173                       "%4 = a string to match; "
174                       "Example: Match tag: not title contains \"foo\"", "Match tag:%1 %2 %3 %4") );
175     v = v.arg( ( m_invert ? i18n(" not") : QLatin1String("") ), m_fieldsModel->pretty_name_of( m_field ), comparisonToString() );
176     if ( m_field == QLatin1String("rating") ) {
177         double r = m_value.toDouble() / 2.0;
178         return v.arg( i18ncp("number of stars in the rating of a track", "%1 star", "%1 stars", r) );
179     } else if ( m_field == QLatin1String("length") ) {
180         return v.arg( QTime(0, 0, 0).addMSecs( m_value.toInt() ).toString( QStringLiteral("H:mm:ss") ) );
181     } else {
182         if ( m_fieldsModel->type_of( m_field ) == FieldTypeString ) {
183             // put quotes around any strings (eg, track title or artist name) ...
184             QString s = i18nc("an arbitrary string surrounded by quotes", "\"%1\"", valueToString() );
185             return v.arg( s );
186         } else {
187             // ... but don't quote put quotes around anything else
188             return v.arg( valueToString() );
189         }
190     }
191 }
192 
193 Collections::QueryMaker*
initQueryMaker(Collections::QueryMaker * qm) const194 ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const
195 {
196     if ( ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) ) {
197         int v = m_value.toInt();
198         int range = static_cast<int>( m_comparer->rangeNum( m_strictness, m_fieldsModel->meta_value_of( m_field ) ) );
199         if ( m_comparison == CompareNumEquals ) {
200             if ( !m_invert ) {
201                 if ( m_strictness < 0.99 ) { // fuzzy approximation of "1.0"
202                     qm->beginAnd();
203                     qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
204                     qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
205                     qm->endAndOr();
206                 } else {
207                     qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals );
208                 }
209             } else {
210                 if ( m_strictness > 0.99 ) {
211                     qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals );
212                 }
213             }
214         } else if ( m_comparison == CompareNumGreaterThan ) {
215             if ( m_invert )
216                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::GreaterThan );
217             else
218                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
219         } else if ( m_comparison == CompareNumLessThan ) {
220             if ( m_invert )
221                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::LessThan );
222             else
223                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
224         }
225     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
226         uint referenceDate = 0;
227         int range = m_comparer->rangeDate( m_strictness );
228         if ( m_comparison == CompareDateBefore ) {
229             referenceDate = m_value.toDateTime().toSecsSinceEpoch();
230             if ( m_invert )
231                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::LessThan );
232             else
233                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan );
234         } else if ( m_comparison == CompareDateOn ) {
235             referenceDate = m_value.toDateTime().toSecsSinceEpoch();
236             if ( !m_invert ) {
237                 qm->beginAnd();
238                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
239                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan );
240                 qm->endAndOr();
241             }
242         } else if ( m_comparison == CompareDateAfter ) {
243             referenceDate = m_value.toDateTime().toSecsSinceEpoch();
244             if ( m_invert )
245                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::GreaterThan );
246             else
247                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
248         } else if ( m_comparison == CompareDateWithin ) {
249             QDateTime now = QDateTime::currentDateTime();
250             DateRange r = m_value.value<DateRange>();
251             switch ( r.second ) {
252                 case 0:
253                     referenceDate = now.addDays( -1 * r.first ).toSecsSinceEpoch();
254                     break;
255                 case 1:
256                     referenceDate = now.addMonths( -1 * r.first ).toSecsSinceEpoch();
257                     break;
258                 case 2:
259                     referenceDate = now.addYears( -1 * r.first ).toSecsSinceEpoch();
260                     break;
261                 default:
262                     break;
263             }
264             if ( m_invert )
265                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::GreaterThan );
266             else
267                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
268         }
269     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeString ) {
270         if ( m_comparison == CompareStrEquals ) {
271             if ( m_invert )
272                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true );
273             else
274                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true );
275         } else if ( m_comparison == CompareStrStartsWith ) {
276             if ( m_invert )
277                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false );
278             else
279                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false );
280         } else if ( m_comparison == CompareStrEndsWith ) {
281             if ( m_invert )
282                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true );
283             else
284                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true );
285         } else if ( m_comparison == CompareStrContains ) {
286             if ( m_invert )
287                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, false );
288             else
289                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, false );
290         }
291         // TODO: regexp
292     } else {
293         error() << "TagMatch cannot initialize QM for unknown type";
294     }
295 
296     return qm;
297 }
298 
299 double
satisfaction(const Meta::TrackList & tl) const300 ConstraintTypes::TagMatch::satisfaction( const Meta::TrackList& tl ) const
301 {
302     double satisfaction = 0.0;
303     foreach( Meta::TrackPtr t, tl ) {
304         if ( matches( t ) ) {
305             satisfaction += 1.0;
306         }
307     }
308     satisfaction /= ( double )tl.size();
309     return satisfaction;
310 }
311 
312 const QBitArray
whatTracksMatch(const Meta::TrackList & tl)313 ConstraintTypes::TagMatch::whatTracksMatch( const Meta::TrackList& tl )
314 {
315     QBitArray match = QBitArray( tl.size() );
316     for ( int i = 0; i < tl.size(); i++ ) {
317         if ( matches( tl.at( i ) ) )
318             match.setBit( i, true );
319     }
320     return match;
321 }
322 
323 int
constraintMatchType() const324 ConstraintTypes::TagMatch::constraintMatchType() const
325 {
326     return ( 0 << 28 ) + m_fieldsModel->index_of( m_field );
327 }
328 
329 
330 QString
comparisonToString() const331 ConstraintTypes::TagMatch::comparisonToString() const
332 {
333     if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) {
334         if ( m_comparison == CompareNumEquals ) {
335             return i18nc("a numerical tag (like year or track number) equals a value","equals");
336         } else if ( m_comparison == CompareNumGreaterThan ) {
337             return i18n("greater than");
338         } else if ( m_comparison == CompareNumLessThan ) {
339             return i18n("less than");
340         }
341     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
342         if ( m_comparison == CompareDateBefore ) {
343             return i18n("before");
344         } else if ( m_comparison == CompareDateOn ) {
345             return i18n("on");
346         } else if ( m_comparison == CompareDateAfter ) {
347             return i18n("after");
348         } else if ( m_comparison == CompareDateWithin ) {
349             return i18n("within");
350         }
351     } else {
352         if ( m_comparison == CompareStrEquals ) {
353             return i18nc("an alphabetical tag (like title or artist name) equals some string","equals");
354         } else if ( m_comparison == CompareStrStartsWith ) {
355             return i18nc("an alphabetical tag (like title or artist name) starts with some string","starts with");
356         } else if ( m_comparison == CompareStrEndsWith ) {
357             return i18nc("an alphabetical tag (like title or artist name) ends with some string","ends with");
358         } else if ( m_comparison == CompareStrContains ) {
359             return i18nc("an alphabetical tag (like title or artist name) contains some string","contains");
360         } else if ( m_comparison == CompareStrRegExp ) {
361             return i18n("regexp");
362         }
363     }
364     return i18n("unknown comparison");
365 }
366 
367 QString
valueToString() const368 ConstraintTypes::TagMatch::valueToString() const
369 {
370     if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
371         if ( m_comparison != CompareDateWithin ) {
372             return m_value.toDate().toString( Qt::ISODate );
373         } else {
374             KLocalizedString unit;
375             switch ( m_value.value<DateRange>().second ) {
376                 case 0:
377                     unit = ki18np("%1 day", "%1 days");
378                     break;
379                 case 1:
380                     unit = ki18np("%1 month", "%1 months");
381                     break;
382                 case 2:
383                     unit = ki18np("%1 year", "%1 years");
384                     break;
385                 default:
386                     break;
387             }
388             return unit.subs( m_value.value<DateRange>().first ).toString();
389         }
390     } else {
391         return m_value.toString();
392     }
393 }
394 
395 bool
matches(Meta::TrackPtr track) const396 ConstraintTypes::TagMatch::matches( Meta::TrackPtr track ) const
397 {
398     if ( !m_matchCache.contains( track ) ) {
399         double v = 0.0;
400         qint64 fmv = m_fieldsModel->meta_value_of( m_field );
401         switch ( fmv ) {
402             case Meta::valUrl:
403                 v = m_comparer->compareStr( track->prettyUrl(), m_comparison, m_value.toString() );
404                 break;
405             case Meta::valTitle:
406                 v = m_comparer->compareStr( track->prettyName(), m_comparison, m_value.toString() );
407                 break;
408             case Meta::valArtist:
409                 v = m_comparer->compareStr( track->artist()->prettyName(), m_comparison, m_value.toString() );
410                 break;
411             case Meta::valAlbum:
412                 v = m_comparer->compareStr( track->album()->prettyName(), m_comparison, m_value.toString() );
413                 break;
414             case Meta::valGenre:
415                 v = m_comparer->compareStr( track->genre()->prettyName(), m_comparison, m_value.toString() );
416                 break;
417             case Meta::valComposer:
418                 v = m_comparer->compareStr( track->composer()->prettyName(), m_comparison, m_value.toString() );
419                 break;
420             case Meta::valYear:
421                 v = m_comparer->compareNum( track->year()->prettyName().toInt(), m_comparison, m_value.toInt(), m_strictness, fmv );
422                 break;
423             case Meta::valComment:
424                 v = m_comparer->compareStr( track->comment(), m_comparison, m_value.toString() );
425                 break;
426             case Meta::valTrackNr:
427                 v = m_comparer->compareNum( track->trackNumber(), m_comparison, m_value.toInt(), m_strictness, fmv );
428                 break;
429             case Meta::valDiscNr:
430                 v = m_comparer->compareNum( track->discNumber(), m_comparison, m_value.toInt(), m_strictness, fmv );
431                 break;
432             case Meta::valLength:
433                 v = m_comparer->compareNum( track->length(), m_comparison, m_value.toInt(), m_strictness, fmv );
434                 break;
435             case Meta::valBitrate:
436                 v = m_comparer->compareNum( track->bitrate(), m_comparison, m_value.toInt(), m_strictness, fmv );
437                 break;
438             case Meta::valFilesize:
439                 v = m_comparer->compareNum( track->filesize(), m_comparison, m_value.toInt(), m_strictness, fmv );
440                 break;
441             case Meta::valCreateDate:
442                 v = m_comparer->compareDate( track->createDate().toSecsSinceEpoch(), m_comparison, m_value, m_strictness );
443                 break;
444             case Meta::valScore:
445                 v = m_comparer->compareNum( track->statistics()->score(), m_comparison, m_value.toDouble(), m_strictness, fmv );
446                 break;
447             case Meta::valRating:
448                 v = m_comparer->compareNum( track->statistics()->rating(), m_comparison, m_value.toInt(), m_strictness, fmv );
449                 break;
450             case Meta::valFirstPlayed:
451                 v = m_comparer->compareDate( track->statistics()->firstPlayed().toSecsSinceEpoch(), m_comparison, m_value, m_strictness );
452                 break;
453             case Meta::valLastPlayed:
454                 v = m_comparer->compareDate( track->statistics()->lastPlayed().toSecsSinceEpoch(), m_comparison, m_value, m_strictness );
455                 break;
456             case Meta::valPlaycount:
457                 v = m_comparer->compareNum( track->statistics()->playCount(), m_comparison, m_value.toInt(), m_strictness, fmv );
458                 break;
459             case Meta::valLabel:
460                 v = m_comparer->compareLabels( track, m_comparison, m_value.toString() );
461                 break;
462             default:
463                 v = 0.0;
464                 break;
465         }
466         if ( m_invert )
467             v = 1.0 - v;
468 
469         m_matchCache.insert( track, ( v > ( (double)qrand() / (double)RAND_MAX ) ) );
470     }
471     return m_matchCache.value( track );
472 }
473 
474 void
setComparison(int c)475 ConstraintTypes::TagMatch::setComparison( int c )
476 {
477     m_comparison = c;
478     m_matchCache.clear();
479     Q_EMIT dataChanged();
480 }
481 
482 void
setField(const QString & s)483 ConstraintTypes::TagMatch::setField( const QString& s )
484 {
485     m_field = s;
486     m_matchCache.clear();
487     Q_EMIT dataChanged();
488 }
489 
490 void
setInvert(bool v)491 ConstraintTypes::TagMatch::setInvert( bool v )
492 {
493     if ( m_invert != v ) {
494         foreach( const Meta::TrackPtr t, m_matchCache.keys() ) {
495             m_matchCache.insert( t, !m_matchCache.value( t ) );
496         }
497     }
498     m_invert = v;
499     Q_EMIT dataChanged();
500 }
501 
502 void
setStrictness(int v)503 ConstraintTypes::TagMatch::setStrictness( int v )
504 {
505     m_strictness = static_cast<double>( v ) / 10.0;
506     m_matchCache.clear();
507 }
508 
509 void
setValue(const QVariant & v)510 ConstraintTypes::TagMatch::setValue( const QVariant& v )
511 {
512     m_value = v;
513     m_matchCache.clear();
514     Q_EMIT dataChanged();
515 }
516 
517 /******************************
518  * Edit Widget                *
519  ******************************/
520 
TagMatchEditWidget(const int comparison,const QString & field,const bool invert,const int strictness,const QVariant & value)521 ConstraintTypes::TagMatchEditWidget::TagMatchEditWidget(
522                         const int comparison,
523                         const QString& field,
524                         const bool invert,
525                         const int strictness,
526                         const QVariant& value )
527         : QWidget( nullptr )
528         , m_fieldsModel( new TagMatchFieldsModel() )
529 {
530     ui.setupUi( this );
531 
532     // plural support in combobox labels
533     connect( ui.spinBox_ValueDateValue, QOverload<int>::of(&QSpinBox::valueChanged),
534              this, &TagMatchEditWidget::slotUpdateComboBoxLabels );
535     ui.comboBox_ValueDateUnit->insertItem(0, i18ncp("within the last %1 days", "day", "days", 0));
536     ui.comboBox_ValueDateUnit->insertItem(1, i18ncp("within the last %1 months", "month", "months", 0));
537     ui.comboBox_ValueDateUnit->insertItem(2, i18ncp("within the last %1 years", "year", "years", 0));
538 
539     // fill in appropriate defaults for some attributes
540     ui.qcalendarwidget_DateSpecific->setSelectedDate( QDate::currentDate() );
541 
542     // 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
543     ui.comboBox_Field->setModel( m_fieldsModel );
544     ui.checkBox_Invert->setChecked( invert );
545 
546     if ( field == QLatin1String("rating") ) {
547         ui.comboBox_ComparisonRating->setCurrentIndex( comparison );
548         ui.slider_StrictnessRating->setValue( strictness );
549         ui.rating_RatingValue->setRating( value.toInt() );
550     } else if ( field == QLatin1String("length") ) {
551         ui.comboBox_ComparisonTime->setCurrentIndex( comparison );
552         ui.slider_StrictnessTime->setValue( strictness );
553         ui.timeEdit_TimeValue->setTime( QTime(0, 0, 0).addMSecs( value.toInt() ) );
554     } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeInt ) {
555         ui.comboBox_ComparisonInt->setCurrentIndex( comparison );
556         ui.slider_StrictnessInt->setValue( strictness );
557         ui.spinBox_ValueInt->setValue( value.toInt() );
558     } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeDate ) {
559         ui.comboBox_ComparisonDate->setCurrentIndex( comparison );
560         ui.slider_StrictnessDate->setValue( strictness );
561         if ( comparison == TagMatch::CompareDateWithin ) {
562             ui.stackedWidget_Date->setCurrentIndex( 1 );
563             ui.spinBox_ValueDateValue->setValue( value.value<DateRange>().first );
564             ui.comboBox_ValueDateUnit->setCurrentIndex( value.value<DateRange>().second );
565         } else {
566             ui.stackedWidget_Date->setCurrentIndex( 0 );
567             ui.qcalendarwidget_DateSpecific->setSelectedDate( value.toDate() );
568         }
569     } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeString ) {
570         ui.comboBox_ComparisonString->setCurrentIndex( comparison );
571         ui.lineEdit_StringValue->setText( value.toString() );
572     }
573 
574     // set this after the slot has been connected so that it also sets the field page correctly
575     ui.comboBox_Field->setCurrentIndex( m_fieldsModel->index_of( field ) );
576 }
577 
~TagMatchEditWidget()578 ConstraintTypes::TagMatchEditWidget::~TagMatchEditWidget()
579 {
580     delete m_fieldsModel;
581 }
582 
583 // ComboBox slots for comparisons
584 void
on_comboBox_ComparisonDate_currentIndexChanged(int c)585 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonDate_currentIndexChanged( int c )
586 {
587     if ( c == TagMatch::CompareDateWithin )
588         ui.stackedWidget_Date->setCurrentIndex( 1 );
589     else
590         ui.stackedWidget_Date->setCurrentIndex( 0 );
591     Q_EMIT comparisonChanged( c );
592 }
593 
594 void
on_comboBox_ComparisonInt_currentIndexChanged(int c)595 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonInt_currentIndexChanged( int c )
596 {
597     Q_EMIT comparisonChanged( c );
598 }
599 
600 void
on_comboBox_ComparisonRating_currentIndexChanged(int c)601 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonRating_currentIndexChanged( int c )
602 {
603     Q_EMIT comparisonChanged( c );
604 }
605 
606 void
on_comboBox_ComparisonString_currentIndexChanged(int c)607 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonString_currentIndexChanged( int c )
608 {
609     Q_EMIT comparisonChanged( c );
610 }
611 
612 void
on_comboBox_ComparisonTime_currentIndexChanged(int c)613 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonTime_currentIndexChanged( int c )
614 {
615     Q_EMIT comparisonChanged( c );
616 }
617 
618 // ComboBox slots for field
619 void
on_comboBox_Field_currentIndexChanged(int idx)620 ConstraintTypes::TagMatchEditWidget::on_comboBox_Field_currentIndexChanged( int idx )
621 {
622     QString field = m_fieldsModel->field_at( idx );
623     int c = 0;
624     int s = 0;
625     QVariant v;
626     if ( field == QLatin1String("length") ) {
627         ui.stackedWidget_Field->setCurrentIndex( 3 );
628         c = ui.comboBox_ComparisonTime->currentIndex();
629         s = ui.slider_StrictnessTime->value();
630         v = QTime(0, 0, 0).msecsTo( ui.timeEdit_TimeValue->time() );
631     } else if ( field == QLatin1String("rating") ) {
632         ui.stackedWidget_Field->setCurrentIndex( 4 );
633         c = ui.comboBox_ComparisonRating->currentIndex();
634         s = ui.slider_StrictnessRating->value();
635         v = ui.rating_RatingValue->rating();
636     } else {
637         if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeInt ) {
638             ui.stackedWidget_Field->setCurrentIndex( 0 );
639             c = ui.comboBox_ComparisonInt->currentIndex();
640             s = ui.slider_StrictnessInt->value();
641             v = ui.spinBox_ValueInt->value();
642         } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeDate ) {
643             ui.stackedWidget_Field->setCurrentIndex( 1 );
644             c = ui.comboBox_ComparisonDate->currentIndex();
645             s = ui.slider_StrictnessDate->value();
646             if ( c == TagMatch::CompareDateWithin ) {
647                 ui.stackedWidget_Date->setCurrentIndex( 1 );
648                 int a = ui.spinBox_ValueDateValue->value();
649                 int b = ui.comboBox_ValueDateUnit->currentIndex();
650                 v = QVariant::fromValue( DateRange( a, b ) );
651             } else {
652                 ui.stackedWidget_Date->setCurrentIndex( 0 );
653                 v = ui.qcalendarwidget_DateSpecific->selectedDate();
654             }
655         } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeString ) {
656             ui.stackedWidget_Field->setCurrentIndex( 2 );
657             c = ui.comboBox_ComparisonString->currentIndex();
658             s = 1.0;
659             v = ui.lineEdit_StringValue->text();
660         }
661     }
662 
663     // TODO: set range limitations and default values depending on field
664 
665     Q_EMIT fieldChanged( field );
666     Q_EMIT valueChanged( v );
667     Q_EMIT comparisonChanged( c );
668     Q_EMIT strictnessChanged( s );
669 }
670 
671 // Invert checkbox slot
672 void
on_checkBox_Invert_clicked(bool v)673 ConstraintTypes::TagMatchEditWidget::on_checkBox_Invert_clicked( bool v )
674 {
675     Q_EMIT invertChanged( v );
676 }
677 
678 // Strictness Slider slots
679 void
on_slider_StrictnessDate_valueChanged(int v)680 ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessDate_valueChanged( int v )
681 {
682     Q_EMIT strictnessChanged( v );
683 }
684 
685 void
on_slider_StrictnessInt_valueChanged(int v)686 ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessInt_valueChanged( int v )
687 {
688     Q_EMIT strictnessChanged( v );
689 }
690 
691 void
on_slider_StrictnessRating_valueChanged(int v)692 ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessRating_valueChanged( int v )
693 {
694     Q_EMIT strictnessChanged( v );
695 }
696 
697 void
on_slider_StrictnessTime_valueChanged(int v)698 ConstraintTypes::TagMatchEditWidget::on_slider_StrictnessTime_valueChanged( int v )
699 {
700     Q_EMIT strictnessChanged( v );
701 }
702 
703 // various value slots
704 void
on_kdatewidget_DateSpecific_changed(const QDate & v)705 ConstraintTypes::TagMatchEditWidget::on_kdatewidget_DateSpecific_changed( const QDate& v )
706 {
707     Q_EMIT valueChanged( QVariant( v ) );
708 }
709 
710 void
on_comboBox_ValueDateUnit_currentIndexChanged(int u)711 ConstraintTypes::TagMatchEditWidget::on_comboBox_ValueDateUnit_currentIndexChanged( int u )
712 {
713     int v = ui.spinBox_ValueDateValue->value();
714     Q_EMIT valueChanged( QVariant::fromValue( DateRange( v, u ) ) );
715 }
716 
717 void
on_spinBox_ValueDateValue_valueChanged(int v)718 ConstraintTypes::TagMatchEditWidget::on_spinBox_ValueDateValue_valueChanged( int v )
719 {
720     int u = ui.comboBox_ValueDateUnit->currentIndex();
721     Q_EMIT valueChanged( QVariant::fromValue( DateRange( v, u ) ) );
722 }
723 
724 void
on_spinBox_ValueInt_valueChanged(int v)725 ConstraintTypes::TagMatchEditWidget::on_spinBox_ValueInt_valueChanged( int v )
726 {
727     Q_EMIT valueChanged( QVariant( v ) );
728 }
729 
730 void
on_lineEdit_StringValue_textChanged(const QString & v)731 ConstraintTypes::TagMatchEditWidget::on_lineEdit_StringValue_textChanged( const QString& v )
732 {
733     Q_EMIT valueChanged( QVariant( v ) );
734 }
735 
736 void
on_rating_RatingValue_ratingChanged(int v)737 ConstraintTypes::TagMatchEditWidget::on_rating_RatingValue_ratingChanged( int v )
738 {
739     Q_EMIT valueChanged( QVariant( v ) );
740 }
741 
742 void
on_timeEdit_TimeValue_timeChanged(const QTime & t)743 ConstraintTypes::TagMatchEditWidget::on_timeEdit_TimeValue_timeChanged( const QTime& t )
744 {
745     int v = QTime(0, 0, 0).msecsTo( t );
746     Q_EMIT valueChanged( QVariant( v ) );
747 }
748 
749 void
slotUpdateComboBoxLabels(int value)750 ConstraintTypes::TagMatchEditWidget::slotUpdateComboBoxLabels( int value )
751 {
752     ui.comboBox_ValueDateUnit->setItemText(0, i18ncp("within the last %1 days", "day", "days", value));
753     ui.comboBox_ValueDateUnit->setItemText(1, i18ncp("within the last %1 months", "month", "months", value));
754     ui.comboBox_ValueDateUnit->setItemText(2, i18ncp("within the last %1 years", "year", "years", value));
755 }
756